Ejemplo n.º 1
0
        private IEnumerable <CharacterDetail> GetStandardCharacters()
        {
            var list = new List <CharacterDetail>();

            for (int booknum = 1; booknum <= BCVRef.LastBook; booknum++)
            {
                string bookCode = BCVRef.NumberToBookCode(booknum);
                list.Add(new CharacterDetail
                {
                    CharacterId           = CharacterVerseData.GetStandardCharacterId(bookCode, CharacterVerseData.StandardCharacter.Narrator),
                    Gender                = CharacterGender.Neuter,
                    StandardCharacterType = CharacterVerseData.StandardCharacter.Narrator,
                    Status                = true
                });
                list.Add(new CharacterDetail
                {
                    CharacterId           = CharacterVerseData.GetStandardCharacterId(bookCode, CharacterVerseData.StandardCharacter.BookOrChapter),
                    Gender                = CharacterGender.Neuter,
                    StandardCharacterType = CharacterVerseData.StandardCharacter.BookOrChapter
                });
                list.Add(new CharacterDetail
                {
                    CharacterId           = CharacterVerseData.GetStandardCharacterId(bookCode, CharacterVerseData.StandardCharacter.ExtraBiblical),
                    Gender                = CharacterGender.Neuter,
                    StandardCharacterType = CharacterVerseData.StandardCharacter.ExtraBiblical
                });
                list.Add(new CharacterDetail
                {
                    CharacterId           = CharacterVerseData.GetStandardCharacterId(bookCode, CharacterVerseData.StandardCharacter.Intro),
                    Gender                = CharacterGender.Neuter,
                    StandardCharacterType = CharacterVerseData.StandardCharacter.Intro
                });
            }
            return(list);
        }
Ejemplo n.º 2
0
        public virtual void AddEntriesFor(int bookNumber, Block block)
        {
            bool added = false;

            foreach (var verse in block.AllVerses)
            {
                if (!ControlCharacterVerseData.Singleton.GetCharacters(bookNumber, block.ChapterNumber, verse, m_versification, true, true)
                    .Any(c => c.Character == block.CharacterId && c.Delivery == (block.Delivery ?? Empty)))
                {
                    foreach (var v in verse.AllVerseNumbers)
                    {
                        var verseRef = new VerseRef(bookNumber, block.ChapterNumber, v, m_versification);
                        verseRef.ChangeVersification(ScrVers.English);
                        added |= base.AddCharacterVerse(new CharacterVerse(verseRef.BBBCCCVVV, block.CharacterId, block.Delivery ?? Empty, Empty, true));
                    }
                }
            }

            if (added)
            {
                Analytics.Track("AddCharacter", new Dictionary <string, string>
                {
                    { "verseReference", block.GetReferenceString(BCVRef.NumberToBookCode(bookNumber)) },
                    { "characterId", block.CharacterId },
                    { "delivery", block.Delivery }
                });
            }
        }
Ejemplo n.º 3
0
        private IEnumerable <string> GetUniqueOtNtBooks()
        {
            var books = new SortedSet <string>(Comparer <string> .Create((a, b) => BCVRef.BookToNumber(a).CompareTo(BCVRef.BookToNumber(b))));

            foreach (Verse reference in References)
            {
                var bcvRef = new BCVRef(reference.VerseText);
                if (bcvRef.BookIsValid)
                {
                    books.Add(BCVRef.NumberToBookCode(bcvRef.Book));
                }
                else if (Char.IsLetter(reference.VerseText.Last()))
                {
                    bcvRef = new BCVRef(reference.VerseText.Substring(0, reference.VerseText.Length - 1));
                    if (bcvRef.BookIsValid)
                    {
                        books.Add(BCVRef.NumberToBookCode(bcvRef.Book));
                    }
                    else
                    {
                        Debug.Fail(reference.VerseText);
                    }
                }
                else
                {
                    Debug.Fail(reference.VerseText);
                }
            }
            return(books);
        }
