/// <summary> /// Renders the specified parameters. /// </summary> /// <param name="parameters">The parameters.</param> /// <param name="renderCompleteHtmlPage">If set to <c>true</c>, render the complete HTML page.</param> /// <returns> /// The output of the rendering. /// </returns> public async Task <RenderedPassage> RenderAsync(RenderingParameters parameters, bool renderCompleteHtmlPage) { // Set up the rendered passage RenderedPassage renderedPassage = new RenderedPassage(); // Get the first provider IProvider?firstProvider = this.Providers.FirstOrDefault(p => p.Id == parameters.PrimaryProvider); if (firstProvider == null) { return(renderedPassage); } // Get the first translation Chapter firstChapter = await firstProvider.GetChapterAsync(parameters.PrimaryTranslation, parameters.PassageReference.ChapterReference); // Setup the previous chapter reference if (firstChapter.PreviousChapterReference.IsValid) { renderedPassage.PreviousPassage = firstChapter.PreviousChapterReference.AsPassageReference(); } else { renderedPassage.PreviousPassage = new PassageReference(); } // Setup the next chapter reference if (firstChapter.NextChapterReference.IsValid) { renderedPassage.NextPassage = firstChapter.NextChapterReference.AsPassageReference(); } else { renderedPassage.NextPassage = new PassageReference(); } // Render appropriately if (parameters.Format == RenderFormat.Text) { // Strip italics if (this.Providers.FirstOrDefault(p => p.Id == parameters.PrimaryProvider)?.SupportsItalics ?? false) { StringBuilder sb = new StringBuilder(); string[] lines = firstChapter.Text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); foreach (string line in lines) { sb.AppendLine(line.StripItalics()); } renderedPassage.Content = sb.ToString(); } else { // We cannot do interlinear in text mode renderedPassage.Content = firstChapter.Text; } } else if (parameters.Format == RenderFormat.Accordance) { // Strip italics if (this.Providers.FirstOrDefault(p => p.Id == parameters.PrimaryProvider)?.SupportsItalics ?? false) { StringBuilder sb = new StringBuilder(); string[] lines = firstChapter.Text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); foreach (string line in lines) { sb.Append(parameters.PassageReference.ChapterReference); sb.Append(':'); sb.AppendLine(line.RenderItalics("i").TrimStart()); } renderedPassage.Content = sb.ToString(); } else { // We cannot do interlinear in text mode renderedPassage.Content = firstChapter.Text; } } else { // Nuke the second translation, if it is the same if (parameters.PrimaryTranslation == parameters.SecondaryTranslation) { parameters.SecondaryTranslation = string.Empty; } // If we have a second translation StringBuilder sb = new StringBuilder(); if (renderCompleteHtmlPage) { sb.AppendLine("<!DOCTYPE html>"); sb.AppendLine("<html><head><meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\" />"); sb.AppendLine("<meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />"); sb.Append("<style>"); sb.Append(parameters.RenderCss()); sb.AppendLine("</style></head><body>"); } if (!string.IsNullOrWhiteSpace(parameters.SecondaryTranslation)) { // Get the second provider IProvider?secondProvider = this.Providers.FirstOrDefault(p => p.Id == (parameters.SecondaryProvider ?? parameters.PrimaryProvider)); if (secondProvider == null) { secondProvider = firstProvider; } // Get the second translation, if specified Chapter secondChapter = await secondProvider.GetChapterAsync(parameters.SecondaryTranslation, parameters.PassageReference.ChapterReference); // If the next chapter reference is invalid, see if this chapter has a valid reference if (!renderedPassage.NextPassage.IsValid && secondChapter.NextChapterReference.IsValid) { renderedPassage.NextPassage = secondChapter.NextChapterReference.AsPassageReference(); } // If the previous chapter reference is invalid, see if this chapter has a valid reference if (!renderedPassage.PreviousPassage.IsValid && secondChapter.PreviousChapterReference.IsValid) { renderedPassage.PreviousPassage = secondChapter.PreviousChapterReference.AsPassageReference(); } // Render both interlinear bool hasContent = false; List <string> lines1 = firstChapter.Text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).ToList(); List <string> lines2 = secondChapter.Text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).ToList(); // Add any missing verses for (int i = 0; i < lines1.Count; i++) { int expectedVerseNumber = i + 1; if (int.TryParse(lines1[i].Substring(0, lines1[i].IndexOf(' ')), out int verseNumber) && verseNumber > expectedVerseNumber) { lines1.Insert(i, $"{expectedVerseNumber} "); } } for (int i = 0; i < lines2.Count; i++) { int expectedVerseNumber = i + 1; if (int.TryParse(lines2[i].Substring(0, lines2[i].IndexOf(' ')), out int verseNumber) && verseNumber > expectedVerseNumber) { lines2.Insert(i, $"{expectedVerseNumber} "); } } // Render each line for (int i = 0; i < lines1.Count; i++) { hasContent = true; if (i < lines2.Count) { RenderedVerse firstAttempt = this.RenderInterlinearLinesAsHtml(lines1[i], lines2[i], parameters, false); RenderedVerse secondAttempt = this.RenderInterlinearLinesAsHtml(lines1[i], lines2[i], parameters, true); // If there are no words in any, skip if (firstAttempt.TotalWordsLine1 == 0 && firstAttempt.TotalWordsLine2 == 0 && firstAttempt.DivergentPhrases == 0 && firstAttempt.WordsInCommon == 0 && secondAttempt.TotalWordsLine1 == 0 && secondAttempt.TotalWordsLine2 == 0 && secondAttempt.DivergentPhrases == 0 && secondAttempt.WordsInCommon == 0) { continue; } // Calculate which rendering to use, based on verse statistics bool useFirstAttempt; if (firstAttempt.WordsInCommon == secondAttempt.WordsInCommon) { // Use the number of divergent phrases, as both have the same number of words in common useFirstAttempt = firstAttempt.DivergentPhrases > secondAttempt.DivergentPhrases; } else { useFirstAttempt = firstAttempt.WordsInCommon > secondAttempt.WordsInCommon; } if (parameters.IsDebug) { // Display debugging information for the renderer // This does not have to be user-friendly output if (i > 0) { sb.Append("<hr style=\"padding:0;border:none;width:100%;height:1px;color:#000;background-color:#000\">"); } // Build the verse statistics output string verseStatistics1 = $" <span style=\"font-family:consolas,courier\">[DivergentPhrases={firstAttempt.DivergentPhrases},TotalWordsLine1={firstAttempt.TotalWordsLine1},TotalWordsLine2={firstAttempt.TotalWordsLine2},WordsInCommon={firstAttempt.WordsInCommon}]</span><br>"; string verseStatistics2 = $" <span style=\"font-family:consolas,courier\">[DivergentPhrases={secondAttempt.DivergentPhrases},TotalWordsLine1={secondAttempt.TotalWordsLine1},TotalWordsLine2={secondAttempt.TotalWordsLine2},WordsInCommon={secondAttempt.WordsInCommon}]</span><br>"; string bestNote = " title=\"This is the method selected as the best by the renderer\""; if (useFirstAttempt) { sb.Append($"<strong style=\"font-family:consolas,courier\"{bestNote}>Reverse Scan*</strong>{verseStatistics1} {firstAttempt.Content}"); sb.Append($"<strong style=\"font-family:consolas,courier\">Forward Scan </strong>{verseStatistics2} {secondAttempt.Content}"); } else { sb.Append($"<strong style=\"font-family:consolas,courier\"{bestNote}>Reverse Scan </strong>{verseStatistics1} {firstAttempt.Content}"); sb.Append($"<strong style=\"font-family:consolas,courier\"{bestNote}>Forward Scan*</strong>{verseStatistics2} {secondAttempt.Content}"); } } else if (useFirstAttempt) { sb.Append(firstAttempt.Content); } else { sb.Append(secondAttempt.Content); } } else { sb.Append(this.RenderInterlinearLinesAsHtml(lines1[i], string.Empty, parameters, false).Content); } } // If lines 2 was longer, add its remaining entries if (lines2.Count > lines1.Count) { sb.AppendLine("</head><body>"); for (int i = lines1.Count; i < lines2.Count; i++) { sb.Append(this.RenderInterlinearLinesAsHtml(string.Empty, lines2[i], parameters, false).Content); } } // Make sure we have some content if (hasContent) { // Get all of the translations List <Translation> translations = new List <Translation>(); await foreach (Translation translation in firstProvider.GetTranslationsAsync()) { translations.Add(translation); } if (secondProvider.Id != firstProvider.Id) { await foreach (Translation translation in secondProvider.GetTranslationsAsync()) { translations.Add(translation); } } // Fix up the tooltips Translation?primaryTranslation = translations.FirstOrDefault(t => t.Code == parameters.PrimaryTranslation); string primaryTranslationName = primaryTranslation?.UniqueName(translations) ?? "Primary Translation"; Translation?secondaryTranslation = translations.FirstOrDefault(t => t.Code == parameters.SecondaryTranslation); string secondaryTranslationName = secondaryTranslation?.UniqueName(translations) ?? "Secondary Translation"; sb.Replace($"<span title=\"{parameters.PrimaryTranslation}\">", $"<span title=\"{primaryTranslationName}\">"); sb.Replace($"<span title=\"{parameters.SecondaryTranslation}\">", $"<span title=\"{secondaryTranslationName}\">"); // Supplement the translation copyrights if the chapter copyrights are missing if (string.IsNullOrWhiteSpace(firstChapter.Copyright) && !string.IsNullOrWhiteSpace(primaryTranslation?.Copyright)) { firstChapter.Copyright = primaryTranslation.Copyright; } if (string.IsNullOrWhiteSpace(secondChapter.Copyright) && !string.IsNullOrWhiteSpace(secondaryTranslation?.Copyright)) { secondChapter.Copyright = secondaryTranslation.Copyright; } // Display copyright if (!string.IsNullOrWhiteSpace(firstChapter.Copyright) || !string.IsNullOrWhiteSpace(secondChapter.Copyright)) { sb.Append("<p class=\"copyright\">"); if (!string.IsNullOrWhiteSpace(firstChapter.Copyright)) { sb.Append($"<strong>{primaryTranslationName}: </strong> {firstChapter.Copyright}"); } if (!string.IsNullOrWhiteSpace(firstChapter.Copyright) && !string.IsNullOrWhiteSpace(secondChapter.Copyright)) { sb.Append("<br>"); } if (!string.IsNullOrWhiteSpace(secondChapter.Copyright)) { sb.Append($"<strong>{secondaryTranslationName}: </strong> {secondChapter.Copyright}"); } sb.AppendLine("</p>"); } } } else { // Just render the first translation foreach (string line in firstChapter.Text.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)) { sb.Append(this.RenderLineAsHtml(line, parameters)); } // Display copyright if (!string.IsNullOrWhiteSpace(firstChapter.Copyright)) { sb.AppendLine($"<p class=\"copyright\">{firstChapter.Copyright}</p>"); } } if (renderCompleteHtmlPage) { // End the document sb.AppendLine("</body></html>"); } renderedPassage.Content = sb.ToString(); } // Return the rendered passage return(renderedPassage); }