/// ------------------------------------------------------------------------------------ /// <summary> /// Given the information of the currently open quotation marks, process the next encountered /// quotation mark, updating the information and generating errors where appropriate. /// </summary> /// <param name="qmTok">The quotation mark token being processed</param> /// <param name="openQuotes">The currently open quotes</param> /// ------------------------------------------------------------------------------------ private void CheckQuote(QuotationMarkToken qmtok, OpenQuotes openQuotes) { GenerateTraceMsg(qmtok, string.Format(qmtok.IsOpener ? Localize("Level {0} quote opened") : Localize("Level {0} quote closed"), (qmtok.IsOpener ? openQuotes.Level + 1 : openQuotes.Level))); if (m_qmCategorizer.IsMarkForLevel(qmtok.Tts.Text, openQuotes.Level + 1) && qmtok.IsOpener) { // The quote is opened properly openQuotes.Level++; openQuotes.Openers.Add(qmtok); return; } else if (m_qmCategorizer.IsMarkForLevel(qmtok.Tts.Text, openQuotes.Level) && (!qmtok.IsOpener || m_qmCategorizer.OpeningAndClosingAreIdentical(openQuotes.Level))) { // The quote is closed properly openQuotes.Level--; openQuotes.Openers.RemoveAt(openQuotes.Level); return; } int possibleQuoteMarkLevel = m_qmCategorizer.Level(qmtok.Tts.Text, openQuotes.Level, qmtok.IsOpener); if (m_fFoundMissingContinuer) { //Debug.Assert(openQuotes.Level == openQuotes.Openers.Count); Debug.Assert(possibleQuoteMarkLevel != 0); int newLevel = qmtok.IsOpener ? possibleQuoteMarkLevel : possibleQuoteMarkLevel - 1; if (newLevel < openQuotes.Openers.Count) { while (openQuotes.Openers.Count > newLevel) openQuotes.Openers.RemoveAt(openQuotes.Openers.Count - 1); } else if (newLevel > openQuotes.Openers.Count) { while (openQuotes.Openers.Count < newLevel) openQuotes.Openers.Add("Missing Quote"); } openQuotes.Level = newLevel; if (qmtok.IsOpener) openQuotes.Openers.Add(qmtok); else if (openQuotes.Openers.Count > 0 && openQuotes.Openers.Count > openQuotes.Level) openQuotes.Openers.RemoveAt(openQuotes.Level); return; } else if (!m_qmCategorizer.TopLevelClosingExists && possibleQuoteMarkLevel == 1 && openQuotes.Level == 1) { // Opens a top-level quote when top-level closing quotes do not exist openQuotes.Openers.RemoveAt(0); openQuotes.Openers.Add(qmtok); return; } else if (possibleQuoteMarkLevel > openQuotes.Level && !qmtok.IsOpener) { // The quote was closed, but was not opened if (!m_qmCategorizer.CollapseAdjacentQuotes || openQuotes.MostRecent == null) { ReportError(qmtok, string.Format(Localize(m_noOpenerMsg), possibleQuoteMarkLevel)); } return; } else if (possibleQuoteMarkLevel > openQuotes.Level + 1 && qmtok.IsOpener) { // The opener for the quote belongs to a quote level that is too high ReportError(qmtok, string.Format( Localize("Unexpected opening mark: level {0}"), possibleQuoteMarkLevel)); // Add missing tokens for skipped levels while (openQuotes.Openers.Count < possibleQuoteMarkLevel - 1) openQuotes.Openers.Add("Missing Quote"); openQuotes.Level = possibleQuoteMarkLevel; openQuotes.Openers.Add(qmtok); return; } else if (possibleQuoteMarkLevel <= openQuotes.Level && qmtok.IsOpener) { // Opens a quote at the level already open or at too far out a level for (int i = openQuotes.Level; i >= possibleQuoteMarkLevel; i--) { if (!(openQuotes.Openers[i - 1] is QToken)) continue; ReportError(openQuotes.Openers[i - 1] as QToken, string.Format(m_noCloserMsg, i)); } openQuotes.Openers.RemoveRange(possibleQuoteMarkLevel - 1, openQuotes.Level - possibleQuoteMarkLevel + 1); openQuotes.Level = possibleQuoteMarkLevel; openQuotes.Openers.Add(qmtok); return; } // A quote outside the current one is closed before the current one for (int i = possibleQuoteMarkLevel; i < openQuotes.Level; i++) { if (!(openQuotes.Openers[i] is QToken)) continue; ReportError(openQuotes.Openers[i] as QToken, string.Format(m_noCloserMsg, i + 1)); } openQuotes.Openers.RemoveRange(possibleQuoteMarkLevel - 1, openQuotes.Level - possibleQuoteMarkLevel); openQuotes.Level = possibleQuoteMarkLevel - 1; }
/// ------------------------------------------------------------------------------------ /// <summary> /// If the token starts a typographic paragraph, store it as a paragraph-start token and /// highlight (shows up on user interface) its text. Otherwise, if the token is /// a quotation mark (either opening or closing, as defined by the quotation /// categorizer), store it as a quotation mark token. /// </summary> /// <param name="tok">The token being processed</param> /// ------------------------------------------------------------------------------------ internal void ProcessToken(ITextToken tok, VerseTextToken verseTok) { if (tok == null) throw new ArgumentNullException("tok"); Debug.Assert(!(tok is VerseTextToken)); if (tok.IsParagraphStart) { TextTokenSubstring tts = new TextTokenSubstring(tok, 0, 0); ParaStartToken pstok = new ParaStartToken(tts, tok.ParaStyleName); m_quotationRelatedTokens.Add(pstok); } AddTextToParaStartTokens(tok); // Find the first non whitespace, non quotation mark character in the token's // text. This will be used in the following loop to determine what quotation // marks precede all other characters in the token (i.e. what quotation marks // begin the paragraph and are possible continuers). Match match = m_regExNonQuotes.Match(tok.Text); int iFirstNoneQMarkChar = (match.Success ? match.Index : -1); // Now find all the quotation marks in the token's text. MatchCollection mc = m_regExQuotes.Matches(tok.Text); // Go through all the quotation marks found, creating quotation tokens // for each. foreach (Match m in mc) { TextTokenSubstring tts = new TextTokenSubstring(tok, m.Index, m.Length); bool fIsParaStart = verseTok != null ? verseTok.IsParagraphStart : tok.IsParagraphStart; bool fIsOpener = m_qmCategorizer.IsInitialPunctuation(tts.Text); bool fPossibleContinuer = (m.Index < iFirstNoneQMarkChar && fIsParaStart); QuotationMarkToken qmt = new QuotationMarkToken(tts, m_qmCategorizer, fIsOpener, fPossibleContinuer); m_quotationRelatedTokens.Add(qmt); } }