Ejemplo n.º 4
0
 private void AddStandardCharacters(List <CharacterDetail> list)
 {
     for (int booknum = 1; booknum <= BCVRef.LastBook; booknum++)
     {
         string bookCode = BCVRef.NumberToBookCode(booknum);
         list.Add(new CharacterDetail
         {
             CharacterId           = CharacterVerseData.GetStandardCharacterId(bookCode, CharacterVerseData.StandardCharacter.Narrator),
             Gender                = CharacterGender.Neuter,
             StandardCharacterType = CharacterVerseData.StandardCharacter.Narrator,
             //Status = true
         });
         list.Add(new CharacterDetail
         {
             CharacterId           = CharacterVerseData.GetStandardCharacterId(bookCode, CharacterVerseData.StandardCharacter.BookOrChapter),
             Gender                = CharacterGender.Neuter,
             StandardCharacterType = CharacterVerseData.StandardCharacter.BookOrChapter
         });
         list.Add(new CharacterDetail
         {
             CharacterId           = CharacterVerseData.GetStandardCharacterId(bookCode, CharacterVerseData.StandardCharacter.ExtraBiblical),
             Gender                = CharacterGender.Neuter,
             StandardCharacterType = CharacterVerseData.StandardCharacter.ExtraBiblical
         });
         list.Add(new CharacterDetail
         {
             CharacterId           = CharacterVerseData.GetStandardCharacterId(bookCode, CharacterVerseData.StandardCharacter.Intro),
             Gender                = CharacterGender.Neuter,
             StandardCharacterType = CharacterVerseData.StandardCharacter.Intro
         });
     }
 }
        private static IEnumerable <Question> GetTrailingCustomizations(int bookNum, SortedDictionary <QuestionKey, Customizations> customizations, Question lastQuestionInBook,
                                                                        Category category, int iQuestion)
        {
            if (customizations != null)
            {
                Debug.Assert(lastQuestionInBook != null && iQuestion > 0 && category != null,
                             $"Book {BCVRef.NumberToBookCode(bookNum)} has no built-in questions - cannot process customizations!");

                while (customizations.Any(c => c.Value.IsAdditionOrInsertion))
                {
                    var insertionForPreviousReference = customizations.FirstOrDefault(c => c.Value.IsAdditionOrInsertion && customizations.All(other => c.Value == other.Value || !other.Key.Matches(c.Key)));
                    var key = insertionForPreviousReference.Key;
                    if (key == null)
                    {
                        Debug.Assert(!customizations.Any(), $"Detected circular chain of customizations for book: {BCVRef.NumberToBookCode(bookNum)}. There were {customizations.Count} customizations that could not be processed!");
                        break;
                    }

                    var newQ = lastQuestionInBook.AddedQuestionAfter = insertionForPreviousReference.Value.PopQuestion(key);
                    if (!insertionForPreviousReference.Value.IsAdditionOrInsertion)
                    {
                        customizations.Remove(key);
                    }
                    category.Questions.Add(newQ);
                    foreach (Question question in GetCustomizations(newQ, category, iQuestion, customizations))
                    {
                        lastQuestionInBook = question;
                        yield return(question);

                        iQuestion++;
                    }
                }
            }
        }
        protected override void AdjustData(ILookup <int, CharacterVerse> bookLookup)
        {
            for (var b = 1; b <= 66; b++)
            {
                var book     = bookLookup[b];
                var narrator = GetStandardCharacterId(BCVRef.NumberToBookCode(b), StandardCharacter.Narrator);
                for (int i = 0; i < book.Count(); i++)
                {
                    var cv = book.ElementAt(i);
                    if (cv.QuoteType == QuoteType.Quotation && cv.DefaultCharacter == narrator)
                    {
                        // PG-1248: cv is a Quotation entry which, if used, would have the quote spoken by the original
                        // speaker. (Even though the default character is the narrator, we ignore that.) if there is a
                        // narrator Quotation entry for this same verse, we want it to be considered as the primary
                        // option. We'll mark the cv we've found as an Alternate, so the quote parser will not
                        // consider it as an option, but the user will still be able to use it to override the default.

                        // Following line should be SingleOrDefault, but this is more efficient
                        var quotationByNarrator = book.FirstOrDefault(a => a.Book == cv.Book &&
                                                                      a.Chapter == cv.Chapter &&
                                                                      a.Verse == cv.Verse &&
                                                                      a.QuoteType == QuoteType.Quotation &&
                                                                      a.Character == narrator);
                        if (quotationByNarrator != null)
                        {
                            cv.ChangeToAlternate();
                        }
                    }
                }
            }
        }
 /// ------------------------------------------------------------------------------------
 /// <summary>
 /// Loads the combo box with the appropriate book ids
 /// </summary>
 /// <param name="canonicalBookIds">1-based Canonical book numbers</param>
 /// ------------------------------------------------------------------------------------
 private void LoadBooks(IEnumerable <int> canonicalBookIds)
 {
     foreach (int canonicalBookId in canonicalBookIds)
     {
         string bookCode = BCVRef.NumberToBookCode(canonicalBookId);
         m_cboBooks.Items.Add(bookCode);
     }
     if (m_cboBooks.Items.Count > 0)
     {
         m_cboBooks.SelectedIndex = 0;
     }
 }
