///////////////////////////////////////////////////////////////////////////// /* Copyright 2001 Ronald S. Burkey This file is part of GutenMark. GutenMark is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. GutenMark is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GutenMark; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Filename: MarkByChar.c Purpose: This analyzes and marks up a line by character and word. Mods: 12/15/01 RSB Split this off from the MarkBody function, which had become incredibly complex and hard-to-read. 12/16/01 RSB Now remove all soft spaces from the beginnings of lines. Began adding all of the new italicizing styles I found on the PG newsgroup. */ // Styles of italicization are indicated by the Status.Italicizing variable, // as follows: // 0 none. // 1 All caps. // 2 HTML -- like hello. // 3 Like // ... and others, not necessarily in this order: // Like _hello_ // Like /hello/ // Like ~~hello~~ // Like *hello* 12/16/01 // Like ~hello~ 12/16/01 // Like _/hello/_ 12/16/01 // Like _*hello*_ 12/16/01 // Like */hello/* 12/16/01 // Like _*/hello/*_ 12/16/01 // Like /:hello:/ 12/16/01 // Like |:hello:| 12/16/01 // // As might be imagined, there's some difficulty distinguishing between // the styles hello and . To this end, a variable called // BracketItalicsCount is kept; initially, the program guesses which style // is which, but after enough of one style or the other is encountered, // as recorded by BracketItalicsCount, the style is locked in. #include #include #include #include #include "AutoMark.h" // We spell these honorifics in reverse to make the search easier. // No need to add single-letter ones, because these are treated specially. // I've also added various names that appear in older texts as // abbreviations, like Jas., Chas., Ed., etc. static char *Honorifics[] = { "rM", "RM", "srM", "SRM", "rD", "RD", "srD", "SRD", "tS", "TS", "etS", "ETS", "arF", "ARF", "rF", "RF", "emM", "EMM", "sM", "SM", "emdM", "EMDM", "ellM", "ELLM", "snoM", "SNOM", "sahC", "SAHC", "saJ", "SAJ", "dE", "DE", "sirhC", "SIRHC", "oeG", "OEG", "derF", "DERF", "mW", "MW" }; // Strings to auto-italicize. Note that adding "PS." to the list has the // happy side effect of not adding spaces to it in end-of-sentence detection. static char *Italicized[] = { "Etc.", "etc.", "viz.", "ie.", "i.e.", "Ie.", "I.e.", "eg.", "e.g.", "Eg.", "E.g.", "et al.", "et cetera", "n.b.", "N.b.", "nota bene", "Nota bene", /* "M.", */ "Ste.", "Mme.", "Mdme.", "Mlle.", "Mons.", "PS." "PPS." }; #define NUM_AUTO_ITALICIZED (sizeof(Italicized)/sizeof(Italicized[0])) static int LengthItalicized[NUM_AUTO_ITALICIZED]; static int LengthsInitialized = 0; // Italicizing delimiters. typedef struct { int Length; char *Start; char *End; } ItalicizingStyle; #define ITALICS_NONE 0 #define ITALICS_ALLCAPS 1 #define ITALICS_HTML 2 #define ITALICS_BRACKET 3 #define ITALICS_UNNAMED 4 ItalicizingStyle ItalicizingStyles[] = { {0, NULL, NULL}, // ITALICS_NONE {0, NULL, NULL}, // ITALICS_ALLCAPS {0, NULL, NULL}, // ITALICS_HTML {1, "<", ">"}, // ITALICS_BRACKET {1, "_", "_"}, // ITALICS_UNNAMED {1, "/", "/"}, // (etc.) {2, "~~", "~~"}, {1, "*", "*"}, {1, "~", "~"}, {2, "_/", "/_"}, {2, "_*", "*_"}, {2, "*/", "/*"}, {3, "_*/", "/*_"}, {2, "/:", ":/"}, {2, "|:", ":|"} }; #define NUM_ITALICIZING_STYLES \ (sizeof (ItalicizingStyles) / sizeof (ItalicizingStyle)) //--------------------------------------------------------------------- // A utility function to determine if a string // (like an italicization delimiter) is at the front of a word. // Returns zero if not, the length of the string if so. // AbsoluteStart is the pointer to the array in which the word is // embedded. DelimStart is a pointer into this string, indicating // the possible delimiter we're trying to test. Target is the // delimiter we're trying to test it against. static int AtWordStart (char *AbsoluteStart, char *DelimStart, char *Target) { char *Prior, *Delim; Prior = DelimStart - 1; for (Delim = DelimStart; *Target; Target++, Delim++) if (*Target != *Delim) return (0); if (!(isspace (*Delim) || ispunct (*Delim)) && (DelimStart == AbsoluteStart || isspace (*Prior) || *Prior == '\'' || *Prior == '\"' || *Prior == '(' || *Prior == '-')) return (Delim - DelimStart); else return (0); } //--------------------------------------------------------------------- // Returns 0 on success. int MarkByChar (AnalysisDataset * Dataset, MarkStatus * Status, char *s) { unsigned long LastLanguages = 0; int AtLineBeginning = 1; char *ss, *sss, *siv, *sv; char Normalized[MAXWORDLENGTH], Full[MAXWORDLENGTH], TestNormalized[MAXWORDLENGTH], TestFull[MAXWORDLENGTH]; int j = 0, k, n; if (!LengthsInitialized) { LengthsInitialized = 1; for (j = 0; j < NUM_AUTO_ITALICIZED; j++) LengthItalicized[j] = strlen (Italicized[j]); } if (Status->Italicizing == ITALICS_NONE) Status->WordItalicizing = 0; for (ss = s; *ss; ss++) { // We remove all soft spaces we find at the beginnings of lines. if (AtLineBeginning && isspace (*ss) && *ss != '\n') { if (AddMarkup (Dataset, Status->LineInfo[0].Offset + ss - s, MarkRemoveChar, 0)) goto DiskError; } else AtLineBeginning = 0; if (isspace (*ss) && ss != s && !isspace (ss[-1])) Status->LastFirstSpace = Status->LineInfo[0].Offset + ss - s; // Status->SentenceStart is a helpful flag for determining if // a word may be at the start of a sentence. It's used // ONLY for the all-caps italicizaton style. Status->SentenceStart |= *ss == '.' || *ss == '?' || *ss == '!' || *ss == ':' || *ss == '\"'; if (Status->SentenceStart) Status->NumBacktracks = Status->Foreignosity = 0; // Pick off words. if (Status->InWord) { if (IsWordChar (*ss)) { if (Status->WordItalicizing && *ss != '\'') { if (AddMarkup (Dataset, Status->LineInfo[0].Offset + ss - s, MarkTolower, 0)) goto DiskError; } } else { Status->InWord = 0; if (Status->WordItalicizing && Status->Italicizing != ITALICS_NONE) { Status->WordItalicizing = Status->Italicizing = ITALICS_NONE; if (AddMarkup (Dataset, Status->LineInfo[0].Offset + ss - s, MarkEndItalics, 0)) goto DiskError; } } } // We don't allow any WORD-based processing in the Prefatory area, // because it's pointless. if (!Status->InWord && IsWordChar (*ss) && *ss != '\'' && !Status->InPreface) { char *sss; int i; int Matched; // Locate the end of the word. for (sss = ss + 1;; sss++) { if (!*sss) break; if (!IsWordChar (*sss)) break; if (*sss == '\'') { i = IsWordChar (sss[1]); if (i == WORD_NOT || 0 != (i & WORD_PUNCT)) break; } } // Process the word. if (sss == ss + 1) Status->NumBacktracks = Status->Foreignosity = 0; if (sss > ss + 1 && sss < ss + MAXWORDLENGTH) { Status->InWord = 1; strncpy (Full, ss, sss - ss); Full[sss - ss] = 0; if (DiacriticalNormalize (Full, Normalized, MAXWORDLENGTH)) { // Because various of the markups which the analyses // below want to trigger conflict with each other, // we can't just start the markups when the analyses // identify the need for them. Instead, we have to // set some state variables instead, and then after // all of the analyses are done resolve the conflicts. // Only then can we apply markup. int WantToItalicize = 0; int WantFirstCharLower = 0; int WantFirstCharUpper = 0; int WantRestOfWordLower = 0; int Want8Bit = 0; // Process ALL-CAPS words against the wordlist created // from the etext, for the purpose of converting to // lower case and italicizing. This is only interesting // if we're not already italicizing. if (!IsStrupr (Full)) Status->FirstWordArea = 0; else if (Status->Italicizing == ITALICS_NONE && !Status->InHeader1 && !Status->InSubtitle && !(Dataset-> FirstCapital && Status->FirstWordArea)) { k = Status->SentenceStart; strcpy (TestFull, Full); strcpy (TestNormalized, Normalized); DiacriticalStrlwr (&TestFull[Status->SentenceStart]); DiacriticalStrlwr (&TestNormalized [Status->SentenceStart]); SearchWordlist (Dataset->Words, TestNormalized, TestFull, &Matched); if (!Matched && !Status->SentenceStart) { TestFull[0] = toupper (TestFull[0]); TestNormalized[0] = toupper (TestNormalized[0]); k = 1; SearchWordlist (Dataset->Words, TestNormalized, TestFull, &Matched); } // !Matched && ... if (Matched) { WantToItalicize = Dataset->FirstItalics || !Status->FirstWordArea; WantRestOfWordLower = 1; if (!k) { WantFirstCharLower = 1; } // !k } // Matched } // !Status->Italicizing && ... // Here we check the word against the global // wordlists/namelists. This helps us to improve // ALL-CAPS italicizing, for words not found in the // etext-derived wordlist above. Also, we can determine // if the word is foreign, or if it's a 7-bit form that // needs to be replaced with an 8-bit form. In theory, // we could also remove soft-hyphens, but I'll leave // this for the future (if ever). i = SearchWordlist (Dataset->Words, Normalized, Full, &Matched); if (Matched) // This can't fail, but still ... { j = Dataset->Words->Words[i].WordlistStatus; if (Dataset->Words->Words[i].Frequent) j &= ~SPELL_NONNATIVE; // If we're not already italicizing, we begin // italicizing for appropriate ALL-CAPS situations. if (!IsStrupr (Full)) Status->FirstWordArea = 0; else if (Status->Italicizing == ITALICS_NONE && !Status->InHeader1 && !Status->InSubtitle && !(Dataset->FirstCapital && Status->FirstWordArea)) { if (0 != (j & SPELL_LOWERCASE) || 0 != (j & SPELL_CAPITALIZED)) { k = Status-> SentenceStart | (j & SPELL_CAPITALIZED); // 12/09/01 RSB: was = 1 */ WantToItalicize = Dataset->FirstItalics || !Status->FirstWordArea; WantRestOfWordLower = 1; if (!k) { WantFirstCharLower = 1; } // !k } // 0 != ... } // !Status->Italicizing && ... // Next, do foreign words. This is seemingly a lot // easier, because we are not so worried about // capitalization, but it's actually pretty tricky. // The problem is that many foreign words are also // recognizable as names or as words in the native // language of the etext, and hence we can easily // get into a situation where a foreign phrase shows // up as a mixture of italicized and non-italicized // words (which is very annoying). Therefore, a // more complex recognition formula is needed. What // we do is to compute an index of "foreignosity", // only partly related to specific words, and // italicize on the basis of this index. We have // two data at our disposal, for individual words: // the SPELL_FOREIGN flag (which is set if the word // has ONLY been recognized as foreign) and the // SPELL_NONNATIVE flag (which is set if the word is // found in a foreign dictionary even if it was // previously found in a native dictionary). // Another difficulty, is that there's a tendency for // the beginnings of foreign phrases to be left off, // if the words correspond to native words. So once // we reach an appropriate foreignosity threshhold, // we have to work backward to account for them. Status->LastForeignosity = Status->Foreignosity; if (Status->SentenceStart) Status->Foreignosity = 0; if (Dataset->Words->Words[i].LikelyName) Status->Foreignosity = 0; else if (0 != (j & SPELL_FOREIGN)) Status->Foreignosity = 256; else if (0 != (j & SPELL_NONNATIVE)) { if (0 == (LastLanguages & Dataset->Words->Words[i]. Languages)) Status->Foreignosity = 0; } else Status->Foreignosity = 0; if (Status->Italicizing != ITALICS_NONE) Status->Foreignosity = 0; LastLanguages = Dataset->Words->Words[i].Languages; if (Status->Italicizing == ITALICS_NONE && !Dataset->NoForeign) { if (Status->Foreignosity >= 256) { if (Status->LastForeignosity < 256) { for (k = 0; k < Status->NumBacktracks; k++) { // In doing the backtracks, we want to // be consistent across languages. We // do this by making sure the language // masks overlap. if (0 == (Status->ForeignBacktracks[k]. Languages & Dataset->Words-> Words[i].Languages)) break; // This markup stuff is okay, // because it applies not to // the current word, but to // prior words where conflicts // have already been resolved. if (AddMarkup (Dataset, Status->ForeignBacktracks[k].Start, MarkBeginItalics, 0)) goto DiskError; if (AddMarkup (Dataset, Status->ForeignBacktracks[k].End, MarkEndItalics, 0)) goto DiskError; } } Status->NumBacktracks = 0; WantToItalicize = 1; } // Status->Foreignosity ... else if (0 != (j & SPELL_NONNATIVE) && !IsStrupr (Full)) { if (Status->NumBacktracks < MAX_BACKTRACKS) { Status->ForeignBacktracks[Status-> NumBacktracks]. Languages = Dataset->Words->Words[i].Languages; Status->ForeignBacktracks[Status-> NumBacktracks]. Start = Status->LineInfo[0].Offset + ss - s; Status->ForeignBacktracks[Status-> NumBacktracks]. End = Status->ForeignBacktracks[Status-> NumBacktracks]. Start + strlen (Full); Status->NumBacktracks++; } } // 0 != ... else Status->NumBacktracks = 0; } // !Status->Italicizing. else Status->NumBacktracks = 0; } // Matched // Now, handle restoration of diacriticals, by replacing // 7-bit ASCII with 8-bit ASCII. if (0 != (j & SPELL_NORMALIZED) && Dataset->Words->Words[i].Match != NULL && !Dataset->NoDiacritical && !Dataset->Words->Words[i].LikelyName) Want8Bit = 1; // Now, apply all of the markups implied by the flags // we just set. k = Status->LineInfo[0].Offset + ss - s; if (WantToItalicize) { if (AddMarkup (Dataset, k, MarkBeginItalics, 0)) goto DiskError; } WantFirstCharUpper = 0; if (Want8Bit) { sss = Dataset->Words->Words[i].Match; if (isupper (*Dataset->Words->Words[i].Normalized) && (Status->SentenceStart || Dataset->Words->Words[i].NotAtBeginning)) WantFirstCharUpper = 1; WantFirstCharLower = 0; } else sss = Dataset->Words->Words[i].Full; if (WantFirstCharLower || WantRestOfWordLower || Want8Bit) { for (n = 0; *sss; sss++, n++) { char c; c = *sss; if (!n) { if (WantFirstCharLower) c = DiacriticalTolower (c); else if (WantFirstCharUpper) c = DiacriticalToupper (c); } else { if (WantRestOfWordLower) c = DiacriticalTolower (c); } if (c == '\'') { if (AddMarkup (Dataset, k, MarkRsquo, c)) goto DiskError; } else { if (AddMarkup (Dataset, k, MarkInsertChar, c)) goto DiskError; } } for (sss = Dataset->Words->Words[i].Full; *sss; sss++, k++) if (AddMarkup (Dataset, k, MarkRemoveChar, 0)) goto DiskError; Status->TripSquote = k; } else k += strlen (Full); if (WantToItalicize) { if (AddMarkup (Dataset, k, MarkEndItalics, 0)) goto DiskError; } } // DiacriticalNormalize ... } // for ... Status->SentenceStart = 0; } // "Smart" double-quotes. if (*ss == '\"' && !IsEndSpace (ss[1]) && (ss == s || isspace (ss[-1]))) { if (AddMarkup (Dataset, Status->LineInfo[0].Offset + ss - s, MarkBeginSmartQuote, 0)) goto DiskError; continue; } if (*ss == '\"' && ss > s && !isspace (ss[-1]) && (IsEndSpace (ss[1]) || (IsEndPunct (ss[1]) && IsEndSpace (ss[2])))) { if (AddMarkup (Dataset, Status->LineInfo[0].Offset + ss - s, MarkEndSmartQuote, 0)) goto DiskError; continue; } // "Smart" single-quotes. This differs a little from // double-quotes, because we interpret ALL single-quotes as // right-quotes UNLESS they fulfill reasonable criteria for // being a left-hand quote AND an ending single quote appears in // the not-too distant future (defined as the end of the line). if (*ss == '\'') { int Right = 1; if (!IsEndSpace (ss[1]) && (ss == s || isspace (ss[-1]) || ss[-1] == '\"')) { char *sss; // Reasonable requirements for an opening quote are // fulfilled. Now let's look to see if we can find a // trailing quote prior to the next candidate for opening // quote. for (sss = ss + 1; *sss; sss++) if (*sss == '\'') { if (!IsEndSpace (sss[1]) && (isspace (sss[-1]) || sss[-1] == '\"')) break; if (!isspace (sss[-1]) && (IsEndSpace (sss[1]) || sss[1] == '\"' || (IsEndPunct (sss[1]) && (IsEndSpace (sss[2]) || sss[1] == '\"')))) { Right = 0; break; } } } if (Right) { k = Status->LineInfo[0].Offset + ss - s; // It may be that this quote was already added as part // of a WORD, and so adding new markup for it would // result in the appearance of two quotes. if (k >= Status->TripSquote) { if (AddMarkup (Dataset, k, MarkRsquo, 0)) goto DiskError; } } else { if (AddMarkup (Dataset, Status->LineInfo[0].Offset + ss - s, MarkLsquo, 0)) goto DiskError; } continue; } // Find multiple dashes. Replace even-length strings of // dashes with half as many mdashes, and odd-length strings // (>1) with mdashes plus a trailing ndash. We also convert // " - " to " — ". if (*ss == '-' && !Dataset->NoMdash && !Status->LineInfo[0].WeirdSequences) { if (ss > s && isspace (ss[-1]) && isspace (ss[1])) { if (AddMarkup (Dataset, Status->LineInfo[0].Offset + ss - s, MarkInsertMdash, 0)) goto DiskError; if (AddMarkup (Dataset, Status->LineInfo[0].Offset + ss - s, MarkRemoveChar, 0)) goto DiskError; ss++; Status->LastFirstSpace = Status->LineInfo[0].Offset + ss - s; } else if (ss[1] == '-') { while (*ss == '-') { if (ss[1] != '-') { if (AddMarkup (Dataset, Status->LineInfo[0].Offset + ss - s, MarkInsertNdash, 0)) goto DiskError; if (AddMarkup (Dataset, Status->LineInfo[0].Offset + ss - s, MarkRemoveChar, 0)) goto DiskError; ss++; } else { if (AddMarkup (Dataset, Status->LineInfo[0].Offset + ss - s, MarkInsertMdash, 0)) goto DiskError; if (AddMarkup (Dataset, Status->LineInfo[0].Offset + ss - s, MarkRemoveChar, 0)) goto DiskError; if (AddMarkup (Dataset, Status->LineInfo[0].Offset + ss - s + 1, MarkRemoveChar, 0)) goto DiskError; ss += 2; } } if (*ss != '.' && *ss != '?' && *ss != '!') { if (AddMarkup (Dataset, Status->LineInfo[0].Offset + ss - s, MarkSoftHyphen, 0)) goto DiskError; } ss--; continue; } } // *** Italicizing *** // End italics on . if (Status->Italicizing == ITALICS_HTML && !strncasecmp (ss, "", 4)) { j = 4; EndItalics: // Set j before coming here. if (AddMarkup (Dataset, Status->LineInfo[0].Offset + ss - s, MarkEndItalics, 0)) goto DiskError; for (; j > 0; j--) { if (AddMarkup (Dataset, Status->LineInfo[0].Offset + ss - s, MarkRemoveChar, 0)) goto DiskError; if (j > 1) ss++; } Status->Italicizing = ITALICS_NONE; continue; } // ... and other italicizing styles. if (Status->Italicizing >= ITALICS_BRACKET) if (!strncmp (ss, ItalicizingStyles[Status->Italicizing].End, ItalicizingStyles[Status->Italicizing].Length)) { j = ItalicizingStyles[Status->Italicizing].Length; goto EndItalics; } // Begin italicizing when encountered. if (Status->Italicizing == ITALICS_NONE && Status->BracketItalicsCount < 5 && !strncasecmp (ss, "", 3)) { j = 3; Status->Italicizing = ITALICS_HTML; StartItalics: // Set j and Status->Italicizing before coming. if (AddMarkup (Dataset, Status->LineInfo[0].Offset + ss - s, MarkBeginItalics, 0)) goto DiskError; for (; j > 0; j--) { if (AddMarkup (Dataset, Status->LineInfo[0].Offset + ss - s, MarkRemoveChar, 0)) goto DiskError; if (j > 1) ss++; } continue; } // Treat the style differently, since we have to // do various odd things to distinguish it from this // emphasis style. if (Status->Italicizing == ITALICS_NONE && *ss == '<' && Status->BracketItalicsCount > 5) goto BracketEntry; if (AtWordStart (s, ss, "<")) { BracketEntry:j = 1; if (ss[1] == '/') Status->BracketItalicsCount = -30000; else if (Status->BracketItalicsCount <= 5 && ss[1] != 0 && ss[2] == '>') ; else { Status->Italicizing = ITALICS_BRACKET; Status->BracketItalicsCount++; goto StartItalics; } } // ... and the other styles. if (Status->Italicizing == ITALICS_NONE) for (j = ITALICS_UNNAMED; j < NUM_ITALICIZING_STYLES; j++) if (AtWordStart (s, ss, ItalicizingStyles[j].Start)) { Status->Italicizing = j; j = ItalicizingStyles[j].Length; goto StartItalics; } // Words to auto-italicize. This shouldn't be necessary any // more, since wordlists have supplanted it. But, it appears // to work, so ... if (Status->Italicizing == ITALICS_NONE) { for (j = 0; j < NUM_AUTO_ITALICIZED; j++) if (0 == strncmp (ss, Italicized[j], LengthItalicized[j])) { if (ss > s && !isspace (ss[-1])) continue; if (AddMarkup (Dataset, Status->LineInfo[0].Offset + ss - s, MarkBeginItalics, 0)) goto DiskError; ss += LengthItalicized[j] - 1; if (AddMarkup (Dataset, Status->LineInfo[0].Offset + ss - s, MarkEndItalics, 0)) goto DiskError; break; } if (j < NUM_AUTO_ITALICIZED) continue; } // Detect a hanging single '-' at the end // of a line. If the hanging dash is not preceded by whitespace, // like " - " or " -- " then it is undoubtedly part of // something like "to-day" and needs to have the trailing space // removed, or else in html it will render as "to- day". Of course, // there's also the larger question of whether it should be reduced // simply to "today", We'll worry about that one later ... if (isspace (*ss) && ss > &s[1] && ss[-1] == '-' && ss[-2] != '-' && !Status->LineInfo[1].Empty && !Status->LineInfo[1].BeginsWhite) { // Search backward for the first non-dash encountered. for (sss = ss - 2; sss >= s && *sss == '-'; sss--); if (sss >= s && isalpha (*sss)) { for (sss = ss + 1; isspace (*sss); sss++); if (*sss == '\0') // Entire remainder of line blank. { for (sss = ss; *sss; sss++) { if (AddMarkup (Dataset, Status->LineInfo[0].Offset + sss - s, MarkRemoveChar, 0)) goto DiskError; } ss = sss - 1; continue; } } } // Detect position where two spaces are needed (namely, after colon // or end-of-sentence). if (isspace (*ss)) { Status->CouldBeName = 1; sss = ss - 1; if (sss > s && (*sss == '\"' || *sss == '\'')) { Status->CouldBeName = 0; sss--; } if (sss > s && *sss == ':' && !Dataset->SingleSpace) { if (AddMarkup (Dataset, Status->LineInfo[0].Offset + ss - s, MarkNbsp, 0)) goto DiskError; } else if (sss > s && (*sss == '.' || *sss == '!' || *sss == '?')) { if (*sss != '.') Status->CouldBeName = 0; // We want to check whether the next character is // capitalized. for (siv = ss + 1; isspace (*siv); siv++); if (*siv == '\0') // Next character is on next line. { if (!Status->LineInfo[1].CapFirstChar) Status->CouldBeName = 0; if (Status->LineInfo[1].VerseCap) goto CapitalizedAfterPeriod; } else // Next character is on the same line. { if (*siv == '\"' || *siv == '\'') { Status->CouldBeName = 0; siv++; } if (*siv >= 'A' && *siv <= 'Z') { CapitalizedAfterPeriod: // Okay, we know that the next char is capitalized, // but it could still be part of a construction like // "Mr. Smith". So we need to detect various // honorifics and eliminate them. Also, we detect // single-letter capitalized abbreviations, as these // could be initials. if (Status->CouldBeName) { if (sss > s && sss[-1] >= 'A' && sss[-1] <= 'Z' && (sss == s + 1 || isspace (sss[-2]))) { // Is something like an initial! No action // needed. } else { for (j = 0; j < sizeof (Honorifics) / sizeof (Honorifics[0]); j++) { for (siv = sss - 1, sv = Honorifics[j]; siv >= s && *sv; siv--, sv++) if (*siv != *sv) break; if (*sv == '\0') break; } if (j >= sizeof (Honorifics) / sizeof (Honorifics[0])) Status->CouldBeName = 0; } } // Finally, we believe that we do need to add the // extra space. if (!Status->CouldBeName && !Dataset->SingleSpace) { if (AddMarkup (Dataset, Status->LineInfo[0].Offset + ss - s, MarkNbsp, 0)) goto DiskError; } } } } } } if (ss != s && !isspace (ss[-1])) Status->LastFirstSpace = Status->LineInfo[0].Offset + ss - s; return (0); DiskError: return (5); }