internal Character(string characterId, string localizedCharacterId = null, string alias = null, string localizedAlias = null, bool projectSpecific = true) { m_characterId = CharacterVerseData.IsCharacterOfType(characterId, CharacterVerseData.StandardCharacter.Narrator) ? s_narrator.CharacterId : characterId; m_localizedCharacterId = localizedCharacterId ?? characterId; m_alias = String.IsNullOrWhiteSpace(alias) ? null : alias; m_localizedAlias = String.IsNullOrWhiteSpace(localizedAlias) ? null : localizedAlias; m_projectSpecific = projectSpecific; }
internal bool AddToReservedGroupIfAppropriate(string characterId) { if (CharacterVerseData.IsCharacterOfType(characterId, CharacterVerseData.StandardCharacter.Narrator)) { AddToBestNarratorGroup(characterId); return(true); } if (CharacterVerseData.IsCharacterStandard(characterId, false)) { if (ExtraBiblicalGroup != null) { ExtraBiblicalGroup.CharacterIds.Add(characterId); return(true); } } return(false); }
public BlockMatchup(BookScript vernacularBook, int iBlock, Action <PortionScript> splitBlocks, Func <VerseRef, bool> isOkayToBreakAtVerse, IReferenceLanguageInfo heSaidProvider) { m_vernacularBook = vernacularBook; int bookNum = BCVRef.BookToNumber(m_vernacularBook.BookId); m_referenceLanguageInfo = heSaidProvider; var blocks = m_vernacularBook.GetScriptBlocks(); var originalAnchorBlock = blocks[iBlock]; var blocksForVersesCoveredByBlock = vernacularBook.GetBlocksForVerse(originalAnchorBlock.ChapterNumber, originalAnchorBlock.InitialStartVerseNumber).ToList(); m_iStartBlock = iBlock - blocksForVersesCoveredByBlock.IndexOf(originalAnchorBlock); while (!blocksForVersesCoveredByBlock.First().StartsAtVerseStart&& blocksForVersesCoveredByBlock.First().InitialStartVerseNumber < originalAnchorBlock.InitialStartVerseNumber) { var prepend = vernacularBook.GetBlocksForVerse(originalAnchorBlock.ChapterNumber, blocksForVersesCoveredByBlock.First().InitialStartVerseNumber).ToList(); prepend.RemoveAt(prepend.Count - 1); m_iStartBlock -= prepend.Count; blocksForVersesCoveredByBlock.InsertRange(0, prepend); } int iLastBlock = m_iStartBlock + blocksForVersesCoveredByBlock.Count - 1; int i = iLastBlock; AdvanceToCleanVerseBreak(blocks, verseNum => { return(isOkayToBreakAtVerse(new VerseRef(bookNum, originalAnchorBlock.ChapterNumber, verseNum))); }, ref i); if (i > iLastBlock) { blocksForVersesCoveredByBlock.AddRange(blocks.Skip(iLastBlock + 1).Take(i - iLastBlock)); } while (CharacterVerseData.IsCharacterOfType(blocksForVersesCoveredByBlock.Last().CharacterId, CharacterVerseData.StandardCharacter.ExtraBiblical)) { blocksForVersesCoveredByBlock.RemoveAt(blocksForVersesCoveredByBlock.Count - 1); } m_portion = new PortionScript(vernacularBook.BookId, blocksForVersesCoveredByBlock.Select(b => b.Clone())); CorrelatedAnchorBlock = m_portion.GetScriptBlocks()[iBlock - m_iStartBlock]; if (splitBlocks != null) { int origCount = m_portion.GetScriptBlocks().Count; splitBlocks(m_portion); m_numberOfBlocksAddedBySplitting = m_portion.GetScriptBlocks().Count - origCount; } }
public void DataIntegrity_RequiredFieldsHaveValidFormatAndThereAreNoDuplicateLines() { Regex regex = new Regex(kRegexBCV + "(?<character>[^\t]+)\t(?<delivery>[^\t]*)\t(?<alias>[^\t]*)\t(?<type>" + typeof(QuoteType).GetRegexEnumValuesString() + ")\t(?<defaultCharacter>[^\t]*)\t(?<parallelPassageRef>[^\t]*)$", RegexOptions.Compiled); Regex extraSpacesRegex = new Regex("^ |\t | \t| $", RegexOptions.Compiled); var set = new HashSet <string>(); ISet <CharacterVerse> uniqueCharacterVerses = new HashSet <CharacterVerse>(); foreach (var line in AllDataLines) { var match = regex.Match(line); Assert.IsTrue(match.Success, "Failed to match line: " + line); var bookId = match.Result("${bookId}"); var bookNum = BCVRef.BookToNumber(bookId); Assert.IsTrue(bookNum > 0, "Line: " + line); Assert.IsTrue(bookNum <= 66, "Line: " + line); var chapterAsString = match.Result("${chapter}"); var chapter = Int32.Parse(chapterAsString); Assert.IsTrue(chapter > 0, "Line: " + line); Assert.IsTrue(chapter <= ScrVers.English.GetLastChapter(bookNum), "Line: " + line); var verseAsString = match.Result("${verse}"); var verse = Int32.Parse(verseAsString); Assert.IsTrue(verse > 0 || verse == 0 && bookId == "PSA", "Line: " + line); Assert.IsTrue(verse <= ScrVers.English.GetLastVerse(bookNum, chapter), "Line: " + line); var sEndVerse = match.Result("${endVerse}"); if (!string.IsNullOrEmpty(sEndVerse)) { var endVerse = Int32.Parse(sEndVerse); Assert.IsTrue(endVerse > verse, "Line: " + line); Assert.IsTrue(endVerse <= 152, "Line: " + line); } var character = match.Result("${character}"); var alias = match.Result("${alias}"); if (!string.IsNullOrEmpty(alias)) { Assert.AreNotEqual(character, alias, "Line: " + line); } var defaultCharacter = match.Result("${defaultCharacter}"); if (!string.IsNullOrEmpty(defaultCharacter)) { Assert.AreNotEqual(character, defaultCharacter, "Line: " + line); Assert.IsFalse(defaultCharacter.Contains("/"), $"Line: {line} has a default character which is a multi-character ID."); } string typeAsString = match.Result("${type}"); if (CharacterVerseData.IsCharacterOfType(character, CharacterVerseData.StandardCharacter.Narrator)) { Assert.AreNotEqual("Dialogue", typeAsString, "Line: " + line); } var matchResult = match.Result("$&"); Assert.IsTrue(set.Add(matchResult), "Duplicate line: " + matchResult); Assert.IsTrue(QuoteType.TryParse(typeAsString, out QuoteType type)); foreach (var bcvRef in CharacterVerseData.GetAllVerses(new [] { bookId, chapterAsString, verseAsString }, () => throw new Exception("This should never happen"))) { var cv = new CharacterVerse(bcvRef, character, match.Result("${delivery}"), alias, false, type, defaultCharacter); Assert.IsTrue(uniqueCharacterVerses.Add(cv), "Line is equivalent to another line even though they are not identical: " + matchResult); } var extraSpacesMatch = extraSpacesRegex.Match(line); Assert.IsFalse(extraSpacesMatch.Success, "Line with extra space(s): " + line); } }
/// <summary>Calculate the minimum number of blocks between two character ids in given collection</summary> public MinimumProximity CalculateMinimumProximity(ISet <string> characterIdsToCalculate) { if (!characterIdsToCalculate.Any()) { return(new MinimumProximity(Int32.MaxValue, null, null, null, null)); } RelatedCharactersData relChar = RelatedCharactersData.Singleton; bool foundFirst = false; int currentBlockCount = 0; int minBlockProximity = Int32.MaxValue; string prevCharacterId = null; ISet <string> prevMatchingCharacterIds = null; BookScript firstBook = null; Block firstBlock = null; BookScript secondBook = null; Block secondBlock = null; BookScript prevBook = null; Block prevBlock = null; bool breakOutOfBothLoops = false; bool calculateAnyRelatedCharacters = characterIdsToCalculate.Any(c => relChar.HasMatchingCharacterIdsOfADifferentAge(c)); foreach (var book in m_booksToConsider) { if (breakOutOfBothLoops) { break; } var countVersesRatherThanBlocks = !m_referenceText.HasContentForBook(book.BookId); var treatAsSameCharacter = m_considerSameExtrabiblicalCharacter[book]; if (m_narrationByAuthor) { var author = BiblicalAuthors.GetAuthorOfBook(book.BookId); if (author.CombineAuthorAndNarrator && !treatAsSameCharacter.Any(set => set.Contains(author.Name))) { if (!m_strictlyAdhereToNarratorPreferences || (characterIdsToCalculate.Contains(book.NarratorCharacterId) && characterIdsToCalculate.Contains(author.Name))) { HashSet <string> charactersToTreatAsOneWithNarrator = treatAsSameCharacter.FirstOrDefault(set => set.Contains(book.NarratorCharacterId)); if (charactersToTreatAsOneWithNarrator == null) { charactersToTreatAsOneWithNarrator = new HashSet <string>(); treatAsSameCharacter.Add(charactersToTreatAsOneWithNarrator); charactersToTreatAsOneWithNarrator.Add(book.NarratorCharacterId); charactersToTreatAsOneWithNarrator.Add(author.Name); } else { charactersToTreatAsOneWithNarrator.Add(author.Name); } } } } // We don't want to treat book ends as being directly adjacent but not infinitely distant, either. currentBlockCount += kDefaultMinimumBlocks * 5 / 3; // The amount of padding is somewhat arbitrary. foreach (var block in book.Blocks) { var characterId = block.CharacterIdInScript; // The original logic here was NOT split out for the single character vs. multiple character scenarios. // This made the code much more readable, but the performance was atrocious since we were creating // extra hashsets and doing extra intersects. Please consider the performance implications of any // changes to this code. (I'm sure it could be optimized further, too...) ISet <string> matchingCharacterIds = null; if (calculateAnyRelatedCharacters && relChar.TryGetMatchingCharacterIdsOfADifferentAge(characterId, out matchingCharacterIds)) { if (matchingCharacterIds.Count == 1) { matchingCharacterIds = null; } } else { foreach (var set in treatAsSameCharacter) { if (set.Contains(characterId)) { matchingCharacterIds = set; break; } } } if (matchingCharacterIds == null) { if ((prevMatchingCharacterIds == null && prevCharacterId == characterId) || (prevMatchingCharacterIds != null && prevMatchingCharacterIds.Contains(characterId))) { currentBlockCount = 0; prevBook = book; prevBlock = block; } else if (characterIdsToCalculate.Contains(characterId) && (!CharacterVerseData.IsCharacterOfType(characterId, CharacterVerseData.StandardCharacter.Narrator) || prevCharacterId == null || !CharacterVerseData.IsCharacterOfType(prevCharacterId, CharacterVerseData.StandardCharacter.Narrator))) { if (ProcessDifferentCharacter(book, block, characterId, matchingCharacterIds, ref foundFirst, ref currentBlockCount, ref minBlockProximity, ref firstBook, ref prevBook, ref firstBlock, ref prevBlock, ref secondBook, ref secondBlock, ref breakOutOfBothLoops, ref prevCharacterId, ref prevMatchingCharacterIds)) { break; } } else { IncrementCount(countVersesRatherThanBlocks, block, ref currentBlockCount); } } else if (prevMatchingCharacterIds != null && matchingCharacterIds.Intersect(prevMatchingCharacterIds).Any()) { currentBlockCount = 0; prevBook = book; prevBlock = block; prevMatchingCharacterIds = matchingCharacterIds; } else if (characterIdsToCalculate.Intersect(matchingCharacterIds).Any()) { if (ProcessDifferentCharacter(book, block, characterId, matchingCharacterIds, ref foundFirst, ref currentBlockCount, ref minBlockProximity, ref firstBook, ref prevBook, ref firstBlock, ref prevBlock, ref secondBook, ref secondBlock, ref breakOutOfBothLoops, ref prevCharacterId, ref prevMatchingCharacterIds)) { break; } } else { IncrementCount(countVersesRatherThanBlocks, block, ref currentBlockCount); } } } return(new MinimumProximity(minBlockProximity, firstBook, secondBook, firstBlock, secondBlock)); }
public void DataIntegrity_RequiredFieldsHaveValidFormatAndThereAreNoDuplicateLines() { Regex regex = new Regex("^(?<bookId>...)\t(?<chapter>\\d+)\t(?<verse>\\d+)(-(?<endVerse>\\d+))?\t(?<character>[^\t]+)\t(?<delivery>[^\t]*)\t(?<alias>[^\t]*)\t(?<type>" + typeof(QuoteType).GetRegexEnumValuesString() + ")\t(?<defaultCharacter>[^\t]*)\t(?<parallelPassageRef>[^\t]*)$", RegexOptions.Compiled); Regex extraSpacesRegex = new Regex("^ |\t | \t| $", RegexOptions.Compiled); string[] allLines = Resources.CharacterVerseData.Split(new[] { "\r", "\n" }, StringSplitOptions.RemoveEmptyEntries); var set = new HashSet <string>(); foreach (var line in allLines.Skip(1)) { if (line.StartsWith("#")) { continue; } var match = regex.Match(line); Assert.IsTrue(match.Success, "Failed to match line: " + line); var bookId = match.Result("${bookId}"); var bookNum = BCVRef.BookToNumber(bookId); Assert.IsTrue(bookNum > 0, "Line: " + line); Assert.IsTrue(bookNum <= 66, "Line: " + line); var chapter = Int32.Parse(match.Result("${chapter}")); Assert.IsTrue(chapter > 0, "Line: " + line); Assert.IsTrue(chapter <= 150, "Line: " + line); var verse = Int32.Parse(match.Result("${verse}")); Assert.IsTrue(verse > 0 || verse == 0 && bookId == "PSA", "Line: " + line); Assert.IsTrue(verse <= 152, "Line: " + line); var sEndVerse = match.Result("${endVerse}"); if (!string.IsNullOrEmpty(sEndVerse)) { var endVerse = Int32.Parse(sEndVerse); Assert.IsTrue(endVerse > verse, "Line: " + line); Assert.IsTrue(endVerse <= 152, "Line: " + line); } var character = match.Result("${character}"); var alias = match.Result("${alias}"); if (!string.IsNullOrEmpty(alias)) { Assert.AreNotEqual(character, alias, "Line: " + line); } var defaultCharacter = match.Result("${defaultCharacter}"); if (!string.IsNullOrEmpty(defaultCharacter)) { Assert.AreNotEqual(character, defaultCharacter, "Line: " + line); } if (CharacterVerseData.IsCharacterOfType(character, CharacterVerseData.StandardCharacter.Narrator)) { Assert.AreNotEqual("Dialogue", match.Result("${type}"), "Line: " + line); } var matchResult = match.Result("$&"); Assert.IsTrue(set.Add(matchResult), "Duplicate line: " + matchResult); var extraSpacesMatch = extraSpacesRegex.Match(line); Assert.IsFalse(extraSpacesMatch.Success, "Line with extra space(s): " + line); } }
/// <summary> /// Parse through the given blocks character by character to determine where we need to break based on quotes /// </summary> /// <returns>A new enumerable of blocks broken up for quotes</returns> public IEnumerable <Block> Parse() { m_outputBlocks = new List <Block>(); var sb = new StringBuilder(); m_quoteLevel = 0; bool blockEndedWithSentenceEndingPunctuation = false; Block blockInWhichDialogueQuoteStarted = null; bool potentialDialogueContinuer = false; bool inPairedFirstLevelQuote = false; bool pendingColon = false; foreach (Block block in m_inputBlocks) { if (block.UserConfirmed) { throw new InvalidOperationException("Should not be parsing blocks that already have user-decisions applied."); } bool thisBlockStartsWithAContinuer = false; if (block.CharacterIsStandard && !block.CharacterIs(m_bookId, CharacterVerseData.StandardCharacter.Narrator)) { m_nextBlockContinuesQuote = false; m_outputBlocks.Add(block); continue; } if (m_quoteLevel == 1 && blockInWhichDialogueQuoteStarted != null && (!IsNormalParagraphStyle(blockInWhichDialogueQuoteStarted.StyleTag) || blockEndedWithSentenceEndingPunctuation || !block.IsFollowOnParagraphStyle)) { DecrementQuoteLevel(); inPairedFirstLevelQuote = false; blockInWhichDialogueQuoteStarted = null; m_nextBlockContinuesQuote = potentialDialogueContinuer = !string.IsNullOrEmpty(s_quoteSystem.QuotationDashEndMarker) || (s_quoteSystem.NormalLevels.Count > 0 && s_quoteSystem.NormalLevels[0].Continue != s_quoteSystem.NormalLevels[0].Open); } m_workingBlock = new Block(block.StyleTag, block.ChapterNumber, block.InitialStartVerseNumber, block.InitialEndVerseNumber) { IsParagraphStart = block.IsParagraphStart }; bool atBeginningOfBlock = true; foreach (BlockElement element in block.BlockElements) { var scriptText = element as ScriptText; if (scriptText == null) { // Add the element to our working list in case we need to move it to the next block (see MoveTrailingElementsIfNecessary) m_nonScriptTextBlockElements.Add(element); var verseElement = element as Verse; if (verseElement != null) { if (!m_workingBlock.BlockElements.Any()) { SetBlockInitialVerseFromVerseElement(verseElement); } if (m_possibleCharactersForCurrentQuote.Any()) { m_possibleCharactersForCurrentQuote = m_possibleCharactersForCurrentQuote.Intersect( m_cvInfo.GetCharacters(m_bookNum, m_workingBlock.ChapterNumber, verseElement.StartVerse, verseElement.EndVerse, versification: m_versification).Select(cv => cv.Character)).ToList(); if (!m_possibleCharactersForCurrentQuote.Any()) { foreach (var multiBlock in m_currentMultiBlockQuote) { multiBlock.MultiBlockQuote = MultiBlockQuote.None; multiBlock.CharacterId = CharacterVerseData.kUnknownCharacter; multiBlock.Delivery = null; } m_currentMultiBlockQuote.Clear(); FlushStringBuilderAndBlock(sb, block.StyleTag, m_quoteLevel > 0, true); SetBlockInitialVerseFromVerseElement(verseElement); m_quoteLevel = 0; m_nextBlockContinuesQuote = false; } } } if (sb.Length > 0) { // Paragraph starts with non-word-forming characters followed by verse number m_workingBlock.BlockElements.Add(new ScriptText(sb.ToString())); } m_workingBlock.BlockElements.Add(element); continue; } sb.Clear(); var content = scriptText.Content; var pos = 0; while (pos < content.Length) { if (pendingColon) { if (s_quoteSystem.NormalLevels.Count > 0) { var matchFirstLevelOpen = m_regexStartsWithFirstLevelOpener.Match(content, pos); if (matchFirstLevelOpen.Success && matchFirstLevelOpen.Index == pos && (pos > 0 || !m_regexHasFirstLevelClose.Match(content, pos + matchFirstLevelOpen.Length).Success)) { DecrementQuoteLevel(); } else { blockInWhichDialogueQuoteStarted = block; } } else { blockInWhichDialogueQuoteStarted = block; } pendingColon = false; } var regex = m_regexes[m_quoteLevel >= m_regexes.Count ? m_regexes.Count - 1 : m_quoteLevel]; var match = regex.Match(content, pos); if (match.Success) { var specialOpeningPunctuationLen = 0; if (match.Index > pos) { specialOpeningPunctuationLen = GetSpecialOpeningPunctuation(content).Length; sb.Append(content.Substring(pos, match.Index - pos)); } pos = match.Index + match.Length; var token = match.Value; if (specialOpeningPunctuationLen > 0) { // this is only the beginning of a block if there are no characters between the special opening punctuation and the quotation mark atBeginningOfBlock &= string.IsNullOrWhiteSpace(content.Substring(specialOpeningPunctuationLen, match.Index - specialOpeningPunctuationLen)); } else { atBeginningOfBlock &= match.Index == 0; } if (atBeginningOfBlock) { if (specialOpeningPunctuationLen == 0) { atBeginningOfBlock = false; } if (m_quoteLevel > 0 && s_quoteSystem.NormalLevels.Count > m_quoteLevel - 1 && token.StartsWith(ContinuerForCurrentLevel)) { thisBlockStartsWithAContinuer = true; int i = ContinuerForCurrentLevel.Length; while (i < token.Length && IsWhiteSpace(token[i])) { i++; } sb.Append(token.Substring(0, i)); if (token.Length == i) { continue; } token = token.Substring(i); } if (m_quoteLevel == 0 && s_quoteSystem.NormalLevels.Count > 0) { string continuerForNextLevel = ContinuerForNextLevel; if (string.IsNullOrEmpty(continuerForNextLevel) || !token.StartsWith(continuerForNextLevel)) { potentialDialogueContinuer = false; } else { thisBlockStartsWithAContinuer = true; if (continuerForNextLevel != OpenerForNextLevel) { IncrementQuoteLevel(); sb.Append(token); continue; } } } else { potentialDialogueContinuer = false; } } if (!thisBlockStartsWithAContinuer) { potentialDialogueContinuer = false; } if ((m_quoteLevel > 0) && (s_quoteSystem.NormalLevels.Count > 0) && token.StartsWith(CloserForCurrentLevel) && blockInWhichDialogueQuoteStarted == null && !ProbablyAnApostrophe(content, match.Index) && inPairedFirstLevelQuote) { sb.Append(token); DecrementQuoteLevel(); if (m_quoteLevel == 0) { if (potentialDialogueContinuer && OpenerForNextLevel == ContinuerForNextLevel) { foreach (var multiBlock in m_currentMultiBlockQuote) { multiBlock.MultiBlockQuote = MultiBlockQuote.None; } m_currentMultiBlockQuote.Clear(); m_nextBlockContinuesQuote = false; } if (m_ignoringNarratorQuotation) { m_ignoringNarratorQuotation = false; } else { FlushStringBuilderAndBlock(sb, block.StyleTag, true); } potentialDialogueContinuer = false; inPairedFirstLevelQuote = false; } } else if (s_quoteSystem.NormalLevels.Count > m_quoteLevel && token.StartsWith(OpenerForNextLevel) && blockInWhichDialogueQuoteStarted == null) { if (m_quoteLevel == 0 && (sb.Length > 0 || m_workingBlock.BlockElements.OfType <ScriptText>().Any(e => !e.Content.All(IsPunctuation)))) { var characters = m_cvInfo.GetCharacters(m_bookNum, m_workingBlock.ChapterNumber, m_workingBlock.LastVerse.StartVerse, m_workingBlock.LastVerse.EndVerse, versification: m_versification).ToList(); // PG-814: If the only character for this verse is a narrator "Quotation", then do not treat it as speech. if (characters.Count == 1 && characters[0].QuoteType == QuoteType.Quotation && CharacterVerseData.IsCharacterOfType(characters[0].Character, CharacterVerseData.StandardCharacter.Narrator)) { m_ignoringNarratorQuotation = true; } else { FlushStringBuilderAndBlock(sb, block.StyleTag, false); } } sb.Append(token); IncrementQuoteLevel(); inPairedFirstLevelQuote = true; } else if (m_quoteLevel == 0 && s_quoteSystem.QuotationDashMarker != null && token.StartsWith(s_quoteSystem.QuotationDashMarker)) { blockEndedWithSentenceEndingPunctuation = false; pendingColon = token.StartsWith(":"); if (pendingColon) { sb.Append(token); } FlushStringBuilderAndBlock(sb, block.StyleTag, false); if (!pendingColon) { blockInWhichDialogueQuoteStarted = block; sb.Append(token); } IncrementQuoteLevel(); inPairedFirstLevelQuote = false; } else if (potentialDialogueContinuer || (m_quoteLevel == 1 && blockInWhichDialogueQuoteStarted != null)) { if (!string.IsNullOrEmpty(s_quoteSystem.QuotationDashEndMarker) && token.StartsWith(s_quoteSystem.QuotationDashEndMarker, StringComparison.Ordinal)) { DecrementQuoteLevel(); potentialDialogueContinuer = false; blockInWhichDialogueQuoteStarted = null; FlushStringBuilderAndBlock(sb, block.StyleTag, true); } else { blockEndedWithSentenceEndingPunctuation = !m_workingBlock.IsFollowOnParagraphStyle && token.EndsWithSentenceEndingPunctuation(); } sb.Append(token); } else { sb.Append(token); } } else { var remainingText = content.Substring(pos); if (m_quoteLevel == 1 && block == blockInWhichDialogueQuoteStarted && remainingText.EndsWithSentenceEndingPunctuation()) { blockEndedWithSentenceEndingPunctuation = true; } sb.Append(remainingText); break; } } FlushStringBuilderToBlockElement(sb); if (sb.Length > 0) { if (!block.IsParagraphStart) { m_outputBlocks.Last().BlockElements.OfType <ScriptText>().Last().Content += sb.ToString(); } } } FlushBlock(block.StyleTag, m_quoteLevel > 0); } if (blockInWhichDialogueQuoteStarted != null || !inPairedFirstLevelQuote) { m_nextBlockContinuesQuote = false; } if (m_nextBlockContinuesQuote) { foreach (var multiBlock in m_currentMultiBlockQuote) { multiBlock.MultiBlockQuote = MultiBlockQuote.None; multiBlock.CharacterId = CharacterVerseData.kUnknownCharacter; multiBlock.Delivery = null; } } else { // In case the last set of blocks were a multi-block quote ProcessMultiBlock(); } return(m_outputBlocks); }
public BlockMatchup(BookScript vernacularBook, int iBlock, Action <PortionScript> splitBlocks, Func <VerseRef, bool> isOkayToBreakAtVerse, IReferenceLanguageInfo heSaidProvider, uint predeterminedBlockCount = 0) { m_vernacularBook = vernacularBook; int bookNum = BCVRef.BookToNumber(m_vernacularBook.BookId); m_referenceLanguageInfo = heSaidProvider; var blocks = m_vernacularBook.GetScriptBlocks(); var originalAnchorBlock = blocks[iBlock]; if (predeterminedBlockCount == 0) { var blocksForVersesCoveredByBlock = vernacularBook.GetBlocksForVerse(originalAnchorBlock.ChapterNumber, originalAnchorBlock.InitialStartVerseNumber).ToList(); var indexOfAnchorBlockInVerse = blocksForVersesCoveredByBlock.IndexOf(originalAnchorBlock); if (indexOfAnchorBlockInVerse < 0) { Logger.WriteEvent($"Anchor block not found in verse: {m_vernacularBook.BookId} {originalAnchorBlock.ChapterNumber}:" + $"{originalAnchorBlock.InitialStartVerseNumber} Verse apparently occurs more than once in the Scripture text."); // REVIEW: This logic assumes that the repeated verse is wholly contained in this onwe block. blocksForVersesCoveredByBlock = new List <Block>() { originalAnchorBlock }; indexOfAnchorBlockInVerse = 0; } m_iStartBlock = iBlock - indexOfAnchorBlockInVerse; while (m_iStartBlock > 0) { if (blocksForVersesCoveredByBlock.First().InitialStartVerseNumber < originalAnchorBlock.InitialStartVerseNumber && !blocksForVersesCoveredByBlock.First().StartsAtVerseStart) { var prepend = vernacularBook.GetBlocksForVerse(originalAnchorBlock.ChapterNumber, blocksForVersesCoveredByBlock.First().InitialStartVerseNumber).ToList(); prepend.RemoveAt(prepend.Count - 1); m_iStartBlock -= prepend.Count; blocksForVersesCoveredByBlock.InsertRange(0, prepend); } if (m_iStartBlock == 0 || isOkayToBreakAtVerse(new VerseRef(bookNum, originalAnchorBlock.ChapterNumber, blocksForVersesCoveredByBlock.First().InitialStartVerseNumber))) { break; } m_iStartBlock--; blocksForVersesCoveredByBlock.Insert(0, blocks[m_iStartBlock]); } int iLastBlock = m_iStartBlock + blocksForVersesCoveredByBlock.Count - 1; int i = iLastBlock; AdvanceToCleanVerseBreak(blocks, verseNum => isOkayToBreakAtVerse(new VerseRef(bookNum, originalAnchorBlock.ChapterNumber, verseNum)), ref i); if (i > iLastBlock) { blocksForVersesCoveredByBlock.AddRange(blocks.Skip(iLastBlock + 1).Take(i - iLastBlock)); } while (CharacterVerseData.IsCharacterOfType(blocksForVersesCoveredByBlock.Last().CharacterId, CharacterVerseData.StandardCharacter.ExtraBiblical)) { blocksForVersesCoveredByBlock.RemoveAt(blocksForVersesCoveredByBlock.Count - 1); } m_portion = new PortionScript(vernacularBook.BookId, blocksForVersesCoveredByBlock.Select(b => b.Clone())); try { CorrelatedAnchorBlock = m_portion.GetScriptBlocks()[iBlock - m_iStartBlock]; } catch (Exception ex) { Logger.WriteEvent(ex.Message); Logger.WriteEvent($"iBlock = {iBlock}; m_iStartBlock = {m_iStartBlock}"); foreach (var block in m_portion.GetScriptBlocks()) { Logger.WriteEvent($"block = {block}"); } throw; } } else { m_iStartBlock = iBlock; m_portion = new PortionScript(vernacularBook.BookId, vernacularBook.GetScriptBlocks().Skip(iBlock).Take((int)predeterminedBlockCount).Select(b => b.Clone())); CorrelatedAnchorBlock = m_portion.GetScriptBlocks().First(); } if (splitBlocks != null) { int origCount = m_portion.GetScriptBlocks().Count; splitBlocks(m_portion); m_numberOfBlocksAddedBySplitting = m_portion.GetScriptBlocks().Count - origCount; } }
/// <summary>Calculate the minimum number of blocks between two character ids in given collection</summary> public MinimumProximity CalculateMinimumProximity(ISet <string> characterIdsToCalculate) { if (!characterIdsToCalculate.Any()) { return(new MinimumProximity(Int32.MaxValue, null, null, null, null)); } RelatedCharactersData relChar = RelatedCharactersData.Singleton; bool foundFirst = false; int currentBlockCount = 0; int minProximity = Int32.MaxValue; string prevCharacterId = null; ISet <string> prevMatchingCharacterIds = null; BookScript firstBook = null; Block firstBlock = null; BookScript secondBook = null; Block secondBlock = null; BookScript prevBook = null; Block prevBlock = null; bool breakOutOfBothLoops = false; bool calculateAnyRelatedCharacters = characterIdsToCalculate.Any(c => relChar.HasMatchingCharacterIdsOfADifferentAge(c)); foreach (var book in m_booksToConsider) { if (breakOutOfBothLoops) { break; } var treatAsSameCharacter = m_considerSameExtrabiblicalCharacter[book]; currentBlockCount += kDefaultMinimumProximity + 20; // 20 is a pretty arbitrary "magic number" foreach (var block in book.Blocks) { var characterId = block.CharacterIdInScript; // The original logic here was NOT split out for the single character vs. multiple character scenarios. // This made the code much more readable, but the performance was atrocious since we were creating // extra hashsets and doing extra intersects. Please consider the performance implications of any // changes to this code. (I'm sure it could be optimized further, too...) ISet <string> matchingCharacterIds = null; if (calculateAnyRelatedCharacters && relChar.TryGetMatchingCharacterIdsOfADifferentAge(characterId, out matchingCharacterIds)) { if (matchingCharacterIds.Count == 1) { matchingCharacterIds = null; } } else { foreach (var kvp in treatAsSameCharacter) { if (kvp.Value == null) { continue; } if (kvp.Value.Contains((characterId))) { matchingCharacterIds = kvp.Value; break; } } } if (matchingCharacterIds == null) { if ((prevMatchingCharacterIds == null && prevCharacterId == characterId) || (prevMatchingCharacterIds != null && prevMatchingCharacterIds.Contains(characterId))) { currentBlockCount = 0; prevBook = book; prevBlock = block; } else if (characterIdsToCalculate.Contains(characterId) && (!CharacterVerseData.IsCharacterOfType(characterId, CharacterVerseData.StandardCharacter.Narrator) || prevCharacterId == null || !CharacterVerseData.IsCharacterOfType(prevCharacterId, CharacterVerseData.StandardCharacter.Narrator))) { if (ProcessDifferentCharacter(book, block, characterId, matchingCharacterIds, ref foundFirst, ref currentBlockCount, ref minProximity, ref firstBook, ref prevBook, ref firstBlock, ref prevBlock, ref secondBook, ref secondBlock, ref breakOutOfBothLoops, ref prevCharacterId, ref prevMatchingCharacterIds)) { break; } } else { currentBlockCount++; } } else if (prevMatchingCharacterIds != null && matchingCharacterIds.Intersect(prevMatchingCharacterIds).Any()) { currentBlockCount = 0; prevBook = book; prevBlock = block; } else if (characterIdsToCalculate.Intersect(matchingCharacterIds).Any()) { if (ProcessDifferentCharacter(book, block, characterId, matchingCharacterIds, ref foundFirst, ref currentBlockCount, ref minProximity, ref firstBook, ref prevBook, ref firstBlock, ref prevBlock, ref secondBook, ref secondBlock, ref breakOutOfBothLoops, ref prevCharacterId, ref prevMatchingCharacterIds)) { break; } } else { currentBlockCount++; } } } return(new MinimumProximity(minProximity, firstBook, secondBook, firstBlock, secondBlock)); }