Ejemplo n.º 8
0
        private static IEnumerable <string> PartialTestamentBookSummary(BookSet bookSet, BookSet setToCompare)
        {
            if (bookSet.Count == 0)
            {
                return(Enumerable.Empty <string>());
            }

            var    result   = new List <string>();
            string start    = null;
            string previous = null;

            foreach (int bookNum in setToCompare.SelectedBookNumbers)
            {
                if (bookSet.IsSelected(bookNum))
                {
                    string bookCode = BCVRef.NumberToBookCode(bookNum);
                    if (start == null)
                    {
                        start = bookCode;
                    }
                    previous = bookCode;
                }
                else
                {
                    if (start != null)
                    {
                        if (start == previous)
                        {
                            result.Add(start);
                        }
                        else
                        {
                            result.Add(start + "-" + previous);
                        }
                        start    = null;
                        previous = null;
                    }
                }
            }
            if (previous != null)
            {
                if (start == previous)
                {
                    result.Add(start);
                }
                else
                {
                    result.Add(start + "-" + previous);
                }
            }
            return(result);
        }
Ejemplo n.º 9
0
 public static void ForEachBookFileInProject(string projectDir, Action <string, string> action)
 {
     string[] files = Directory.GetFiles(projectDir, "???" + Constants.kBookScriptFileExtension);
     for (int i = 1; i <= BCVRef.LastBook; i++)
     {
         string bookCode         = BCVRef.NumberToBookCode(i);
         string possibleFileName = Path.Combine(projectDir, bookCode + Constants.kBookScriptFileExtension);
         if (files.Contains(possibleFileName))
         {
             action(bookCode, possibleFileName);
         }
     }
 }
        public MockedBookForQuoteSystem(int bookNum, QuoteSystem desiredQuoteSystem, bool highlyConsistentData, bool includeSecondLevelQuotes)
        {
#if USE_RANDOM_SEED
            Debug.WriteLine("Seed base = " + s_seedBase);
            var seed = s_seedBase + bookNum;
#else
            var seed = 300 + bookNum;
#endif
            m_random                   = new Random(seed);
            m_desiredQuoteSystem       = desiredQuoteSystem;
            m_highlyConsistentData     = highlyConsistentData;
            m_includeSecondLevelQuotes = includeSecondLevelQuotes && m_desiredQuoteSystem.NormalLevels.Count > 1;
            BookId = BCVRef.NumberToBookCode(bookNum);
        }
Ejemplo n.º 11
0
        private void LoadBooks()
        {
            var files = BookScriptFiles;

            for (int i = 1; i <= BCVRef.LastBook; i++)
            {
                string bookCode   = BCVRef.NumberToBookCode(i);
                var    bookScript = TryLoadBook(files, bookCode);
                if (bookScript != null)
                {
                    m_books.Add(bookScript);
                }
            }
        }
