Esempio n. 1
0
        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;
                }
            }
        }
Esempio n. 2
0
        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 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;
                }
            }
        }