protected override void HandleCurrentBlockChanged() { if (m_inHandleCurrentBlockChanged) { return; } m_inHandleCurrentBlockChanged = true; if (CharacterVerseData.IsCharacterExtraBiblical(CurrentBlock.CharacterId)) { throw new InvalidOperationException("Cannot attempt to match an extra-biblical block to a reference text."); } if (AttemptRefBlockMatchup) { if (CurrentReferenceTextMatchup == null || !CurrentReferenceTextMatchup.IncludesBlock(CurrentBlock)) { SetBlockMatchupForCurrentVerse(); } else { CurrentReferenceTextMatchup?.ChangeAnchor(CurrentBlock); } } base.HandleCurrentBlockChanged(); m_inHandleCurrentBlockChanged = false; }
private void CalculateAlignmentPercentage() { int totalBlocksForExport = 0; int blocksNotAlignedToReferenceText = 0; var refText = m_projectToAnalyze.ReferenceText; if (refText == null) { m_alignmentPercent = 0; return; } foreach (var book in refText.GetBooksWithBlocksConnectedToReferenceText(m_projectToAnalyze)) { var blocks = book.GetScriptBlocks(); if (!refText.CanDisplayReferenceTextForBook(book) || book.SingleVoice) { totalBlocksForExport += blocks.Count; } else { foreach (Block block in blocks) { totalBlocksForExport++; if (!CharacterVerseData.IsCharacterExtraBiblical(block.CharacterId) && !block.MatchesReferenceText) { blocksNotAlignedToReferenceText++; } } } } m_alignmentPercent = MathUtilities.PercentAsDouble(totalBlocksForExport - blocksNotAlignedToReferenceText, totalBlocksForExport); }
private void CalculateAlignmentPercentage() { int totalBlocksForExport = 0; int blocksNotAlignedToReferenceText = 0; var refText = m_projectToAnalyze.ReferenceText; if (refText == null) { m_alignmentPercent = 0; return; } // Note: Since we want this to be as efficient as possible and all we care about here is statistics, // we don't need to bother with applying narrator overrides. foreach (var book in refText.GetBooksWithBlocksConnectedToReferenceText(m_projectToAnalyze, false)) { var blocks = book.GetScriptBlocks(); if (!refText.CanDisplayReferenceTextForBook(book) || book.SingleVoice) { totalBlocksForExport += blocks.Count; } else { foreach (Block block in blocks) { totalBlocksForExport++; if (!CharacterVerseData.IsCharacterExtraBiblical(block.CharacterId) && !block.MatchesReferenceText) { blocksNotAlignedToReferenceText++; } } } } m_alignmentPercent = MathUtilities.PercentAsDouble(totalBlocksForExport - blocksNotAlignedToReferenceText, totalBlocksForExport); }
protected override void HandleCurrentBlockChanged() { if (m_inHandleCurrentBlockChanged) { return; } m_inHandleCurrentBlockChanged = true; Debug.Assert(!CharacterVerseData.IsCharacterExtraBiblical(CurrentBlock.CharacterId)); if (CurrentReferenceTextMatchup == null || !CurrentReferenceTextMatchup.IncludesBlock(CurrentBlock)) { bool doMatchup = CurrentBlock.MultiBlockQuote == MultiBlockQuote.None; if (CurrentBlock.MultiBlockQuote == MultiBlockQuote.Start) { var firstVerseInMultiBlockQuote = CurrentBlock.InitialEndVerseNumber == 0 ? CurrentBlock.InitialStartVerseNumber : CurrentBlock.InitialEndVerseNumber; var lastVerseInMultiBlockQuote = GetNthBlockInCurrentBook(GetIndicesOfQuoteContinuationBlocks(CurrentBlock).Last()).LastVerseNum; doMatchup = firstVerseInMultiBlockQuote == lastVerseInMultiBlockQuote; if (!doMatchup) { ClearBlockMatchup(); } } if (doMatchup) { SetBlockMatchupForCurrentVerse(); } } else if (CurrentReferenceTextMatchup != null) { CurrentReferenceTextMatchup.ChangeAnchor(CurrentBlock); } base.HandleCurrentBlockChanged(); m_inHandleCurrentBlockChanged = false; }
private static void FindAllScriptureBlocksThroughVerse(IReadOnlyList <Block> blockList, VerseRef endVerse, ref int i, ScrVers versification) { for (; ;) { var nextScriptureBlock = blockList.Skip(i + 1).FirstOrDefault(b => !CharacterVerseData.IsCharacterExtraBiblical(b.CharacterId)); if (nextScriptureBlock == null) { break; } var nextVerseRef = new VerseRef(endVerse.BookNum, nextScriptureBlock.ChapterNumber, nextScriptureBlock.InitialStartVerseNumber, versification); if (nextVerseRef > endVerse) { break; } i++; } }
public bool IsModified(Character newCharacter, Delivery newDelivery) { Block currentBlock = CurrentBlockInOriginal; if (CharacterVerseData.IsCharacterExtraBiblical(currentBlock.CharacterId)) { return(false); // Can't change these. } if (newCharacter == null) { return(!(currentBlock.CharacterIsUnclear || currentBlock.CharacterId == null)); } if (newCharacter.IsNarrator) { if (!currentBlock.CharacterIs(CurrentBookId, CharacterVerseData.StandardCharacter.Narrator)) { return(true); } } else if (newCharacter.CharacterId != currentBlock.CharacterId) { return(true); } if (newDelivery == null) { return(true); } if (newDelivery.IsNormal) { return(!string.IsNullOrEmpty(currentBlock.Delivery)); } return(newDelivery.Text != currentBlock.Delivery); }
public static ConcurrentDictionary <BookScript, IReadOnlyList <Block> > Unparse(IEnumerable <BookScript> books) { var blocksInBook = new ConcurrentDictionary <BookScript, IReadOnlyList <Block> >(); Parallel.ForEach(books, book => { var oldBlocks = book.GetScriptBlocks(); var newBlocks = new List <Block>(); Block currentBlock = null; foreach (var oldBlock in oldBlocks) { // is this a new chapter? if (oldBlock.IsParagraphStart || (currentBlock == null)) { if (currentBlock != null) { newBlocks.Add(currentBlock); } if (CharacterVerseData.IsCharacterExtraBiblical(oldBlock.CharacterId) && !oldBlock.UserConfirmed) { newBlocks.Add(oldBlock.Clone()); currentBlock = null; continue; } else { currentBlock = new Block(oldBlock.StyleTag, oldBlock.ChapterNumber, oldBlock.InitialStartVerseNumber, oldBlock.InitialEndVerseNumber); currentBlock.IsParagraphStart = oldBlock.IsParagraphStart; } } foreach (var element in oldBlock.BlockElements) { if (element is Verse) { currentBlock.BlockElements.Add(element.Clone()); continue; } // element is Glyssen.ScriptText // check if this text should be appended to the previous element var lastElement = currentBlock.BlockElements.LastOrDefault() as ScriptText; if (lastElement != null) { lastElement.Content += ((ScriptText)element).Content; } else { currentBlock.BlockElements.Add(element.Clone()); } } } // add the last block now if (currentBlock != null) { newBlocks.Add(currentBlock); } blocksInBook.AddOrUpdate(book, newBlocks, (script, list) => newBlocks); }); return(blocksInBook); }
private void MatchVernBlocksToReferenceTextBlocks(IReadOnlyList <Block> vernBlockList, string bookId, ScrVers vernacularVersification) { int bookNum = BCVRef.BookToNumber(bookId); var refBook = Books.Single(b => b.BookId == bookId); var refBlockList = refBook.GetScriptBlocks(); int iRefBlock = 0; // The following is a minor performance enhancement to help when just hooking up one verse's worth of blocks. // In it's current form, it doesn't work for tests that have partial reference texts that don't start at verse 1. // Also, it doesn't take versification mappings into consideration. //if (vernBlockList[0].ChapterNumber > 1) //{ // iRefBlock = refBook.GetIndexOfFirstBlockForVerse(vernBlockList[0].ChapterNumber, ); // if (iRefBlock < 0) // { // // Apparently there is no reference text for the verse(s) we're interested in. // return; // } //} for (int iVernBlock = 0; iVernBlock < vernBlockList.Count && iRefBlock < refBlockList.Count; iVernBlock++, iRefBlock++) { var currentVernBlock = vernBlockList[iVernBlock]; var currentRefBlock = refBlockList[iRefBlock]; var vernInitStartVerse = new VerseRef(bookNum, currentVernBlock.ChapterNumber, currentVernBlock.InitialStartVerseNumber, vernacularVersification); var refInitStartVerse = new VerseRef(bookNum, currentRefBlock.ChapterNumber, currentRefBlock.InitialStartVerseNumber, Versification); var type = CharacterVerseData.GetStandardCharacterType(currentVernBlock.CharacterId); switch (type) { case CharacterVerseData.StandardCharacter.BookOrChapter: if (currentVernBlock.IsChapterAnnouncement) { var refChapterBlock = new Block(currentVernBlock.StyleTag, currentVernBlock.ChapterNumber); refChapterBlock.BlockElements.Add(new ScriptText(GetFormattedChapterAnnouncement(bookId, currentVernBlock.ChapterNumber))); if (currentRefBlock.IsChapterAnnouncement && currentRefBlock.MatchesReferenceText) { refChapterBlock.SetMatchedReferenceBlock(currentRefBlock.ReferenceBlocks.Single().Clone()); } currentVernBlock.SetMatchedReferenceBlock(refChapterBlock); if (currentRefBlock.IsChapterAnnouncement) { continue; } } goto case CharacterVerseData.StandardCharacter.ExtraBiblical; case CharacterVerseData.StandardCharacter.ExtraBiblical: if (type == CharacterVerseData.GetStandardCharacterType(currentRefBlock.CharacterId)) { currentVernBlock.SetMatchedReferenceBlock(currentRefBlock); continue; } goto case CharacterVerseData.StandardCharacter.Intro; case CharacterVerseData.StandardCharacter.Intro: // This will be re-incremented in the for loop, so it effectively allows // the vern index to advance while keeping the ref index the same. iRefBlock--; continue; default: if (refInitStartVerse > vernInitStartVerse || currentVernBlock.MatchesReferenceText) { iRefBlock--; continue; } break; } while (CharacterVerseData.IsCharacterExtraBiblical(currentRefBlock.CharacterId) || vernInitStartVerse > refInitStartVerse) { iRefBlock++; if (iRefBlock == refBlockList.Count) { return; // couldn't find a ref block to use at all. } currentRefBlock = refBlockList[iRefBlock]; refInitStartVerse = new VerseRef(bookNum, currentRefBlock.ChapterNumber, currentRefBlock.InitialStartVerseNumber, vernacularVersification); } var indexOfVernVerseStart = iVernBlock; var indexOfRefVerseStart = iRefBlock; BlockMatchup.AdvanceToCleanVerseBreak(vernBlockList, i => true, ref iVernBlock); var lastVernVerseFound = new VerseRef(bookNum, vernBlockList[iVernBlock].ChapterNumber, vernBlockList[iVernBlock].LastVerseNum, vernacularVersification); FindAllScriptureBlocksThroughVerse(refBlockList, lastVernVerseFound, ref iRefBlock, Versification); int numberOfVernBlocksInVerseChunk = iVernBlock - indexOfVernVerseStart + 1; int numberOfRefBlocksInVerseChunk = iRefBlock - indexOfRefVerseStart + 1; for (int i = 0; i < numberOfVernBlocksInVerseChunk && i < numberOfRefBlocksInVerseChunk; i++) { var vernBlockInVerseChunk = vernBlockList[indexOfVernVerseStart + i]; var refBlockInVerseChunk = refBlockList[indexOfRefVerseStart + i]; if (BlocksMatch(bookNum, vernBlockInVerseChunk, refBlockInVerseChunk, vernacularVersification) || (numberOfVernBlocksInVerseChunk == 1 && numberOfRefBlocksInVerseChunk == 1 && BlocksEndWithSameVerse(bookNum, vernBlockInVerseChunk, refBlockInVerseChunk, vernacularVersification))) { if (i == numberOfVernBlocksInVerseChunk - 1 && i < numberOfRefBlocksInVerseChunk - 1) { vernBlockInVerseChunk.MatchesReferenceText = false; vernBlockInVerseChunk.ReferenceBlocks = new List <Block>(refBlockList.Skip(indexOfRefVerseStart + i).Take(numberOfRefBlocksInVerseChunk - i)); break; } vernBlockInVerseChunk.SetMatchedReferenceBlock(refBlockList[indexOfRefVerseStart + i]); } else { iVernBlock = indexOfVernVerseStart + i; int j = 0; if (numberOfVernBlocksInVerseChunk - i >= 2) { // Look from the bottom up for (; j + 1 < numberOfVernBlocksInVerseChunk && j + i < numberOfRefBlocksInVerseChunk; j++) { vernBlockInVerseChunk = vernBlockList[indexOfVernVerseStart + numberOfVernBlocksInVerseChunk - j - 1]; refBlockInVerseChunk = refBlockList[indexOfRefVerseStart + numberOfRefBlocksInVerseChunk - j - 1]; if (BlocksMatch(bookNum, vernBlockInVerseChunk, refBlockInVerseChunk, vernacularVersification)) { vernBlockInVerseChunk.SetMatchedReferenceBlock(refBlockInVerseChunk); } else { break; } } } vernBlockList[iVernBlock].MatchesReferenceText = false; vernBlockList[iVernBlock].ReferenceBlocks = new List <Block>(refBlockList.Skip(indexOfRefVerseStart + i).Take(numberOfRefBlocksInVerseChunk - i - j)); iRefBlock = indexOfRefVerseStart + numberOfRefBlocksInVerseChunk - 1; break; } } } }
private bool IsRelevant(Block block, bool ignoreExcludeUserConfirmed = false) { if (block.MultiBlockQuote == MultiBlockQuote.Continuation || block.MultiBlockQuote == MultiBlockQuote.ChangeOfDelivery) { return(false); } if (!ignoreExcludeUserConfirmed && (Mode & BlocksToDisplay.ExcludeUserConfirmed) > 0 && block.UserConfirmed) { return(false); } if ((Mode & BlocksToDisplay.NeedAssignments) > 0) { return(BlockNeedsAssignment(block)); } if ((Mode & BlocksToDisplay.NotAlignedToReferenceText) > 0) { if (!block.IsScripture) { return(false); } if (s_lastMatchup == null || !s_lastMatchup.OriginalBlocks.Contains(block)) { s_lastMatchup = m_project.ReferenceText.GetBlocksForVerseMatchedToReferenceText(CurrentBook, m_navigator.GetIndicesOfSpecificBlock(block).BlockIndex, m_project.Versification); } return(s_lastMatchup.OriginalBlocks.Count() > 1 && !s_lastMatchup.CorrelatedBlocks.All(b => b.MatchesReferenceText)); } if ((Mode & BlocksToDisplay.AllExpectedQuotes) > 0) { return(IsBlockInVerseWithExpectedQuote(block)); } if ((Mode & BlocksToDisplay.MissingExpectedQuote) > 0) { if (CurrentBookIsSingleVoice) { return(false); } if (block.IsQuote || CharacterVerseData.IsCharacterExtraBiblical(block.CharacterId)) { return(false); } IEnumerable <BCVRef> versesWithPotentialMissingQuote = ControlCharacterVerseData.Singleton.GetCharacters(CurrentBookId, block.ChapterNumber, block.InitialStartVerseNumber, block.LastVerseNum, versification: Versification).Where(c => c.IsExpected).Select(c => c.BcvRef); var withPotentialMissingQuote = versesWithPotentialMissingQuote as IList <BCVRef> ?? versesWithPotentialMissingQuote.ToList(); if (!withPotentialMissingQuote.Any()) { return(false); } // REVIEW: This method peeks forward/backward from the *CURRENT* block, which might not be the block passed in to this method. return(CurrentBlockHasMissingExpectedQuote(withPotentialMissingQuote)); } if ((Mode & BlocksToDisplay.MoreQuotesThanExpectedSpeakers) > 0) { if (!block.IsQuote || CurrentBookIsSingleVoice) { return(false); } var expectedSpeakers = ControlCharacterVerseData.Singleton.GetCharacters(CurrentBookId, block.ChapterNumber, block.InitialStartVerseNumber, block.InitialEndVerseNumber, versification: Versification).Distinct(new CharacterEqualityComparer()).Count(); var actualquotes = 1; // this is the quote represented by the given block. if (actualquotes > expectedSpeakers) { return(true); } // REVIEW: This method peeks forward/backward from the *CURRENT* block, which might not be the block passed in to this method. // Check surrounding blocks to count quote blocks for same verse. actualquotes += m_navigator.PeekBackwardWithinBookWhile(b => b.ChapterNumber == block.ChapterNumber && b.InitialStartVerseNumber == block.InitialStartVerseNumber) .Count(b => b.IsQuote && (b.MultiBlockQuote == MultiBlockQuote.Start || b.MultiBlockQuote == MultiBlockQuote.None)); if (actualquotes > expectedSpeakers) { return(true); } actualquotes += m_navigator.PeekForwardWithinBookWhile(b => b.ChapterNumber == block.ChapterNumber && b.InitialStartVerseNumber == block.InitialStartVerseNumber) .Count(b => b.IsQuote && (b.MultiBlockQuote == MultiBlockQuote.Start || b.MultiBlockQuote == MultiBlockQuote.None)); return(actualquotes > expectedSpeakers); } if ((Mode & BlocksToDisplay.AllScripture) > 0) { return(block.IsScripture); } if ((Mode & BlocksToDisplay.AllQuotes) > 0) { return(block.IsQuote); } return(false); }
private void MatchVernBlocksToReferenceTextBlocks(IReadOnlyList <Block> vernBlockList, string bookId, ScrVers vernacularVersification, bool forceMatch = false) { int bookNum = BCVRef.BookToNumber(bookId); var refBook = Books.Single(b => b.BookId == bookId); var refBlockList = refBook.GetScriptBlocks(); int iRefBlock = 0; int iRefBlockMemory = -1; // The following is a minor performance enhancement to help when just hooking up one verse's worth of blocks. // In it's current form, it doesn't work for tests that have partial reference texts that don't start at verse 1. // Also, it doesn't take versification mappings into consideration. //if (vernBlockList[0].ChapterNumber > 1) //{ // iRefBlock = refBook.GetIndexOfFirstBlockForVerse(vernBlockList[0].ChapterNumber, ); // if (iRefBlock < 0) // { // // Apparently there is no reference text for the verse(s) we're interested in. // return; // } //} for (int iVernBlock = 0; iVernBlock < vernBlockList.Count; iVernBlock++, iRefBlock++) { if (iRefBlock >= refBlockList.Count && iRefBlockMemory < 0) { // We still have more vernacular verses to consider. Most likely, this is an // additional alternate ending (in Mark). It's not very efficient, but we'll just // start back at the beginning of the reference text block collection. iRefBlock = 0; } var currentVernBlock = vernBlockList[iVernBlock]; // TODO: This handles the only case I know of (and for which there is a test) where a versification pulls in verses // from the end of the book to an earlier spot, namely Romans 14:24-26 <- Romans 16:25-27. If we ever have this same // kind of behavior that is pulling from somewhere other than the *end* of the book, the logic to reset iRefBlock // based on iRefBlockMemory will need to be moved/added elsewhere. if (iRefBlockMemory >= 0 && iRefBlock >= refBlockList.Count) { iRefBlock = iRefBlockMemory; iRefBlockMemory = -1; } var currentRefBlock = refBlockList[iRefBlock]; var vernInitStartVerse = new VerseRef(bookNum, currentVernBlock.ChapterNumber, currentVernBlock.InitialStartVerseNumber, vernacularVersification); var refInitStartVerse = new VerseRef(bookNum, currentRefBlock.ChapterNumber, currentRefBlock.InitialStartVerseNumber, Versification); var type = CharacterVerseData.GetStandardCharacterType(currentVernBlock.CharacterId); switch (type) { case CharacterVerseData.StandardCharacter.BookOrChapter: if (currentVernBlock.IsChapterAnnouncement) { var refChapterBlock = new Block(currentVernBlock.StyleTag, currentVernBlock.ChapterNumber); refChapterBlock.BlockElements.Add(new ScriptText(GetFormattedChapterAnnouncement(bookId, currentVernBlock.ChapterNumber))); if (HasSecondaryReferenceText) { if (currentRefBlock.IsChapterAnnouncement && currentRefBlock.MatchesReferenceText) { refChapterBlock.SetMatchedReferenceBlock(currentRefBlock.ReferenceBlocks.Single().Clone()); } else if (!currentRefBlock.IsChapterAnnouncement) { // Find the reference text's chapter announcement to get the secondary reference text chapter announcement var i = iRefBlock + 1; while (i < refBlockList.Count) { var workingRefBlock = refBlockList[i]; var workingRefInitStartVerse = new VerseRef(bookNum, workingRefBlock.ChapterNumber, workingRefBlock.InitialStartVerseNumber, Versification); var compareResult = workingRefInitStartVerse.CompareTo(vernInitStartVerse); if (compareResult > 0) // break out early; we passed the verse reference, so there is no chapter label { break; } if (compareResult == 0 && workingRefBlock.IsChapterAnnouncement && workingRefBlock.MatchesReferenceText) { refChapterBlock.SetMatchedReferenceBlock(workingRefBlock.ReferenceBlocks.Single().Clone()); break; } i++; } } } currentVernBlock.SetMatchedReferenceBlock(refChapterBlock); if (currentRefBlock.IsChapterAnnouncement) { continue; } } goto case CharacterVerseData.StandardCharacter.ExtraBiblical; case CharacterVerseData.StandardCharacter.ExtraBiblical: if (type == CharacterVerseData.GetStandardCharacterType(currentRefBlock.CharacterId)) { currentVernBlock.SetMatchedReferenceBlock(currentRefBlock); continue; } goto case CharacterVerseData.StandardCharacter.Intro; case CharacterVerseData.StandardCharacter.Intro: // This will be re-incremented in the for loop, so it effectively allows // the vern index to advance while keeping the ref index the same. iRefBlock--; continue; default: if (refInitStartVerse.CompareTo(vernInitStartVerse) > 0 || currentVernBlock.MatchesReferenceText) { iRefBlock--; continue; } break; } while (CharacterVerseData.IsCharacterExtraBiblical(currentRefBlock.CharacterId) || vernInitStartVerse > refInitStartVerse) { iRefBlock++; if (iRefBlock == refBlockList.Count) { return; // couldn't find a ref block to use at all. } currentRefBlock = refBlockList[iRefBlock]; refInitStartVerse = new VerseRef(bookNum, currentRefBlock.ChapterNumber, currentRefBlock.InitialStartVerseNumber, vernacularVersification); } var indexOfVernVerseStart = iVernBlock; var indexOfRefVerseStart = iRefBlock; BlockMatchup.AdvanceToCleanVerseBreak(vernBlockList, i => true, ref iVernBlock); var lastVernVerseFound = new VerseRef(bookNum, vernBlockList[iVernBlock].ChapterNumber, vernBlockList[iVernBlock].LastVerseNum, vernacularVersification); FindAllScriptureBlocksThroughVerse(refBlockList, lastVernVerseFound, ref iRefBlock, Versification); int numberOfVernBlocksInVerseChunk = iVernBlock - indexOfVernVerseStart + 1; int numberOfRefBlocksInVerseChunk = iRefBlock - indexOfRefVerseStart + 1; for (int i = indexOfVernVerseStart; i <= iVernBlock; i++) { if (vernBlockList[i].CharacterIs(bookId, CharacterVerseData.StandardCharacter.ExtraBiblical)) { numberOfVernBlocksInVerseChunk--; } } if (numberOfVernBlocksInVerseChunk == 1 && numberOfRefBlocksInVerseChunk > 1) { var lastRefBlockInVerseChunk = refBlockList[iRefBlock]; var refVerse = new VerseRef(bookNum, lastRefBlockInVerseChunk.ChapterNumber, lastRefBlockInVerseChunk.InitialStartVerseNumber, Versification); if (lastVernVerseFound.CompareTo(refVerse) == 0 && lastVernVerseFound.BBBCCCVVV < refVerse.BBBCCCVVV) { // A versification difference has us pulling a verse from later in the text, so we need to get out and look beyond our current // index in the ref text, but remember this spot so we can come back to it. iRefBlockMemory = indexOfRefVerseStart; iRefBlock--; iVernBlock--; continue; } // Since there's only one vernacular block for this verse (or verse bridge), just combine all // ref blocks into one and call it a match. vernBlockList[indexOfVernVerseStart].SetMatchedReferenceBlock(bookNum, vernacularVersification, this, refBlockList.Skip(indexOfRefVerseStart).Take(numberOfRefBlocksInVerseChunk)); continue; } for (int i = 0; i < numberOfVernBlocksInVerseChunk && i < numberOfRefBlocksInVerseChunk; i++) { var vernBlockInVerseChunk = vernBlockList[indexOfVernVerseStart + i]; var refBlockInVerseChunk = refBlockList[indexOfRefVerseStart + i]; if (BlocksMatch(bookNum, vernBlockInVerseChunk, refBlockInVerseChunk, vernacularVersification) || (numberOfVernBlocksInVerseChunk == 1 && numberOfRefBlocksInVerseChunk == 1 && BlocksEndWithSameVerse(bookNum, vernBlockInVerseChunk, refBlockInVerseChunk, vernacularVersification))) { if (i == numberOfVernBlocksInVerseChunk - 1 && i < numberOfRefBlocksInVerseChunk - 1) { vernBlockInVerseChunk.MatchesReferenceText = false; vernBlockInVerseChunk.ReferenceBlocks = new List <Block>(refBlockList.Skip(indexOfRefVerseStart + i).Take(numberOfRefBlocksInVerseChunk - i)); break; } vernBlockInVerseChunk.SetMatchedReferenceBlock(refBlockList[indexOfRefVerseStart + i]); } else { iVernBlock = indexOfVernVerseStart + i; if (vernBlockList[iVernBlock].CharacterIs(bookId, CharacterVerseData.StandardCharacter.ExtraBiblical)) { iVernBlock++; } int j = 0; if (numberOfVernBlocksInVerseChunk - i >= 2) { // Look from the bottom up for (; j + 1 < numberOfVernBlocksInVerseChunk && j + i < numberOfRefBlocksInVerseChunk; j++) { vernBlockInVerseChunk = vernBlockList[indexOfVernVerseStart + numberOfVernBlocksInVerseChunk - j - 1]; refBlockInVerseChunk = refBlockList[indexOfRefVerseStart + numberOfRefBlocksInVerseChunk - j - 1]; if (BlocksMatch(bookNum, vernBlockInVerseChunk, refBlockInVerseChunk, vernacularVersification)) { vernBlockInVerseChunk.SetMatchedReferenceBlock(refBlockInVerseChunk); } else { break; } } } var numberOfUnmatchedRefBlocks = numberOfRefBlocksInVerseChunk - i - j; var remainingRefBlocks = refBlockList.Skip(indexOfRefVerseStart + i).Take(numberOfUnmatchedRefBlocks); if (numberOfVernBlocksInVerseChunk == 1 && numberOfUnmatchedRefBlocks > 1) { // Since there's only one vernacular block for this verse (or verse bridge), just combine all // ref blocks into one and call it a match. vernBlockInVerseChunk.SetMatchedReferenceBlock(bookNum, vernacularVersification, this, remainingRefBlocks); } else { var remainingRefBlocksList = remainingRefBlocks.ToList(); if (!remainingRefBlocksList.Any()) { // do nothing (PG-1085) } else if (forceMatch) { var refBlock = remainingRefBlocksList[0]; for (int rb = 1; rb < remainingRefBlocksList.Count; rb++) { refBlock.CombineWith(remainingRefBlocksList[rb]); } vernBlockList[iVernBlock].SetMatchedReferenceBlock(refBlock); } else { vernBlockList[iVernBlock].MatchesReferenceText = false; vernBlockList[iVernBlock].ReferenceBlocks = remainingRefBlocksList; } iRefBlock = indexOfRefVerseStart + numberOfRefBlocksInVerseChunk - 1; } break; } } var indexOfLastVernVerseInVerseChunk = indexOfVernVerseStart + numberOfVernBlocksInVerseChunk - 1; if (vernBlockList[indexOfLastVernVerseInVerseChunk].MatchesReferenceText) { iVernBlock = indexOfLastVernVerseInVerseChunk; } } }
private void MatchVernBlocksToReferenceTextBlocks(IReadOnlyList <Block> vernBlockList, string bookId, ScrVers vernacularVersification, bool forceMatch = false) { int bookNum = BCVRef.BookToNumber(bookId); var refBook = Books.Single(b => b.BookId == bookId); var refBlockList = refBook.GetScriptBlocks(); int iRefBlock = 0; int iRefBlockMemory = -1; // The following is a minor performance enhancement to help when just hooking up one verse's worth of blocks. // In it's current form, it doesn't work for tests that have partial reference texts that don't start at verse 1. // Also, it doesn't take versification mappings into consideration. //if (vernBlockList[0].ChapterNumber > 1) //{ // iRefBlock = refBook.GetIndexOfFirstBlockForVerse(vernBlockList[0].ChapterNumber, ); // if (iRefBlock < 0) // { // // Apparently there is no reference text for the verse(s) we're interested in. // return; // } //} for (int iVernBlock = 0; iVernBlock < vernBlockList.Count; iVernBlock++, iRefBlock++) { var currentVernBlock = vernBlockList[iVernBlock]; if (iRefBlock >= refBlockList.Count) { if (iRefBlockMemory < 0) { Debug.Assert(iVernBlock > 0); if (currentVernBlock.LastVerseNum == vernBlockList[iVernBlock - 1].LastVerseNum) { // Either this is a section head, in which case we don't care until we get to the // next actual Scripture block; or the vernacular happens to have more blocks in the // last verse at the end of the book, in which case they will just go unmatched. continue; } // We still have more vernacular verses to consider. Most likely, this is an // additional alternate ending (in Mark). if (bookId != "MRK") { Logger.WriteMinorEvent("Reference text matching went off end of ref block list for " + $"vern block {currentVernBlock}"); } iRefBlock = refBook.GetIndexOfFirstBlockForVerse(currentVernBlock.ChapterNumber, currentVernBlock.InitialStartVerseNumber); } else { // This handles the only case I know of (and for which there is a test) where a versification pulls in verses // from the end of the book to an earlier spot, namely Romans 14:24-26 <- Romans 16:25-27. If we ever have this same // kind of behavior that is pulling from somewhere other than the *end* of the book, the logic to reset iRefBlock // based on iRefBlockMemory will need to be moved/added elsewhere. iRefBlock = iRefBlockMemory; iRefBlockMemory = -1; } } var currentRefBlock = refBlockList[iRefBlock]; var vernInitStartVerse = currentVernBlock.StartRef(bookNum, vernacularVersification); var refInitStartVerse = currentRefBlock.StartRef(bookNum, Versification); var type = CharacterVerseData.GetStandardCharacterType(currentVernBlock.CharacterId); switch (type) { case CharacterVerseData.StandardCharacter.BookOrChapter: if (currentVernBlock.IsChapterAnnouncement) { var refChapterBlock = new Block(currentVernBlock.StyleTag, currentVernBlock.ChapterNumber) { CharacterId = currentVernBlock.CharacterId }; refChapterBlock.BlockElements.Add(new ScriptText(GetFormattedChapterAnnouncement(bookId, currentVernBlock.ChapterNumber))); if (HasSecondaryReferenceText) { if (currentRefBlock.IsChapterAnnouncement && currentRefBlock.MatchesReferenceText) { refChapterBlock.SetMatchedReferenceBlock(currentRefBlock.ReferenceBlocks.Single().Clone()); } else if (!currentRefBlock.IsChapterAnnouncement) { // Find the reference text's chapter announcement to get the secondary reference text chapter announcement var i = iRefBlock + 1; while (i < refBlockList.Count) { var workingRefBlock = refBlockList[i]; var workingRefInitStartVerse = workingRefBlock.StartRef(bookNum, Versification); var compareResult = workingRefInitStartVerse.CompareTo(vernInitStartVerse); if (compareResult > 0) // break out early; we passed the verse reference, so there is no chapter label { break; } if (compareResult == 0 && workingRefBlock.IsChapterAnnouncement && workingRefBlock.MatchesReferenceText) { refChapterBlock.SetMatchedReferenceBlock(workingRefBlock.ReferenceBlocks.Single().Clone()); break; } i++; } } } currentVernBlock.SetMatchedReferenceBlock(refChapterBlock); if (!currentRefBlock.IsChapterAnnouncement && currentRefBlock.ChapterNumber == currentVernBlock.ChapterNumber) { iRefBlock--; } continue; } goto case CharacterVerseData.StandardCharacter.ExtraBiblical; // Book title case CharacterVerseData.StandardCharacter.ExtraBiblical: if (type == CharacterVerseData.GetStandardCharacterType(currentRefBlock.CharacterId)) { currentVernBlock.SetMatchedReferenceBlock(currentRefBlock); continue; } goto case CharacterVerseData.StandardCharacter.Intro; case CharacterVerseData.StandardCharacter.Intro: // This will be re-incremented in the for loop, so it effectively allows // the vern index to advance while keeping the ref index the same. iRefBlock--; continue; default: if (refInitStartVerse.CompareTo(vernInitStartVerse) > 0 || currentVernBlock.MatchesReferenceText) { iRefBlock--; continue; } break; } while (CharacterVerseData.IsCharacterExtraBiblical(currentRefBlock.CharacterId) || vernInitStartVerse > refInitStartVerse) { iRefBlock++; if (iRefBlock == refBlockList.Count) { return; // couldn't find a ref block to use at all. } currentRefBlock = refBlockList[iRefBlock]; refInitStartVerse = currentRefBlock.StartRef(bookNum, vernacularVersification); } var indexOfVernVerseStart = iVernBlock; var indexOfRefVerseStart = iRefBlock; BlockMatchup.AdvanceToCleanVerseBreak(vernBlockList, i => true, ref iVernBlock); var lastVernVerseFound = new VerseRef(bookNum, vernBlockList[iVernBlock].ChapterNumber, vernBlockList[iVernBlock].LastVerseNum, vernacularVersification); FindAllScriptureBlocksThroughVerse(refBlockList, lastVernVerseFound, ref iRefBlock, Versification); int numberOfVernBlocksInVerseChunk = iVernBlock - indexOfVernVerseStart + 1; int numberOfRefBlocksInVerseChunk = iRefBlock - indexOfRefVerseStart + 1; for (int i = indexOfVernVerseStart; i <= iVernBlock; i++) { if (vernBlockList[i].CharacterIs(bookId, CharacterVerseData.StandardCharacter.ExtraBiblical)) { numberOfVernBlocksInVerseChunk--; } } if (numberOfVernBlocksInVerseChunk == 1 && numberOfRefBlocksInVerseChunk > 1) { var lastRefBlockInVerseChunk = refBlockList[iRefBlock]; var refVerse = lastRefBlockInVerseChunk.StartRef(bookNum, Versification); if (lastVernVerseFound.CompareTo(refVerse) == 0 && lastVernVerseFound.BBBCCCVVV < refVerse.BBBCCCVVV) { // A versification difference has us pulling a verse from later in the text, so we need to get out and look beyond our current // index in the ref text, but remember this spot so we can come back to it. iRefBlockMemory = indexOfRefVerseStart; iRefBlock--; iVernBlock--; continue; } // Since there's only one vernacular block for this verse (or verse bridge), just combine all // ref blocks into one and call it a match. vernBlockList[indexOfVernVerseStart].SetMatchedReferenceBlock(bookNum, vernacularVersification, this, refBlockList.Skip(indexOfRefVerseStart).Take(numberOfRefBlocksInVerseChunk)); continue; } for (int i = 0; i < numberOfVernBlocksInVerseChunk && i < numberOfRefBlocksInVerseChunk; i++) { var vernBlockInVerseChunk = vernBlockList[indexOfVernVerseStart + i]; var refBlockInVerseChunk = refBlockList[indexOfRefVerseStart + i]; if (BlocksMatch(bookNum, vernBlockInVerseChunk, refBlockInVerseChunk, vernacularVersification) || (numberOfVernBlocksInVerseChunk == 1 && numberOfRefBlocksInVerseChunk == 1 && BlocksEndWithSameVerse(bookNum, vernBlockInVerseChunk, refBlockInVerseChunk, vernacularVersification))) { if (i == numberOfVernBlocksInVerseChunk - 1 && i < numberOfRefBlocksInVerseChunk - 1) { // This is the last vernacular block in the list, but we have more than one ref block to account for. if (numberOfRefBlocksInVerseChunk - i == 2 && // Exactly 2 reference blocks remaining to be assigned i > 0 && // There is a preceding vernacular block // The following line could be uncommented to constrain this only to the original intended condition // if we find cases where we are coming in here but shouldn't: //vernBlockInVerseChunk.CharacterIs(bookId, CharacterVerseData.StandardCharacter.Narrator) && // Can only safely combine reference blocks if they are for the same character: vernBlockList[indexOfVernVerseStart + i - 1].ReferenceBlocks.All(r => r.CharacterId == refBlockList[indexOfRefVerseStart + i + 1].CharacterId) && BlocksMatch(bookNum, vernBlockList[indexOfVernVerseStart + i - 1], // Preceding vern block's character & end ref are compatible refBlockList[indexOfRefVerseStart + i + 1], vernacularVersification)) // with following ref block { // This code was specifically written for PG-794, the case where the vernacular has the narrator announcing // the speech afterwards instead of beforehand (as is typically the case in the reference text). In that // case we want to assign the "he said" reference text to the current vernacular block and attach the // following reference text block to the preceding vernacular block. // Because we are not explicitly checking to see if this block is a narrator block, this condition can // also be matched in other rare cases (for a somewhat contrived example where the reference // text has a trailing "he said" but the vernacular does not, see unit test // ApplyTo_MultipleSpeakersInVerse_SpeakersBeginCorrespondingThenDoNotCorrespond_ReferenceTextCopiedIntoBestMatchedVerseBlocks.) // Even in such cases, it seems likely that we would want to attach the following reference text // block to the preceding vernacular block if it is a better match. var precedingVernBlock = vernBlockList[indexOfVernVerseStart + i - 1]; precedingVernBlock.AppendUnmatchedReferenceBlock(refBlockList[indexOfRefVerseStart + i + 1]); vernBlockInVerseChunk.SetMatchedReferenceBlock(refBlockList[indexOfRefVerseStart + i]); } else { vernBlockInVerseChunk.SetUnmatchedReferenceBlocks(refBlockList.Skip(indexOfRefVerseStart + i).Take(numberOfRefBlocksInVerseChunk - i)); } break; } vernBlockInVerseChunk.SetMatchedReferenceBlock(refBlockList[indexOfRefVerseStart + i]); } else { iVernBlock = indexOfVernVerseStart + i; if (vernBlockList[iVernBlock].CharacterIs(bookId, CharacterVerseData.StandardCharacter.ExtraBiblical)) { iVernBlock++; } int j = 0; var iLastVernBlockMatchedFromBottomUp = -1; if (numberOfVernBlocksInVerseChunk - i >= 2) { // Look from the bottom up for (; j < numberOfVernBlocksInVerseChunk && j + i < numberOfRefBlocksInVerseChunk; j++) { var iCurrVernBottomUp = indexOfVernVerseStart + numberOfVernBlocksInVerseChunk - j - 1; vernBlockInVerseChunk = vernBlockList[iCurrVernBottomUp]; if (vernBlockInVerseChunk.MatchesReferenceText) { break; } refBlockInVerseChunk = refBlockList[indexOfRefVerseStart + numberOfRefBlocksInVerseChunk - j - 1]; if (BlocksMatch(bookNum, vernBlockInVerseChunk, refBlockInVerseChunk, vernacularVersification)) { vernBlockInVerseChunk.SetMatchedReferenceBlock(refBlockInVerseChunk); iLastVernBlockMatchedFromBottomUp = iCurrVernBottomUp; } else { break; } } } var numberOfUnmatchedRefBlocks = numberOfRefBlocksInVerseChunk - i - j; var remainingRefBlocks = refBlockList.Skip(indexOfRefVerseStart + i).Take(numberOfUnmatchedRefBlocks); if (numberOfVernBlocksInVerseChunk == 1 && numberOfUnmatchedRefBlocks > 1) { // Since there's only one vernacular block for this verse (or verse bridge), just combine all // ref blocks into one and call it a match. vernBlockInVerseChunk.SetMatchedReferenceBlock(bookNum, vernacularVersification, this, remainingRefBlocks); } else { var remainingRefBlocksList = remainingRefBlocks.ToList(); if (!remainingRefBlocksList.Any()) { // do nothing (PG-1085) } else if (forceMatch) { var refBlock = remainingRefBlocksList[0]; for (int rb = 1; rb < remainingRefBlocksList.Count; rb++) { refBlock.CombineWith(remainingRefBlocksList[rb]); } vernBlockList[iVernBlock].SetMatchedReferenceBlock(refBlock); } else { if (remainingRefBlocksList.Count == 1 && vernBlockList[iVernBlock].MatchesReferenceText && vernBlockList[iVernBlock].ReferenceBlocks.Single().CharacterId != remainingRefBlocksList[0].CharacterId) { // See if the immediately following or preceding block is a better match var otherIndicesToTry = (i <= iVernBlock) ? new [] { iVernBlock - 1, iVernBlock + 1 } : new [] { iVernBlock + 1, iVernBlock - 1 }; foreach (var iPreOrPost in otherIndicesToTry.Where(o => vernBlockList.Count > o && o >= 0)) { if (vernBlockList[iPreOrPost].ReferenceBlocks.FirstOrDefault()?.CharacterId == remainingRefBlocksList[0].CharacterId || vernBlockList[iPreOrPost].CharacterId == vernBlockList[iVernBlock].CharacterId) { if (!vernBlockList[iPreOrPost].ReferenceBlocks.Any()) { vernBlockList[iPreOrPost].SetUnmatchedReferenceBlocks(remainingRefBlocksList); } else if (iPreOrPost < iVernBlock) // Pre { vernBlockList[iPreOrPost].AppendUnmatchedReferenceBlocks(remainingRefBlocksList); } else // Post { vernBlockList[iPreOrPost].InsertUnmatchedReferenceBlocks(0, remainingRefBlocksList); } remainingRefBlocksList = null; break; } } } if (remainingRefBlocksList != null) { if (vernBlockList[iVernBlock].ReferenceBlocks.Any()) { // After matching things up as best we could from the top down and the bottom up, we // ran out of suitable "holes" in the vernacular. Since our "target" vernacular block // already has a reference block, we either need to prepend or append any other // unmatched ref blocks. We don't want to change the order of the ref blocks, so we // have to be careful. The variable i represents where we got to in our (top-down) // matching, so generally if we didn't get all the way down to our target block, we // insert the remaining ref blocks before and if we got past it, then we append to // the end. But if we attached the reference block to our target block in the bottom- // up matching, then the remaining blocks are actually *before* the existing matched // block, so we need to insert. Really, iLastVernBlockMatchedFromBottomUp could just // be a Boolean flag: fMatchedTargetVernBlockDuringBottomUpMatching. if (i < iVernBlock || i == iLastVernBlockMatchedFromBottomUp) { vernBlockList[iVernBlock].InsertUnmatchedReferenceBlocks(0, remainingRefBlocksList); } else { vernBlockList[iVernBlock].AppendUnmatchedReferenceBlocks(remainingRefBlocksList); } } else { vernBlockList[iVernBlock].SetUnmatchedReferenceBlocks(remainingRefBlocksList); } } } iRefBlock = indexOfRefVerseStart + numberOfRefBlocksInVerseChunk - 1; } break; } } var indexOfLastVernVerseInVerseChunk = indexOfVernVerseStart + numberOfVernBlocksInVerseChunk - 1; if (vernBlockList[indexOfLastVernVerseInVerseChunk].ReferenceBlocks.Any()) { iVernBlock = indexOfLastVernVerseInVerseChunk; } } }