Ejemplo n.º 12
0
 /// ------------------------------------------------------------------------------------
 /// <summary>
 /// Gets the start and end references for the line of text with the given marker. (Sets
 /// m_currentStartRef and m_currentEndRef.)
 /// </summary>
 /// <param name="marker"></param>
 /// <param name="lineContents"></param>
 /// <param name="literalVerse"></param>
 /// <returns></returns>
 /// ------------------------------------------------------------------------------------
 private void GetReferenceForLine(string marker, ref string lineContents,
                                  ref string literalVerse)
 {
     // If this is a marker that will change the reference, then update
     // the reference
     if (marker == ScrImportSet.s_markerBook)
     {
         int book = BCVRef.BookToNumber(lineContents.ToUpper());
         if (book > 0)
         {
             m_currentStartRef.Book    = book;
             m_currentStartRef.Chapter = 1;
             m_currentStartRef.Verse   = 0;
         }
         m_currentEndRef = new BCVRef(m_currentStartRef);
         m_seenIdInFile  = true;
     }
     else if (marker == ScrImportSet.s_markerChapter)
     {
         string chapterText = lineContents;
         try
         {
             m_currentStartRef.Chapter = ScrReference.ChapterToInt(chapterText, out lineContents);
             m_currentStartRef.Verse   = 1;
             m_currentEndRef           = new BCVRef(m_currentStartRef);
         }
         catch (ArgumentException)
         {
             throw new ScriptureUtilsException(SUE_ErrorCode.InvalidChapterNumber,
                                               m_currentFile.FileName, m_lineNumber, lineContents,
                                               BCVRef.NumberToBookCode(m_currentStartRef.Book), chapterText, null);
         }
     }
     else if (marker == ScrImportSet.s_markerVerse)
     {
         BCVRef.VerseToScrRef(lineContents.TrimStart(),
                              out literalVerse, out lineContents, ref m_currentStartRef, ref m_currentEndRef);
     }
     else
     {
         if (lineContents == " ")
         {
             lineContents = string.Empty;
         }
     }
 }
        private void RenameCharacter(int bookNum, int chapter, int verse, string existingCharacterId, string newCharacterId)
        {
            var block = m_testProject.Books.Single(book => book.BookId == BCVRef.NumberToBookCode(bookNum)).GetBlocksForVerse(chapter, verse)
                        .Single(b => b.CharacterId == existingCharacterId);

            block.CharacterId = newCharacterId;

            m_testProject.ProjectCharacterVerseData.Add(new CharacterVerse(new BCVRef(bookNum, chapter, verse),
                                                                           newCharacterId, string.Empty, string.Empty, true));

            if (!m_testProject.AllCharacterDetailDictionary.ContainsKey(newCharacterId))
            {
                m_testProject.AddProjectCharacterDetail(new CharacterDetail {
                    CharacterId = newCharacterId
                });
            }
        }
        public void Guess_NoTextForVersesWithQuotations_ReturnsDefaultQuoteSystem()
        {
            var mockedBooks = new List <IScrBook>();

            for (int i = 1; i < BCVRef.LastBook; i++)
            {
                var mockedBook = MockRepository.GenerateMock <IScrBook>();
                mockedBook.Stub(x => x.BookId).Return(BCVRef.NumberToBookCode(i));
                mockedBook.Stub(x => x.GetVerseText(Arg <int> .Is.Anything, Arg <int> .Is.Anything)).Return(string.Empty);
                mockedBooks.Add(mockedBook);
            }

            bool certain;

            Assert.AreEqual(QuoteSystem.Default, QuoteSystemGuesser.Guess(ControlCharacterVerseData.Singleton, mockedBooks, ScrVers.English, out certain));
            Assert.IsFalse(certain);
        }
        private void RenameCharacter(int bookNum, int chapter, int verse, string existingCharacterId, string newCharacterId)
        {
            var block = m_testProject.Books.Single(book => book.BookId == BCVRef.NumberToBookCode(bookNum)).GetBlocksForVerse(chapter, verse)
                        .Single(b => b.CharacterId == existingCharacterId);

            block.CharacterId = newCharacterId;

            m_testProject.ProjectCharacterVerseData.AddEntriesFor(bookNum, block);

            if (!m_testProject.AllCharacterDetailDictionary.ContainsKey(newCharacterId))
            {
                m_testProject.AddProjectCharacterDetail(new CharacterDetail {
                    CharacterId = newCharacterId
                });
            }

            m_testProject.ClearCharacterStatistics();             // This simulates behavior in UI when user makes changes in AssignCharacterDlg
        }
Ejemplo n.º 16
0
        /// ------------------------------------------------------------------------------------
        /// <summary>
        /// Construct an import exception whose message contains a description of the error,
        /// including the offending segment, optional additional information, and BCV details.
        /// </summary>
        /// <param name="errorCode">numeric error code</param>
        /// <param name="moreInfo">Details about the specific problem</param>
        /// <param name="fileName">file name where the error was encountered</param>
        /// <param name="lineNumber">line number in the file where the error occurred</param>
        /// <param name="lineContents">Contents of problematic line (or segment)</param>
        /// <param name="scrRef">scripture reference where the error occurred</param>
        /// ------------------------------------------------------------------------------------
        public ScriptureUtilsException(SUE_ErrorCode errorCode, string moreInfo,
                                       string fileName, int lineNumber, string lineContents, BCVRef scrRef)
        {
            bool fIncludeLineInfo;

            ErrorCode = errorCode;
            GetErrorMsgAndHelpTopic(errorCode, out m_message, out m_helpTopic, out fIncludeLineInfo);

            // Add file and Scripture reference information.
            m_message += Environment.NewLine + Environment.NewLine +
                         FormatErrorDetails(fileName, fIncludeLineInfo, lineNumber, lineContents,
                                            BCVRef.NumberToBookCode(scrRef.Book), scrRef.Chapter.ToString(), scrRef.Verse.ToString());

            // Add any details about the error.
            if (!string.IsNullOrEmpty(moreInfo))
            {
                m_message += Environment.NewLine + moreInfo;
            }
        }
Ejemplo n.º 17
0
        /// <summary>
        /// Gets all known (potential and/or expected) characters for the given reference range. If any of
        /// them are Hypothetical, those characters will only be included if the reference text contains a
        /// block actually assigned to that hypothetical character. Otherwise, the hypothetical character
        /// will be treated as a narrator "quotation."
        /// </summary>
        public HashSet <CharacterSpeakingMode> GetCharacters(int bookId, int chapter, IReadOnlyCollection <IVerse> verses,
                                                             ScrVers versification = null, bool includeAlternatesAndRareQuotes = false, bool includeNarratorOverrides = false)
        {
            Debug.Assert(!includeNarratorOverrides, "Can't think of any valid reason the quote parser should ever want to consider" +
                         "narrator overrides. These should be applied only after user has done disambiguation.");
            var result = m_cvInfo.GetCharacters(bookId, chapter, verses, versification,
                                                includeAlternatesAndRareQuotes, includeNarratorOverrides);
            var hypotheticalsToReplace = result.Where(cv =>
            {
                if (cv.QuoteType == QuoteType.Hypothetical)
                {
                    var refTextBook = m_referenceText.GetBook(bookId);
                    if (refTextBook != null)
                    {
                        return(!refTextBook.GetBlocksForVerse(chapter,
                                                              verses.First().StartVerse, verses.Last().EndVerse).Any(b => b.CharacterId == cv.Character));
                    }
                    // REVIEW: Should we replace hypotheticals if there is no reference text for this book?
                }
                return(false);
            }).Select(c => c.Character).ToList();

            if (hypotheticalsToReplace.Any())
            {
                result.Add(new CharacterSpeakingMode(CharacterVerseData.GetStandardCharacterId(BCVRef.NumberToBookCode(bookId), CharacterVerseData.StandardCharacter.Narrator),
                                                     String.Empty, String.Empty, false, QuoteType.Quotation));
                result.RemoveAll(e => e.QuoteType == QuoteType.Hypothetical && hypotheticalsToReplace.Contains(e.Character));
            }

            return(result);
        }
Ejemplo n.º 18
0
        private void AddAnnotations(List <List <object> > data)
        {
            var annotationRowIndexes = new List <int>();

            int    lastIndexOfPreviousVerse   = 0;
            BCVRef previousReferenceTextVerse = null;
            IEnumerable <VerseAnnotation> annotationsForPreviousVerse = null;

            for (int i = 0; i < data.Count; i++)
            {
                var row = data[i];
                if (HasReferenceText(row))
                {
                    var referenceTextVerse = GetBcvRefForRow(row);
                    if (referenceTextVerse != previousReferenceTextVerse)
                    {
                        if (previousReferenceTextVerse != null && annotationsForPreviousVerse != null)
                        {
                            foreach (var verseAnnotation in annotationsForPreviousVerse.Where(va => va.Annotation is Pause))
                            {
                                if (ExportAnnotationsInSeparateRows)
                                {
                                    var rowIndex = lastIndexOfPreviousVerse + 1 + verseAnnotation.Offset;
                                    data.Insert(rowIndex,
                                                GetExportDataForAnnotation(verseAnnotation, BCVRef.NumberToBookCode(previousReferenceTextVerse.Book),
                                                                           previousReferenceTextVerse.Chapter, previousReferenceTextVerse.Verse.ToString()));
                                    i++;
                                    annotationRowIndexes.Add(rowIndex);
                                }
                                else
                                {
                                    annotationRowIndexes.Add(AddAnnotationData(data, lastIndexOfPreviousVerse, verseAnnotation));
                                }
                            }
                        }
                        if (referenceTextVerse != previousReferenceTextVerse)
                        {
                            var annotationsForVerse = ControlAnnotations.Singleton.GetAnnotationsForVerse(referenceTextVerse);
                            foreach (var verseAnnotation in annotationsForVerse.Where(va => va.Annotation is Sound))
                            {
                                if (ExportAnnotationsInSeparateRows)
                                {
                                    var rowIndex = i++ + verseAnnotation.Offset;
                                    data.Insert(rowIndex,
                                                GetExportDataForAnnotation(verseAnnotation, BCVRef.NumberToBookCode(referenceTextVerse.Book),
                                                                           referenceTextVerse.Chapter, referenceTextVerse.Verse.ToString()));
                                    annotationRowIndexes.Add(rowIndex);
                                }
                                else
                                {
                                    annotationRowIndexes.Add(AddAnnotationData(data, i, verseAnnotation));
                                }
                            }

                            annotationsForPreviousVerse = annotationsForVerse;
                            previousReferenceTextVerse  = referenceTextVerse;
                        }
                    }
                    lastIndexOfPreviousVerse = i;
                }
            }

            m_annotatedRowIndexes = annotationRowIndexes;
        }
Ejemplo n.º 19
0
        private static void CompareAndCombineLists(bool includeFcbhNarrator, StringBuilder sb)
        {
            sb.Append("Control File Version").Append(kTab).Append(Environment.NewLine);
            sb.Append("#").Append(kTab).Append("C").Append(kTab).Append("V").Append(kTab)
            .Append("Character ID").Append(kTab).Append("FCBH Character ID").Append(kTab).Append("Delivery").Append(kTab)
            .Append("Alias").Append(kTab).Append("Quote Type").Append(kTab).Append("Default Character").Append(kTab)
            .Append("Parallel Passage").Append(Environment.NewLine);

            var allFcbh = TemplateData.All(includeFcbhNarrator);
            var control = ControlCharacterVerseData.Singleton.GetAllQuoteInfo();

            var fcbhDictionary = new Dictionary <BCVRef, List <TemplateDatum> >();

            foreach (TemplateDatum d in allFcbh)
            {
                List <TemplateDatum> list;
                if (fcbhDictionary.TryGetValue(d.BcvRef, out list))
                {
                    if (!list.Select(f => f.CharacterId).Contains(d.CharacterId))
                    {
                        list.Add(d);
                    }
                }
                else
                {
                    list = new List <TemplateDatum> {
                        d
                    };
                    fcbhDictionary.Add(d.BcvRef, list);
                }
            }

            var controlDictionary = new Dictionary <BCVRef, List <Glyssen.Character.CharacterVerse> >();

            foreach (Glyssen.Character.CharacterVerse cv in control)
            {
                List <Glyssen.Character.CharacterVerse> list;
                if (controlDictionary.TryGetValue(cv.BcvRef, out list))
                {
                    list.Add(cv);
                }
                else
                {
                    list = new List <Glyssen.Character.CharacterVerse> {
                        cv
                    };
                    controlDictionary.Add(cv.BcvRef, list);
                }
            }

            foreach (BCVRef bcvRef in new SortedSet <BCVRef>(fcbhDictionary.Keys.Union(controlDictionary.Keys)))
            {
                List <TemplateDatum> fcbhList;
                List <Glyssen.Character.CharacterVerse> controlList;
                bool hasFcbh    = fcbhDictionary.TryGetValue(bcvRef, out fcbhList);
                bool hasControl = controlDictionary.TryGetValue(bcvRef, out controlList);
                if (hasFcbh && fcbhList.Count == 1 && hasControl && controlList.Count == 1)
                {
                    var cv        = controlList[0];
                    var fcbhDatum = fcbhList[0];
                    sb.Append(cv.BookCode).Append(kTab)
                    .Append(cv.Chapter).Append(kTab).Append(cv.Verse).Append(kTab).Append(cv.Character).Append(kTab)
                    .Append(fcbhDatum.CharacterId).Append(kTab).Append(cv.Delivery).Append(kTab).Append(cv.Alias).Append(kTab)
                    .Append(cv.QuoteType).Append(kTab).Append(cv.DefaultCharacter).Append(kTab).Append(cv.ParallelPassageReferences)
                    .Append(Environment.NewLine);
                }
                else
                {
                    if (hasControl)
                    {
                        foreach (Glyssen.Character.CharacterVerse cv in controlList)
                        {
                            if (hasFcbh && fcbhList.Select(f => f.CharacterId).Contains(cv.Character))
                            {
                                sb.Append(cv.BookCode).Append(kTab)
                                .Append(cv.Chapter).Append(kTab).Append(cv.Verse).Append(kTab).Append(cv.Character).Append(kTab)
                                .Append(cv.Character).Append(kTab).Append(cv.Delivery).Append(kTab).Append(cv.Alias).Append(kTab)
                                .Append(cv.QuoteType).Append(kTab).Append(cv.DefaultCharacter).Append(kTab).Append(cv.ParallelPassageReferences)
                                .Append(Environment.NewLine);
                                foreach (TemplateDatum d in fcbhList.Where(f => f.CharacterId == cv.Character))
                                {
                                    d.IsProcessed = true;
                                }
                            }
                            else
                            {
                                sb.Append(cv.BookCode).Append(kTab)
                                .Append(cv.Chapter).Append(kTab).Append(cv.Verse).Append(kTab).Append(cv.Character).Append(kTab)
                                .Append(kTab).Append(cv.Delivery).Append(kTab).Append(cv.Alias).Append(kTab)
                                .Append(cv.QuoteType).Append(kTab).Append(cv.DefaultCharacter).Append(kTab).Append(cv.ParallelPassageReferences)
                                .Append(Environment.NewLine);
                            }
                        }
                    }
                    if (hasFcbh)
                    {
                        foreach (TemplateDatum d in fcbhList.Where(f => !f.IsProcessed))
                        {
                            if (!d.BcvRef.BookIsValid)
                            {
                                Debug.Fail("Invalid book: " + d.BcvRef);
                            }
                            var bookCode = BCVRef.NumberToBookCode(d.BcvRef.Book);
                            if (string.IsNullOrWhiteSpace(bookCode))
                            {
                                Debug.Fail("Invalid book code: " + bookCode);
                            }
                            sb.Append(bookCode).Append(kTab)
                            .Append(d.BcvRef.Chapter).Append(kTab).Append(d.BcvRef.Verse).Append(kTab).Append(kTab)
                            .Append(d.CharacterId).Append(kTab).Append(kTab).Append(kTab)
                            .Append(kTab).Append(kTab)
                            .Append(Environment.NewLine);
                        }
                    }
                }
            }
        }