From b4cbfc512f393c554aa97795a331e530a11a59e0 Mon Sep 17 00:00:00 2001 From: mark-sil Date: Wed, 15 Nov 2023 08:36:55 -0500 Subject: [PATCH 001/285] LT-21648: Use lcm 11.0.0-beta0081 Change-Id: Icfba3db3245b38f6155a774876f6caa745ecbeed --- Build/mkall.targets | 2 +- Build/nuget-common/packages.config | 18 +++++++++--------- Src/AppForTests.config | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Build/mkall.targets b/Build/mkall.targets index 6106ffae17..8b4024de05 100644 --- a/Build/mkall.targets +++ b/Build/mkall.targets @@ -285,7 +285,7 @@ 5.2.0-beta0003 13.0.0-beta0076 9.4.0.1-beta - 10.2.0-beta0075 + 11.0.0-beta0081 70.1.123 2.5.13 diff --git a/Build/nuget-common/packages.config b/Build/nuget-common/packages.config index 96cd1c5591..3eda2bb8f0 100644 --- a/Build/nuget-common/packages.config +++ b/Build/nuget-common/packages.config @@ -50,15 +50,15 @@ - - - - - - - - - + + + + + + + + + diff --git a/Src/AppForTests.config b/Src/AppForTests.config index 4e524462f8..6641ea1f45 100644 --- a/Src/AppForTests.config +++ b/Src/AppForTests.config @@ -4,7 +4,7 @@ - From 5b6167f8f51c5c39de98fed401287bfdb4c50e4f Mon Sep 17 00:00:00 2001 From: Jason Naylor Date: Fri, 10 Nov 2023 14:24:36 -0800 Subject: [PATCH 002/285] First draft of the code for adding a Word export option * Add export menu item and stubs for calling our word export code Change-Id: Ie488274342882cbad46be8ced5b23df86492f872 --- .../Export Templates/MicrosoftWord.xml | 4 + Src/xWorks/DictionaryExportService.cs | 30 ++++++- Src/xWorks/ExportDialog.cs | 83 ++++++++++++------- 3 files changed, 88 insertions(+), 29 deletions(-) create mode 100644 DistFiles/Language Explorer/Export Templates/MicrosoftWord.xml diff --git a/DistFiles/Language Explorer/Export Templates/MicrosoftWord.xml b/DistFiles/Language Explorer/Export Templates/MicrosoftWord.xml new file mode 100644 index 0000000000..4e07146b5b --- /dev/null +++ b/DistFiles/Language Explorer/Export Templates/MicrosoftWord.xml @@ -0,0 +1,4 @@ + + diff --git a/Src/xWorks/DictionaryExportService.cs b/Src/xWorks/DictionaryExportService.cs index 729315647b..2f8aaf7ef6 100644 --- a/Src/xWorks/DictionaryExportService.cs +++ b/Src/xWorks/DictionaryExportService.cs @@ -77,7 +77,35 @@ internal int CountReversalIndexEntries(IReversalIndex ri) return entries.Length; } - public void ExportDictionaryContent(string xhtmlPath, DictionaryConfigurationModel configuration = null, IThreadedProgress progress = null) + public void ExportDictionaryForWord(string filePath, DictionaryConfigurationModel configuration = null, IThreadedProgress progress = null) + { + using (ClerkActivator.ActivateClerkMatchingExportType(DictionaryType, m_propertyTable, m_mediator)) + { + configuration = configuration ?? new DictionaryConfigurationModel(DictionaryConfigurationListener.GetCurrentConfiguration(m_propertyTable, "Dictionary"), m_cache); + var publicationDecorator = ConfiguredLcmGenerator.GetPublicationDecoratorAndEntries(m_propertyTable, out var entriesToSave, DictionaryType); + if (progress != null) + progress.Maximum = entriesToSave.Length; + // TODO: Create and add call to our Word content generator LcmWordGenerator(entriesToSave, publication, configuration, filePath, progress); + } + } + + public void ExportReversalForWord(string filePath, string reversalWs, DictionaryConfigurationModel configuration = null, IThreadedProgress progress = null) + { + Guard.AgainstNullOrEmptyString(reversalWs, nameof(reversalWs)); + using (ClerkActivator.ActivateClerkMatchingExportType(ReversalType, m_propertyTable, m_mediator)) + using (ReversalIndexActivator.ActivateReversalIndex(reversalWs, m_propertyTable, m_cache)) + { + configuration = configuration ?? new DictionaryConfigurationModel( + DictionaryConfigurationListener.GetCurrentConfiguration(m_propertyTable, "ReversalIndex"), m_cache); + var publicationDecorator = ConfiguredLcmGenerator.GetPublicationDecoratorAndEntries(m_propertyTable, out var entriesToSave, ReversalType); + if (progress != null) + progress.Maximum = entriesToSave.Length; + + // TODO: Create and add call to our Word content generator LcmWordGenerator(entriesToSave, publication, configuration, filePath, progress); + } + } + + public void ExportDictionaryContent(string xhtmlPath, DictionaryConfigurationModel configuration = null, IThreadedProgress progress = null) { using (ClerkActivator.ActivateClerkMatchingExportType(DictionaryType, m_propertyTable, m_mediator)) { diff --git a/Src/xWorks/ExportDialog.cs b/Src/xWorks/ExportDialog.cs index acfd69d18f..98898db732 100644 --- a/Src/xWorks/ExportDialog.cs +++ b/Src/xWorks/ExportDialog.cs @@ -83,7 +83,8 @@ protected internal enum FxtTypes kftGrammarSketch, kftClassifiedDict, kftSemanticDomains, - kftWebonary + kftWebonary, + kftWordOpenXml } // ReSharper restore InconsistentNaming protected internal struct FxtType @@ -638,6 +639,7 @@ private void btnExport_Click(object sender, EventArgs e) case FxtTypes.kftWebonary: ProcessWebonaryExport(); return; + case FxtTypes.kftWordOpenXml: default: using (var dlg = new SaveFileDialogAdapter()) { @@ -680,7 +682,7 @@ private void btnExport_Click(object sender, EventArgs e) m_propertyTable.SetPropertyPersistence("ExportDlgShowInFolder", true); } } - } + } private static void OpenExportFolder(string sDirectory, string sFileName) { @@ -838,39 +840,61 @@ protected void DoExport(string outPath, bool fLiftOutput) progressDlg.Restartable = true; progressDlg.RunTask(true, ExportGrammarSketch, outPath, ft.m_sDataType, ft.m_sXsltFiles); break; - } - TrackingHelper.TrackExport(m_areaOrig, exportType, ImportExportStep.Succeeded); + case FxtTypes.kftWordOpenXml: + progressDlg.Minimum = 0; + progressDlg.Maximum = 1000; + progressDlg.AllowCancel = true; + progressDlg.Restartable = true; + progressDlg.RunTask(true, ExportWordOpenXml, outPath, ft.m_sDataType, ft.m_sXsltFiles); + break; + } - catch (WorkerThreadException e) + TrackingHelper.TrackExport(m_areaOrig, exportType, ImportExportStep.Succeeded); + } + catch (WorkerThreadException e) + { + TrackingHelper.TrackExport(m_areaOrig, exportType, ImportExportStep.Failed); + if (e.InnerException is CancelException) { - TrackingHelper.TrackExport(m_areaOrig, exportType, ImportExportStep.Failed); - if (e.InnerException is CancelException) - { - MessageBox.Show(this, e.InnerException.Message); - m_ce = null; - } - else if (e.InnerException is LiftFormatException) - { - // Show the pretty yellow semi-crash dialog box, with instructions for the - // user to report the bug. - var app = m_propertyTable.GetValue("App"); - ErrorReporter.ReportException(new Exception(xWorksStrings.ksLiftExportBugReport, e.InnerException), - app.SettingsKey, m_propertyTable.GetValue("FeedbackInfoProvider").SupportEmailAddress, this, false); - } - else - { - string msg = xWorksStrings.ErrorExporting_ProbablyBug + Environment.NewLine + e.InnerException.Message; - MessageBox.Show(this, msg); - } + MessageBox.Show(this, e.InnerException.Message); + m_ce = null; + } + else if (e.InnerException is LiftFormatException) + { + // Show the pretty yellow semi-crash dialog box, with instructions for the + // user to report the bug. + var app = m_propertyTable.GetValue("App"); + ErrorReporter.ReportException(new Exception(xWorksStrings.ksLiftExportBugReport, e.InnerException), + app.SettingsKey, m_propertyTable.GetValue("FeedbackInfoProvider").SupportEmailAddress, this, false); } - finally + else { - m_progressDlg = null; - m_dumper = null; - Close(); + string msg = xWorksStrings.ErrorExporting_ProbablyBug + Environment.NewLine + e.InnerException.Message; + MessageBox.Show(this, msg); } } + finally + { + m_progressDlg = null; + m_dumper = null; + Close(); + } + } + } + + private object ExportWordOpenXml(IThreadedProgress progress, object[] args) + { + if (args.Length < 1) + return null; + var filePath = (string)args[0]; + var exportService = new DictionaryExportService(m_propertyTable, m_mediator); + exportService.ExportDictionaryForWord(filePath, null, progress); + foreach (var reversal in m_cache.ServiceLocator.GetInstance().AllInstances()) + { + exportService.ExportReversalForWord(filePath, reversal.WritingSystem, new DictionaryConfigurationModel()); } + return null; + } private object ExportConfiguredXhtml(IThreadedProgress progress, object[] args) { @@ -1261,6 +1285,9 @@ protected virtual void ConfigureItem(XmlDocument document, ListViewItem item, Xm case "webonary": ft.m_ft = FxtTypes.kftWebonary; break; + case "wordOpenXml": + ft.m_ft = FxtTypes.kftWordOpenXml; + break; case "LIFT": ft.m_ft = FxtTypes.kftLift; break; From 3a9900526bcda1919e33e4ba75b4993bf5e1a95d Mon Sep 17 00:00:00 2001 From: mark-sil Date: Wed, 15 Nov 2023 13:48:30 -0500 Subject: [PATCH 003/285] LT-21671: Add OpenXml nuget package Change-Id: I299c324a979245138fa1c3275fa13055255608a5 --- Build/mkall.targets | 1 + Build/nuget-common/packages.config | 1 + Src/xWorks/xWorks.csproj | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/Build/mkall.targets b/Build/mkall.targets index 8b4024de05..5ce41487c4 100644 --- a/Build/mkall.targets +++ b/Build/mkall.targets @@ -649,6 +649,7 @@ + diff --git a/Build/nuget-common/packages.config b/Build/nuget-common/packages.config index 3eda2bb8f0..c3a295c2ac 100644 --- a/Build/nuget-common/packages.config +++ b/Build/nuget-common/packages.config @@ -8,6 +8,7 @@ + diff --git a/Src/xWorks/xWorks.csproj b/Src/xWorks/xWorks.csproj index c1fd6b8d9e..a4d4c12beb 100644 --- a/Src/xWorks/xWorks.csproj +++ b/Src/xWorks/xWorks.csproj @@ -152,6 +152,10 @@ False ..\..\Output\Debug\DesktopAnalytics.dll + + False + ..\..\Output\Debug\DocumentFormat.OpenXml.dll + ..\..\packages\DotNetZip.1.13.7\lib\net40\DotNetZip.dll @@ -280,6 +284,7 @@ False ..\..\Output\Debug\UIAdapterInterfaces.dll + ..\..\Output\Debug\xCore.dll False From 6042763d417c248a432051a944c5e59d6eac6b15 Mon Sep 17 00:00:00 2001 From: Jason Naylor Date: Wed, 15 Nov 2023 13:58:26 -0800 Subject: [PATCH 004/285] Fix LT-21677 - Use exact matching for lang tags * Also generates consistent quote usage for css ' instead of " - As best as I can tell our changes to merge styles and generate less specific selectors have caused the lang selection to become an issue. |= matches 'ur' or 'ur-x-latn' but if a user has both the wrong match might win - Also some of our tests were apparently broken before by the '|=' in the regular expression matching and were generating false negative results Change-Id: Iee46321ce036ae34e7349265d8bcf6b191368719 --- Src/xWorks/CssGenerator.cs | 8 +++--- Src/xWorks/xWorksTests/CssGeneratorTests.cs | 32 ++++++++++----------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Src/xWorks/CssGenerator.cs b/Src/xWorks/CssGenerator.cs index f4b613cdac..af9eda414d 100644 --- a/Src/xWorks/CssGenerator.cs +++ b/Src/xWorks/CssGenerator.cs @@ -288,7 +288,7 @@ private static List GenerateCssForWritingSystems(string selector, str { // We want only the character type settings from the styleName style since we're applying them // to a span. - var wsRule = new StyleRule { Value = selector + String.Format("[lang|=\"{0}\"]", aws.LanguageTag) }; + var wsRule = new StyleRule { Value = selector + String.Format("[lang=\'{0}\']", aws.LanguageTag) }; var styleDecls = GenerateCssStyleFromLcmStyleSheet(styleName, aws.Handle, propertyTable); wsRule.Declarations.Properties.AddRange(GetOnlyCharacterStyle(styleDecls)); styleRules.Add(wsRule); @@ -666,7 +666,7 @@ private static List GenerateCssFromWsOptions(ConfigurableDictionaryNo // if the writing system isn't a magic name just use it otherwise find the right one from the magic list var wsIdString = possiblyMagic == 0 ? ws.Id : WritingSystemServices.GetWritingSystemList(cache, possiblyMagic, true).First().Id; var wsId = cache.LanguageWritingSystemFactoryAccessor.GetWsFromStr(wsIdString); - var wsRule = new StyleRule {Value = baseSelection + String.Format("[lang|=\"{0}\"]", wsIdString)}; + var wsRule = new StyleRule {Value = baseSelection + String.Format("[lang=\'{0}\']", wsIdString)}; if (!string.IsNullOrEmpty(configNode.Style)) wsRule.Declarations.Properties.AddRange(GenerateCssStyleFromLcmStyleSheet(configNode.Style, wsId, propertyTable)); if (!IsEmptyRule(wsRule)) @@ -829,7 +829,7 @@ private static List GenerateSelectorsFromNode(ConfigurableDictionaryN for (var i = enabledWsOptions.Length - 1; i > 0; i--) { betweenSelector = (i == enabledWsOptions.Length - 1 ? string.Empty : betweenSelector + ",") + - $"{selectorOfWsOptOwner} span+span[lang|='{enabledWsOptions[i].Id}']:before"; + $"{selectorOfWsOptOwner} span+span[lang='{enabledWsOptions[i].Id}']:before"; } } break; @@ -1436,7 +1436,7 @@ private static void AddFontInfoCss(BaseStyleInfo projectStyle, StyleDeclaration // fontName still null means not set in Normal Style, then get default fonts from WritingSystems configuration. // Comparison, projectStyle.Name == "Normal", required to limit the font-family definition to the - // empty span (ie span[lang|="en"]{}. If not included, font-family will be added to many more spans. + // empty span (ie span[lang="en"]{}. If not included, font-family will be added to many more spans. if (fontName == null && projectStyle.Name == "Normal") { var lgWritingSysytem = cache.ServiceLocator.WritingSystemManager.get_EngineOrNull(wsId); diff --git a/Src/xWorks/xWorksTests/CssGeneratorTests.cs b/Src/xWorks/xWorksTests/CssGeneratorTests.cs index 82e381bfc0..6bb1391a9f 100644 --- a/Src/xWorks/xWorksTests/CssGeneratorTests.cs +++ b/Src/xWorks/xWorksTests/CssGeneratorTests.cs @@ -544,7 +544,7 @@ public void GenerateCssForConfiguration_DefinitionOrGlossBeforeAfterConfigGenera //SUT var cssResult = CssGenerator.GenerateCssFromConfiguration(model, m_propertyTable); VerifyRegex(cssResult, @".definitionorgloss> span:first-child:before{.*content:'<';.*}", "Before not generated."); - VerifyRegex(cssResult, @".definitionorgloss> span\+span\[lang\|=\'en\']:before{.*content:',';.*}", "Between not generated."); + VerifyRegex(cssResult, @".definitionorgloss> span\+span\[lang\=\'en\']:before{.*content:',';.*}", "Between not generated."); VerifyRegex(cssResult, @".definitionorgloss> span:last-child:after{.*content:'>';.*}", "After not generated."); } @@ -1223,7 +1223,7 @@ public void GenerateCssForStyleName_DefaultVernMagicConfigResultsInRealLanguageC //SUT var cssResult = CssGenerator.GenerateCssFromConfiguration(model, m_propertyTable); //Verify that vernacular was converted into french to match the vernholder node - Assert.That(cssResult, Contains.Substring(".vernholder> span[lang|=\"fr\"]")); + Assert.That(cssResult, Contains.Substring(".vernholder> span[lang='fr']")); } [Test] @@ -1247,7 +1247,7 @@ public void GenerateCssForStyleName_DefaultAnalysisMagicConfigResultsInRealLangu //SUT var cssResult = CssGenerator.GenerateCssFromConfiguration(model, m_propertyTable); //Verify that analysis was converted into english to match the analyholder node - Assert.That(cssResult, Contains.Substring(".analyholder> span[lang|=\"en\"]")); + Assert.That(cssResult, Contains.Substring(".analyholder> span[lang='en']")); } [Test] @@ -1400,8 +1400,8 @@ public void GenerateCssForConfiguration_CharStyleSubscriptWorks() var cssResult = CssGenerator.GenerateCssFromConfiguration(model, m_propertyTable); //make sure that fontinfo with the subscript overrides made it into css VerifyExtraFontInfoInCss(0, FwSuperscriptVal.kssvSub, FwUnderlineType.kuntNone, Color.Black, cssResult); - Assert.IsTrue(Regex.Match(cssResult, @".*\.sil*\.fieldworks.xworks.testrootclass>\s*span\[lang|='fr']\{.*position\:relative\*top\:-0.2em.*", RegexOptions.Singleline).Success, - "Subscript's positiion not generated properly"); + Assert.IsTrue(Regex.Match(cssResult, @".*\.sil*\.fieldworks.xworks.testrootclass>\s*span\[lang='fr'\]\{.*position\:relative;\s*top\:0.3em.*", RegexOptions.Singleline).Success, + "Subscript's position not generated properly"); } [Test] @@ -1427,8 +1427,8 @@ public void GenerateCssForConfiguration_CharStyleSuperscriptWorks() var cssResult = CssGenerator.GenerateCssFromConfiguration(model, m_propertyTable); //make sure that fontinfo with the superscript overrides made it into css VerifyExtraFontInfoInCss(0, FwSuperscriptVal.kssvSuper, FwUnderlineType.kuntNone, Color.Black, cssResult); - Assert.IsTrue(Regex.Match(cssResult, @".*\.sil*\.fieldworks.xworks.testrootclass>\s*span\[lang|='fr']\{.*position\:relative\*top\:\0.2em.*", RegexOptions.Singleline).Success, - "Superscript's positiion not generated properly"); + Assert.IsTrue(Regex.Match(cssResult, @".*\.sil*\.fieldworks.xworks.testrootclass>\s*span\[lang='fr']\{.*position\:relative;\s*top\:-0.6em.*", RegexOptions.Singleline).Success, + "Superscript's position not generated properly"); } [Test] @@ -2076,7 +2076,7 @@ public void GenerateCssForConfiguration_WritingSystemAudioWorks() // SUT var cssResult = CssGenerator.GenerateCssFromConfiguration(model, m_propertyTable); // Not using regex to avoid figuring out all the escapes necessary - Assert.That(cssResult, Contains.Substring(".lexemeformoa> span[lang|=\"en-Zxxx-x-audio\"]{")); + Assert.That(cssResult, Contains.Substring(".lexemeformoa> span[lang='en-Zxxx-x-audio']{")); VerifyRegex(cssResult, @"a.en-Zxxx-x-audio{.*text-decoration:none;.*}", "Audio not generated."); } @@ -2376,7 +2376,7 @@ public void GenerateCssForConfiguration_PrimaryEntryReferencesTypeContextWorks() VerifyRegex(cssResult, @"^\.testheadword:after{\s*content:'ah';\s*}", "Headword's selector should *not* have changed due to factoring"); VerifyRegex(cssResult, @"\s*\.reversename>\s*span:first-child:before{\s*content:'beef';\s*}"); - VerifyRegex(cssResult, @"\s*\.reversename>\s*span+span\[lang|='" + lang2 + @"'\]:before{\s*content:'viet';\s*}"); + VerifyRegex(cssResult, @"\s*\.reversename>\s*span\+span\[lang='" + lang2 + @"'\]:before{\s*content:'viet';\s*}"); VerifyRegex(cssResult, @"\s*\.reversename>\s*span:last-child:after{\s*content:'aft';\s*}"); } @@ -2455,7 +2455,7 @@ public void GenerateCssForConfiguration_BetweenMultiWsWithoutAbbrSpanWorks() PopulateFieldsForTesting(entry); // SUT var cssResult = CssGenerator.GenerateCssFromConfiguration(model, m_propertyTable); - VerifyRegex(cssResult, @".*\.lexemeform>\s*span\+span\[lang\|\='fr'\]:before{.*content:','.*}", + VerifyRegex(cssResult, @".*\.lexemeform>\s*span\+span\[lang\='fr'\]:before{.*content:','.*}", "Between Multi-WritingSystem without Abbr selector not generated."); } @@ -2951,8 +2951,8 @@ public void GenerateCssForConfiguration_WsSpanWithNormalStyle() PopulateFieldsForTesting(testEntryNode); // Default (no ws) style info const string defaultStyle = "body{font-size:10pt;}"; - const string englishStyle = "span[lang|=\"en\"]{font-family:'english',serif;color:#F00;}"; - const string frenchStyle = "span[lang|=\"fr\"]{font-family:'french',serif;color:#008000;}"; + const string englishStyle = "span[lang='en']{font-family:'english',serif;color:#F00;}"; + const string frenchStyle = "span[lang='fr']{font-family:'french',serif;color:#008000;}"; //SUT var cssResult = CssGenerator.GenerateCssFromConfiguration(model, m_propertyTable); Assert.That(Regex.Replace(cssResult, @"\t|\n|\r", ""), Contains.Substring(defaultStyle + englishStyle + frenchStyle)); @@ -2999,8 +2999,8 @@ public void GenerateCssForConfiguration_NormalStyleForWsDoesNotOverrideNodeStyle }; PopulateFieldsForTesting(entryNode); // Default (no ws) style info - const string englishGeneralStyle = "span[lang|=\"en\"]{font-family:'english',serif;color:#F00;}"; - const string definitionSelector = ".definition span[lang|=\"en\"]{color:#FF0;}"; + const string englishGeneralStyle = "span[lang='en']{font-family:'english',serif;color:#F00;}"; + const string definitionSelector = ".definition span[lang='en']{color:#FF0;}"; //SUT var cssResult = CssGenerator.GenerateCssFromConfiguration(model, m_propertyTable); // Using substring instead of regex to avoid spending all the time figuring out which regex characters to escape in this css @@ -3131,7 +3131,7 @@ public void GenerateCssForConfiguration_DictionaryMinorUnusedDoesNotOverride() // The problem we are testing for occurred in the section of CssGenerator labeled: // "Then generate the rules for all the writing system overrides" // So I chose to check specifically for one of the default writing systems; DefaultAnalWs would have worked too. - var vernStyle = "span[lang|=\"" + vernWs + "\"]{color:#008000;}"; + var vernStyle = "span[lang='" + vernWs + "']{color:#008000;}"; Assert.That(Regex.Replace(cssResult, @"\t|\n|\r", ""), Contains.Substring(@"div.minorentryvariant " + vernStyle), "Dictionary-Secondary Paragraph Style should be generated."); } @@ -3828,7 +3828,7 @@ public void GenerateCssForConfiguration_SpecificLanguageColorIsNotOverridenByPar // default (analysis ws) rule const string regexPrimary = @"^\.extendednotecontents\{\s*color:#008000;"; // specific (embedded vernacular ws) rule affecting any span inside .extendednotecontents (at any level) - const string regexSpecific = @"^\.extendednotecontents span\[lang|='fr']\{\s*color:#00F"; + const string regexSpecific = @"^\.extendednotecontents span\[lang='fr']\{\s*color:#00F"; VerifyRegex(result, regexPrimary, "The css for the default color should be there."); VerifyRegex(result, regexSpecific, "The css for the specific language color should be there."); } From 9b49e37a189f759e21c23a818e1975a01b28c713 Mon Sep 17 00:00:00 2001 From: mark-sil Date: Wed, 15 Nov 2023 09:13:23 -0500 Subject: [PATCH 005/285] =?UTF-8?q?LT-21648:=20Add=20=E2=80=9CCaption=20(o?= =?UTF-8?q?r=20Headword)=E2=80=9D=20to=20Dictionary=20Config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In Dictionary Configuration->Pictures add an option that will first try to get the Caption for a writing system. If there isn’t a caption then try to get the Headword for the writing system. Note: In the fwdictconfig files we also made changes to Pictures->Headword so that they display the Headwords for the specified writing system. Previously we were always displaying the headword for the default ws, regardless of which ws the user selected. If multipe ws’s were selected we were displaying the default headword multiple times. Change-Id: I91d66eebaba69a832a30c2fafe6c452e9b6dbf27 --- .../Dictionary/Hybrid.fwdictconfig | 21 ++- .../Dictionary/Lexeme.fwdictconfig | 14 +- .../Dictionary/Root.fwdictconfig | 28 +++- Src/xWorks/ConfiguredLcmGenerator.cs | 31 ++++ .../ConfiguredXHTMLGeneratorTestHelpers.cs | 2 +- .../ConfiguredXHTMLGeneratorTests.cs | 133 ++++++++++++++++++ 6 files changed, 219 insertions(+), 10 deletions(-) diff --git a/DistFiles/Language Explorer/DefaultConfigurations/Dictionary/Hybrid.fwdictconfig b/DistFiles/Language Explorer/DefaultConfigurations/Dictionary/Hybrid.fwdictconfig index 55b3c99278..7ae9d0e438 100644 --- a/DistFiles/Language Explorer/DefaultConfigurations/Dictionary/Hybrid.fwdictconfig +++ b/DistFiles/Language Explorer/DefaultConfigurations/Dictionary/Hybrid.fwdictconfig @@ -1112,13 +1112,18 @@ + + + + - + @@ -2111,13 +2116,18 @@ + + + + - + @@ -3621,13 +3631,18 @@ + + + + - + diff --git a/DistFiles/Language Explorer/DefaultConfigurations/Dictionary/Lexeme.fwdictconfig b/DistFiles/Language Explorer/DefaultConfigurations/Dictionary/Lexeme.fwdictconfig index e43fae4152..2c37665ca9 100644 --- a/DistFiles/Language Explorer/DefaultConfigurations/Dictionary/Lexeme.fwdictconfig +++ b/DistFiles/Language Explorer/DefaultConfigurations/Dictionary/Lexeme.fwdictconfig @@ -1001,13 +1001,18 @@ + + + + - + @@ -1947,13 +1952,18 @@ + + + + - + diff --git a/DistFiles/Language Explorer/DefaultConfigurations/Dictionary/Root.fwdictconfig b/DistFiles/Language Explorer/DefaultConfigurations/Dictionary/Root.fwdictconfig index 9836860cb1..95b314eee0 100644 --- a/DistFiles/Language Explorer/DefaultConfigurations/Dictionary/Root.fwdictconfig +++ b/DistFiles/Language Explorer/DefaultConfigurations/Dictionary/Root.fwdictconfig @@ -868,13 +868,18 @@ + + + + - + @@ -1917,13 +1922,18 @@ + + + + - + @@ -2949,13 +2959,18 @@ + + + + - + @@ -4393,13 +4408,18 @@ + + + + - + diff --git a/Src/xWorks/ConfiguredLcmGenerator.cs b/Src/xWorks/ConfiguredLcmGenerator.cs index 6b37009d9b..bcd2f95d33 100644 --- a/Src/xWorks/ConfiguredLcmGenerator.cs +++ b/Src/xWorks/ConfiguredLcmGenerator.cs @@ -378,6 +378,13 @@ internal static string GenerateContentForFieldByReflection(object field, Configu return ret.ToString(); } } + if (config.FieldDescription == "CaptionOrHeadword") + { + if (field is ICmPicture) + { + return GenerateContentForCaptionOrHeadword(field as ICmPicture, config, settings); + } + } if (config.IsCustomField && config.SubField == null) { // REVIEW: We have overloaded terms here, this is a C# class not a css class, consider a different name @@ -864,6 +871,30 @@ private static string GenerateContentForDefOrGloss(ILexSense sense, Configurable return string.Empty; } + private static string GenerateContentForCaptionOrHeadword(ICmPicture picture, ConfigurableDictionaryNode config, GeneratorSettings settings) + { + var wsOption = config.DictionaryNodeOptions as DictionaryNodeWritingSystemOptions; + if (wsOption == null) + throw new ArgumentException(@"Configuration nodes for MultiString fields should have WritingSystemOptions", "config"); + var bldr = new StringBuilder(); + foreach (var option in wsOption.Options) + { + if (option.IsEnabled) + { + int wsId; + ITsString bestString = picture.GetCaptionOrHeadword(option.Id, out wsId); + if (bestString != null) + { + var contentItem = GenerateWsPrefixAndString(config, settings, wsOption, wsId, bestString, Guid.Empty); + bldr.Append(contentItem); + } + } + } + if (bldr.Length > 0) + return settings.ContentGenerator.WriteProcessedCollection(false, bldr.ToString(), GetClassNameAttributeForConfig(config)); + return String.Empty; + } + internal static string CopyFileSafely(GeneratorSettings settings, string source, string relativeDestination) { if (!File.Exists(source)) diff --git a/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorTestHelpers.cs b/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorTestHelpers.cs index 166c691e9b..64039baab7 100644 --- a/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorTestHelpers.cs +++ b/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorTestHelpers.cs @@ -448,7 +448,7 @@ internal static ICmPicture CreatePicture(LcmCache cache, bool exists = true, str if (caption != null) { var wsHandle = cache.WritingSystemFactory.GetWsFromStr(ws); - pic.Caption.set_String(wsHandle, TsStringUtils.MakeString("caption", wsHandle)); + pic.Caption.set_String(wsHandle, TsStringUtils.MakeString(caption, wsHandle)); } var file = cache.ServiceLocator.GetInstance().Create(); if (cache.LangProject.MediaOC.Any()) diff --git a/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorTests.cs b/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorTests.cs index 2dfbe96618..f35c7879a3 100644 --- a/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorTests.cs +++ b/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorTests.cs @@ -1277,6 +1277,139 @@ public void GenerateContentForEntry_DontDisplayNotSure() AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(gramName2, 1); } + [Test] + public void GenerateContentForEntry_CaptionOrHeadwordGetsCaption() + { + var wsOpts = GetWsOptionsForLanguages(new[] { "en" }); + var headwordNode = new ConfigurableDictionaryNode + { + FieldDescription = "MLHeadWord", + CSSClassNameOverride = "headword", + DictionaryNodeOptions = wsOpts + }; + + var captionOrHeadwordNode = new ConfigurableDictionaryNode { FieldDescription = "CaptionOrHeadword", DictionaryNodeOptions = wsOpts }; + var pictureNode = new ConfigurableDictionaryNode + { + DictionaryNodeOptions = new DictionaryNodePictureOptions(), + FieldDescription = "PicturesOfSenses", + CSSClassNameOverride = "Pictures", + Children = new List { captionOrHeadwordNode } + }; + + var sensesNode = new ConfigurableDictionaryNode + { + FieldDescription = "Senses", + }; + var mainEntryNode = new ConfigurableDictionaryNode + { + Children = new List { sensesNode, pictureNode, headwordNode }, + FieldDescription = "LexEntry" + }; + CssGeneratorTests.PopulateFieldsForTesting(mainEntryNode); + var entryOne = CreateInterestingLexEntry(Cache); + AddHeadwordToEntry(entryOne, "HeadwordEn", m_wsEn); + var sense = entryOne.SensesOS[0]; + sense.PicturesOS.Add(CreatePicture(Cache, true, "captionEn", "en")); + + var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); + //SUT + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings); + const string captionOrHeadwordContainsCaption = "/div[@class='lexentry']/span[@class='pictures']/div[@class='picture']/div[@class='captionContent']/span[@class='captionorheadword']//span[text()='captionEn']"; + //This assert is dependent on the specific entry data created in CreateInterestingLexEntry + AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(captionOrHeadwordContainsCaption, 1); + } + + [Test] + public void GenerateContentForEntry_CaptionOrHeadwordGetsHeadword() + { + var wsOpts = GetWsOptionsForLanguages(new[] { "en" }); + var headwordNode = new ConfigurableDictionaryNode + { + FieldDescription = "MLHeadWord", + CSSClassNameOverride = "headword", + DictionaryNodeOptions = wsOpts + }; + + var captionOrHeadwordNode = new ConfigurableDictionaryNode { FieldDescription = "CaptionOrHeadword", DictionaryNodeOptions = wsOpts }; + var pictureNode = new ConfigurableDictionaryNode + { + DictionaryNodeOptions = new DictionaryNodePictureOptions(), + FieldDescription = "PicturesOfSenses", + CSSClassNameOverride = "Pictures", + Children = new List { captionOrHeadwordNode } + }; + + var sensesNode = new ConfigurableDictionaryNode + { + FieldDescription = "Senses", + }; + var mainEntryNode = new ConfigurableDictionaryNode + { + Children = new List { sensesNode, pictureNode, headwordNode }, + FieldDescription = "LexEntry" + }; + CssGeneratorTests.PopulateFieldsForTesting(mainEntryNode); + var entryOne = CreateInterestingLexEntry(Cache); + AddHeadwordToEntry(entryOne, "HeadwordEn", m_wsEn); + var sense = entryOne.SensesOS[0]; + sense.PicturesOS.Add(CreatePicture(Cache, true, null, "en")); // Create the picture with a null caption. + + var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); + //SUT + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings); + const string captionOrHeadwordContainsHeadword = "/div[@class='lexentry']/span[@class='pictures']/div[@class='picture']/div[@class='captionContent']/span[@class='captionorheadword']//span[text()='HeadwordEn']"; + //This assert is dependent on the specific entry data created in CreateInterestingLexEntry + AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(captionOrHeadwordContainsHeadword, 1); + } + + [Test] + public void GenerateContentForEntry_CaptionOrHeadword_HandlePerWs() + { + var wsOpts = GetWsOptionsForLanguages(new[] { "en", "fr" }); + var headwordNode = new ConfigurableDictionaryNode + { + FieldDescription = "MLHeadWord", + CSSClassNameOverride = "headword", + DictionaryNodeOptions = wsOpts + }; + + var captionOrHeadwordNode = new ConfigurableDictionaryNode { FieldDescription = "CaptionOrHeadword", DictionaryNodeOptions = wsOpts }; + var pictureNode = new ConfigurableDictionaryNode + { + DictionaryNodeOptions = new DictionaryNodePictureOptions(), + FieldDescription = "PicturesOfSenses", + CSSClassNameOverride = "Pictures", + Children = new List { captionOrHeadwordNode } + }; + + var sensesNode = new ConfigurableDictionaryNode + { + FieldDescription = "Senses", + }; + var mainEntryNode = new ConfigurableDictionaryNode + { + Children = new List { sensesNode, pictureNode, headwordNode }, + FieldDescription = "LexEntry" + }; + CssGeneratorTests.PopulateFieldsForTesting(mainEntryNode); + var entryOne = CreateInterestingLexEntry(Cache); + AddHeadwordToEntry(entryOne, "HeadwordEn", m_wsEn); + AddHeadwordToEntry(entryOne, "HeadwordFr", m_wsFr); + var sense = entryOne.SensesOS[0]; + sense.PicturesOS.Add(CreatePicture(Cache, true, "captionEn", "en")); // Create the picture with a en caption, but no fr caption. + + var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); + //SUT + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings); + const string captionOrHeadwordContainsCaptionEn = "/div[@class='lexentry']/span[@class='pictures']/div[@class='picture']/div[@class='captionContent']/span[@class='captionorheadword']//span[@lang='en' and text()='captionEn']"; + const string captionOrHeadwordContainsHeadwordFr = "/div[@class='lexentry']/span[@class='pictures']/div[@class='picture']/div[@class='captionContent']/span[@class='captionorheadword']//span[@lang='fr' and text()='HeadwordFr']"; + //This assert is dependent on the specific entry data created in CreateInterestingLexEntry + AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(captionOrHeadwordContainsCaptionEn, 1); + AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(captionOrHeadwordContainsHeadwordFr, 1); + } + + [Test] public void GenerateContentForEntry_DefinitionOrGlossWorks() { From 819712c111e56629e222389d27116f00454222ae Mon Sep 17 00:00:00 2001 From: Jason Naylor Date: Thu, 16 Nov 2023 10:41:36 -0800 Subject: [PATCH 006/285] Fix LT-20890 - Get SenseNumber styling working * Use the WritingSystem from the HomographConfiguration in the xhtml span for the SenseNumber as well as in the style generation Change-Id: I69d1922961c4087cb4baf9b92a43209e0d06fea9 --- Src/xWorks/ConfiguredLcmGenerator.cs | 7 ++++++- Src/xWorks/CssGenerator.cs | 12 +++++++++++- Src/xWorks/ILcmContentGenerator.cs | 2 +- Src/xWorks/LcmJsonGenerator.cs | 2 +- Src/xWorks/LcmXhtmlGenerator.cs | 3 ++- .../xWorksTests/ConfiguredXHTMLGeneratorTests.cs | 11 +++++++---- 6 files changed, 28 insertions(+), 9 deletions(-) diff --git a/Src/xWorks/ConfiguredLcmGenerator.cs b/Src/xWorks/ConfiguredLcmGenerator.cs index bcd2f95d33..b13382363a 100644 --- a/Src/xWorks/ConfiguredLcmGenerator.cs +++ b/Src/xWorks/ConfiguredLcmGenerator.cs @@ -26,6 +26,7 @@ using SIL.FieldWorks.Common.FwUtils; using SIL.FieldWorks.Common.Widgets; using SIL.LCModel; +using SIL.LCModel.DomainImpl; using SIL.LCModel.DomainServices; using SIL.LCModel.Infrastructure; using SIL.LCModel.Utils; @@ -1650,6 +1651,7 @@ private static string GenerateContentForSenses(ConfigurableDictionaryNode config if (senseNode != null) info.ParentSenseNumberingStyle = senseNode.ParentSenseNumberingStyle; + info.HomographConfig = settings.Cache.ServiceLocator.GetInstance(); // Calculating isThisSenseNumbered may make sense to do for each item in the foreach loop below, but because of how the answer // is determined, the answer for all sibling senses is the same as for the first sense in the collection. // So calculating outside the loop for performance. @@ -2094,9 +2096,11 @@ private static string GenerateSenseNumberSpanIfNeeded(ConfigurableDictionaryNode var senseOptions = senseConfigNode.DictionaryNodeOptions as DictionaryNodeSenseOptions; var formattedSenseNumber = GetSenseNumber(senseOptions.NumberingStyle, ref info); + info.HomographConfig = settings.Cache.ServiceLocator.GetInstance(); + var senseNumberWs = string.IsNullOrEmpty(info.HomographConfig.WritingSystem) ? "en" : info.HomographConfig.WritingSystem; if (string.IsNullOrEmpty(formattedSenseNumber)) return string.Empty; - return settings.ContentGenerator.GenerateSenseNumber(formattedSenseNumber); + return settings.ContentGenerator.GenerateSenseNumber(formattedSenseNumber, senseNumberWs); } private static string GetSenseNumber(string numberingStyle, ref SenseInfo info) @@ -3103,6 +3107,7 @@ internal struct SenseInfo public int SenseCounter { get; set; } public string SenseOutlineNumber { get; set; } public string ParentSenseNumberingStyle { get; set; } + public HomographConfiguration HomographConfig { get; set; } } } diff --git a/Src/xWorks/CssGenerator.cs b/Src/xWorks/CssGenerator.cs index af9eda414d..de049a4492 100644 --- a/Src/xWorks/CssGenerator.cs +++ b/Src/xWorks/CssGenerator.cs @@ -23,6 +23,9 @@ using XCore; using Property = ExCSS.Property; using SIL.FieldWorks.Common.FwUtils; +using SIL.FieldWorks.FwCoreDlgControls; +using SIL.LCModel.Core.WritingSystems; +using SIL.LCModel.DomainImpl; namespace SIL.FieldWorks.XWorks { @@ -35,6 +38,7 @@ public class CssGenerator : ILcmStylesGenerator internal const string BeforeAfterBetweenStyleName = "Dictionary-Context"; internal const string LetterHeadingStyleName = "Dictionary-LetterHeading"; + internal const string SenseNumberStyleName = "Dictionary-SenseNumber"; internal const string DictionaryNormal = "Dictionary-Normal"; internal const string DictionaryMinor = "Dictionary-Minor"; internal const string WritingSystemPrefix = "writingsystemprefix"; @@ -416,7 +420,13 @@ private static List GenerateCssForSenses(ConfigurableDictionaryNode c if (senseOptions.DisplayEachSenseInAParagraph) selectors = new List(RemoveBeforeAfterSelectorRules(selectors)); styleRules.AddRange(CheckRangeOfRulesForEmpties(selectors)); + + var cache = propertyTable.GetValue("cache"); + var senseNumberLanguage = cache.ServiceLocator.GetInstance().WritingSystem; + senseNumberLanguage = string.IsNullOrEmpty(senseNumberLanguage) ? "en" : senseNumberLanguage; + var senseNumberWsId = cache.WritingSystemFactory.GetWsFromStr(senseNumberLanguage); var senseNumberRule = new StyleRule(); + // Not using SelectClassName here; sense and sensenumber are siblings and the configNode is for the Senses collection. // Select the base plus the node's unmodified class attribute and append the sensenumber matcher. var senseNumberSelector = string.Format("{0} .sensenumber", senseContentSelector); @@ -424,7 +434,7 @@ private static List GenerateCssForSenses(ConfigurableDictionaryNode c senseNumberRule.Value = senseNumberSelector; if(!String.IsNullOrEmpty(senseOptions.NumberStyle)) { - senseNumberRule.Declarations.Properties.AddRange(GenerateCssStyleFromLcmStyleSheet(senseOptions.NumberStyle, DefaultStyle, propertyTable)); + senseNumberRule.Declarations.Properties.AddRange(GenerateCssStyleFromLcmStyleSheet(senseOptions.NumberStyle, senseNumberWsId, propertyTable)); } if (!IsEmptyRule(senseNumberRule)) styleRules.Add(senseNumberRule); diff --git a/Src/xWorks/ILcmContentGenerator.cs b/Src/xWorks/ILcmContentGenerator.cs index 8eb9d40657..3c2067076b 100644 --- a/Src/xWorks/ILcmContentGenerator.cs +++ b/Src/xWorks/ILcmContentGenerator.cs @@ -55,7 +55,7 @@ string GenerateGroupingNode(object field, string className, ConfigurableDictiona void WriteProcessedContents(IFragmentWriter writer, string contents); string AddImage(string classAttribute, string srcAttribute, string pictureGuid); string AddImageCaption(string captionContent); - string GenerateSenseNumber(string formattedSenseNumber); + string GenerateSenseNumber(string formattedSenseNumber, string senseNumberWs); string AddLexReferences(bool generateLexType, string lexTypeContent, string className, string referencesContent, bool typeBefore); void BeginCrossReference(IFragmentWriter writer, bool isBlockProperty, string className); void EndCrossReference(IFragmentWriter writer); diff --git a/Src/xWorks/LcmJsonGenerator.cs b/Src/xWorks/LcmJsonGenerator.cs index 24d7150dfb..64eb9e56a3 100644 --- a/Src/xWorks/LcmJsonGenerator.cs +++ b/Src/xWorks/LcmJsonGenerator.cs @@ -324,7 +324,7 @@ public string AddImageCaption(string captionContent) return captionContent; } - public string GenerateSenseNumber(string formattedSenseNumber) + public string GenerateSenseNumber(string formattedSenseNumber, string wsId) { return formattedSenseNumber; } diff --git a/Src/xWorks/LcmXhtmlGenerator.cs b/Src/xWorks/LcmXhtmlGenerator.cs index 7445826f60..201ac843fc 100644 --- a/Src/xWorks/LcmXhtmlGenerator.cs +++ b/Src/xWorks/LcmXhtmlGenerator.cs @@ -913,13 +913,14 @@ public string AddImageCaption(string captionContent) } } - public string GenerateSenseNumber(string formattedSenseNumber) + public string GenerateSenseNumber(string formattedSenseNumber, string senseNumberWs) { var bldr = new StringBuilder(); using (var xw = XmlWriter.Create(bldr, new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment })) { xw.WriteStartElement("span"); xw.WriteAttributeString("class", "sensenumber"); + xw.WriteAttributeString("lang", senseNumberWs); xw.WriteString(formattedSenseNumber); xw.WriteEndElement(); xw.Flush(); diff --git a/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorTests.cs b/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorTests.cs index f35c7879a3..e60a8a0679 100644 --- a/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorTests.cs +++ b/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorTests.cs @@ -1968,9 +1968,12 @@ public void GenerateContentForEntry_LexemeBasedConsidersComplexFormsMainEntries( /// if this is not the only sense, then number it. /// (See LT-17906.) /// - [Test] - public void GenerateContentForEntry_SenseNumbersGeneratedForMultipleSenses() + [TestCase("en")] + [TestCase("fr")] + public void GenerateContentForEntry_SenseNumbersGeneratedForMultipleSenses(string homographWs) { + var homographConfig = Cache.ServiceLocator.GetInstance(); + homographConfig.WritingSystem = homographWs; var wsOpts = GetWsOptionsForLanguages(new[] { "en" }); var glossNode = new ConfigurableDictionaryNode { FieldDescription = "Gloss", DictionaryNodeOptions = wsOpts }; var sensesNode = new ConfigurableDictionaryNode @@ -1991,8 +1994,8 @@ public void GenerateContentForEntry_SenseNumbersGeneratedForMultipleSenses() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings); - const string senseNumberOne = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='1']]//span[@lang='en' and text()='gloss']"; - const string senseNumberTwo = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='2']]//span[@lang='en' and text()='second gloss']"; + string senseNumberOne = $"/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and @lang='{homographWs}' and text()='1']]//span[@lang='en' and text()='gloss']"; + string senseNumberTwo = $"/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and @lang='{homographWs}' and text()='2']]//span[@lang='en' and text()='second gloss']"; //This assert is dependent on the specific entry data created in CreateInterestingLexEntry AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(senseNumberOne, 1); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(senseNumberTwo, 1); From b6c0e01b9d29f6b7c2f5346b16389874de1b95b5 Mon Sep 17 00:00:00 2001 From: Jason Naylor Date: Thu, 16 Nov 2023 14:11:20 -0800 Subject: [PATCH 007/285] Fix LT-21670: Use the CustomHomographNumbers for Sense Numbers * Note: When LT-21669 is completed it will need to fill in the CustomHomographNumbers in the HomographConfiguration for this fix to continue working Change-Id: I14ddf1421ba049f7ea0f0225ddbe1e2d333af880 --- Src/xWorks/ConfiguredLcmGenerator.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Src/xWorks/ConfiguredLcmGenerator.cs b/Src/xWorks/ConfiguredLcmGenerator.cs index b13382363a..35f48688f3 100644 --- a/Src/xWorks/ConfiguredLcmGenerator.cs +++ b/Src/xWorks/ConfiguredLcmGenerator.cs @@ -2117,7 +2117,16 @@ private static string GetSenseNumber(string numberingStyle, ref SenseInfo info) nextNumber = GetRomanSenseCounter(numberingStyle, info.SenseCounter); break; default: // handles %d and %O. We no longer support "%z" (1 b iii) because users can hand-configure its equivalent - nextNumber = info.SenseCounter.ToString(); + // Use the digits from the CustomHomographNumbers if they are defined + if (info.HomographConfig.CustomHomographNumbers.Count == 10) + { + nextNumber = info.HomographConfig.CustomHomographNumbers[info.SenseCounter]; + } + else + { + // If the writing system somehow has no NumberingSystem then default to the sense counter number in Ascii + nextNumber = info.SenseCounter.ToString(); + } break; } info.SenseOutlineNumber = GenerateSenseOutlineNumber(info, nextNumber); From cd0f97348c6cd671657857433388125a944ae2c5 Mon Sep 17 00:00:00 2001 From: Joseph Myers Date: Thu, 9 Nov 2023 08:59:17 +0700 Subject: [PATCH 008/285] Added cmd capability to clone and open a project When launched from CMD with the proper arguments, flex will now interface with flexbridge to obtain a specific project with minimal user interaction. Currently, flexbridge confirms with the user before downloading from the given URI using a simple prompt. This change required a number of new parameters to be passed to flexbridge, and we opted to overload the launching method, to help preserve functionality for any external callers. I also changed the Process.Start logic slightly in order to deliver the user/pass to the process over environment variable, as opposed to less secure solutions. This work is mostly done, but it doesn't support custom project names yet. And I still need to consider input checking for the new CMD arguments. Ticket link for FB: https://github.com/sillsdev/flexbridge/pull/388 Change-Id: Ib4a85a628216f382780aaa50f2e70bd720ef00a2 --- .../FwControls/ObtainProjectMethod.cs | 16 ++++++ Src/Common/FieldWorks/FieldWorks.cs | 11 ++++ Src/Common/FwUtils/FLExBridgeHelper.cs | 55 ++++++++++++++++-- Src/Common/FwUtils/FwLinkArgs.cs | 56 +++++++++++++++++++ 4 files changed, 134 insertions(+), 4 deletions(-) diff --git a/Src/Common/Controls/FwControls/ObtainProjectMethod.cs b/Src/Common/Controls/FwControls/ObtainProjectMethod.cs index 629071b711..c150ea52dc 100644 --- a/Src/Common/Controls/FwControls/ObtainProjectMethod.cs +++ b/Src/Common/Controls/FwControls/ObtainProjectMethod.cs @@ -2,6 +2,7 @@ // This software is licensed under the LGPL, version 2.1 or later // (http://www.gnu.org/licenses/lgpl-2.1.html) +using System; using System.Collections.Generic; using System.ComponentModel; using System.IO; @@ -63,6 +64,21 @@ public static string ObtainProjectFromAnySource(Form parent, IHelpTopicProvider return projectFileFullPath; } + /// + /// Get a new FW project from the specified Mercurial repository. + /// + /// Null if the operation was cancelled or otherwise did not work. The full pathname of an fwdata file, if it did work. + public static string ObtainProject(Uri repoUri, string repoName, string userName, string passWord, string repoIdentifier, out ObtainedProjectType obtainedProjectType) + { + var result = FLExBridgeHelper.LaunchFieldworksBridge(FwDirectoryFinder.ProjectsDirectory, userName, FLExBridgeHelper.Obtain, null, + LcmCache.ModelVersion, FLExBridgeHelper.LiftVersion, null, null, out _, out var projectFileFullPath, repoUri, repoName, passWord, repoIdentifier); + + //Assume project type + obtainedProjectType = result ? ObtainedProjectType.FieldWorks : ObtainedProjectType.None; + + return projectFileFullPath; + } + private static void EnsureLinkedFoldersExist(string fwdataFileFullPathname) { var projectFolder = Path.GetDirectoryName(fwdataFileFullPathname); diff --git a/Src/Common/FieldWorks/FieldWorks.cs b/Src/Common/FieldWorks/FieldWorks.cs index b34f6b3777..a1f8713e9a 100644 --- a/Src/Common/FieldWorks/FieldWorks.cs +++ b/Src/Common/FieldWorks/FieldWorks.cs @@ -1352,6 +1352,17 @@ private static ProjectId DetermineProject(FwAppArgs args) if (TryCommandLineOption(projId, out projectOpenError)) return projId; + if (!string.IsNullOrEmpty(args.Password) && !string.IsNullOrEmpty(args.ProjectUri) && !string.IsNullOrEmpty(args.Username)) + { + var projectFile = ObtainProjectMethod.ObtainProject(new Uri(args.ProjectUri), args.Database, args.Username, args.Password, args.RepoIdentifier, out _); + if (!string.IsNullOrEmpty(projectFile)) + { + var projectName = Path.GetFileNameWithoutExtension(projectFile); + projId = new ProjectId(args.DatabaseType, projectName); + return projId; + } + } + // If this app hasn't been run before, ask user about opening sample DB. var app = GetOrCreateApplication(args); if (app.RegistrySettings.FirstTimeAppHasBeenRun) diff --git a/Src/Common/FwUtils/FLExBridgeHelper.cs b/Src/Common/FwUtils/FLExBridgeHelper.cs index c36a0285e4..8fb8669dc5 100644 --- a/Src/Common/FwUtils/FLExBridgeHelper.cs +++ b/Src/Common/FwUtils/FLExBridgeHelper.cs @@ -215,6 +215,35 @@ static FLExBridgeHelper() public static bool LaunchFieldworksBridge(string projectFolder, string userName, string command, string projectGuid, int fwmodelVersionNumber, string liftModelVersionNumber, string writingSystemId, Action onNonBlockerCommandComplete, out bool changesReceived, out string projectName) + { + return LaunchFieldworksBridge(projectFolder, userName, command, projectGuid, fwmodelVersionNumber, liftModelVersionNumber, writingSystemId, onNonBlockerCommandComplete, + out changesReceived, out projectName, null, null, null, null); + } + + /// + /// Launches the FLExBridge application with the given commands and locks out the FLEx interface until the bridge + /// is closed. + /// + /// The entire FieldWorks project folder path. + /// Must include the project folder and project name with "fwdata" extension. + /// Empty is OK if not send_receive command. + /// the username to use in Chorus commits + /// obtain, start, send_receive, view_notes + /// Optional Lang Project guid, that is only used with the 'move_lift' command + /// Version of LIFT schema that is supported by FLEx. + /// The id of the first vernacular writing system + /// Current FDO model version number + /// Callback called when a non-blocker command has completed + /// true if S/R made changes to the project. + /// Name of the project to be opened after launch returns. + /// Full URI of the project, if known beforehand. + /// The name of the project, if known beforehand. + /// The authentication credentials which will allow access to the repo to clone, if known beforehand. + /// The authentication credentials which will allow access to the repo to clone, if known beforehand. + /// true if successful, false otherwise + public static bool LaunchFieldworksBridge(string projectFolder, string userName, string command, string projectGuid, + int fwmodelVersionNumber, string liftModelVersionNumber, string writingSystemId, Action onNonBlockerCommandComplete, + out bool changesReceived, out string projectName, Uri projectUri, string name, string credentialsPassword, string repoIdentifier) { _pipeID = string.Format(@"SendReceive{0}{1}", projectFolder, command); _flexBridgeTerminated = false; @@ -222,6 +251,17 @@ public static bool LaunchFieldworksBridge(string projectFolder, string userName, var args = ""; projectName = ""; _projectName = ""; + string userCredentials = null; + if (projectUri != null) + { + var uriWithoutCredentials = projectUri.AbsoluteUri.Replace(projectUri.UserInfo + "@", ""); + AddArg(ref args, "-uri", uriWithoutCredentials); + AddArg(ref args, "-project", name); + AddArg(ref args, "-user", userName); + AddArg(ref args, "-repositoryIdentifier", repoIdentifier); + userCredentials = string.Join(":", userName, credentialsPassword); + } + var userNameActual = userName; if (string.IsNullOrEmpty(userName)) userNameActual = Environment.UserName; // default so we can always pass something. @@ -279,13 +319,13 @@ public static bool LaunchFieldworksBridge(string projectFolder, string userName, if (!host.Initialize("FLExBridgeEndpoint" + _pipeID, AlertFlex, CleanupHost)) return false; - LaunchFlexBridge(host, command, args, onNonBlockerCommandComplete, ref changesReceived, ref projectName); + LaunchFlexBridge(host, command, args, onNonBlockerCommandComplete, userCredentials, ref changesReceived, ref projectName); return true; } private static void LaunchFlexBridge(IIPCHost host, string command, string args, Action onNonBlockerCommandComplete, - ref bool changesReceived, ref string projectName) + string userPass, ref bool changesReceived, ref string projectName) { string flexbridgeLauncher = FullFieldWorksBridgePath(); if (Platform.IsUnix) @@ -298,8 +338,16 @@ private static void LaunchFlexBridge(IIPCHost host, string command, string args, } // Launch the bridge process. - using (Process.Start(flexbridgeLauncher, args)) + using (var process = new Process()) { + var startInfo = new ProcessStartInfo(); + if (userPass != null) startInfo.EnvironmentVariables["CHORUS_CREDENTIALS"] = userPass; + startInfo.UseShellExecute = false; + startInfo.FileName = flexbridgeLauncher; + startInfo.Arguments = args; + + process.StartInfo = startInfo; + process.Start(); } var nonFlexBlockers = new HashSet @@ -380,7 +428,6 @@ private static void AddArg(ref string extant, string flag, string value) extant += flag; if (!string.IsNullOrEmpty(value)) { - bool hasWhitespace; if (value.Any(Char.IsWhiteSpace)) { extant += " \"" + value + "\""; diff --git a/Src/Common/FwUtils/FwLinkArgs.cs b/Src/Common/FwUtils/FwLinkArgs.cs index 009e51cff6..c83ff3b1ed 100644 --- a/Src/Common/FwUtils/FwLinkArgs.cs +++ b/Src/Common/FwUtils/FwLinkArgs.cs @@ -444,6 +444,14 @@ public class FwAppArgs : FwLinkArgs public const string kAppServerMode = "appServerMode"; /// Command-line argument: flag that tells FW to bring up a dialog to set an associated project. public const string kChooseProject = "chooseProject"; + /// Command-line argument: authentication username + public const string kUser = "user"; + /// Command-line argument: authentication password + public const string kPass = "password"; + /// Command-line argument: remote URI of the desired project + public const string kUri = "projectUri"; + /// Command-line argument: The ID used to identify repositories, regardless of their source URL, i.e. "log -r0 --template " + SurroundWithQuotes("{node}") + public const string kIdentifier = "repositoryIdentifier"; #endregion #region Member variables @@ -454,6 +462,10 @@ public class FwAppArgs : FwLinkArgs private string m_backupFile = string.Empty; private string m_restoreOptions = string.Empty; private string m_chooseProjectFile = string.Empty; + private string m_user = string.Empty; + private string m_pass = string.Empty; + private string m_uri = string.Empty; + private string m_repoIdentifier = string.Empty; #endregion #region Properties @@ -589,6 +601,46 @@ public string ChooseProjectFile { get { return m_chooseProjectFile; } } + + /// ------------------------------------------------------------------------------------ + /// + /// Gets the Send/Receive username. + /// + /// ------------------------------------------------------------------------------------ + public string Username + { + get { return m_user; } + } + + /// ------------------------------------------------------------------------------------ + /// + /// Gets the Send/Receive password. + /// + /// ------------------------------------------------------------------------------------ + public string Password + { + get { return m_pass; } + } + + /// ------------------------------------------------------------------------------------ + /// + /// Gets the remote URI for the target project. + /// + /// ------------------------------------------------------------------------------------ + public string ProjectUri + { + get { return m_uri; } + } + + /// ------------------------------------------------------------------------------------ + /// + /// Gets the ID used to identify the repository. + /// + /// ------------------------------------------------------------------------------------ + public string RepoIdentifier + { + get { return m_repoIdentifier; } + } #endregion #region Constructor @@ -922,6 +974,10 @@ private void ProcessArg(string name, string value) case kAppServerMode: AppServerMode = true; break; case kTag: m_tag = value; break; case kTool: m_toolName = value; break; + case kUser: m_user = value; break; + case kPass: m_pass = value; break; + case kUri: m_uri = value; break; + case kIdentifier: m_repoIdentifier = value; break; case kGuid: if (value != "null") TargetGuid = new Guid(value); From a9b0c06ab00c237a9bd08c514709deb00fd3d419 Mon Sep 17 00:00:00 2001 From: Jason Naylor Date: Fri, 17 Nov 2023 14:07:35 -0800 Subject: [PATCH 009/285] Fix LT-21679 - Make custom numbers work for more than 9 senses Change-Id: I5441798f86b6e3d7be6d46197f3b9cc8455ce579 --- Src/xWorks/ConfiguredLcmGenerator.cs | 11 +++++----- .../ConfiguredXHTMLGeneratorTests.cs | 22 ++++++++++++++++--- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/Src/xWorks/ConfiguredLcmGenerator.cs b/Src/xWorks/ConfiguredLcmGenerator.cs index 35f48688f3..a855bb83a0 100644 --- a/Src/xWorks/ConfiguredLcmGenerator.cs +++ b/Src/xWorks/ConfiguredLcmGenerator.cs @@ -2117,15 +2117,14 @@ private static string GetSenseNumber(string numberingStyle, ref SenseInfo info) nextNumber = GetRomanSenseCounter(numberingStyle, info.SenseCounter); break; default: // handles %d and %O. We no longer support "%z" (1 b iii) because users can hand-configure its equivalent + nextNumber = info.SenseCounter.ToString(); // Use the digits from the CustomHomographNumbers if they are defined if (info.HomographConfig.CustomHomographNumbers.Count == 10) { - nextNumber = info.HomographConfig.CustomHomographNumbers[info.SenseCounter]; - } - else - { - // If the writing system somehow has no NumberingSystem then default to the sense counter number in Ascii - nextNumber = info.SenseCounter.ToString(); + for (var digit = 0; digit < 10; ++digit) + { + nextNumber = nextNumber.Replace(digit.ToString(), info.HomographConfig.CustomHomographNumbers[digit]); + } } break; } diff --git a/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorTests.cs b/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorTests.cs index e60a8a0679..6c9a7fb08b 100644 --- a/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorTests.cs +++ b/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorTests.cs @@ -1967,13 +1967,20 @@ public void GenerateContentForEntry_LexemeBasedConsidersComplexFormsMainEntries( /// If the numbering style for Senses says to number it, and /// if this is not the only sense, then number it. /// (See LT-17906.) + /// Also verify that custom homograph numbers are used and we can count past 9 with them /// - [TestCase("en")] - [TestCase("fr")] - public void GenerateContentForEntry_SenseNumbersGeneratedForMultipleSenses(string homographWs) + [TestCase("en", null)] + [TestCase("fr", null)] + [TestCase("fr", new [] { "y", "1", "2", "3", "4", "5", "6", "7", "8", "9" })] + public void GenerateContentForEntry_SenseNumbersGeneratedForMultipleSenses(string homographWs, string[] customHomographs) { var homographConfig = Cache.ServiceLocator.GetInstance(); + var tenthSenseNumber = customHomographs == null ? "10" : customHomographs[1] + customHomographs[0]; homographConfig.WritingSystem = homographWs; + if (customHomographs != null) + { + homographConfig.CustomHomographNumbers = new List(customHomographs); + } var wsOpts = GetWsOptionsForLanguages(new[] { "en" }); var glossNode = new ConfigurableDictionaryNode { FieldDescription = "Gloss", DictionaryNodeOptions = wsOpts }; var sensesNode = new ConfigurableDictionaryNode @@ -1991,11 +1998,20 @@ public void GenerateContentForEntry_SenseNumbersGeneratedForMultipleSenses(strin CssGeneratorTests.PopulateFieldsForTesting(mainEntryNode); var testEntry = CreateInterestingLexEntry(Cache); AddSenseToEntry(testEntry, "second gloss", m_wsEn, Cache); + AddSenseToEntry(testEntry, "3", m_wsEn, Cache); + AddSenseToEntry(testEntry, "4", m_wsEn, Cache); + AddSenseToEntry(testEntry, "5", m_wsEn, Cache); + AddSenseToEntry(testEntry, "6", m_wsEn, Cache); + AddSenseToEntry(testEntry, "7", m_wsEn, Cache); + AddSenseToEntry(testEntry, "8", m_wsEn, Cache); + AddSenseToEntry(testEntry, "9", m_wsEn, Cache); + AddSenseToEntry(testEntry, "10", m_wsEn, Cache); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings); string senseNumberOne = $"/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and @lang='{homographWs}' and text()='1']]//span[@lang='en' and text()='gloss']"; string senseNumberTwo = $"/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and @lang='{homographWs}' and text()='2']]//span[@lang='en' and text()='second gloss']"; + string senseNumberTen = $"//span[@class='sensecontent']/spansenses/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and @lang='{homographWs}' and text()='{tenthSenseNumber}']]//span[@lang='en' and text()='10']"; //This assert is dependent on the specific entry data created in CreateInterestingLexEntry AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(senseNumberOne, 1); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(senseNumberTwo, 1); From 4f6b99b226aef008aed6ef3b94aa3a6fef638270 Mon Sep 17 00:00:00 2001 From: mark-sil Date: Wed, 22 Nov 2023 13:58:24 -0500 Subject: [PATCH 010/285] LT-21438: Webonary - generate entries before headings Previously we were generating the letter headings based on what we anticipated would be the list of entries. This submission changes the order so we now generate the entries first, then generate the corresponding letter headings. Change-Id: I086a2023b597be89ef871f593d4badfae42aab52 --- Src/xWorks/DictionaryExportService.cs | 14 +++++------ Src/xWorks/LcmJsonGenerator.cs | 7 +++++- Src/xWorks/UploadToWebonaryController.cs | 12 ++++------ .../xWorksTests/LcmJsonGeneratorTests.cs | 23 ++++++++++++++++--- 4 files changed, 38 insertions(+), 18 deletions(-) diff --git a/Src/xWorks/DictionaryExportService.cs b/Src/xWorks/DictionaryExportService.cs index 2f8aaf7ef6..fa6c5981bd 100644 --- a/Src/xWorks/DictionaryExportService.cs +++ b/Src/xWorks/DictionaryExportService.cs @@ -135,14 +135,14 @@ private void ExportConfiguredXhtml(string xhtmlPath, DictionaryConfigurationMode LcmXhtmlGenerator.SavePublishedHtmlWithStyles(entriesToSave, publicationDecorator, int.MaxValue, configuration, m_propertyTable, xhtmlPath, progress); } - public List ExportConfiguredJson(string folderPath, DictionaryConfigurationModel configuration) + public List ExportConfiguredJson(string folderPath, DictionaryConfigurationModel configuration, out int[] entryIds) { using (ClerkActivator.ActivateClerkMatchingExportType(DictionaryType, m_propertyTable, m_mediator)) { var publicationDecorator = ConfiguredLcmGenerator.GetPublicationDecoratorAndEntries(m_propertyTable, out var entriesToSave, DictionaryType); return LcmJsonGenerator.SavePublishedJsonWithStyles(entriesToSave, publicationDecorator, BatchSize, configuration, m_propertyTable, - Path.Combine(folderPath, "configured.json"), null); + Path.Combine(folderPath, "configured.json"), null, out entryIds); } } @@ -154,9 +154,9 @@ public List ExportConfiguredReversalJson(string folderPath, string rever using (ReversalIndexActivator.ActivateReversalIndex(reversalWs, m_propertyTable, m_cache)) { var publicationDecorator = ConfiguredLcmGenerator.GetPublicationDecoratorAndEntries(m_propertyTable, - out entryIds, ReversalType); - return LcmJsonGenerator.SavePublishedJsonWithStyles(entryIds, publicationDecorator, BatchSize, - configuration, m_propertyTable, Path.Combine(folderPath, $"reversal_{reversalWs}.json"), null); + out var entriesToSave, ReversalType); + return LcmJsonGenerator.SavePublishedJsonWithStyles(entriesToSave, publicationDecorator, BatchSize, + configuration, m_propertyTable, Path.Combine(folderPath, $"reversal_{reversalWs}.json"), null, out entryIds); } } @@ -351,13 +351,13 @@ public void ActivatePublication(string publication) public JObject ExportDictionaryContentJson(string siteName, IEnumerable templateFileNames, IEnumerable reversals, + int[] entryIds, string exportPath = null) { using (ClerkActivator.ActivateClerkMatchingExportType(DictionaryType, m_propertyTable, m_mediator)) { - ConfiguredLcmGenerator.GetPublicationDecoratorAndEntries(m_propertyTable, out var entriesToSave, DictionaryType); var clerk = m_propertyTable.GetValue("ActiveClerk", null); - return LcmJsonGenerator.GenerateDictionaryMetaData(siteName, templateFileNames, reversals, entriesToSave, exportPath, m_cache, clerk); + return LcmJsonGenerator.GenerateDictionaryMetaData(siteName, templateFileNames, reversals, entryIds, exportPath, m_cache, clerk); } } } diff --git a/Src/xWorks/LcmJsonGenerator.cs b/Src/xWorks/LcmJsonGenerator.cs index 64eb9e56a3..e99dfc3819 100644 --- a/Src/xWorks/LcmJsonGenerator.cs +++ b/Src/xWorks/LcmJsonGenerator.cs @@ -519,11 +519,13 @@ public void InsertRawJson(string jsonContent) /// could index the entries after upload and before processing. /// public static List SavePublishedJsonWithStyles(int[] entriesToSave, DictionaryPublicationDecorator publicationDecorator, int batchSize, - DictionaryConfigurationModel configuration, PropertyTable propertyTable, string jsonPath, IThreadedProgress progress) + DictionaryConfigurationModel configuration, PropertyTable propertyTable, string jsonPath, IThreadedProgress progress, out int[] entryIds) { var entryCount = entriesToSave.Length; var cssPath = Path.ChangeExtension(jsonPath, "css"); var cache = propertyTable.GetValue("cache", null); + var entryIdsList = new List(); + // Don't display letter headers if we're showing a preview in the Edit tool or we're not sorting by headword using (var cssWriter = new StreamWriter(cssPath, false, Encoding.UTF8)) { @@ -581,6 +583,7 @@ public static List SavePublishedJsonWithStyles(int[] entriesToSave, Dict entryObject.displayXhtml = entryData.Item3.ToString(); jsonWriter.WriteRaw(entryObject.ToString()); jsonWriter.WriteRaw(","); + entryIdsList.Add(entryData.Item1.Hvo); } jsonWriter.WriteEndArray(); jsonWriter.Flush(); @@ -594,6 +597,8 @@ public static List SavePublishedJsonWithStyles(int[] entriesToSave, Dict cssWriter.Write(CssGenerator.GenerateCssFromConfiguration(configuration, readOnlyPropertyTable)); cssWriter.Flush(); + + entryIds = entryIdsList.ToArray(); return generatedEntries; } } diff --git a/Src/xWorks/UploadToWebonaryController.cs b/Src/xWorks/UploadToWebonaryController.cs index 105c4bf49b..7362a4760f 100644 --- a/Src/xWorks/UploadToWebonaryController.cs +++ b/Src/xWorks/UploadToWebonaryController.cs @@ -103,12 +103,12 @@ private void ExportDictionaryContent(string tempDirectoryToCompress, UploadToWeb webonaryView.UpdateStatus(xWorksStrings.ExportingEntriesToWebonaryCompleted); } - private JObject GenerateDictionaryMetadataContent(UploadToWebonaryModel model, + private JObject GenerateDictionaryMetadataContent(UploadToWebonaryModel model, int[] entryIds, IEnumerable templateFileNames, string tempDirectoryForExport) { return m_exportService.ExportDictionaryContentJson(model.SiteName, templateFileNames, model.Reversals.Where(kvp => model.SelectedReversals.Contains(kvp.Key)).Select(kvp => kvp.Value), - tempDirectoryForExport); + entryIds, tempDirectoryForExport); } internal static void CompressExportedFiles(string tempDirectoryToCompress, string zipFileToUpload, IUploadToWebonaryView webonaryView) @@ -572,18 +572,16 @@ public void UploadToWebonary(UploadToWebonaryModel model, IUploadToWebonaryView GenerateConfigurationTemplates(configuration, m_cache, tempDirectoryForExport); view.UpdateStatus(xWorksStrings.ksPreparingDataForWebonary); - var metadataContent = GenerateDictionaryMetadataContent(model, + int[] entryIds; + var entries = m_exportService.ExportConfiguredJson(tempDirectoryForExport, configuration, out entryIds); + var metadataContent = GenerateDictionaryMetadataContent(model, entryIds, templateFileNames, tempDirectoryForExport); view.UpdateStatus(xWorksStrings.ksWebonaryFinishedDataPrep); - var entries = - m_exportService.ExportConfiguredJson(tempDirectoryForExport, - configuration); var allRequestsSucceeded = PostEntriesToWebonary(model, view, entries, false); var reversalClerk = RecordClerk.FindClerk(m_propertyTable, "AllReversalEntries"); foreach (var selectedReversal in model.SelectedReversals) { - int[] entryIds; var writingSystem = model.Reversals[selectedReversal].WritingSystem; entries = m_exportService.ExportConfiguredReversalJson( tempDirectoryForExport, writingSystem, out entryIds, diff --git a/Src/xWorks/xWorksTests/LcmJsonGeneratorTests.cs b/Src/xWorks/xWorksTests/LcmJsonGeneratorTests.cs index 18e21fd0d1..52e1e68f99 100644 --- a/Src/xWorks/xWorksTests/LcmJsonGeneratorTests.cs +++ b/Src/xWorks/xWorksTests/LcmJsonGeneratorTests.cs @@ -1102,7 +1102,7 @@ public void SavePublishedJsonWithStyles_DisplayXhtmlPopulated() var results = LcmJsonGenerator.SavePublishedJsonWithStyles(new[] { testEntry.Hvo }, DefaultDecorator, 1, new DictionaryConfigurationModel { Parts = new List { mainEntryNode } }, - m_propertyTable, "test.json", null); + m_propertyTable, "test.json", null, out int[] _); var expectedResults = @"{""xhtmlTemplate"":""lexentry"",""guid"":""g" + testEntry.Guid + @""",""letterHead"": ""c"",""sortIndex"": 0, ""homographnumber"":""0"",""citationform"":[{""lang"":""fr"",""value"":""Citation""}], ""displayXhtml"":""
0Citation
""}"; @@ -1133,7 +1133,7 @@ public void SavePublishedJsonWithStyles_BatchingWorks() var results = LcmJsonGenerator.SavePublishedJsonWithStyles(new[] { testEntry.Hvo, testEntry2.Hvo, testEntry3.Hvo }, DefaultDecorator, testBatchSize, new DictionaryConfigurationModel { Parts = new List { mainEntryNode } }, - m_propertyTable, "test.json", null); + m_propertyTable, "test.json", null, out int[] _); Assert.That(results.Count, Is.EqualTo(2)); // 3 entries makes 2 batches at batchSize of 2 Assert.That(results[0].Count, Is.EqualTo(testBatchSize)); // one full batch of 2 Assert.That(results[1].Count, Is.EqualTo(1)); // one lonely entry in the last batch @@ -1156,12 +1156,29 @@ public void SavePublishedJsonWithStyles_HiddenMinorEntry_DoesNotThrow() ConfiguredXHTMLGeneratorTests.SetPublishAsMinorEntry(minorEntry, false); var result = LcmJsonGenerator.SavePublishedJsonWithStyles(new[] { minorEntry.Hvo }, - DefaultDecorator, 1, configModel, m_propertyTable, "test.json", null); + DefaultDecorator, 1, configModel, m_propertyTable, "test.json", null, out int[] _); Assert.AreEqual(1, result.Count, "batches"); Assert.AreEqual(0, result[0].Count, "entries"); } + [Test] + public void SavePublishedJsonWithStyles_MinorEntryNotPublished() + { + var configModel = ConfiguredXHTMLGeneratorTests.CreateInterestingConfigurationModel(Cache, m_propertyTable); + var mainEntry = ConfiguredXHTMLGeneratorTests.CreateInterestingLexEntry(Cache); + var minorEntry = ConfiguredXHTMLGeneratorTests.CreateInterestingLexEntry(Cache); + ConfiguredXHTMLGeneratorTests.CreateVariantForm(Cache, mainEntry, minorEntry); + ConfiguredXHTMLGeneratorTests.SetPublishAsMinorEntry(minorEntry, false); + + var result = LcmJsonGenerator.SavePublishedJsonWithStyles(new[] { mainEntry.Hvo, minorEntry.Hvo }, + DefaultDecorator, 10, configModel, m_propertyTable, "test.json", null, out int[] entryIds); + + Assert.AreEqual(1, result.Count, "batches"); + Assert.AreEqual(1, result[0].Count, "entries"); + Assert.AreEqual(result[0].Count, entryIds.Length); + } + [Test] public void GenerateXHTMLForEntry_EmbeddedWritingSystemOfOppositeDirectionGeneratesCorrectResult() { From 1da61dce6889570018fac700d4a9f52feed06b63 Mon Sep 17 00:00:00 2001 From: mark-sil Date: Thu, 7 Dec 2023 16:35:07 -0500 Subject: [PATCH 011/285] LT-21424: Do lowercase matching for all occurrence indexes When trying to get a best guess do lowercase matching regardless of where the occurrence is in the segment. Also use LCM 11.0.0-beta0083. Change-Id: I7bb6f233bf74ab72bfec5e911bfd39e4959fa713 --- Build/mkall.targets | 2 +- Build/nuget-common/packages.config | 18 +++++++++--------- Src/LexText/Interlinear/InterlinVc.cs | 3 +-- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/Build/mkall.targets b/Build/mkall.targets index 5ce41487c4..6d50c20bb3 100644 --- a/Build/mkall.targets +++ b/Build/mkall.targets @@ -285,7 +285,7 @@ 5.2.0-beta0003 13.0.0-beta0076 9.4.0.1-beta - 11.0.0-beta0081 + 11.0.0-beta0083 70.1.123 2.5.13 diff --git a/Build/nuget-common/packages.config b/Build/nuget-common/packages.config index c3a295c2ac..be5a41057b 100644 --- a/Build/nuget-common/packages.config +++ b/Build/nuget-common/packages.config @@ -51,15 +51,15 @@ - - - - - - - - - + + + + + + + + + diff --git a/Src/LexText/Interlinear/InterlinVc.cs b/Src/LexText/Interlinear/InterlinVc.cs index 7bb9ef5683..16ebcc3e8f 100644 --- a/Src/LexText/Interlinear/InterlinVc.cs +++ b/Src/LexText/Interlinear/InterlinVc.cs @@ -2487,8 +2487,7 @@ private void RecordGuessIfAvailable(AnalysisOccurrence occurrence) IAnalysis wag = occurrence.Analysis; IAnalysis wagGuess; // now record the guess in the decorator. - // Todo JohnT: if occurrence.Indx is 0, record using DefaultStartSentenceFlid. - if (GuessServices.TryGetBestGuess(occurrence, out wagGuess)) + if (GuessServices.TryGetBestGuess(occurrence, out wagGuess, false)) { SetObjProp(wag.Hvo, InterlinViewDataCache.AnalysisMostApprovedFlid, wagGuess.Hvo); SetInt(wagGuess.Analysis.Hvo, InterlinViewDataCache.OpinionAgentFlid, (int)GuessServices.GetOpinionAgent(wagGuess.Analysis)); From e3c50d36c91a6522a66a128c7b090787e321dbfd Mon Sep 17 00:00:00 2001 From: Jason Naylor Date: Wed, 29 Nov 2023 13:54:35 -0800 Subject: [PATCH 012/285] Modify the buildLocalLibraries.sh to work with the new Nuget world Change-Id: Idbbcf3ccabd4ad1201f3a53c769e89d61aec0944 --- Build/buildLocalLibraries.sh | 339 ++++++++++++++++++++++++----------- Build/mkall.targets | 59 +++--- 2 files changed, 269 insertions(+), 129 deletions(-) diff --git a/Build/buildLocalLibraries.sh b/Build/buildLocalLibraries.sh index 03471d3721..aaaeb21a1a 100755 --- a/Build/buildLocalLibraries.sh +++ b/Build/buildLocalLibraries.sh @@ -1,116 +1,253 @@ #!/bin/bash # script for building libpalaso, liblcm and chorus libraries locally for debugging FLEx -# You must also indicate that you are using local libraries or edit the LibraryDevelopment.properties file -# with the path to the library outputs (i.e. C:/libpalaso/output/Debug) - -########## Parameters ############ -# Edit these parameters according to the configurations on your machine -buildcommand="C:/Program Files (x86)/MSBuild/14.0/Bin/MSBuild.exe" -BUILD_CONFIG=Debug -########### End Parameters ############# - -set -e -o pipefail -PROGRAM="$(basename "$0")" - -copy_curl() { - echo "curl $2 <= $1" - curl -# -L -o $2 $1 +# Review: Do we also need to delete the nuget files out of packages + +libpalaso_dir="" +liblcm_dir="" +chorus_dir="" +mkall_targets_file="mkall.targets" +packages_dir="../packages" + +# dotnet pack result version regex +version_regex="\s*Successfully created package.*\.([0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+)?)\.nupkg" + +# Function to display usage information +function display_usage { + echo "Usage: $0 [OPTIONS]" + echo "Options:" + echo " -p, --libpalaso Specify libpalaso directory path and delete specified files, then run 'dotnet pack'" + echo " -l, --liblcm Specify liblcm directory path and run 'dotnet pack'" + echo " -c, --chorus Specify chorus directory path and delete specified files, then run 'dotnet pack'" + echo " -v, --version Set version numbers for the selected library in the mkall.targets and packages.config (does not delete packages or run pack)" + echo " -h, --help Display this help message" + exit 1 } -printUsage() { - echo "buildLocalLibraries x86|x64 [PALASOROOT] [LIBLCMROOT] [CHORUSROOT] [BUILDCOMMAND]" +# Function to run 'dotnet pack' in the liblcm directory +function delete_and_pack_liblcm { + if [ -n "$liblcm_dir" ]; then + + # Check if the specified directory exists + if [ ! -d "$packages_dir" ]; then + echo "Error: The specified packages directory does not exist: $packages_dir" + exit 1 + fi + + if [ "$use_manual_version" == true ]; then + version_number="$manual_version" + else + echo "Deleting files starting with 'SIL.LCModel' in $packages_dir" + find "$packages_dir" -name 'SIL.LCModel*' -exec rm -f -r {} \; + + echo "Running 'dotnet pack' in the liblcm directory: $liblcm_dir" + pack_output=$(cd "$liblcm_dir" && dotnet pack) + + # Extract version number using regex + if [[ $pack_output =~ $version_regex ]]; then + version_number=${BASH_REMATCH[1]} + echo "Version number extracted from dotnet pack output: $version_number" + else + echo "Error: Unable to extract version number from dotnet pack output. (Maybe build failure or nothing needed building?)" + exit 1 + fi + copy_pdb_files "$liblcm_dir/artifacts/Debug/net461" + fi + + # Update LcmNugetVersion in mkall.targets + update_mkall_targets "LcmNugetVersion" "$version_number" + + # Update packages.config with extracted version + update_packages_config "SIL.LCModel" "$version_number" + + fi } -osName=`uname -s` - -if [ "$5" != "" ] -then - buildcommand="$5" -fi - -if [ "$1" == "x86" ] -then - libpalasoPlatform="Mixed Platforms" - liblcmPlatform="x86" - ICUBuildType="Win32" -elif [ "$1" == "x64" ] -then - libpalasoPlatform="x64" - liblcmPlatform="x64" - ICUBuildType="Win64" -else - printUsage - exit -fi +# Function to delete specified files in the chorus directory and run 'dotnet pack' +function delete_and_pack_chorus { + if [ -n "$chorus_dir" ]; then + + # Check if the specified directory exists + if [ ! -d "$packages_dir" ]; then + echo "Error: The specified packages directory does not exist: $packages_dir" + exit 1 + fi + + if [ "$use_manual_version" == true ]; then + version_number="$manual_version" + else + prefix="SIL.Chorus" + echo "Deleting files starting with specified prefix in $packages_dir" + + find "$packages_dir" -name "${prefix}*" -exec rm -f -r {} \; + + echo "Running 'dotnet pack' in the chorus directory: $chorus_dir" + pack_output=$(cd "$chorus_dir" && dotnet pack) + + # Extract version number using regex + if [[ $pack_output =~ $version_regex ]]; then + version_number=${BASH_REMATCH[1]} + echo "Version number extracted from dotnet pack output: $version_number" + else + echo "Error: Unable to extract version number from dotnet pack output." + exit 1 + fi + copy_pdb_files "$chorus_dir/Output/Debug/net461" + fi + + # Update ChorusNugetVersion in mkall.targets + update_mkall_targets "ChorusNugetVersion" "$version_number" + # Update packages.config with extracted version + update_packages_config "SIL.Chorus" "$version_number" + fi +} -# Get the path to the libpalaso, chorus and LCModel cloned repositories on your machine -# Repositories are available at github.com/sillsdev -if [ "$2" == "" ] -then - read -p "Enter the full path to your local libpalaso repo: " libpalasoRepo -else - libpalasoRepo="$2" -fi -if [ "$3" == "" ] -then - read -p "Enter the full path to your local liblcm repo: " liblcmRepo -else - liblcmRepo="$3" -fi -if [ "$4" == "" ] -then - read -p "Enter the full path to your local chorus repo: " chorusRepo -else - chorusRepo="$4" -fi +# Function to delete specified files in the libpalaso directory and run 'dotnet pack' +function delete_and_pack_libpalaso { + if [ -n "$libpalaso_dir" ]; then + + # Check if the specified directory exists + if [ ! -d "$packages_dir" ]; then + echo "Error: The specified packages directory does not exist: $packages_dir" + exit 1 + fi + prefixes=("SIL.Core" "SIL.Windows" "SIL.DblBundle" "SIL.WritingSystems" "SIL.Dictionary" "SIL.Lift" "SIL.Lexicon" "SIL.Archiving") + if [ "$use_manual_version" == true ]; then + version_number="$manual_version" + else + echo "Deleting files starting with specified prefixes in $packages_dir" + + for prefix in "${prefixes[@]}"; do + find "$packages_dir" -name "${prefix}*" -exec rm -f -r {} \; + done + + echo "Running 'dotnet pack' in the libpalaso directory: $libpalaso_dir" + pack_output=$(cd "$libpalaso_dir" && dotnet pack) + + # Extract version number using regex + if [[ $pack_output =~ $version_regex ]]; then + version_number=${BASH_REMATCH[1]} + echo "Version number extracted from dotnet pack output: $version_number" + else + echo "Error: Unable to extract version number from dotnet pack output." + exit 1 + fi + copy_pdb_files "$libpalaso_dir/output/Debug/net461" + fi + + # Update PalasoNugetVersion in mkall.targets + update_mkall_targets "PalasoNugetVersion" "$version_number" + + # Update packages.config with extracted version for each prefix + for prefix in "${prefixes[@]}"; do + update_packages_config "$prefix" "$version_number" + done + fi +} -############### build libpalaso ############# -cd ${libpalasoRepo}/build -if [[ ${osName} == "Linux" ]] -then - ./buildupdate.mono.sh - MONO=Mono - (. ../environ && "${buildcommand}" /target:build /verbosity:quiet /property:Configuration=$BUILD_CONFIG$MONO /property:Platform="${libpalasoPlatform}" Palaso.proj) -else - ./buildupdate.win.sh - MONO= - "${buildcommand}" /target:build /verbosity:quiet /property:Configuration=$BUILD_CONFIG /property:Platform="${libpalasoPlatform}" Palaso.proj -fi +# Function to update specified element in mkall.targets +function update_mkall_targets { + local element="$1" + local version_number="$2" + if [ -f "$mkall_targets_file" ]; then + echo "Updating $element in $mkall_targets_file to $version_number" + sed -i "s/<$element>.*<\/$element>/<${element}>$version_number<\/${element}>/" "$mkall_targets_file" + else + echo "Error: $mkall_targets_file not found." + exit 1 + fi +} +# Function to update packages.config with extracted version for a given package ID prefix +function update_packages_config { + local id_prefix="$1" + local version_number="$2" + local packages_config_file="nuget-common/packages.config" + if [ -f "$packages_config_file" ]; then + echo "Updating $packages_config_file with version $version_number for packages with ID starting with $id_prefix" + + # Use sed to modify lines starting with the specified package ID + sed -i 's/\(package id="'$id_prefix'[\.a-zA-Z0-9]*" \)version="[0-9\.]*[-a-zA-Z0-9]*"/\1version="'$version_number'"/' "$packages_config_file" + else + echo "Error: $packages_config_file not found." + exit 1 + fi +} +# Function to copy .pdb files from artifacts directory to the specified output directory +function copy_pdb_files { + local artifacts_dir="$1" + local output_dir="../Output/Debug" + local downloads_dir="../Downloads" + + # Check if the artifacts directory exists + if [ ! -d "$artifacts_dir" ]; then + echo "Error: The specified artifacts directory does not exist: $artifacts_dir" + exit 1 + fi + + if [ ! -d "$output_dir" ]; then + echo "Error: The output directory does not exist: $output_dir" + exit 1 + fi + + if [ ! -d "$downloads_dir" ]; then + echo "Error: The downloads directory does not exist: $downloads_dir" + exit 1 + fi + + # Copy .pdb files to the output directory + find "$artifacts_dir" -name '*.pdb' -exec cp {} "$output_dir" \; -exec cp {} "$downloads_dir" + + echo ".pdb files copied from $artifacts_dir to $output_dir and $downloads_dir" +} -copy_curl http://build.palaso.org/guestAuth/repository/download/Libraries_Icu4c${ICUBuildType}FieldWorksContinuous/latest.lastSuccessful/icudt54.dll ../output/${BUILD_CONFIG}$MONO/${Platform}/icudt54.dll -copy_curl http://build.palaso.org/guestAuth/repository/download/Libraries_Icu4c${ICUBuildType}FieldWorksContinuous/latest.lastSuccessful/icuin54.dll ../output/${BUILD_CONFIG}$MONO/${Platform}/icuin54.dll -copy_curl http://build.palaso.org/guestAuth/repository/download/Libraries_Icu4c${ICUBuildType}FieldWorksContinuous/latest.lastSuccessful/icuuc54.dll ../output/${BUILD_CONFIG}$MONO/${Platform}/icuuc54.dll -copy_curl http://build.palaso.org/guestAuth/repository/download/Libraries_Icu4c${ICUBuildType}FieldWorksContinuous/latest.lastSuccessful/icutu54.dll ../output/${BUILD_CONFIG}$MONO/${Platform}/icutu54.dll -copy_curl http://build.palaso.org/guestAuth/repository/download/Libraries_Icu4c${ICUBuildType}FieldWorksContinuous/latest.lastSuccessful/gennorm2.exe ../output/${BUILD_CONFIG}$MONO/${Platform}/gennorm2.exe - - -############### build liblcm ############## -cd $liblcmRepo -mkdir -p ${liblcmRepo}/lib/downloads -cp -r ${libpalasoRepo}/output/${BUILD_CONFIG}/* lib/downloads -if [[ ${osName} == "Linux" ]] -then - (. environ && "${buildcommand}" /target:Build /property:Configuration=$BUILD_CONFIG /property:Platform="${liblcmPlatform}" /property:UseLocalFiles=True LCM.sln) -else - "${buildcommand}" /target:Build /property:Configuration=$BUILD_CONFIG /property:Platform="${liblcmPlatform}" /property:UseLocalFiles=True LCM.sln +# Parse command-line options +while [[ $# -gt 0 ]]; do + case "$1" in + -p|--libpalaso) + libpalaso_dir="$2" + shift 2 + ;; + -l|--liblcm) + liblcm_dir="$2" + shift 2 + ;; + -c|--chorus) + chorus_dir="$2" + shift 2 + ;; + -v|--version) + manual_version="$2" + use_manual_version=true + shift 2 + ;; + -h|--help) + display_usage + ;; + *) + echo "Error: Unknown option '$1'" + display_usage + ;; + esac +done + +# Display usage if no options are provided +if [ -z "$libpalaso_dir" ] && [ -z "$liblcm_dir" ] && [ -z "$chorus_dir" ]; then + display_usage fi +# Display the provided directory paths +echo "libpalaso directory: $libpalaso_dir" +echo "liblcm directory: $liblcm_dir" +echo "chorus directory: $chorus_dir" +# Delete specified files in the libpalaso directory and run 'dotnet pack' +delete_and_pack_libpalaso -############### build chorus ############## -cd ${chorusRepo}/build -cp -a ${libpalasoRepo}/output/${BUILD_CONFIG}$MONO/* ../lib/${BUILD_CONFIG}$MONO -cp -a ${libpalasoRepo}/output/${BUILD_CONFIG}$MONO/${Platform}/* ../lib/${BUILD_CONFIG}$MONO -if [[ ${osName} == "Linux" ]] -then - ./TestBuild.sh $BUILD_CONFIG -else - ./buildupdate.win.sh - "${buildcommand}" /target:Compile /verbosity:quiet /property:Configuration=$BUILD_CONFIG Chorus.proj -fi - - +# Delete specified files in the liblcm directory and run 'dotnet pack' +delete_and_pack_liblcm -echo $(date +"%F %T") $PROGRAM: "Finished" +# Delete specified files in the chorus directory and run 'dotnet pack' +delete_and_pack_chorus -#End Script +echo $(date +"%F %T") "Local build and pack finished" +# print a hint for how to use local .pdb files in cyan +tput setaf 6; echo "Build FLEx with /p:UsingLocalLibraryBuild=true to keep the local .pdb files" \ No newline at end of file diff --git a/Build/mkall.targets b/Build/mkall.targets index 6d50c20bb3..e7c267f83f 100644 --- a/Build/mkall.targets +++ b/Build/mkall.targets @@ -479,17 +479,20 @@ $(IcuNugetVersion)runtimes/**/*.*true $(IcuNugetVersion)build/native/**/*.*true - $(LcmNugetVersion)lib/net461/*.* - $(LcmNugetVersion)contentFiles/**/*.* - $(LcmNugetVersion)tools/net461/*.* - $(LcmNugetVersion)lib/net461/*.* - $(LcmNugetVersion)contentFiles/**/*.* - $(LcmNugetVersion)lib/net461/*.* - $(LcmNugetVersion)lib/net461/*.* - $(LcmNugetVersion)lib/net461/*.* - $(LcmNugetVersion)lib/net461/*.* - $(LcmNugetVersion)lib/net461/*.* - $(LcmNugetVersion)lib/net461/*.* + + + + $(LcmNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) + $(LcmNugetVersion)contentFiles/**/*.*$(UsingLocalLibraryBuild) + $(LcmNugetVersion)tools/net461/*.*$(UsingLocalLibraryBuild) + $(LcmNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) + $(LcmNugetVersion)contentFiles/**/*.*$(UsingLocalLibraryBuild) + $(LcmNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) + $(LcmNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) + $(LcmNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) + $(LcmNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) + $(LcmNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) + $(LcmNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) 1.4.0lib/net45/*.*true 7.1.0-final.1.21458.1lib/net45/*.*true 1.2.5554lib/net/*.*true @@ -499,27 +502,27 @@ 4.7.3lib/net45/*.*true 4.4.0lib/netstandard2.0/*.*true - $(PalasoNugetVersion)lib/net461/*.* + $(PalasoNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) $(PalasoNugetVersion)contentFiles/any/any/*.*true - $(PalasoNugetVersion)lib/net461/*.* - $(PalasoNugetVersion)lib/net461/*.* + $(PalasoNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) + $(PalasoNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) $(PalasoNugetVersion)build/**/*.*true - $(PalasoNugetVersion)lib/net461/*.* + $(PalasoNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) $(PalasoNugetVersion)contentFiles/any/any/*.*true - $(PalasoNugetVersion)lib/net461/*.* - $(PalasoNugetVersion)lib/net461/*.* - $(PalasoNugetVersion)lib/net461/*.* - $(PalasoNugetVersion)lib/net461/*.* - $(PalasoNugetVersion)lib/net461/*.* + $(PalasoNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) + $(PalasoNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) + $(PalasoNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) + $(PalasoNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) + $(PalasoNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) $(PalasoNugetVersion)build/Interop.WIA.dlltrue $(PalasoNugetVersion)build/x64/Interop.WIA.dlltrue - $(PalasoNugetVersion)lib/net461/*.* + $(PalasoNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) $(PalasoNugetVersion)build/*.*true $(PalasoNugetVersion)contentFiles/any/any/*.*true - $(PalasoNugetVersion)lib/net461/*.* - $(PalasoNugetVersion)lib/net461/*.* - $(PalasoNugetVersion)lib/net461/*.* - $(PalasoNugetVersion)lib/net461/*.* + $(PalasoNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) + $(PalasoNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) + $(PalasoNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) + $(PalasoNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) 4.5.4lib/net461/*.*true 4.6.0lib/netstandard2.0/*.*true 6.0.0lib/net461/*.* @@ -531,9 +534,9 @@ 1.0.0.39lib/net461/*.*true 1.0.0.39lib/net461/*.*true - $(ChorusNugetVersion)lib/net461/*.* - $(ChorusNugetVersion)lib/net461/*.* - $(ChorusNugetVersion)lib/net461/*.* + $(ChorusNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) + $(ChorusNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) + $(ChorusNugetVersion)lib/net461/*.*$(UsingLocalLibraryBuild) 4.9.4lib/net45/*.*true 1.0.16lib/net461/*.* From 09665623872b2a36498d1bbd3835d2379c9783bb Mon Sep 17 00:00:00 2001 From: mark-sil Date: Tue, 12 Dec 2023 11:19:13 -0500 Subject: [PATCH 013/285] buildLocalLibraries.sh: fixed problem with copying pdb files The command to copy the pdb files was failing with the following error: find: missing argument to `-exec' Change-Id: I0cb868a87c80e468e24c96c4568d1cec9898a0e0 --- Build/buildLocalLibraries.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Build/buildLocalLibraries.sh b/Build/buildLocalLibraries.sh index aaaeb21a1a..925db030e3 100755 --- a/Build/buildLocalLibraries.sh +++ b/Build/buildLocalLibraries.sh @@ -194,7 +194,7 @@ function copy_pdb_files { fi # Copy .pdb files to the output directory - find "$artifacts_dir" -name '*.pdb' -exec cp {} "$output_dir" \; -exec cp {} "$downloads_dir" + find "$artifacts_dir" -name '*.pdb' -exec cp {} "$output_dir" \; -exec cp {} "$downloads_dir" \; echo ".pdb files copied from $artifacts_dir to $output_dir and $downloads_dir" } From 3b7d4a9d7cfcee1edec5ee13168685781e02b293 Mon Sep 17 00:00:00 2001 From: mark-sil Date: Thu, 4 Jan 2024 09:11:10 -0500 Subject: [PATCH 014/285] =?UTF-8?q?LT-21424:=20Check=20lowercase=20for=20p?= =?UTF-8?q?arsing=20and=20=E2=80=98Try=20a=20word=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If we don’t get results for a word that contains uppercase characters, then try the lowercase word. Change-Id: I53c6ca897bdd76434ac37d3b4148fa4b9d877847 --- .../ParserCoreTests/ParseWorkerTests.cs | 230 ++++++++++++++++++ .../ParserCoreTests/ParserCoreTests.csproj | 1 + Src/LexText/ParserCore/ParserWorker.cs | 52 +++- 3 files changed, 282 insertions(+), 1 deletion(-) create mode 100644 Src/LexText/ParserCore/ParserCoreTests/ParseWorkerTests.cs diff --git a/Src/LexText/ParserCore/ParserCoreTests/ParseWorkerTests.cs b/Src/LexText/ParserCore/ParserCoreTests/ParseWorkerTests.cs new file mode 100644 index 0000000000..46c86ce6a4 --- /dev/null +++ b/Src/LexText/ParserCore/ParserCoreTests/ParseWorkerTests.cs @@ -0,0 +1,230 @@ +// Copyright (c) 2024 SIL International +// This software is licensed under the LGPL, version 2.1 or later +// (http://www.gnu.org/licenses/lgpl-2.1.html) + +using System; +using NUnit.Framework; +using SIL.LCModel.Core.Text; +using SIL.LCModel.Core.WritingSystems; +using SIL.LCModel; +using SIL.LCModel.Infrastructure; +using XCore; +using System.Xml.Linq; +using SIL.ObjectModel; +using System.Collections.Generic; +using System.Linq; + +namespace SIL.FieldWorks.WordWorks.Parser +{ + [TestFixture] + public class ParseWorkerTests : MemoryOnlyBackendProviderTestBase + { + #region Data Members + private String m_taskDetailsString; + private IdleQueue m_idleQueue; + private CoreWritingSystemDefinition m_vernacularWS; + #endregion Data Members + + #region Non-test methods + private IWfiWordform FindOrCreateWordform(string form) + { + ILcmServiceLocator servLoc = Cache.ServiceLocator; + IWfiWordform wf = servLoc.GetInstance().GetMatchingWordform(m_vernacularWS.Handle, form); + if (wf == null) + { + UndoableUnitOfWorkHelper.Do("Undo create", "Redo create", m_actionHandler, + () => wf = servLoc.GetInstance().Create(TsStringUtils.MakeString(form, m_vernacularWS.Handle))); + } + return wf; + } + + protected IWfiWordform CheckAnalysisSize(string form, int expectedSize, bool isStarting) + { + IWfiWordform wf = FindOrCreateWordform(form); + int actualSize = wf.AnalysesOC.Count; + string msg = String.Format("Wrong number of {0} analyses for: {1}", isStarting ? "starting" : "ending", form); + Assert.AreEqual(expectedSize, actualSize, msg); + return wf; + } + + protected void ExecuteIdleQueue() + { + foreach (var task in m_idleQueue) + task.Delegate(task.Parameter); + m_idleQueue.Clear(); + } + + private void HandleTaskUpdate(TaskReport task) + { + m_taskDetailsString = task?.Details?.ToString(); + } + #endregion // Non-tests + + #region Setup and TearDown + public override void FixtureSetup() + { + base.FixtureSetup(); + m_vernacularWS = Cache.ServiceLocator.WritingSystems.DefaultVernacularWritingSystem; + m_idleQueue = new IdleQueue {IsPaused = true}; + } + + public override void FixtureTeardown() + { + m_vernacularWS = null; + m_idleQueue.Dispose(); + m_idleQueue = null; + + base.FixtureTeardown(); + } + + public override void TestTearDown() + { + UndoAll(); + base.TestTearDown(); + } + + /// ------------------------------------------------------------------------------------ + /// + /// End the undoable UOW and Undo everything. + /// + /// ------------------------------------------------------------------------------------ + protected void UndoAll() + { + // Undo the UOW (or more than one of them, if the test made new ones). + while (m_actionHandler.CanUndo()) + m_actionHandler.Undo(); + + // Need to 'Commit' to clear out redo stack, + // since nothing is really saved. + m_actionHandler.Commit(); + } + #endregion Setup and TearDown + + #region Tests + [Test] + public void TryAWord() + { + XDocument lowerXDoc = new XDocument(new XComment("cats")); + var parserWorker = new ParserWorker(Cache, HandleTaskUpdate, m_idleQueue, null); + parserWorker.Parser = new TestParserClass(null, lowerXDoc); + + // SUT + parserWorker.TryAWord("Cats", false, null); + Assert.AreEqual(m_taskDetailsString, lowerXDoc.ToString()); + m_taskDetailsString = null; + + // SUT + parserWorker.TryAWord("cats", false, null); + Assert.AreEqual(m_taskDetailsString, lowerXDoc.ToString()); + } + + [Test] + public void UpdateWordform() + { + IWfiWordform catsLowerTest = CheckAnalysisSize("cats", 0, true); + IWfiWordform catsUpperTest = CheckAnalysisSize("Cats", 0, true); + ILexDb ldb = Cache.LanguageProject.LexDbOA; + + ParseResult lowerResult = null; + UndoableUnitOfWorkHelper.Do("Undo stuff", "Redo stuff", m_actionHandler, () => + { + // Noun + ILexEntry catN = Cache.ServiceLocator.GetInstance().Create(); + IMoStemAllomorph catNForm = Cache.ServiceLocator.GetInstance().Create(); + catN.AlternateFormsOS.Add(catNForm); + catNForm.Form.VernacularDefaultWritingSystem = TsStringUtils.MakeString("catn", m_vernacularWS.Handle); + IMoStemMsa catNMsa = Cache.ServiceLocator.GetInstance().Create(); + catN.MorphoSyntaxAnalysesOC.Add(catNMsa); + + lowerResult = new ParseResult(new[] + { + new ParseAnalysis(new[] + { + new ParseMorph(catNForm, catNMsa), + }) + }); + }); + + var parserWorker = new ParserWorker(Cache, HandleTaskUpdate, m_idleQueue, null); + parserWorker.Parser = new TestParserClass(lowerResult, null); + + // SUT + var bVal = parserWorker.UpdateWordform(catsUpperTest, ParserPriority.Low); + ExecuteIdleQueue(); + Assert.IsTrue(bVal); + CheckAnalysisSize("Cats", 1, false); + CheckAnalysisSize("cats", 0, false); + + // SUT + bVal = parserWorker.UpdateWordform(catsLowerTest, ParserPriority.Low); + ExecuteIdleQueue(); + Assert.IsTrue(bVal); + CheckAnalysisSize("Cats", 1, false); + CheckAnalysisSize("cats", 1, false); + } + #endregion // Tests + } + + /// + /// IParser class used for testing the ParseWorker. + /// This test class only returns results for lowercase words. Uppercase + /// words return empty results. + /// + public class TestParserClass : DisposableBase, IParser + { + private readonly ParseResult m_lowerResult; + private readonly XDocument m_lowerXDoc; + + public TestParserClass(ParseResult lowerResult, XDocument lowerXDoc) + { + m_lowerResult = lowerResult; + m_lowerXDoc = lowerXDoc; + } + + public bool IsUpToDate() { return true; } + + public void Update() { } + + public void Reset() { } + + /// + /// If the input word is lowercase then return the lowercase results. + /// Else return empty results. + /// + public ParseResult ParseWord(string word) + { + if (word == word.ToLower()) + { + return m_lowerResult; + } + else + { + return new ParseResult(Enumerable.Empty()); + } + } + + /// + /// If the input word is lowercase then return the lowercase results. + /// Else return empty results. + /// + public XDocument ParseWordXml(string word) + { + if (word == word.ToLower()) + { + return m_lowerXDoc; + } + else + { + return new XDocument(); + } + } + + /// + /// Not implemented. + /// + public XDocument TraceWordXml(string word, IEnumerable selectTraceMorphs) + { + return null; + } + } +} diff --git a/Src/LexText/ParserCore/ParserCoreTests/ParserCoreTests.csproj b/Src/LexText/ParserCore/ParserCoreTests/ParserCoreTests.csproj index d7738d63a2..fef7275253 100644 --- a/Src/LexText/ParserCore/ParserCoreTests/ParserCoreTests.csproj +++ b/Src/LexText/ParserCore/ParserCoreTests/ParserCoreTests.csproj @@ -208,6 +208,7 @@ AssemblyInfoForTests.cs + Code diff --git a/Src/LexText/ParserCore/ParserWorker.cs b/Src/LexText/ParserCore/ParserWorker.cs index 1980597681..7015212615 100644 --- a/Src/LexText/ParserCore/ParserWorker.cs +++ b/Src/LexText/ParserCore/ParserWorker.cs @@ -109,7 +109,33 @@ public void TryAWord(string sForm, bool fDoTrace, int[] sSelectTraceMorphs) using (var task = new TaskReport(string.Format(ParserCoreStrings.ksTraceWordformX, sForm), m_taskUpdateHandler)) { string normForm = CustomIcu.GetIcuNormalizer(FwNormalizationMode.knmNFD).Normalize(sForm); - task.Details = fDoTrace ? m_parser.TraceWordXml(normForm, sSelectTraceMorphs) : m_parser.ParseWordXml(normForm); + + // Get the lowercase word. + var cf = new CaseFunctions(m_cache.ServiceLocator.WritingSystems.DefaultVernacularWritingSystem); + string normFormLower = CustomIcu.GetIcuNormalizer(FwNormalizationMode.knmNFD).Normalize(cf.ToLower(sForm)); + + // The word is already lowercase, just return the xml. + if (normForm == normFormLower) + { + task.Details = fDoTrace ? m_parser.TraceWordXml(normForm, sSelectTraceMorphs) : m_parser.ParseWordXml(normForm); + } + // The word is uppercase, make a ParseWord() call for the uppercase word to determine if we should try to get + // the xml for the uppercase word or for the lowercase word. + else + { + ParseResult result = m_parser.ParseWord(normForm); + + // Parse of uppercase word was successful, get it's xml. + if (result.Analyses.Count > 0 && result.ErrorMessage == null) + { + task.Details = fDoTrace ? m_parser.TraceWordXml(normForm, sSelectTraceMorphs) : m_parser.ParseWordXml(normForm); + } + // Parse of uppercase word was not successful, try to get the xml for the lowercase word. + else + { + task.Details = fDoTrace ? m_parser.TraceWordXml(normFormLower, sSelectTraceMorphs) : m_parser.ParseWordXml(normFormLower); + } + } } } @@ -137,6 +163,22 @@ public bool UpdateWordform(IWfiWordform wordform, ParserPriority priority) ParseResult result = m_parser.ParseWord( CustomIcu.GetIcuNormalizer(FwNormalizationMode.knmNFD) .Normalize(form.Text.Replace(' ', '.'))); + + // If the parse of the original word was not successful,then try to parse the lowercase word. + if (result.Analyses.Count == 0 || result.ErrorMessage != null) + { + var cf = new CaseFunctions(m_cache.ServiceLocator.WritingSystemManager.Get(form.get_WritingSystemAt(0))); + string sLower = cf.ToLower(form.Text); + + // Try parsing the lowercase word if it is different from the original word. + if (sLower != form.Text) + { + result = m_parser.ParseWord( + CustomIcu.GetIcuNormalizer(FwNormalizationMode.knmNFD) + .Normalize(sLower.Replace(' ', '.'))); + } + } + if (wordformHash == result.GetHashCode()) return false; @@ -159,5 +201,13 @@ public void ReloadGrammarAndLexicon() m_parser.Reset(); CheckNeedsUpdate(); } + + /// ------------------------------------------------------------------------------------ + /// + /// Should only be used for tests! + /// + /// ------------------------------------------------------------------------------------ + internal IParser Parser { set => m_parser = value; } + } } From 813ccd863fce743a7fcdbe49d84178d114e6f51a Mon Sep 17 00:00:00 2001 From: mark-sil Date: Tue, 9 Jan 2024 13:18:24 -0500 Subject: [PATCH 015/285] =?UTF-8?q?LT-21424:=20Fix=20=E2=80=98Approve=20Th?= =?UTF-8?q?roughout=20this=20Text=E2=80=99=20for=20capitals?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit An additional change was needed to make ‘Approve Throughout this Text’ do lowercase matching regardless of where the occurrence is in the segment. Change-Id: I649104e9292d0f89baf2c2224298b3a95325cb2d --- .../Interlinear/FocusBoxController.ApproveAndMove.cs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Src/LexText/Interlinear/FocusBoxController.ApproveAndMove.cs b/Src/LexText/Interlinear/FocusBoxController.ApproveAndMove.cs index 15b638ab1b..95c62f3aea 100644 --- a/Src/LexText/Interlinear/FocusBoxController.ApproveAndMove.cs +++ b/Src/LexText/Interlinear/FocusBoxController.ApproveAndMove.cs @@ -507,8 +507,6 @@ public void ApproveGuessOrChangesForWholeTextAndMoveNext(Command cmd) return; } SaveAnalysisForAnnotation(SelectedOccurrence, newAnalysisTree); - // determine if we confirmed on a sentence initial wordform to its lowercased form - bool fIsSentenceInitialCaseChange = oldWf != wf; if (wf != null) { ApplyAnalysisToInstancesOfWordform(newAnalysisTree.Analysis, oldWf, wf); @@ -535,10 +533,8 @@ private void ApplyAnalysisToInstancesOfWordform(IAnalysis newAnalysis, IWfiWordf foreach (var occ in navigator.GetAnalysisOccurrencesAdvancingInStText().ToList()) { // We certainly want to update any occurrence that exactly matches the wordform of the analysis we are confirming. - // If oldWordform is different, we are confirming a different case form from what occurred in the text, - // and we only confirm these if SelectedOccurrence and occ are both sentence-initial. - // We want to do that only for sentence-initial occurrences. - if (occ.Analysis == newWordform || (occ.Analysis == oldWordform && occ.Index == 0 && SelectedOccurrence.Index == 0)) + // If oldWordform is different, we are confirming a different case form from what occurred in the text. + if (occ.Analysis == newWordform || occ.Analysis == oldWordform) occ.Segment.AnalysesRS[occ.Index] = newAnalysis; } } From 94e5bc6963e0508fa781c8cbe11da71ce8aea8fe Mon Sep 17 00:00:00 2001 From: Ariel Rorabaugh Date: Tue, 21 Nov 2023 18:12:56 -0500 Subject: [PATCH 016/285] Refactored export + Initial word export w/o styles * Added an IFragment interface. * Refactored ConfiguredLcmGenerator, LcmJsonGenerator, and LcmXhtmlGenerator to pass IFragments rather than strings. * Setup an initial Word Generator without styles. * Generator tests expect results to be strings, not IFragments. Updated the generator tests to call ToString on all results. Change-Id: Iff4bd970ecfd5c181f07db04852ce583da5deac1 --- Src/xWorks/ConfiguredLcmGenerator.cs | 421 +++++----- Src/xWorks/DictionaryExportService.cs | 13 +- Src/xWorks/ExportDialog.cs | 2 +- Src/xWorks/ILcmContentGenerator.cs | 49 +- Src/xWorks/LcmJsonGenerator.cs | 158 ++-- Src/xWorks/LcmWordGenerator.cs | 759 ++++++++++++++++++ Src/xWorks/LcmXhtmlGenerator.cs | 185 +++-- Src/xWorks/StringFragment.cs | 70 ++ Src/xWorks/XhtmlDocView.cs | 4 +- Src/xWorks/xWorks.csproj | 2 + .../ConfiguredLcmUsfmGeneratorTests.cs | 44 +- .../ConfiguredXHTMLGeneratorReversalTests.cs | 24 +- .../ConfiguredXHTMLGeneratorTests.cs | 372 ++++----- Src/xWorks/xWorksTests/CssGeneratorTests.cs | 6 +- .../xWorksTests/LcmJsonGeneratorTests.cs | 46 +- 15 files changed, 1554 insertions(+), 601 deletions(-) create mode 100644 Src/xWorks/LcmWordGenerator.cs create mode 100644 Src/xWorks/StringFragment.cs diff --git a/Src/xWorks/ConfiguredLcmGenerator.cs b/Src/xWorks/ConfiguredLcmGenerator.cs index a855bb83a0..6c4a9a5059 100644 --- a/Src/xWorks/ConfiguredLcmGenerator.cs +++ b/Src/xWorks/ConfiguredLcmGenerator.cs @@ -2,36 +2,37 @@ // This software is licensed under the LGPL, version 2.1 or later // (http://www.gnu.org/licenses/lgpl-2.1.html) -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading; -using System.Web.UI.WebControls; using ExCSS; +using Icu.Collation; using SIL.Code; -using SIL.LCModel.Core.Cellar; -using SIL.LCModel.Core.Text; -using SIL.LCModel.Core.WritingSystems; using SIL.FieldWorks.Common.Controls; -using SIL.FieldWorks.Filters; using SIL.FieldWorks.Common.Framework; -using SIL.LCModel.Core.KernelInterfaces; using SIL.FieldWorks.Common.FwUtils; using SIL.FieldWorks.Common.Widgets; +using SIL.FieldWorks.Filters; using SIL.LCModel; +using SIL.LCModel.Core.Cellar; +using SIL.LCModel.Core.KernelInterfaces; +using SIL.LCModel.Core.Text; +using SIL.LCModel.Core.WritingSystems; using SIL.LCModel.DomainImpl; using SIL.LCModel.DomainServices; using SIL.LCModel.Infrastructure; using SIL.LCModel.Utils; using SIL.PlatformUtilities; using SIL.Reporting; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Web.UI.WebControls; using XCore; using FileUtils = SIL.LCModel.Utils.FileUtils; using UnitType = ExCSS.UnitType; @@ -112,11 +113,47 @@ private static bool IsCanceling(IThreadedProgress progress) return progress != null && progress.IsCanceling; } + internal static StringBuilder GenerateLetterHeaderIfNeeded(ICmObject entry, + ref string lastHeader, Collator headwordWsCollator, + ConfiguredLcmGenerator.GeneratorSettings settings, RecordClerk clerk = null) + { + // If performance is an issue these dummies can be stored between calls + var dummyOne = + new Dictionary>(); + var dummyTwo = new Dictionary>(); + var dummyThree = new Dictionary>(); + var cache = settings.Cache; + var wsString = ConfiguredLcmGenerator.GetWsForEntryType(entry, cache); + var firstLetter = ConfiguredExport.GetLeadChar( + ConfiguredLcmGenerator.GetSortWordForLetterHead(entry, clerk), wsString, dummyOne, + dummyTwo, dummyThree, + headwordWsCollator, cache); + if (firstLetter != lastHeader && !string.IsNullOrEmpty(firstLetter)) + { + var headerTextBuilder = new StringBuilder(); + var upperCase = + new CaseFunctions(cache.ServiceLocator.WritingSystemManager.Get(wsString)) + .ToTitle(firstLetter); + var lowerCase = firstLetter.Normalize(); + headerTextBuilder.Append(upperCase); + if (lowerCase != upperCase) + { + headerTextBuilder.Append(' '); + headerTextBuilder.Append(lowerCase); + } + lastHeader = firstLetter; + + return headerTextBuilder; + } + + return new StringBuilder(""); + } + /// - /// This method uses a ThreadPool to execute the given individualActions in parallel. - /// It waits for all the individualActions to complete and then returns. - /// - internal static void SpawnEntryGenerationThreadsAndWait(List individualActions, IThreadedProgress progress) + /// This method uses a ThreadPool to execute the given individualActions in parallel. + /// It waits for all the individualActions to complete and then returns. + /// + internal static void SpawnEntryGenerationThreadsAndWait(List individualActions, IThreadedProgress progress) { var actionCount = individualActions.Count; //Note that our COM classes all implement the STA threading model, while the ThreadPool always uses MTA model threads. @@ -218,7 +255,7 @@ internal static string GetWsForEntryType(ICmObject entry, LcmCache cache) /// If it is a Minor Entry, first checks whether the entry should be published as a Minor Entry; then, generates XHTML for each applicable /// Minor Entry configuration node. /// - public static string GenerateContentForEntry(ICmObject entryObj, DictionaryConfigurationModel configuration, + public static IFragment GenerateContentForEntry(ICmObject entryObj, DictionaryConfigurationModel configuration, DictionaryPublicationDecorator publicationDecorator, GeneratorSettings settings, int index = -1) { if (IsMainEntry(entryObj, configuration)) @@ -227,23 +264,23 @@ public static string GenerateContentForEntry(ICmObject entryObj, DictionaryConfi var entry = (ILexEntry)entryObj; return entry.PublishAsMinorEntry ? GenerateContentForMinorEntry(entry, configuration, publicationDecorator, settings, index) - : string.Empty; + : settings.ContentGenerator.CreateFragment(); } - public static string GenerateContentForMainEntry(ICmObject entry, ConfigurableDictionaryNode configuration, + public static IFragment GenerateContentForMainEntry(ICmObject entry, ConfigurableDictionaryNode configuration, DictionaryPublicationDecorator publicationDecorator, GeneratorSettings settings, int index) { if (configuration.DictionaryNodeOptions != null && ((ILexEntry)entry).ComplexFormEntryRefs.Any() && !IsListItemSelectedForExport(configuration, entry)) - return string.Empty; + return settings.ContentGenerator.CreateFragment(); return GenerateContentForEntry(entry, configuration, publicationDecorator, settings, index); } - private static string GenerateContentForMinorEntry(ICmObject entry, DictionaryConfigurationModel configuration, + private static IFragment GenerateContentForMinorEntry(ICmObject entry, DictionaryConfigurationModel configuration, DictionaryPublicationDecorator publicationDecorator, GeneratorSettings settings, int index) { // LT-15232: show minor entries using only the last applicable Minor Entry node (not more than once) var applicablePart = configuration.Parts.Skip(1).LastOrDefault(part => IsListItemSelectedForExport(part, entry)); - return applicablePart == null ? string.Empty : GenerateContentForEntry(entry, applicablePart, publicationDecorator, settings, index); + return applicablePart == null ? settings.ContentGenerator.CreateFragment() : GenerateContentForEntry(entry, applicablePart, publicationDecorator, settings, index); } /// @@ -265,7 +302,7 @@ internal static bool IsMainEntry(ICmObject entry, DictionaryConfigurationModel c /// Generates content with the GeneratorSettings.ContentGenerator for an ICmObject for a specific ConfigurableDictionaryNode /// the configuration node must match the entry type - internal static string GenerateContentForEntry(ICmObject entry, ConfigurableDictionaryNode configuration, + internal static IFragment GenerateContentForEntry(ICmObject entry, ConfigurableDictionaryNode configuration, DictionaryPublicationDecorator publicationDecorator, GeneratorSettings settings, int index = -1) { Guard.AgainstNull(settings, nameof(settings)); @@ -293,17 +330,18 @@ internal static string GenerateContentForEntry(ICmObject entry, ConfigurableDict if (!configuration.IsEnabled) { - return string.Empty; + return settings.ContentGenerator.CreateFragment(); } + var pieces = configuration.ReferencedOrDirectChildren .Select(config => GenerateContentForFieldByReflection(entry, config, publicationDecorator, settings)) - .Where(content => !string.IsNullOrEmpty(content)).ToList(); + .Where(content => content!=null && !string.IsNullOrEmpty(content.ToString())).ToList(); if (pieces.Count == 0) - return string.Empty; - var bldr = new StringBuilder(); + return settings.ContentGenerator.CreateFragment(); + var bldr = settings.ContentGenerator.CreateFragment(); using (var xw = settings.ContentGenerator.CreateWriter(bldr)) { var clerk = settings.PropertyTable.GetValue("ActiveClerk", null); @@ -313,8 +351,12 @@ internal static string GenerateContentForEntry(ICmObject entry, ConfigurableDict settings.ContentGenerator.AddEntryData(xw, pieces); settings.ContentGenerator.EndEntry(xw); xw.Flush(); - return CustomIcu.GetIcuNormalizer(FwNormalizationMode.knmNFC) - .Normalize(bldr.ToString()); // All content should be in NFC (LT-18177) + + // Do not normalize the string if exporting to word doc--it is not needed and will cause loss of document styles + if (bldr is LcmWordGenerator.DocFragment) + return bldr; + + return settings.ContentGenerator.CreateFragment(CustomIcu.GetIcuNormalizer(FwNormalizationMode.knmNFC).Normalize(bldr.ToString())); // All content should be in NFC (LT-18177) } } catch (ArgumentException) @@ -348,13 +390,13 @@ public static string GetClassNameAttributeForConfig(ConfigurableDictionaryNode c /// write out appropriate content using the settings parameter. /// /// We use a significant amount of boilerplate code for fields and subfields. Make sure you update both. - internal static string GenerateContentForFieldByReflection(object field, ConfigurableDictionaryNode config, + internal static IFragment GenerateContentForFieldByReflection(object field, ConfigurableDictionaryNode config, DictionaryPublicationDecorator publicationDecorator, GeneratorSettings settings, SenseInfo info = new SenseInfo(), bool fUseReverseSubField = false) { if (!config.IsEnabled) { - return string.Empty; + return settings.ContentGenerator.CreateFragment(); } var cache = settings.Cache; var entryType = field.GetType(); @@ -371,12 +413,12 @@ internal static string GenerateContentForFieldByReflection(object field, Configu } if (field is ILexEntryRef) { - var ret = new StringBuilder(); + var ret = settings.ContentGenerator.CreateFragment(); foreach (var sense in (((field as ILexEntryRef).Owner as ILexEntry).AllSenses)) { ret.Append(GenerateContentForDefOrGloss(sense, config, settings)); } - return ret.ToString(); + return ret; } } if (config.FieldDescription == "CaptionOrHeadword") @@ -391,7 +433,7 @@ internal static string GenerateContentForFieldByReflection(object field, Configu // REVIEW: We have overloaded terms here, this is a C# class not a css class, consider a different name var customFieldOwnerClassName = GetClassNameForCustomFieldParent(config, settings.Cache); if (!GetPropValueForCustomField(field, config, cache, customFieldOwnerClassName, config.FieldDescription, ref propertyValue)) - return string.Empty; + return settings.ContentGenerator.CreateFragment(); } else { @@ -413,7 +455,7 @@ internal static string GenerateContentForFieldByReflection(object field, Configu var msg = string.Format("Issue with finding {0} for {1}", config.FieldDescription, entryType); ShowConfigDebugInfo(msg, config); #endif - return string.Empty; + return settings.ContentGenerator.CreateFragment(); } propertyValue = GetValueFromMember(property, field); GetSortedReferencePropertyValue(config, ref propertyValue, field); @@ -421,7 +463,7 @@ internal static string GenerateContentForFieldByReflection(object field, Configu // If the property value is null there is nothing to generate if (propertyValue == null) { - return string.Empty; + return settings.ContentGenerator.CreateFragment(); } if (!string.IsNullOrEmpty(config.SubField)) { @@ -431,7 +473,7 @@ internal static string GenerateContentForFieldByReflection(object field, Configu if (!GetPropValueForCustomField(propertyValue, config, cache, ((ICmObject)propertyValue).ClassName, config.SubField, ref propertyValue)) { - return string.Empty; + return settings.ContentGenerator.CreateFragment(); } } else @@ -445,14 +487,14 @@ internal static string GenerateContentForFieldByReflection(object field, Configu var msg = String.Format("Issue with finding (subField) {0} for (subType) {1}", subField, subType); ShowConfigDebugInfo(msg, config); #endif - return string.Empty; + return settings.ContentGenerator.CreateFragment(); } propertyValue = subProp.GetValue(propertyValue, new object[] { }); GetSortedReferencePropertyValue(config, ref propertyValue, field); } // If the property value is null there is nothing to generate if (propertyValue == null) - return string.Empty; + return settings.ContentGenerator.CreateFragment(); } ICmFile fileProperty; ICmObject fileOwner; @@ -462,7 +504,7 @@ internal static string GenerateContentForFieldByReflection(object field, Configu switch (typeForNode) { case PropertyType.CollectionType: - return !IsCollectionEmpty(propertyValue) ? GenerateContentForCollection(propertyValue, config, publicationDecorator, field, settings, info) : string.Empty; + return !IsCollectionEmpty(propertyValue) ? GenerateContentForCollection(propertyValue, config, publicationDecorator, field, settings, info) : settings.ContentGenerator.CreateFragment(); case PropertyType.MoFormType: return GenerateContentForMoForm(propertyValue as IMoForm, config, settings); @@ -501,9 +543,10 @@ internal static string GenerateContentForFieldByReflection(object field, Configu : GenerateContentForAudioFile(fileProperty.ClassName, fileOwner.Guid.ToString(), srcAttr, LoudSpeaker, settings); } } - return string.Empty; + return settings.ContentGenerator.CreateFragment(); } - var bldr = new StringBuilder(GenerateContentForValue(field, propertyValue, config, settings)); + + var bldr = GenerateContentForValue(field, propertyValue, config, settings); if (config.ReferencedOrDirectChildren != null) { foreach (var child in config.ReferencedOrDirectChildren) @@ -511,10 +554,10 @@ internal static string GenerateContentForFieldByReflection(object field, Configu bldr.Append(GenerateContentForFieldByReflection(propertyValue, child, publicationDecorator, settings)); } } - return bldr.ToString(); + return bldr; } - private static string GenerateContentForGroupingNode(object field, ConfigurableDictionaryNode config, + private static IFragment GenerateContentForGroupingNode(object field, ConfigurableDictionaryNode config, DictionaryPublicationDecorator publicationDecorator, GeneratorSettings settings) { if (config.ReferencedOrDirectChildren != null && config.ReferencedOrDirectChildren.Any(child => child.IsEnabled)) @@ -523,7 +566,7 @@ private static string GenerateContentForGroupingNode(object field, ConfigurableD return settings.ContentGenerator.GenerateGroupingNode(field, className, config, publicationDecorator, settings, (f, c, p, s) => GenerateContentForFieldByReflection(f, c, p, s)); } - return string.Empty; + return settings.ContentGenerator.CreateFragment(); } /// @@ -615,10 +658,10 @@ private static bool GetPropValueForCustomField(object fieldOwner, ConfigurableDi return true; } - private static string GenerateContentForVideoFile(string className, string mediaId, string srcAttribute, string caption, GeneratorSettings settings) + private static IFragment GenerateContentForVideoFile(string className, string mediaId, string srcAttribute, string caption, GeneratorSettings settings) { if (string.IsNullOrEmpty(srcAttribute) && string.IsNullOrEmpty(caption)) - return string.Empty; + return settings.ContentGenerator.CreateFragment(); // This creates a link that will open the video in the same window as the dictionary view/preview // refreshing will bring it back to the dictionary return settings.ContentGenerator.GenerateVideoLinkContent(className, GetSafeXHTMLId(mediaId), srcAttribute, caption); @@ -749,43 +792,45 @@ private static string GetClassNameForCustomFieldParent(ConfigurableDictionaryNod return parentNodeType.Name; } - private static string GenerateContentForPossibility(object propertyValue, ConfigurableDictionaryNode config, + private static IFragment GenerateContentForPossibility(object propertyValue, ConfigurableDictionaryNode config, DictionaryPublicationDecorator publicationDecorator, GeneratorSettings settings) { if (config.ReferencedOrDirectChildren == null || !config.ReferencedOrDirectChildren.Any(node => node.IsEnabled)) - return string.Empty; - var bldr = new StringBuilder(); + return settings.ContentGenerator.CreateFragment(); + var bldr = settings.ContentGenerator.CreateFragment(); foreach (var child in config.ReferencedOrDirectChildren) { var content = GenerateContentForFieldByReflection(propertyValue, child, publicationDecorator, settings); bldr.Append(content); } - if (bldr.Length > 0) + if (bldr.Length() > 0) { var className = settings.StylesGenerator.AddStyles(config).Trim('.'); - return settings.ContentGenerator.WriteProcessedObject(false, bldr.ToString(), className); + return settings.ContentGenerator.WriteProcessedObject(false, bldr, className); } - return string.Empty; + + // bldr is a fragment that is empty of text, since length = 0 + return bldr; } - private static string GenerateContentForPictureCaption(object propertyValue, ConfigurableDictionaryNode config, GeneratorSettings settings) + private static IFragment GenerateContentForPictureCaption(object propertyValue, ConfigurableDictionaryNode config, GeneratorSettings settings) { // todo: get sense numbers and captions into the same div and get rid of this if else - string content; + IFragment content; if (config.DictionaryNodeOptions != null) content = GenerateContentForStrings(propertyValue as IMultiString, config, settings); else content = GenerateContentForString(propertyValue as ITsString, config, settings); - if (!string.IsNullOrEmpty(content)) + if (!content.IsNullOrEmpty()) { var className = settings.StylesGenerator.AddStyles(config).Trim('.'); return settings.ContentGenerator.WriteProcessedObject(true, content, className); } - return string.Empty; + return settings.ContentGenerator.CreateFragment(); } - private static string GenerateContentForPicture(ICmFile pictureFile, ConfigurableDictionaryNode config, ICmObject owner, + private static IFragment GenerateContentForPicture(ICmFile pictureFile, ConfigurableDictionaryNode config, ICmObject owner, GeneratorSettings settings) { var srcAttribute = GenerateSrcAttributeFromFilePath(pictureFile, settings.UseRelativePaths ? "pictures" : null, settings); @@ -797,7 +842,7 @@ private static string GenerateContentForPicture(ICmFile pictureFile, Configurabl var ownerGuid = owner.Guid.ToString(); return settings.ContentGenerator.AddImage(className, srcAttribute, ownerGuid); } - return string.Empty; + return settings.ContentGenerator.CreateFragment(); } /// @@ -844,12 +889,12 @@ private static string GenerateSrcAttributeForMediaFromFilePath(string filename, return settings.UseRelativePaths ? filePath : new Uri(filePath).ToString(); } - private static string GenerateContentForDefOrGloss(ILexSense sense, ConfigurableDictionaryNode config, GeneratorSettings settings) + private static IFragment GenerateContentForDefOrGloss(ILexSense sense, ConfigurableDictionaryNode config, GeneratorSettings settings) { var wsOption = config.DictionaryNodeOptions as DictionaryNodeWritingSystemOptions; if (wsOption == null) throw new ArgumentException(@"Configuration nodes for MultiString fields whould have WritingSystemOptions", "config"); - var bldr = new StringBuilder(); + var bldr = settings.ContentGenerator.CreateFragment(); foreach (var option in wsOption.Options) { if (option.IsEnabled) @@ -864,20 +909,21 @@ private static string GenerateContentForDefOrGloss(ILexSense sense, Configurable } } - if (bldr.Length > 0) + if (bldr.Length() > 0) { var className = settings.StylesGenerator.AddStyles(config).Trim('.'); ; - return settings.ContentGenerator.WriteProcessedCollection(false, bldr.ToString(), className); + return settings.ContentGenerator.WriteProcessedCollection(false, bldr, className); } - return string.Empty; + // bldr is a fragment that is empty of text, since length = 0 + return bldr; } - private static string GenerateContentForCaptionOrHeadword(ICmPicture picture, ConfigurableDictionaryNode config, GeneratorSettings settings) + private static IFragment GenerateContentForCaptionOrHeadword(ICmPicture picture, ConfigurableDictionaryNode config, GeneratorSettings settings) { var wsOption = config.DictionaryNodeOptions as DictionaryNodeWritingSystemOptions; if (wsOption == null) throw new ArgumentException(@"Configuration nodes for MultiString fields should have WritingSystemOptions", "config"); - var bldr = new StringBuilder(); + var bldr = settings.ContentGenerator.CreateFragment(); foreach (var option in wsOption.Options) { if (option.IsEnabled) @@ -891,9 +937,10 @@ private static string GenerateContentForCaptionOrHeadword(ICmPicture picture, Co } } } - if (bldr.Length > 0) - return settings.ContentGenerator.WriteProcessedCollection(false, bldr.ToString(), GetClassNameAttributeForConfig(config)); - return String.Empty; + if (bldr.Length() > 0) + return settings.ContentGenerator.WriteProcessedCollection(false, bldr, GetClassNameAttributeForConfig(config)); + // bldr is a fragment that is empty of text, since length = 0 + return bldr; } internal static string CopyFileSafely(GeneratorSettings settings, string source, string relativeDestination) @@ -1302,11 +1349,11 @@ private static MemberInfo GetProperty(Type lookupType, ConfigurableDictionaryNod return propInfo; } - private static string GenerateContentForMoForm(IMoForm moForm, ConfigurableDictionaryNode config, GeneratorSettings settings) + private static IFragment GenerateContentForMoForm(IMoForm moForm, ConfigurableDictionaryNode config, GeneratorSettings settings) { // Don't export if there is no such data if (moForm == null) - return string.Empty; + return settings.ContentGenerator.CreateFragment(); if (config.ReferencedOrDirectChildren != null && config.ReferencedOrDirectChildren.Any()) { throw new NotImplementedException("Children for MoForm types not yet supported."); @@ -1317,12 +1364,12 @@ private static string GenerateContentForMoForm(IMoForm moForm, ConfigurableDicti /// /// This method will generate the XHTML that represents a collection and its contents /// - private static string GenerateContentForCollection(object collectionField, ConfigurableDictionaryNode config, + private static IFragment GenerateContentForCollection(object collectionField, ConfigurableDictionaryNode config, DictionaryPublicationDecorator pubDecorator, object collectionOwner, GeneratorSettings settings, SenseInfo info = new SenseInfo()) { // To be used for things like shared grammatical info - var sharedCollectionInfo = string.Empty; - var bldr = new StringBuilder(); + var sharedCollectionInfo = settings.ContentGenerator.CreateFragment(); + var bldr = settings.ContentGenerator.CreateFragment(); IEnumerable collection; if (collectionField is IEnumerable) { @@ -1396,14 +1443,14 @@ private static string GenerateContentForCollection(object collectionField, Confi } } - if (bldr.Length > 0 || sharedCollectionInfo.Length > 0) + if (bldr.Length() > 0 || sharedCollectionInfo.Length() > 0) { var className = settings.StylesGenerator.AddStyles(config).Trim('.'); ; return config.DictionaryNodeOptions is DictionaryNodeSenseOptions ? - settings.ContentGenerator.WriteProcessedSenses(false, bldr.ToString(), className, sharedCollectionInfo) : - settings.ContentGenerator.WriteProcessedCollection(false, bldr.ToString(), className); + settings.ContentGenerator.WriteProcessedSenses(false, bldr, className, sharedCollectionInfo) : + settings.ContentGenerator.WriteProcessedCollection(false, bldr, className); } - return string.Empty; + return settings.ContentGenerator.CreateFragment(); } private static bool IsLexReferenceCollection(ConfigurableDictionaryNode config) @@ -1464,10 +1511,10 @@ private static bool IsPrimaryEntryReference(ConfigurableDictionaryNode config, o return false; } - private static string GenerateContentForEntryRefCollection(ConfigurableDictionaryNode config, IEnumerable collection, ICmObject collectionOwner, + private static IFragment GenerateContentForEntryRefCollection(ConfigurableDictionaryNode config, IEnumerable collection, ICmObject collectionOwner, DictionaryPublicationDecorator pubDecorator, GeneratorSettings settings, ConfigurableDictionaryNode typeNode, bool isComplex) { - var bldr = new StringBuilder(); + var bldr = settings.ContentGenerator.CreateFragment(); var lerCollection = collection.Cast().ToList(); // ComplexFormsNotSubentries is a filtered version of VisibleComplexFormBackRefs, so it doesn't have it's own VirtualOrdering. @@ -1495,11 +1542,11 @@ private static string GenerateContentForEntryRefCollection(ConfigurableDictionar foreach (var item in lerCollection) bldr.Append(GenerateCollectionItemContent(config, pubDecorator, item, collectionOwner, settings)); } - return bldr.ToString(); + return bldr; } private static void GenerateContentForLexEntryRefsByType(ConfigurableDictionaryNode config, List lerCollection, object collectionOwner, DictionaryPublicationDecorator pubDecorator, - GeneratorSettings settings, StringBuilder bldr, ConfigurableDictionaryNode typeNode, bool isComplex) + GeneratorSettings settings, IFragment bldr, ConfigurableDictionaryNode typeNode, bool isComplex) { var lexEntryTypes = isComplex ? settings.Cache.LangProject.LexDbOA.ComplexEntryTypesOA.ReallyReallyAllPossibilities @@ -1543,7 +1590,7 @@ private static void GenerateContentForLexEntryRefsByType(ConfigurableDictionaryN } private static void GenerateContentForSubentries(ConfigurableDictionaryNode config, IEnumerable collection, ICmObject collectionOwner, - DictionaryPublicationDecorator pubDecorator, GeneratorSettings settings, StringBuilder bldr) + DictionaryPublicationDecorator pubDecorator, GeneratorSettings settings, IFragment bldr) { var listOptions = config.DictionaryNodeOptions as DictionaryNodeListOptions; var typeNode = config.ReferencedOrDirectChildren.FirstOrDefault(n => n.FieldDescription == LookupComplexEntryType); @@ -1622,8 +1669,8 @@ private static bool IsCollectionInNeedOfSorting(string fieldDescr) /// /// This method will generate the Content that represents a senses collection and its contents /// - private static string GenerateContentForSenses(ConfigurableDictionaryNode config, DictionaryPublicationDecorator publicationDecorator, - GeneratorSettings settings, IEnumerable senseCollection, SenseInfo info, ref string sharedGramInfo) + private static IFragment GenerateContentForSenses(ConfigurableDictionaryNode config, DictionaryPublicationDecorator publicationDecorator, + GeneratorSettings settings, IEnumerable senseCollection, SenseInfo info, ref IFragment sharedGramInfo) { // Check whether all the senses have been excluded from publication. See https://jira.sil.org/browse/LT-15697. var filteredSenseCollection = new List(); @@ -1635,7 +1682,7 @@ private static string GenerateContentForSenses(ConfigurableDictionaryNode config filteredSenseCollection.Add(item); } if (filteredSenseCollection.Count == 0) - return string.Empty; + return settings.ContentGenerator.CreateFragment(); var isSubsense = config.Parent != null && config.FieldDescription == config.Parent.FieldDescription; string lastGrammaticalInfo, langId; var isSameGrammaticalInfo = IsAllGramInfoTheSame(config, filteredSenseCollection, isSubsense, out lastGrammaticalInfo, out langId); @@ -1656,14 +1703,14 @@ private static string GenerateContentForSenses(ConfigurableDictionaryNode config // is determined, the answer for all sibling senses is the same as for the first sense in the collection. // So calculating outside the loop for performance. var isThisSenseNumbered = ShouldThisSenseBeNumbered(filteredSenseCollection[0], config, filteredSenseCollection); - var bldr = new StringBuilder(); + var bldr = settings.ContentGenerator.CreateFragment(); foreach (var item in filteredSenseCollection) { info.SenseCounter++; bldr.Append(GenerateSenseContent(config, publicationDecorator, item, isThisSenseNumbered, settings, isSameGrammaticalInfo, info)); } settings.StylesGenerator.AddStyles(config); - return bldr.ToString(); + return bldr; } /// @@ -1714,12 +1761,12 @@ child.DictionaryNodeOptions is DictionaryNodeSenseOptions && !string.IsNullOrEmpty(((DictionaryNodeSenseOptions)child.DictionaryNodeOptions).NumberingStyle)); } - private static string InsertGramInfoBeforeSenses(ILexSense item, ConfigurableDictionaryNode gramInfoNode, + private static IFragment InsertGramInfoBeforeSenses(ILexSense item, ConfigurableDictionaryNode gramInfoNode, DictionaryPublicationDecorator publicationDecorator, GeneratorSettings settings) { var content = GenerateContentForFieldByReflection(item, gramInfoNode, publicationDecorator, settings); - if (string.IsNullOrEmpty(content)) - return string.Empty; + if (content.IsNullOrEmpty()) + return settings.ContentGenerator.CreateFragment(); return settings.ContentGenerator.GenerateGramInfoBeforeSensesContent(content); } @@ -1805,11 +1852,11 @@ private static bool CheckIfAllGramInfoTheSame(ConfigurableDictionaryNode config, return true; } - private static string GenerateSenseContent(ConfigurableDictionaryNode config, DictionaryPublicationDecorator publicationDecorator, + private static IFragment GenerateSenseContent(ConfigurableDictionaryNode config, DictionaryPublicationDecorator publicationDecorator, object item, bool isThisSenseNumbered, GeneratorSettings settings, bool isSameGrammaticalInfo, SenseInfo info) { var senseNumberSpan = GenerateSenseNumberSpanIfNeeded(config, isThisSenseNumbered, ref info, settings); - var bldr = new StringBuilder(); + var bldr = settings.ContentGenerator.CreateFragment(); if (config.ReferencedOrDirectChildren != null) { foreach (var child in config.ReferencedOrDirectChildren) @@ -1820,23 +1867,22 @@ private static string GenerateSenseContent(ConfigurableDictionaryNode config, Di } } } - if (bldr.Length == 0) - return string.Empty; + if (bldr.Length() == 0) + return bldr; var senseContent = bldr.ToString(); - bldr.Clear(); return settings.ContentGenerator.AddSenseData(senseNumberSpan, IsBlockProperty(config), ((ICmObject)item).Owner.Guid, senseContent, GetCollectionItemClassAttribute(config)); } - private static string GeneratePictureContent(ConfigurableDictionaryNode config, DictionaryPublicationDecorator publicationDecorator, + private static IFragment GeneratePictureContent(ConfigurableDictionaryNode config, DictionaryPublicationDecorator publicationDecorator, object item, GeneratorSettings settings) { if (item is ICmPicture cmPic && !File.Exists(cmPic.PictureFileRA?.AbsoluteInternalPath)) { Logger.WriteEvent($"Skipping generating picture because there is no file at {cmPic.PictureFileRA?.AbsoluteInternalPath ?? "all"}"); - return string.Empty; + return settings.ContentGenerator.CreateFragment(); } - var bldr = new StringBuilder(); + var bldr = settings.ContentGenerator.CreateFragment(); var contentGenerator = settings.ContentGenerator; using (var writer = contentGenerator.CreateWriter(bldr)) { @@ -1855,7 +1901,8 @@ private static string GeneratePictureContent(ConfigurableDictionaryNode config, // Note: this SenseNumber comes from a field in the FDO model (not generated based on a DictionaryNodeSenseOptions). // Should we choose in the future to generate the Picture's sense number using ConfiguredLcmGenerator based on a SenseOption, // we will need to pass the SenseOptions to this point in the call tree. - var captionBldr = new StringBuilder(); + + var captionBldr = settings.ContentGenerator.CreateFragment(); foreach (var child in config.ReferencedOrDirectChildren) { if (child.FieldDescription != "PictureFileRA") @@ -1865,26 +1912,26 @@ private static string GeneratePictureContent(ConfigurableDictionaryNode config, } } - if (captionBldr.Length != 0) + if (captionBldr.Length() != 0) { contentGenerator.WriteProcessedContents(writer, settings.ContentGenerator.AddImageCaption(captionBldr.ToString())); } writer.Flush(); } - return bldr.ToString(); + return bldr; } - private static string GenerateCollectionItemContent(ConfigurableDictionaryNode config, DictionaryPublicationDecorator publicationDecorator, + private static IFragment GenerateCollectionItemContent(ConfigurableDictionaryNode config, DictionaryPublicationDecorator publicationDecorator, object item, object collectionOwner, GeneratorSettings settings, ConfigurableDictionaryNode factoredTypeField = null) { if (item is IMultiStringAccessor) return GenerateContentForStrings((IMultiStringAccessor)item, config, settings); if ((config.DictionaryNodeOptions is DictionaryNodeListOptions && !IsListItemSelectedForExport(config, item, collectionOwner)) || config.ReferencedOrDirectChildren == null) - return string.Empty; + return settings.ContentGenerator.CreateFragment(); - var bldr = new StringBuilder(); + var bldr = settings.ContentGenerator.CreateFragment(); var listOptions = config.DictionaryNodeOptions as DictionaryNodeListOptions; if (listOptions is DictionaryNodeListAndParaOptions) { @@ -1907,16 +1954,15 @@ private static string GenerateCollectionItemContent(ConfigurableDictionaryNode c bldr.Append(GenerateContentForFieldByReflection(item, child, publicationDecorator, settings)); } } - if (bldr.Length == 0) - return string.Empty; - var collectionContent = bldr.ToString(); - bldr.Clear(); + if (bldr.Length() == 0) + return bldr; + var collectionContent = bldr; return settings.ContentGenerator.AddCollectionItem(IsBlockProperty(config), GetCollectionItemClassAttribute(config), collectionContent); } private static void GenerateContentForLexRefCollection(ConfigurableDictionaryNode config, IEnumerable collection, ICmObject cmOwner, DictionaryPublicationDecorator pubDecorator, - GeneratorSettings settings, StringBuilder bldr) + GeneratorSettings settings, IFragment bldr) { // The collection of ILexReferences has already been sorted by type, // so we'll now group all the targets by LexRefType and sort their targets alphabetically before generating XHTML @@ -2001,23 +2047,23 @@ private static int CompareLexRefTargets(Tuple lhs, } /// Content for Targets and nodes, except Type, which is returned in ref string typeXHTML - private static string GenerateCrossReferenceChildren(ConfigurableDictionaryNode config, DictionaryPublicationDecorator publicationDecorator, + private static IFragment GenerateCrossReferenceChildren(ConfigurableDictionaryNode config, DictionaryPublicationDecorator publicationDecorator, List> referenceList, object collectionOwner, GeneratorSettings settings) { if (config.ReferencedOrDirectChildren == null) - return string.Empty; - var xBldr = new StringBuilder(); + return settings.ContentGenerator.CreateFragment(); + var xBldr = settings.ContentGenerator.CreateFragment(); using (var xw = settings.ContentGenerator.CreateWriter(xBldr)) { settings.ContentGenerator.BeginCrossReference(xw, IsBlockProperty(config), GetCollectionItemClassAttribute(config)); var targetInfo = referenceList.FirstOrDefault(); if (targetInfo == null) - return string.Empty; + return settings.ContentGenerator.CreateFragment(); var reference = targetInfo.Item2; if (LexRefTypeTags.IsUnidirectional((LexRefTypeTags.MappingTypes)reference.OwnerType.MappingType) && LexRefDirection(reference, collectionOwner) == ":r") { - return string.Empty; + return settings.ContentGenerator.CreateFragment(); } foreach (var child in config.ReferencedOrDirectChildren.Where(c => c.IsEnabled)) { @@ -2065,18 +2111,18 @@ private static string GenerateCrossReferenceChildren(ConfigurableDictionaryNode settings.ContentGenerator.EndCrossReference(xw); // config xw.Flush(); } - return xBldr.ToString(); + return xBldr; } - private static string GenerateSubentryTypeChild(ConfigurableDictionaryNode config, DictionaryPublicationDecorator publicationDecorator, + private static IFragment GenerateSubentryTypeChild(ConfigurableDictionaryNode config, DictionaryPublicationDecorator publicationDecorator, ILexEntry subEntry, object mainEntryOrSense, GeneratorSettings settings) { if (!config.IsEnabled) - return string.Empty; + return settings.ContentGenerator.CreateFragment(); var complexEntryRef = EntryRefForSubentry(subEntry, mainEntryOrSense); return complexEntryRef == null - ? string.Empty + ? settings.ContentGenerator.CreateFragment() : GenerateContentForCollection(complexEntryRef.ComplexEntryTypesRS, config, publicationDecorator, subEntry, settings); } @@ -2088,10 +2134,10 @@ private static ILexEntryRef EntryRefForSubentry(ILexEntry subEntry, object mainE return complexEntryRef; } - private static string GenerateSenseNumberSpanIfNeeded(ConfigurableDictionaryNode senseConfigNode, bool isThisSenseNumbered, ref SenseInfo info, GeneratorSettings settings) + private static IFragment GenerateSenseNumberSpanIfNeeded(ConfigurableDictionaryNode senseConfigNode, bool isThisSenseNumbered, ref SenseInfo info, GeneratorSettings settings) { if (!isThisSenseNumbered) - return string.Empty; + return settings.ContentGenerator.CreateFragment(); var senseOptions = senseConfigNode.DictionaryNodeOptions as DictionaryNodeSenseOptions; @@ -2099,7 +2145,7 @@ private static string GenerateSenseNumberSpanIfNeeded(ConfigurableDictionaryNode info.HomographConfig = settings.Cache.ServiceLocator.GetInstance(); var senseNumberWs = string.IsNullOrEmpty(info.HomographConfig.WritingSystem) ? "en" : info.HomographConfig.WritingSystem; if (string.IsNullOrEmpty(formattedSenseNumber)) - return string.Empty; + return settings.ContentGenerator.CreateFragment(); return settings.ContentGenerator.GenerateSenseNumber(formattedSenseNumber, senseNumberWs); } @@ -2163,24 +2209,24 @@ private static string GetRomanSenseCounter(string numberingStyle, int senseNumbe return roman; } - private static string GenerateContentForICmObject(ICmObject propertyValue, ConfigurableDictionaryNode config, GeneratorSettings settings) + private static IFragment GenerateContentForICmObject(ICmObject propertyValue, ConfigurableDictionaryNode config, GeneratorSettings settings) { // Don't export if there is no such data if (propertyValue == null || config.ReferencedOrDirectChildren == null || !config.ReferencedOrDirectChildren.Any(node => node.IsEnabled)) - return string.Empty; - var bldr = new StringBuilder(); + return settings.ContentGenerator.CreateFragment(); + var bldr = settings.ContentGenerator.CreateFragment(); foreach (var child in config.ReferencedOrDirectChildren) { var content = GenerateContentForFieldByReflection(propertyValue, child, null, settings); bldr.Append(content); } - if (bldr.Length > 0) + if (bldr.Length() > 0) { var className = settings.StylesGenerator.AddStyles(config).Trim('.'); ; - return settings.ContentGenerator.WriteProcessedObject(false, bldr.ToString(), className); + return settings.ContentGenerator.WriteProcessedObject(false, bldr, className); } - return string.Empty; + return bldr; } /// Write the class element in the span for an individual item in the collection @@ -2355,7 +2401,7 @@ private static bool IsCollectionEmpty(object collection) /// data to generate xhtml for /// /// - private static string GenerateContentForValue(object field, object propertyValue, ConfigurableDictionaryNode config, GeneratorSettings settings) + private static IFragment GenerateContentForValue(object field, object propertyValue, ConfigurableDictionaryNode config, GeneratorSettings settings) { // If we're working with a headword, either for this entry or another one (Variant or Complex Form, etc.), store that entry's GUID // so we can generate a link to the main or minor entry for this headword. @@ -2403,13 +2449,13 @@ private static string GenerateContentForValue(object field, object propertyValue if (!TsStringUtils.IsNullOrEmpty((ITsString)propertyValue)) { var content = GenerateContentForString((ITsString)propertyValue, config, settings, guid); - if (!string.IsNullOrEmpty(content)) + if (!content.IsNullOrEmpty()) { var className = settings.StylesGenerator.AddStyles(config).Trim('.'); ; return settings.ContentGenerator.WriteProcessedCollection(false, content, className); } } - return string.Empty; + return settings.ContentGenerator.CreateFragment(); } if (propertyValue is IMultiStringAccessor) { @@ -2445,22 +2491,23 @@ private static string GenerateContentForValue(object field, object propertyValue } else if (propertyValue is IStText) { - var bldr = new StringBuilder(); + var bldr = settings.ContentGenerator.CreateFragment(); foreach (var para in (propertyValue as IStText).ParagraphsOS) { var stp = para as IStTxtPara; if (stp == null) continue; var contentPara = GenerateContentForString(stp.Contents, config, settings, guid); - if (!string.IsNullOrEmpty(contentPara)) + if (!contentPara.IsNullOrEmpty()) { bldr.Append(contentPara); - bldr.AppendLine(); + bldr.AppendBreak(); } } - if (bldr.Length > 0) - return settings.ContentGenerator.WriteProcessedCollection(true, bldr.ToString(), GetClassNameAttributeForConfig(config)); - return string.Empty; + if (bldr.Length() > 0) + return settings.ContentGenerator.WriteProcessedCollection(true, bldr, GetClassNameAttributeForConfig(config)); + // bldr is empty of text + return bldr; } else { @@ -2472,11 +2519,11 @@ private static string GenerateContentForValue(object field, object propertyValue { Debug.WriteLine(String.Format("What do I do with {0}?", propertyValue.GetType().Name)); } - return String.Empty; + return settings.ContentGenerator.CreateFragment(); } } - private static string WriteElementContents(object propertyValue, + private static IFragment WriteElementContents(object propertyValue, ConfigurableDictionaryNode config, GeneratorSettings settings) { var content = propertyValue.ToString(); @@ -2485,10 +2532,10 @@ private static string WriteElementContents(object propertyValue, return settings.ContentGenerator.AddProperty(GetClassNameAttributeForConfig(config), IsBlockProperty(config), content); } - return String.Empty; + return settings.ContentGenerator.CreateFragment(); } - private static string GenerateContentForStrings(IMultiStringAccessor multiStringAccessor, ConfigurableDictionaryNode config, + private static IFragment GenerateContentForStrings(IMultiStringAccessor multiStringAccessor, ConfigurableDictionaryNode config, GeneratorSettings settings) { return GenerateContentForStrings(multiStringAccessor, config, settings, Guid.Empty); @@ -2498,7 +2545,7 @@ private static string GenerateContentForStrings(IMultiStringAccessor multiString /// This method will generate an XHTML span with a string for each selected writing system in the /// DictionaryWritingSystemOptions of the configuration that also has data in the given IMultiStringAccessor /// - private static string GenerateContentForStrings(IMultiStringAccessor multiStringAccessor, ConfigurableDictionaryNode config, + private static IFragment GenerateContentForStrings(IMultiStringAccessor multiStringAccessor, ConfigurableDictionaryNode config, GeneratorSettings settings, Guid guid) { var wsOptions = config.DictionaryNodeOptions as DictionaryNodeWritingSystemOptions; @@ -2509,8 +2556,8 @@ private static string GenerateContentForStrings(IMultiStringAccessor multiString // TODO pH 2014.12: this can generate an empty span if no checked WS's contain data // gjm 2015.12 but this will help some (LT-16846) if (multiStringAccessor == null || multiStringAccessor.StringCount == 0) - return String.Empty; - var bldr = new StringBuilder(); + return settings.ContentGenerator.CreateFragment(); + var bldr = settings.ContentGenerator.CreateFragment(); foreach (var option in wsOptions.Options) { if (!option.IsEnabled) @@ -2539,22 +2586,23 @@ private static string GenerateContentForStrings(IMultiStringAccessor multiString } var contentItem = GenerateWsPrefixAndString(config, settings, wsOptions, wsId, bestString, guid); - if (!String.IsNullOrEmpty(contentItem)) + if (!String.IsNullOrEmpty(contentItem.ToString())) bldr.Append(contentItem); } - if (bldr.Length > 0) + if (bldr.Length() > 0) { var className = settings.StylesGenerator.AddStyles(config).Trim('.'); ; - return settings.ContentGenerator.WriteProcessedCollection(false, bldr.ToString(), className); + return settings.ContentGenerator.WriteProcessedCollection(false, bldr, className); } - return string.Empty; + // bldr is empty of text + return bldr; } /// /// This method will generate an XHTML span with a string for each selected writing system in the /// DictionaryWritingSystemOptions of the configuration that also has data in the given IMultiAccessorBase /// - private static string GenerateContentForVirtualStrings(ICmObject owningObject, IMultiAccessorBase multiStringAccessor, + private static IFragment GenerateContentForVirtualStrings(ICmObject owningObject, IMultiAccessorBase multiStringAccessor, ConfigurableDictionaryNode config, GeneratorSettings settings, Guid guid) { var wsOptions = config.DictionaryNodeOptions as DictionaryNodeWritingSystemOptions; @@ -2562,7 +2610,8 @@ private static string GenerateContentForVirtualStrings(ICmObject owningObject, I { throw new ArgumentException(@"Configuration nodes for MultiString fields should have WritingSystemOptions", "config"); } - var bldr = new StringBuilder(); + + var bldr = settings.ContentGenerator.CreateFragment(); foreach (var option in wsOptions.Options) { if (!option.IsEnabled) @@ -2585,39 +2634,40 @@ private static string GenerateContentForVirtualStrings(ICmObject owningObject, I var requestedString = multiStringAccessor.get_String(wsId); bldr.Append(GenerateWsPrefixAndString(config, settings, wsOptions, wsId, requestedString, guid)); } - if (bldr.Length > 0) + if (bldr.Length() > 0) { var className = settings.StylesGenerator.AddStyles(config).Trim('.'); - return settings.ContentGenerator.WriteProcessedCollection(false, bldr.ToString(), className); + return settings.ContentGenerator.WriteProcessedCollection(false, bldr, className); } - return String.Empty; + // bldr is empty of text + return bldr; } - private static string GenerateWsPrefixAndString(ConfigurableDictionaryNode config, GeneratorSettings settings, + private static IFragment GenerateWsPrefixAndString(ConfigurableDictionaryNode config, GeneratorSettings settings, DictionaryNodeWritingSystemOptions wsOptions, int wsId, ITsString requestedString, Guid guid) { if (String.IsNullOrEmpty(requestedString.Text)) { - return String.Empty; + return settings.ContentGenerator.CreateFragment(); } var wsName = settings.Cache.WritingSystemFactory.get_EngineOrNull(wsId).Id; var content = GenerateContentForString(requestedString, config, settings, guid, wsName); - if (String.IsNullOrEmpty(content)) - return String.Empty; + if (String.IsNullOrEmpty(content.ToString())) + return settings.ContentGenerator.CreateFragment(); return settings.ContentGenerator.GenerateWsPrefixWithString(settings, wsOptions.DisplayWritingSystemAbbreviations, wsId, content); } - private static string GenerateContentForString(ITsString fieldValue, ConfigurableDictionaryNode config, + private static IFragment GenerateContentForString(ITsString fieldValue, ConfigurableDictionaryNode config, GeneratorSettings settings, string writingSystem = null) { return GenerateContentForString(fieldValue, config, settings, Guid.Empty, writingSystem); } - private static string GenerateContentForString(ITsString fieldValue, ConfigurableDictionaryNode config, + private static IFragment GenerateContentForString(ITsString fieldValue, ConfigurableDictionaryNode config, GeneratorSettings settings, Guid linkTarget, string writingSystem = null) { if (TsStringUtils.IsNullOrEmpty(fieldValue)) - return string.Empty; + return settings.ContentGenerator.CreateFragment(); if (writingSystem != null && writingSystem.Contains("audio")) { var fieldText = fieldValue.Text; @@ -2627,7 +2677,7 @@ private static string GenerateContentForString(ITsString fieldValue, Configurabl var srcAttr = GenerateSrcAttributeForMediaFromFilePath(fieldText, "AudioVisual", settings); var fileContent = GenerateContentForAudioFile(writingSystem, audioId, srcAttr, string.Empty, settings); var content = GenerateAudioWsContent(writingSystem, linkTarget, fileContent, settings); - if (!string.IsNullOrEmpty(content)) + if (!content.IsNullOrEmpty()) return settings.ContentGenerator.WriteProcessedObject(false, content, null); } } @@ -2640,7 +2690,7 @@ private static string GenerateContentForString(ITsString fieldValue, Configurabl { // use the passed in writing system unless null // otherwise use the first option from the DictionaryNodeWritingSystemOptions or english if the options are null - var bldr = new StringBuilder(); + var bldr = settings.ContentGenerator.CreateFragment(); try { using (var writer = settings.ContentGenerator.CreateWriter(bldr)) @@ -2686,7 +2736,7 @@ private static string GenerateContentForString(ITsString fieldValue, Configurabl } writer.Flush(); - return bldr.ToString(); + return bldr; } } catch (Exception e) @@ -2711,11 +2761,11 @@ private static string GenerateContentForString(ITsString fieldValue, Configurabl return settings.ContentGenerator.GenerateErrorContent(badStrBuilder); } } - return string.Empty; + return settings.ContentGenerator.CreateFragment(); } - private static string GenerateAudioWsContent(string wsId, - Guid linkTarget, string fileContent, GeneratorSettings settings) + private static IFragment GenerateAudioWsContent(string wsId, + Guid linkTarget, IFragment fileContent, GeneratorSettings settings) { return settings.ContentGenerator.AddAudioWsContent(wsId, linkTarget, fileContent); } @@ -2776,11 +2826,11 @@ private static void GenerateRunWithPossibleLink(GeneratorSettings settings, stri /// Source location path for audio file /// Inner text for hyperlink (unicode icon for audio) /// - private static string GenerateContentForAudioFile(string classname, + private static IFragment GenerateContentForAudioFile(string classname, string audioId, string srcAttribute, string audioIcon, GeneratorSettings settings) { if (string.IsNullOrEmpty(audioId) && string.IsNullOrEmpty(srcAttribute) && string.IsNullOrEmpty(audioIcon)) - return string.Empty; + return settings.ContentGenerator.CreateFragment(); var safeAudioId = GetSafeXHTMLId(audioId); return settings.ContentGenerator.GenerateAudioLinkContent(classname, srcAttribute, audioIcon, safeAudioId); } @@ -2801,7 +2851,7 @@ private static bool IsUSFM(string candidate) return USFMTableStart.IsMatch(candidate); } - private static string GenerateTablesFromUSFM(ITsString usfm, ConfigurableDictionaryNode config, GeneratorSettings settings, string writingSystem) + private static IFragment GenerateTablesFromUSFM(ITsString usfm, ConfigurableDictionaryNode config, GeneratorSettings settings, string writingSystem) { var delimiters = new Regex(@"\\d\s").Matches(usfm.Text); @@ -2811,7 +2861,7 @@ private static string GenerateTablesFromUSFM(ITsString usfm, ConfigurableDiction return GenerateTableFromUSFM(usfm, config, settings, writingSystem); } - var bldr = new StringBuilder(); + var bldr = settings.ContentGenerator.CreateFragment(); // If there is a table before the first title, generate it if (delimiters[0].Index > 0) { @@ -2824,12 +2874,12 @@ private static string GenerateTablesFromUSFM(ITsString usfm, ConfigurableDiction bldr.Append(GenerateTableFromUSFM(usfm.GetSubstring(delimiters[i].Index, lim), config, settings, writingSystem)); } - return bldr.ToString(); + return bldr; } - private static string GenerateTableFromUSFM(ITsString usfm, ConfigurableDictionaryNode config, GeneratorSettings settings, string writingSystem) + private static IFragment GenerateTableFromUSFM(ITsString usfm, ConfigurableDictionaryNode config, GeneratorSettings settings, string writingSystem) { - var bldr = new StringBuilder(); + var bldr = settings.ContentGenerator.CreateFragment(); using (var writer = settings.ContentGenerator.CreateWriter(bldr)) { // Regular expression to match the end of a string or a table row marker at the end of a title or row @@ -2867,7 +2917,7 @@ select match.Groups["rowcontents"] into rowContentsGroup settings.ContentGenerator.EndTable(writer); writer.Flush(); } - return bldr.ToString(); + return bldr; // TODO (Hasso) 2021.06: impl for JSON } @@ -3133,4 +3183,17 @@ public interface IFragmentWriter : IDisposable { void Flush(); } + + /// + /// A document fragment + /// + public interface IFragment + { + void Append(IFragment frag); + void AppendBreak(); + string ToString(); + int Length(); + bool IsNullOrEmpty(); + void Clear(); + } } diff --git a/Src/xWorks/DictionaryExportService.cs b/Src/xWorks/DictionaryExportService.cs index fa6c5981bd..d67d537df1 100644 --- a/Src/xWorks/DictionaryExportService.cs +++ b/Src/xWorks/DictionaryExportService.cs @@ -81,11 +81,12 @@ public void ExportDictionaryForWord(string filePath, DictionaryConfigurationMode { using (ClerkActivator.ActivateClerkMatchingExportType(DictionaryType, m_propertyTable, m_mediator)) { - configuration = configuration ?? new DictionaryConfigurationModel(DictionaryConfigurationListener.GetCurrentConfiguration(m_propertyTable, "Dictionary"), m_cache); - var publicationDecorator = ConfiguredLcmGenerator.GetPublicationDecoratorAndEntries(m_propertyTable, out var entriesToSave, DictionaryType); - if (progress != null) + configuration = configuration ?? new DictionaryConfigurationModel(DictionaryConfigurationListener.GetCurrentConfiguration(m_propertyTable, "Dictionary"), m_cache); + var publicationDecorator = ConfiguredLcmGenerator.GetPublicationDecoratorAndEntries(m_propertyTable, out var entriesToSave, DictionaryType); + if (progress != null) progress.Maximum = entriesToSave.Length; - // TODO: Create and add call to our Word content generator LcmWordGenerator(entriesToSave, publication, configuration, filePath, progress); + + LcmWordGenerator.SavePublishedDocx(entriesToSave, publicationDecorator, int.MaxValue, configuration, m_propertyTable, filePath, progress); } } @@ -101,7 +102,9 @@ public void ExportReversalForWord(string filePath, string reversalWs, Dictionary if (progress != null) progress.Maximum = entriesToSave.Length; - // TODO: Create and add call to our Word content generator LcmWordGenerator(entriesToSave, publication, configuration, filePath, progress); + string reversalFilePath = filePath.Split(new string[] { ".docx"}, StringSplitOptions.None)[0] + "-" + reversalWs + ".docx"; + + LcmWordGenerator.SavePublishedDocx(entriesToSave, publicationDecorator, int.MaxValue, configuration, m_propertyTable, reversalFilePath, progress); } } diff --git a/Src/xWorks/ExportDialog.cs b/Src/xWorks/ExportDialog.cs index 98898db732..7f63aedc80 100644 --- a/Src/xWorks/ExportDialog.cs +++ b/Src/xWorks/ExportDialog.cs @@ -891,7 +891,7 @@ private object ExportWordOpenXml(IThreadedProgress progress, object[] args) exportService.ExportDictionaryForWord(filePath, null, progress); foreach (var reversal in m_cache.ServiceLocator.GetInstance().AllInstances()) { - exportService.ExportReversalForWord(filePath, reversal.WritingSystem, new DictionaryConfigurationModel()); + exportService.ExportReversalForWord(filePath, reversal.WritingSystem); } return null; } diff --git a/Src/xWorks/ILcmContentGenerator.cs b/Src/xWorks/ILcmContentGenerator.cs index 3c2067076b..0a75535915 100644 --- a/Src/xWorks/ILcmContentGenerator.cs +++ b/Src/xWorks/ILcmContentGenerator.cs @@ -14,18 +14,19 @@ namespace SIL.FieldWorks.XWorks /// public interface ILcmContentGenerator { - string GenerateWsPrefixWithString(ConfiguredLcmGenerator.GeneratorSettings settings, bool displayAbbreviation, int wsId, string content); - string GenerateAudioLinkContent(string classname, string srcAttribute, string caption, string safeAudioId); - string WriteProcessedObject(bool isBlock, string elementContent, string className); - string WriteProcessedCollection(bool isBlock, string elementContent, string className); - string GenerateGramInfoBeforeSensesContent(string content); - string GenerateGroupingNode(object field, string className, ConfigurableDictionaryNode config, DictionaryPublicationDecorator publicationDecorator, ConfiguredLcmGenerator.GeneratorSettings settings, - Func childContentGenerator); - string AddSenseData(string senseNumberSpan, bool isBlockProperty, Guid ownerGuid, string senseContent, string className); - string AddCollectionItem(bool isBlock, string collectionItemClass, string content); - string AddProperty(string className, bool isBlockProperty, string content); - - IFragmentWriter CreateWriter(StringBuilder bldr); + IFragment GenerateWsPrefixWithString(ConfiguredLcmGenerator.GeneratorSettings settings, bool displayAbbreviation, int wsId, IFragment content); + IFragment GenerateAudioLinkContent(string classname, string srcAttribute, string caption, string safeAudioId); + IFragment WriteProcessedObject(bool isBlock, IFragment elementContent, string className); + IFragment WriteProcessedCollection(bool isBlock, IFragment elementContent, string className); + IFragment GenerateGramInfoBeforeSensesContent(IFragment content); + IFragment GenerateGroupingNode(object field, string className, ConfigurableDictionaryNode config, DictionaryPublicationDecorator publicationDecorator, ConfiguredLcmGenerator.GeneratorSettings settings, + Func childContentGenerator); + IFragment AddSenseData(IFragment senseNumberSpan, bool isBlockProperty, Guid ownerGuid, string senseContent, string className); + IFragment AddCollectionItem(bool isBlock, string collectionItemClass, IFragment content); + IFragment AddProperty(string className, bool isBlockProperty, string content); + IFragment CreateFragment(); + IFragment CreateFragment(string str); + IFragmentWriter CreateWriter(IFragment fragment); void StartMultiRunString(IFragmentWriter writer, string writingSystem); void EndMultiRunString(IFragmentWriter writer); void StartBiDiWrapper(IFragmentWriter writer, bool rightToLeft); @@ -39,30 +40,30 @@ string GenerateGroupingNode(object field, string className, ConfigurableDictiona void AddToRunContent(IFragmentWriter writer, string txtContent); void AddLineBreakInRunContent(IFragmentWriter writer); void StartTable(IFragmentWriter writer); - void AddTableTitle(IFragmentWriter writer, string content); + void AddTableTitle(IFragmentWriter writer, IFragment content); void StartTableBody(IFragmentWriter writer); void StartTableRow(IFragmentWriter writer); - void AddTableCell(IFragmentWriter writer, bool isHead, int colSpan, HorizontalAlign alignment, string content); + void AddTableCell(IFragmentWriter writer, bool isHead, int colSpan, HorizontalAlign alignment, IFragment content); void EndTableRow(IFragmentWriter writer); void EndTableBody(IFragmentWriter writer); void EndTable(IFragmentWriter writer); void StartEntry(IFragmentWriter writer, string className, Guid entryGuid, int index, RecordClerk clerk); - void AddEntryData(IFragmentWriter writer, List pieces); + void AddEntryData(IFragmentWriter writer, List pieces); void EndEntry(IFragmentWriter writer); void AddCollection(IFragmentWriter writer, bool isBlockProperty, string className, string content); void BeginObjectProperty(IFragmentWriter writer, bool isBlockProperty, string getCollectionItemClassAttribute); void EndObject(IFragmentWriter writer); - void WriteProcessedContents(IFragmentWriter writer, string contents); - string AddImage(string classAttribute, string srcAttribute, string pictureGuid); - string AddImageCaption(string captionContent); - string GenerateSenseNumber(string formattedSenseNumber, string senseNumberWs); - string AddLexReferences(bool generateLexType, string lexTypeContent, string className, string referencesContent, bool typeBefore); + void WriteProcessedContents(IFragmentWriter writer, IFragment contents); + IFragment AddImage(string classAttribute, string srcAttribute, string pictureGuid); + IFragment AddImageCaption(string captionContent); + IFragment GenerateSenseNumber(string formattedSenseNumber, string senseNumberWs); + IFragment AddLexReferences(bool generateLexType, IFragment lexTypeContent, string className, string referencesContent, bool typeBefore); void BeginCrossReference(IFragmentWriter writer, bool isBlockProperty, string className); void EndCrossReference(IFragmentWriter writer); - string WriteProcessedSenses(bool isBlock, string senseContent, string className, string sharedCollectionInfo); - string AddAudioWsContent(string wsId, Guid linkTarget, string fileContent); - string GenerateErrorContent(StringBuilder badStrBuilder); - string GenerateVideoLinkContent(string className, string mediaId, string srcAttribute, + IFragment WriteProcessedSenses(bool isBlock, IFragment senseContent, string className, IFragment sharedCollectionInfo); + IFragment AddAudioWsContent(string wsId, Guid linkTarget, IFragment fileContent); + IFragment GenerateErrorContent(StringBuilder badStrBuilder); + IFragment GenerateVideoLinkContent(string className, string mediaId, string srcAttribute, string caption); } } \ No newline at end of file diff --git a/Src/xWorks/LcmJsonGenerator.cs b/Src/xWorks/LcmJsonGenerator.cs index e99dfc3819..1b16b330b4 100644 --- a/Src/xWorks/LcmJsonGenerator.cs +++ b/Src/xWorks/LcmJsonGenerator.cs @@ -2,6 +2,13 @@ // This software is licensed under the LGPL, version 2.1 or later // (http://www.gnu.org/licenses/lgpl-2.1.html) +using Icu.Collation; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using SIL.FieldWorks.Common.Controls; +using SIL.FieldWorks.Common.FwUtils; +using SIL.LCModel; +using SIL.LCModel.Utils; using System; using System.Collections.Generic; using System.Diagnostics; @@ -10,13 +17,6 @@ using System.Text; using System.Threading; using System.Web.UI.WebControls; -using Icu.Collation; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using SIL.FieldWorks.Common.Controls; -using SIL.FieldWorks.Common.FwUtils; -using SIL.LCModel; -using SIL.LCModel.Utils; using XCore; namespace SIL.FieldWorks.XWorks @@ -38,13 +38,13 @@ public LcmJsonGenerator(LcmCache cache) Cache = cache; } - public string GenerateWsPrefixWithString(ConfiguredLcmGenerator.GeneratorSettings settings, - bool displayAbbreviation, int wsId, string content) + public IFragment GenerateWsPrefixWithString(ConfiguredLcmGenerator.GeneratorSettings settings, + bool displayAbbreviation, int wsId, IFragment content) { return content; } - public string GenerateAudioLinkContent(string classname, string srcAttribute, string caption, + public IFragment GenerateAudioLinkContent(string classname, string srcAttribute, string caption, string safeAudioId) { /*"audio": { @@ -55,10 +55,10 @@ public string GenerateAudioLinkContent(string classname, string srcAttribute, st dynamic audioObject = new JObject(); audioObject.id = safeAudioId; audioObject.src = srcAttribute.Replace("\\", "/"); // expecting relative paths only - return WriteProcessedObject(false, audioObject.ToString(), "value"); + return WriteProcessedObject(false, new StringFragment(audioObject.ToString()), "value"); } - public string GenerateVideoLinkContent(string className, string mediaId, + public IFragment GenerateVideoLinkContent(string className, string mediaId, string srcAttribute, string caption) { @@ -66,64 +66,83 @@ public string GenerateVideoLinkContent(string className, string mediaId, dynamic videoObject = new JObject(); videoObject.id = mediaId; videoObject.src = srcAttribute.Replace("\\", "/"); // expecting relative paths only - return WriteProcessedObject(false, videoObject.ToString(), "value"); + return WriteProcessedObject(false, new StringFragment(videoObject.ToString()), "value"); } - public string WriteProcessedObject(bool isBlock, string elementContent, string className) + public IFragment WriteProcessedObject(bool isBlock, IFragment elementContent, string className) { - if (elementContent.StartsWith("{")) + if (elementContent.ToString().StartsWith("{")) return WriteProcessedContents(elementContent, className, string.Empty, ","); - return WriteProcessedContents(elementContent.TrimEnd(','), className, "{", "},"); + + ((StringFragment)elementContent).TrimEnd(','); + return WriteProcessedContents(elementContent, className, "{", "},"); } - public string WriteProcessedCollection(bool isBlock, string elementContent, string className) + public IFragment WriteProcessedCollection(bool isBlock, IFragment elementContent, string className) { - return WriteProcessedContents(elementContent.TrimEnd(','), className, "[", "],"); + ((StringFragment)elementContent).TrimEnd(','); + return WriteProcessedContents(elementContent, className, "[", "],"); } - private string WriteProcessedContents(string elementContent, string className, string begin, string end) + private IFragment WriteProcessedContents(IFragment elementContent, string className, string begin, string end) { - if (string.IsNullOrEmpty(elementContent)) - return string.Empty; + if (elementContent.IsNullOrEmpty()) + return new StringFragment(); + var bldr = new StringBuilder(); + var fragment = new StringFragment(bldr); + if (!string.IsNullOrEmpty(className)) { bldr.Append($"\"{className}\": "); } bldr.Append(begin); - bldr.Append(elementContent.TrimEnd(',')); + bldr.Append(elementContent.ToString().TrimEnd(',')); bldr.Append(end); - return bldr.ToString(); + return fragment; } - public string GenerateGramInfoBeforeSensesContent(string content) + public IFragment GenerateGramInfoBeforeSensesContent(IFragment content) { // The grammatical info is generated as a json property on 'senses' - return $"{content}"; + return content; } - public string GenerateGroupingNode(object field, string className, ConfigurableDictionaryNode config, + public IFragment GenerateGroupingNode(object field, string className, ConfigurableDictionaryNode config, DictionaryPublicationDecorator publicationDecorator, ConfiguredLcmGenerator.GeneratorSettings settings, - Func childContentGenerator) + Func childContentGenerator) { //TODO: Decide how to handle grouping nodes in the json api - return string.Empty; + return new StringFragment(); + } + + public IFragment AddCollectionItem(bool isBlock, string className, IFragment content) + { + var fragment = new StringFragment(); + fragment.StrBuilder.Append(content.IsNullOrEmpty() ? string.Empty : $"{{{content}}},"); + return fragment; + } + + public IFragment AddProperty(string className, bool isBlockProperty, string content) + { + var fragment = new StringFragment($"\"{className}\": \"{content}\","); + return fragment; } - public string AddCollectionItem(bool isBlock, string className, string content) + public IFragment CreateFragment() { - return string.IsNullOrEmpty(content)? string.Empty : $"{{{content}}},"; + return new StringFragment(); } - public string AddProperty(string className, bool isBlockProperty, string content) + public IFragment CreateFragment(string str) { - return $"\"{className}\": \"{content}\","; + return new StringFragment(str); } - public IFragmentWriter CreateWriter(StringBuilder bldr) + public IFragmentWriter CreateWriter(IFragment bldr) { - return new JsonFragmentWriter(bldr); + return new JsonFragmentWriter(((StringFragment)bldr).StrBuilder); } public void StartMultiRunString(IFragmentWriter writer, string writingSystem) @@ -192,7 +211,7 @@ public void StartTable(IFragmentWriter writer) // TODO: decide on a useful json representation for tables } - public void AddTableTitle(IFragmentWriter writer, string content) + public void AddTableTitle(IFragmentWriter writer, IFragment content) { // TODO: decide on a useful json representation for tables } @@ -207,7 +226,7 @@ public void StartTableRow(IFragmentWriter writer) // TODO: decide on a useful json representation for tables } - public void AddTableCell(IFragmentWriter writer, bool isHead, int colSpan, HorizontalAlign alignment, string content) + public void AddTableCell(IFragmentWriter writer, bool isHead, int colSpan, HorizontalAlign alignment, IFragment content) { // TODO: decide on a useful json representation for tables } @@ -255,7 +274,7 @@ public void StartEntry(IFragmentWriter xw, string className, Guid entryGuid, int jsonWriter.InsertRawJson(","); } - public void AddEntryData(IFragmentWriter xw, List pieces) + public void AddEntryData(IFragmentWriter xw, List pieces) { pieces.ForEach(((JsonFragmentWriter)xw).InsertRawJson); } @@ -269,7 +288,7 @@ public void AddCollection(IFragmentWriter writer, bool isBlockProperty, string c { ((JsonFragmentWriter)writer).InsertPropertyName(className); BeginArray(writer); - WriteProcessedContents(writer, content); + WriteProcessedContents(writer, new StringFragment(content)); EndArray(writer); } @@ -295,18 +314,25 @@ public void EndObject(IFragmentWriter writer) ((JsonFragmentWriter)writer).EndObject(); } - public void WriteProcessedContents(IFragmentWriter writer, string contents) + public void WriteProcessedContents(IFragmentWriter writer, IFragment contents) { - if (!string.IsNullOrEmpty(contents)) + if (!contents.IsNullOrEmpty()) { // Try not to double up, but do try to end content with a ',' for building up objects - ((JsonFragmentWriter)writer).InsertRawJson(contents.TrimEnd(',') + ","); + string curStr = contents.ToString(); + StringBuilder bldr = ((StringFragment)contents).StrBuilder; + bldr.Clear(); + bldr.Append(curStr.TrimEnd(',') + ","); + ((JsonFragmentWriter)writer).InsertRawJson(contents); } } - public string AddImage(string classAttribute, string srcAttribute, string pictureGuid) + public IFragment AddImage(string classAttribute, string srcAttribute, string pictureGuid) { var bldr = new StringBuilder(); + var fragment = new StringFragment(); + fragment.StrBuilder = bldr; + var sw = new StringWriter(bldr); using (var xw = new JsonTextWriter(sw)) { @@ -315,24 +341,26 @@ public string AddImage(string classAttribute, string srcAttribute, string pictur xw.WritePropertyName("src"); xw.WriteValue(srcAttribute.Replace("\\", "/")); // expecting relative paths only xw.Flush(); - return bldr.ToString(); + return fragment; } } - public string AddImageCaption(string captionContent) + public IFragment AddImageCaption(string captionContent) { - return captionContent; + return new StringFragment(captionContent); } - public string GenerateSenseNumber(string formattedSenseNumber, string wsId) + public IFragment GenerateSenseNumber(string formattedSenseNumber, string wsId) { - return formattedSenseNumber; + return new StringFragment(formattedSenseNumber); } - public string AddLexReferences(bool generateLexType, string lexTypeContent, string className, + public IFragment AddLexReferences(bool generateLexType, IFragment lexTypeContent, string className, string referencesContent, bool typeBefore) { var bldr = new StringBuilder(); + var fragment = new StringFragment(bldr); + var sw = new StringWriter(bldr); using (var xw = new JsonTextWriter(sw)) { @@ -341,7 +369,7 @@ public string AddLexReferences(bool generateLexType, string lexTypeContent, stri if (generateLexType && typeBefore) { xw.WritePropertyName("referenceType"); - xw.WriteValue(lexTypeContent); + xw.WriteValue(lexTypeContent.ToString()); } // Write an array with the references. xw.WritePropertyName("references"); @@ -352,13 +380,13 @@ public string AddLexReferences(bool generateLexType, string lexTypeContent, stri if (generateLexType && !typeBefore) { xw.WritePropertyName("referenceType"); - xw.WriteValue(lexTypeContent); + xw.WriteValue(lexTypeContent.ToString()); } xw.WriteEndObject(); xw.WriteRaw(","); xw.Flush(); - return bldr.ToString(); + return fragment; } } @@ -378,35 +406,37 @@ public void EndCrossReference(IFragmentWriter writer) /// /// Generates data for all senses of an entry. For better processing of json add sharedGramInfo as a separate property object /// - public string WriteProcessedSenses(bool isBlock, string sensesContent, string classAttribute, string sharedGramInfo) + public IFragment WriteProcessedSenses(bool isBlock, IFragment sensesContent, string classAttribute, IFragment sharedGramInfo) { - return $"{sharedGramInfo}{WriteProcessedCollection(isBlock, sensesContent, classAttribute)}"; + return new StringFragment($"{sharedGramInfo.ToString()}{WriteProcessedCollection(isBlock, sensesContent, classAttribute)}"); } - public string AddAudioWsContent(string wsId, Guid linkTarget, string fileContent) + public IFragment AddAudioWsContent(string wsId, Guid linkTarget, IFragment fileContent) { - return $"{{\"guid\":\"g{linkTarget}\",\"lang\":\"{wsId}\",{fileContent}}}"; + return new StringFragment($"{{\"guid\":\"g{linkTarget}\",\"lang\":\"{wsId}\",{fileContent}}}"); } - public string GenerateErrorContent(StringBuilder badStrBuilder) + public IFragment GenerateErrorContent(StringBuilder badStrBuilder) { // We can't generate comments in json - But adding unicode tofu in front of the cleaned bad string should help // highlight the problem content without crashing the user or blocking the rest of the export - return $"\\u+0FFF\\u+0FFF\\u+0FFF{badStrBuilder}"; + return new StringFragment($"\\u+0FFF\\u+0FFF\\u+0FFF{badStrBuilder}"); } - public string AddSenseData(string senseNumberSpan, bool isBlock, Guid ownerGuid, + public IFragment AddSenseData(IFragment senseNumberSpan, bool isBlock, Guid ownerGuid, string senseContent, string className) { var bldr = new StringBuilder(); + var fragment = new StringFragment(bldr); + var sw = new StringWriter(bldr); using (var xw = new JsonTextWriter(sw)) { xw.WriteStartObject(); - if (!string.IsNullOrEmpty(senseNumberSpan)) + if (!senseNumberSpan.IsNullOrEmpty()) { xw.WritePropertyName("senseNumber"); - xw.WriteValue(senseNumberSpan); + xw.WriteValue(senseNumberSpan.ToString()); } xw.WritePropertyName("guid"); xw.WriteValue("g" + ownerGuid); @@ -414,7 +444,8 @@ public string AddSenseData(string senseNumberSpan, bool isBlock, Guid ownerGuid, xw.WriteEndObject(); xw.WriteRaw(","); xw.Flush(); - return bldr.ToString(); + + return fragment; } } @@ -503,6 +534,11 @@ public void InsertRawJson(string jsonContent) { jsonWriter.WriteRaw(jsonContent); } + + public void InsertRawJson(IFragment jsonContent) + { + jsonWriter.WriteRaw(jsonContent.ToString()); + } } /// diff --git a/Src/xWorks/LcmWordGenerator.cs b/Src/xWorks/LcmWordGenerator.cs new file mode 100644 index 0000000000..31b1ec3635 --- /dev/null +++ b/Src/xWorks/LcmWordGenerator.cs @@ -0,0 +1,759 @@ +// Copyright (c) 2014-$year$ SIL International +// This software is licensed under the LGPL, version 2.1 or later +// (http://www.gnu.org/licenses/lgpl-2.1.html) + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Web.UI.WebControls; +using DocumentFormat.OpenXml; +using Icu.Collation; +using SIL.FieldWorks.Common.Controls; +using SIL.FieldWorks.Common.FwUtils; +using SIL.LCModel; +using SIL.LCModel.Core.Text; +using SIL.LCModel.Utils; +using XCore; +using DocumentFormat.OpenXml.Packaging; +using DocumentFormat.OpenXml.Wordprocessing; + +namespace SIL.FieldWorks.XWorks +{ + // This alias is to be used when creating Wordprocessing Text objects, + // since there are multiple different Text types across the packages we are using. + using WP = DocumentFormat.OpenXml.Wordprocessing; + + public class LcmWordGenerator : ILcmContentGenerator, ILcmStylesGenerator + { + private LcmCache Cache { get; } + public LcmWordGenerator(LcmCache cache) + { + Cache = cache; + } + + public static void SavePublishedDocx(int[] entryHvos, DictionaryPublicationDecorator publicationDecorator, int batchSize, DictionaryConfigurationModel configuration, + XCore.PropertyTable propertyTable, string filePath, IThreadedProgress progress = null) + { + using (MemoryStream mem = new MemoryStream()) + { + DocFragment fragment = new DocFragment(mem); + + var entryCount = entryHvos.Length; + var cssPath = System.IO.Path.ChangeExtension(filePath, "css"); + var clerk = propertyTable.GetValue("ActiveClerk", null); + var cache = propertyTable.GetValue("cache", null); + + + var readOnlyPropertyTable = new ReadOnlyPropertyTable(propertyTable); + var settings = new ConfiguredLcmGenerator.GeneratorSettings(cache, readOnlyPropertyTable, true, true, System.IO.Path.GetDirectoryName(filePath), + ConfiguredLcmGenerator.IsEntryStyleRtl(readOnlyPropertyTable, configuration), System.IO.Path.GetFileName(cssPath) == "configured.css") + { ContentGenerator = new LcmWordGenerator(cache) }; + string lastHeader = null; + var entryContents = new Tuple[entryCount]; + var entryActions = new List(); + + // For every entry generate an action that will produce the doc fragment for that entry + for (var i = 0; i < entryCount; ++i) + { + var hvo = entryHvos.ElementAt(i); + var entry = cache.ServiceLocator.GetObject(hvo); + var entryStringBuilder = new DocFragment(); + entryContents[i] = new Tuple(entry, entryStringBuilder); + + var generateEntryAction = new Action(() => + { + var entryContent = ConfiguredLcmGenerator.GenerateContentForEntry(entry, configuration, publicationDecorator, settings); + entryStringBuilder.Append(entryContent); + if (progress != null) + progress.Position++; + }); + + entryActions.Add(generateEntryAction); + } + + // Generate all the document fragments (in parallel) + if (progress != null) + progress.Message = xWorksStrings.ksGeneratingDisplayFragments; + ConfiguredLcmGenerator.SpawnEntryGenerationThreadsAndWait(entryActions, progress); + + // Generate the letter headers and insert the document fragments into the full file + if (progress != null) + progress.Message = xWorksStrings.ksArrangingDisplayFragments; + var wsString = entryContents.Length > 0 ? ConfiguredLcmGenerator.GetWsForEntryType(entryContents[0].Item1, settings.Cache) : null; + var col = FwUtils.GetCollatorForWs(wsString); + + foreach (var entry in entryContents) + { + if (!entry.Item2.IsNullOrEmpty()) + { + IFragment letterHeader = GenerateLetterHeaderIfNeeded(entry.Item1, + ref lastHeader, col, settings, clerk); + + // If needed, append letter header to the word doc + if (!letterHeader.IsNullOrEmpty()) + fragment.Append(letterHeader); + + // TODO: when/how are styles applied to the letter headers? + // Append the entry to the word doc + fragment.Append(entry.Item2); + } + } + col?.Dispose(); + + if (progress != null) + progress.Message = xWorksStrings.ksGeneratingStyleInfo; + + // TODO: Generate styles + + fragment.DocFrag.Dispose(); + + // Create mode will overwrite any existing document at the given filePath; + // this is expected behavior that the user is warned about + // if they choose to export to an existing file. + using (FileStream fileStream = new FileStream(filePath, System.IO.FileMode.Create)) + { + mem.WriteTo(fileStream); + } + + } + } + + internal static IFragment GenerateLetterHeaderIfNeeded(ICmObject entry, ref string lastHeader, Collator headwordWsCollator, ConfiguredLcmGenerator.GeneratorSettings settings, RecordClerk clerk = null) + { + StringBuilder headerTextBuilder = ConfiguredLcmGenerator.GenerateLetterHeaderIfNeeded(entry, ref lastHeader, + headwordWsCollator, settings, clerk); + + return new DocFragment(headerTextBuilder.ToString()); + + } + + // ILcmStylesGenerator functions to implement + public void AddGlobalStyles(DictionaryConfigurationModel model, ReadOnlyPropertyTable propertyTable) + { + //TODO + return; + } + + public string AddStyles(ConfigurableDictionaryNode node) + { + // TODO + return "TODO: AddStyles"; + } + + public void Init(ReadOnlyPropertyTable propertyTable) + { + // TODO + return; + } + + // ILcmContentGenerator functions to implement + public IFragment GenerateWsPrefixWithString(ConfiguredLcmGenerator.GeneratorSettings settings, + bool displayAbbreviation, int wsId, IFragment content) + { + return content; + } + + public IFragment GenerateAudioLinkContent(string classname, string srcAttribute, string caption, string safeAudioId) + { + // TODO + return new DocFragment("TODO: generate audio link content"); + } + public IFragment WriteProcessedObject(bool isBlock, IFragment elementContent, string className) + { + return WriteProcessedContents(elementContent, className); + } + public IFragment WriteProcessedCollection(bool isBlock, IFragment elementContent, string className) + { + return WriteProcessedContents(elementContent, className); + } + + private IFragment WriteProcessedContents(IFragment elementContent, string className) + { + // TODO: + // Currently we don't use the class name here. + // We don't want to write the class name to the document, + // but we may use it to set styles. + // Do we need write it here, for it to be used when determining style? + + if (elementContent.IsNullOrEmpty()) + return new DocFragment(); + + return elementContent; + } + + public IFragment GenerateGramInfoBeforeSensesContent(IFragment content) + { + return content; + } + public IFragment GenerateGroupingNode(object field, string className, ConfigurableDictionaryNode config, DictionaryPublicationDecorator publicationDecorator, ConfiguredLcmGenerator.GeneratorSettings settings, + Func childContentGenerator) + { + //TODO: handle grouping nodes + return new DocFragment("TODO: handle grouping nodes"); + } + + public IFragment AddSenseData(IFragment senseNumberSpan, bool isBlockProperty, Guid ownerGuid, string senseContent, string className) + { + var senseCont = new DocFragment(senseContent); + // Add sense numbers if needed + if (!senseNumberSpan.IsNullOrEmpty()) + { + senseNumberSpan.Append(senseCont); + return senseNumberSpan; + } + + return senseCont; + } + public IFragment AddCollectionItem(bool isBlock, string collectionItemClass, IFragment content) + { + return content.IsNullOrEmpty() ? new DocFragment() : content; + } + public IFragment AddProperty(string className, bool isBlockProperty, string content) + { + return new DocFragment(content); + } + + public IFragment CreateFragment() + { + return new DocFragment(); + } + + public IFragment CreateFragment(string str) + { + return new DocFragment(str); + } + + public class DocFragment : IFragment + { + internal MemoryStream MemStr { get; } + internal WordprocessingDocument DocFrag { get; } + internal Body DocBody { get; } + + /// + /// Constructs a new memory stream and creates an empty doc fragment + /// that writes to that stream. + /// + public DocFragment() + { + MemStr = new MemoryStream(); + DocFrag = WordprocessingDocument.Open(MemStr, true); + + // Initialize the document and body. + MainDocumentPart mainDocPart = DocFrag.AddMainDocumentPart(); + mainDocPart.Document = new WP.Document(); + DocBody = mainDocPart.Document.AppendChild(new WP.Body()); + } + + /// + /// Initializes the memory stream from the argument and creates + /// an empty doc fragment that writes to that stream. + /// + public DocFragment(MemoryStream str) + { + MemStr = str; + DocFrag = WordprocessingDocument.Open(str, true); + + // Initialize the document and body. + MainDocumentPart mainDocPart = DocFrag.AddMainDocumentPart(); + mainDocPart.Document = new WP.Document(); + DocBody = mainDocPart.Document.AppendChild(new WP.Body()); + } + + /// + /// Constructs a new memory stream and creates a non-empty doc fragment, + /// containing the given string, that writes to that stream. + /// + public DocFragment(string str) : this() + { + // Add text to the fragment + Paragraph para = DocBody.AppendChild(new Paragraph()); + Run run = para.AppendChild(new Run()); + + if (!string.IsNullOrEmpty(str)) + { + // For spaces to show correctly, set preserve spaces on the text element + WP.Text txt = new WP.Text(str); + txt.Space = SpaceProcessingModeValues.Preserve; + run.AppendChild(txt); + } + else + { + // For spaces to show correctly, set preserve spaces on the text element + WP.Text txt = new WP.Text(String.Empty); + txt.Space = SpaceProcessingModeValues.Preserve; + run.AppendChild(txt); + } + } + + /// + /// Returns content of the doc fragment as a string. + /// Be careful using this as document styles won't be preserved in a string. + /// This function is primarily used inside the Length() function + /// to check the length of text in a doc fragment. + /// + public override string ToString() + { + if (IsNullOrEmpty()) + { + return string.Empty; + } + + return ToString(DocBody); + } + + private string ToString(OpenXmlElement textBody) + { + var FragStr = new StringBuilder(); + foreach (var docSection in textBody.Elements()) + { + switch (docSection.LocalName) + { + // Text + case "t": + FragStr.Append(docSection.InnerText); + break; + + // Carriage return/page break + case "cr": + case "br": + FragStr.AppendLine(); + break; + + // Tab + case "tab": + FragStr.Append("\t"); + break; + + // Paragraph + case "p": + FragStr.Append(ToString(docSection)); + FragStr.AppendLine(); + break; + + default: + FragStr.Append(ToString(docSection)); + break; + } + } + + return FragStr.ToString(); + } + + public int Length() + { + string str = ToString(); + return str.Length; + } + + /// + /// Appends one doc fragment to another. + /// Use this if styles have already been applied + /// and if not attempting to append within the same paragraph. + /// + public void Append(IFragment frag) + { + + foreach (Paragraph para in ((DocFragment)frag).DocBody.OfType().ToList()) + { + // Append each paragraph. It is necessary to deep clone the node to maintain its tree of document properties + // and to ensure its styles will be maintained in the copy. + this.DocBody.AppendChild(para.CloneNode(true)); + } + } + + /// + /// Appends a new run inside the last paragraph of the doc fragment. + /// The run will be added to the end of the paragraph. + /// + public void Append(Run run) + { + // Deep clone the run b/c of its tree of properties and to maintain styles. + Paragraph lastPar = GetLastParagraph(); + lastPar.AppendChild(run.CloneNode(true)); + } + + public void AppendBreak() + { + // Breaks are automatically added between different paragraphs. + // A null op here is sufficient, unless we want line breaks within a paragraph or run. + } + + public void AppendSpace() + { + Run lastRun = GetLastRun(); + WP.Text txt = new WP.Text(" "); + // For spaces to show correctly, set preserve spaces on the text element + txt.Space = SpaceProcessingModeValues.Preserve; + lastRun.AppendChild(txt); + } + + public bool IsNullOrEmpty() + { + // A docbody with no children is an empty document. + if (MemStr == null || DocFrag == null || DocBody == null || !DocBody.HasChildren) + { + return true; + } + return false; + } + + public void Clear() + { + // Clear() method is not used for the word generator. + throw new NotImplementedException(); + } + + /// + /// Returns last paragraph in the document if it contains any, + /// else creates and returns a new paragraph. + /// + public Paragraph GetLastParagraph() + { + List parList = DocBody.OfType().ToList(); + if (parList.Any()) + return parList.Last(); + return GetNewParagraph(); + } + + /// + /// Creates and returns a new paragraph. + /// + public Paragraph GetNewParagraph() + { + Paragraph newPar = DocBody.AppendChild(new Paragraph()); + return newPar; + } + + /// + /// Returns last run in the document if it contains any, + /// else creates and returns a new run. + /// + private Run GetLastRun() + { + Paragraph lastPara = GetLastParagraph(); + List runList = lastPara.OfType().ToList(); + if (runList.Any()) + return runList.Last(); + + return lastPara.AppendChild(new Run()); + } + } + + public IFragmentWriter CreateWriter(IFragment frag) + { + return new WordFragmentWriter((DocFragment)frag); + } + + public class WordFragmentWriter : IFragmentWriter + { + public DocFragment WordFragment { get; } + private bool isDisposed; + internal Dictionary collatorCache = new Dictionary(); + + public WordFragmentWriter(DocFragment frag) + { + WordFragment = frag; + } + + public void Dispose() + { + foreach (var cachEntry in collatorCache.Values) + { + cachEntry?.Dispose(); + } + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + Debug.WriteLineIf(!disposing, "****** Missing Dispose() call for " + GetType().Name + ". ****** "); + if (!isDisposed) + { + WordFragment.DocFrag.Dispose(); + WordFragment.MemStr.Dispose(); + isDisposed = true; + } + } + + public void Flush() + { + WordFragment.MemStr.Flush(); + } + + public void Insert(IFragment frag) + { + WordFragment.Append(frag); + } + + public void Insert(Run run) + { + WordFragment.Append(run); + } + + /// + /// Gets and returns the last run in the document, if one exists. + /// Otherwise, creates and returns a new run. + /// + public Run GetCurrentRun() + { + List runList = WordFragment.DocBody.Descendants().ToList(); + if (runList.Any()) + return runList.Last(); + + // If there is no run, create one + Run lastRun = WordFragment.DocBody.AppendChild(new Run()); + return lastRun; + } + + /// + /// Get the last paragraph in the doc if it contains any, + /// and add a new run to it. + /// Else, create and add the run to a new paragraph. + /// + public void CreateRun() + { + Paragraph curPar = WordFragment.GetLastParagraph(); + curPar.AppendChild(new Run()); + } + + /*public void AddStyleToRun() + { + // Grab the latest run and add a style + Run lastRun = GetCurrentRun(); + + // TODO: add style + + }*/ + } + + public void StartMultiRunString(IFragmentWriter writer, string writingSystem) + { + return; + } + public void EndMultiRunString(IFragmentWriter writer) + { + return; + } + public void StartBiDiWrapper(IFragmentWriter writer, bool rightToLeft) + { + return; + } + public void EndBiDiWrapper(IFragmentWriter writer) + { + return; + } + + /// + /// Creates a new run that is appended to the doc's last paragraph, + /// if one exists, or to a new paragraph otherwise. + /// + /// + /// + public void StartRun(IFragmentWriter writer, string writingSystem) + { + ((WordFragmentWriter)writer).CreateRun(); + } + public void EndRun(IFragmentWriter writer) + { + // Ending the run should be a null op for word writer + // Beginning a new run is sufficient to end the old run + // and to ensure new styles/content are applied to the new run. + } + public void SetRunStyle(IFragmentWriter writer, string css) + { + // Grab the current run and set its style + Run currentRun = ((WordFragmentWriter)writer).GetCurrentRun(); + + // TODO: get the style indicated by the string css class + // For now, use bold as a default style in order to test setting styles + + // If run already has properties, append the new style to run properties + if (currentRun.RunProperties != null) + currentRun.RunProperties.Append(new WP.Bold()); + + // Otherwise create run properties and append the style + else + { + currentRun.RunProperties = new WP.RunProperties(); + currentRun.RunProperties.Append(new WP.Bold()); + } + } + public void StartLink(IFragmentWriter writer, Guid destination) + { + return; + } + public void StartLink(IFragmentWriter writer, string externalDestination) + { + return; + } + public void EndLink(IFragmentWriter writer) + { + return; + } + + /// + /// Adds text to the last run in the doc, if one exists. + /// Creates a new run from the text otherwise. + /// + public void AddToRunContent(IFragmentWriter writer, string txtContent) + { + // For spaces to show correctly, set preserve spaces on the new text element + WP.Text txt = new WP.Text(txtContent); + txt.Space = SpaceProcessingModeValues.Preserve; + ((WordFragmentWriter)writer).GetCurrentRun() + .AppendChild(txt); + } + public void AddLineBreakInRunContent(IFragmentWriter writer) + { + ((WordFragmentWriter)writer).GetCurrentRun() + .AppendChild(new WP.Break()); + } + public void StartTable(IFragmentWriter writer) + { + return; + } + public void AddTableTitle(IFragmentWriter writer, IFragment content) + { + return; + } + public void StartTableBody(IFragmentWriter writer) + { + return; + } + public void StartTableRow(IFragmentWriter writer) + { + return; + } + public void AddTableCell(IFragmentWriter writer, bool isHead, int colSpan, HorizontalAlign alignment, IFragment content) + { + return; + } + public void EndTableRow(IFragmentWriter writer) + { + return; + } + public void EndTableBody(IFragmentWriter writer) + { + return; + } + public void EndTable(IFragmentWriter writer) + { + return; + } + + public void StartEntry(IFragmentWriter writer, string className, Guid entryGuid, int index, RecordClerk clerk) + { + // Each entry starts a new paragraph, and any entry data added will be added within the same paragraph. + // Create a new paragraph for the entry. + DocFragment wordDoc = ((WordFragmentWriter)writer).WordFragment; + Paragraph entryPar = wordDoc.GetNewParagraph(); + + // TODO: paragraph-level styles can be set here. + } + public void AddEntryData(IFragmentWriter writer, List pieces) + { + // TODO: In theory the pieces in the list here are already styled--where are run-level styles first set? + foreach (IFragment piece in pieces) + { + WordFragmentWriter wordWriter = ((WordFragmentWriter)writer); + + // Each piece contains one run. These runs should reside in the same paragraph. + // So we append each run instead of the IFragments directly. + // Character formatting & style of each run will be preserved. + List runs = ((DocFragment)piece).DocBody.Descendants().ToList(); + foreach (Run run in runs) + { + // For spaces to show correctly, set preserve spaces on the text element + WP.Text txt = new WP.Text(" "); + txt.Space = SpaceProcessingModeValues.Preserve; + run.AppendChild(txt); + wordWriter.Insert(run); + } + } + } + public void EndEntry(IFragmentWriter writer) + { + return; + } + public void AddCollection(IFragmentWriter writer, bool isBlockProperty, string className, string content) + { + return; + } + public void BeginObjectProperty(IFragmentWriter writer, bool isBlockProperty, string getCollectionItemClassAttribute) + { + return; + } + public void EndObject(IFragmentWriter writer) + { + return; + } + public void WriteProcessedContents(IFragmentWriter writer, IFragment contents) + { + if (contents.IsNullOrEmpty()) + { + ((WordFragmentWriter)writer).Insert(contents); + } + } + public IFragment AddImage(string classAttribute, string srcAttribute, string pictureGuid) + { + return new DocFragment("TODO: add image"); + } + public IFragment AddImageCaption(string captionContent) + { + return new DocFragment("TODO: add image caption"); + } + public IFragment GenerateSenseNumber(string formattedSenseNumber, string senseNumberWs) + { + // TODO: for styles, do we need to do something with the writing system? + return new DocFragment(formattedSenseNumber); + } + public IFragment AddLexReferences(bool generateLexType,IFragment lexTypeContent, string className, string referencesContent, bool typeBefore) + { + var fragment = new DocFragment(); + // Generate the factored ref types element (if before). + if (generateLexType && typeBefore) + { + fragment.Append(WriteProcessedObject(false, lexTypeContent, className)); + } + // Then add all the contents for the LexReferences (e.g. headwords) + fragment.Append(new DocFragment(referencesContent)); + // Generate the factored ref types element (if after). + if (generateLexType && !typeBefore) + { + fragment.Append(WriteProcessedObject(false, lexTypeContent, className)); + } + + return fragment; + } + public void BeginCrossReference(IFragmentWriter writer, bool isBlockProperty, string className) + { + return; + } + public void EndCrossReference(IFragmentWriter writer) + { + return; + } + public IFragment WriteProcessedSenses(bool isBlock, IFragment senseContent, string className, IFragment sharedGramInfo) + { + sharedGramInfo.Append(senseContent); + return sharedGramInfo; + } + public IFragment AddAudioWsContent(string wsId, Guid linkTarget, IFragment fileContent) + { + return new DocFragment("TODO: add audiows content"); + } + public IFragment GenerateErrorContent(StringBuilder badStrBuilder) + { + return new DocFragment($"Error generating content for string: '{badStrBuilder}'"); + } + public IFragment GenerateVideoLinkContent(string className, string mediaId, string srcAttribute, + string caption) + { + return new DocFragment("TODO: generate video link content"); + } + } +} diff --git a/Src/xWorks/LcmXhtmlGenerator.cs b/Src/xWorks/LcmXhtmlGenerator.cs index 201ac843fc..6da6f8ded9 100644 --- a/Src/xWorks/LcmXhtmlGenerator.cs +++ b/Src/xWorks/LcmXhtmlGenerator.cs @@ -2,14 +2,6 @@ // This software is licensed under the LGPL, version 2.1 or later // (http://www.gnu.org/licenses/lgpl-2.1.html) -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Text; -using System.Web.UI.WebControls; -using System.Xml; using Icu.Collation; using SIL.FieldWorks.Common.Controls; using SIL.FieldWorks.Common.FwUtils; @@ -18,6 +10,14 @@ using SIL.LCModel.Core.WritingSystems; using SIL.LCModel.DomainServices; using SIL.LCModel.Utils; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Text; +using System.Web.UI.WebControls; +using System.Xml; using XCore; namespace SIL.FieldWorks.XWorks @@ -162,39 +162,27 @@ private static bool IsExport(ConfiguredLcmGenerator.GeneratorSettings settings) internal static void GenerateLetterHeaderIfNeeded(ICmObject entry, ref string lastHeader, XmlWriter xhtmlWriter, Collator headwordWsCollator, ConfiguredLcmGenerator.GeneratorSettings settings, RecordClerk clerk = null) { - // If performance is an issue these dummy's can be stored between calls - var dummyOne = new Dictionary>(); - var dummyTwo = new Dictionary>(); - var dummyThree = new Dictionary>(); + StringBuilder headerTextBuilder = ConfiguredLcmGenerator.GenerateLetterHeaderIfNeeded(entry, ref lastHeader, + headwordWsCollator, settings, clerk); + var cache = settings.Cache; - var wsString = ConfiguredLcmGenerator.GetWsForEntryType(entry, settings.Cache); - var firstLetter = ConfiguredExport.GetLeadChar(ConfiguredLcmGenerator.GetSortWordForLetterHead(entry, clerk), wsString, dummyOne, dummyTwo, dummyThree, - headwordWsCollator, cache); - if (firstLetter != lastHeader && !string.IsNullOrEmpty(firstLetter)) + var wsString = ConfiguredLcmGenerator.GetWsForEntryType(entry, cache); + + if (headerTextBuilder.Length > 0) { - var headerTextBuilder = new StringBuilder(); - var upperCase = new CaseFunctions(cache.ServiceLocator.WritingSystemManager.Get(wsString)).ToTitle(firstLetter); - var lowerCase = firstLetter.Normalize(); - headerTextBuilder.Append(upperCase); - if (lowerCase != upperCase) - { - headerTextBuilder.Append(' '); - headerTextBuilder.Append(lowerCase); - } xhtmlWriter.WriteStartElement("div"); xhtmlWriter.WriteAttributeString("class", "letHead"); xhtmlWriter.WriteStartElement("span"); xhtmlWriter.WriteAttributeString("class", "letter"); xhtmlWriter.WriteAttributeString("lang", wsString); - var wsRightToLeft = cache.WritingSystemFactory.get_Engine(wsString).RightToLeftScript; + var wsRightToLeft = + cache.WritingSystemFactory.get_Engine(wsString).RightToLeftScript; if (wsRightToLeft != settings.RightToLeft) xhtmlWriter.WriteAttributeString("dir", wsRightToLeft ? "rtl" : "ltr"); xhtmlWriter.WriteString(TsStringUtils.Compose(headerTextBuilder.ToString())); xhtmlWriter.WriteEndElement(); xhtmlWriter.WriteEndElement(); xhtmlWriter.WriteWhitespace(Environment.NewLine); - - lastHeader = firstLetter; } } @@ -229,7 +217,7 @@ public static string GenerateEntryHtmlWithStyles(ICmObject entry, DictionaryConf exportSettings.StylesGenerator.AddGlobalStyles(configuration, new ReadOnlyPropertyTable(propertyTable)); GenerateOpeningHtml(previewCssPath, custCssPath, exportSettings, writer); var content = ConfiguredLcmGenerator.GenerateContentForEntry(entry, configuration, pubDecorator, exportSettings); - writer.WriteRaw(content); + writer.WriteRaw(content.ToString()); GenerateClosingHtml(writer); writer.Flush(); cssWriter.Write(((CssGenerator)exportSettings.StylesGenerator).GetStylesString()); @@ -349,13 +337,13 @@ private static void GenerateBottomOfPageButtonsIfNeeded(ConfiguredLcmGenerator.G GeneratePageButtons(settings, entryHvos, pageRanges, currentPageBounds, xhtmlWriter); } - public static List GenerateNextFewEntries(DictionaryPublicationDecorator publicationDecorator, int[] entryHvos, + public static List GenerateNextFewEntries(DictionaryPublicationDecorator publicationDecorator, int[] entryHvos, string currentConfigPath, ConfiguredLcmGenerator.GeneratorSettings settings, Tuple oldCurrentPageRange, Tuple oldAdjacentPageRange, int entriesToAddCount, out Tuple currentPage, out Tuple adjacentPage) { GenerateAdjustedPageButtons(entryHvos, settings, oldCurrentPageRange, oldAdjacentPageRange, entriesToAddCount, out currentPage, out adjacentPage); - var entries = new List(); + var entries = new List(); DictionaryConfigurationModel currentConfig = new DictionaryConfigurationModel(currentConfigPath, settings.Cache); if (oldCurrentPageRange.Item1 > oldAdjacentPageRange.Item1) { @@ -550,9 +538,10 @@ private static List> GetPageRanges(int[] entryHvos, int entriesP return pageRanges; } - public string GenerateWsPrefixWithString(ConfiguredLcmGenerator.GeneratorSettings settings, bool displayAbbreviation, int wsId, string content) + public IFragment GenerateWsPrefixWithString(ConfiguredLcmGenerator.GeneratorSettings settings, bool displayAbbreviation, int wsId, IFragment content) { var bldr = new StringBuilder(); + var fragment = new StringFragment(bldr); using (var xw = XmlWriter.Create(bldr, new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment })) { if (displayAbbreviation) @@ -563,15 +552,16 @@ public string GenerateWsPrefixWithString(ConfiguredLcmGenerator.GeneratorSetting xw.WriteString(prefix); xw.WriteEndElement(); } - xw.WriteRaw(content); + xw.WriteRaw(content.ToString()); xw.Flush(); - return bldr.ToString(); + return fragment; } } - public string GenerateAudioLinkContent(string classname, string srcAttribute, string caption, string safeAudioId) + public IFragment GenerateAudioLinkContent(string classname, string srcAttribute, string caption, string safeAudioId) { var bldr = new StringBuilder(); + var fragment = new StringFragment(bldr); using (var xw = XmlWriter.Create(bldr, new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment })) { xw.WriteStartElement("audio"); @@ -591,58 +581,62 @@ public string GenerateAudioLinkContent(string classname, string srcAttribute, st xw.WriteRaw(""); xw.WriteFullEndElement(); xw.Flush(); - return bldr.ToString(); + return fragment; } } - public string WriteProcessedObject(bool isBlock, string elementContent, string className) + public IFragment WriteProcessedObject(bool isBlock, IFragment elementContent, string className) { return WriteProcessedContents(isBlock, elementContent, className); } - public string WriteProcessedCollection(bool isBlock, string elementContent, string className) + public IFragment WriteProcessedCollection(bool isBlock, IFragment elementContent, string className) { return WriteProcessedContents(isBlock, elementContent, className); } - private string WriteProcessedContents(bool asBlock, string xmlContent, string className) + private IFragment WriteProcessedContents(bool asBlock, IFragment xmlContent, string className) { - if (!String.IsNullOrEmpty(xmlContent)) + if (!xmlContent.IsNullOrEmpty()) { var bldr = new StringBuilder(); + var fragment = new StringFragment(bldr); using (var xw = XmlWriter.Create(bldr, new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment })) { xw.WriteStartElement(asBlock ? "div" : "span"); if (!String.IsNullOrEmpty(className)) xw.WriteAttributeString("class", className); - xw.WriteRaw(xmlContent); + xw.WriteRaw(xmlContent.ToString()); xw.WriteEndElement(); xw.Flush(); - return bldr.ToString(); + return fragment; } } - return String.Empty; + return new StringFragment(); } - public string GenerateGramInfoBeforeSensesContent(string content) + public IFragment GenerateGramInfoBeforeSensesContent(IFragment content) { var bldr = new StringBuilder(); + var fragment = new StringFragment(bldr); using (var xw = XmlWriter.Create(bldr, new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment })) { xw.WriteStartElement("span"); xw.WriteAttributeString("class", "sharedgrammaticalinfo"); - xw.WriteRaw(content); + xw.WriteRaw(content.ToString()); xw.WriteEndElement(); xw.Flush(); - return bldr.ToString(); + return fragment; } } - public string GenerateGroupingNode(object field, string className, ConfigurableDictionaryNode config, + public IFragment GenerateGroupingNode(object field, string className, ConfigurableDictionaryNode config, DictionaryPublicationDecorator publicationDecorator, ConfiguredLcmGenerator.GeneratorSettings settings, - Func childContentGenerator) + Func childContentGenerator) { var bldr = new StringBuilder(); + var fragment = new StringFragment(bldr); + using (var xw = XmlWriter.Create(bldr, new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment })) { xw.WriteStartElement("span"); @@ -656,17 +650,28 @@ public string GenerateGroupingNode(object field, string className, ConfigurableD } var innerContents = innerBuilder.ToString(); if (String.IsNullOrEmpty(innerContents)) - return String.Empty; + new StringFragment(); xw.WriteRaw(innerContents); xw.WriteEndElement(); // xw.Flush(); } - return bldr.ToString(); + return fragment; } - public IFragmentWriter CreateWriter(StringBuilder bldr) + public IFragment CreateFragment() { - return new XmlFragmentWriter(XmlWriter.Create(bldr, new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment })); + return new StringFragment(); + } + + public IFragment CreateFragment(string str) + { + return new StringFragment(str); + } + + public IFragmentWriter CreateWriter(IFragment bldr) + { + var strbldr = (StringFragment)bldr; + return new XmlFragmentWriter(XmlWriter.Create(strbldr.StrBuilder, new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment })); } public class XmlFragmentWriter : IFragmentWriter @@ -769,11 +774,11 @@ public void StartTable(IFragmentWriter writer) ((XmlFragmentWriter)writer).Writer.WriteStartElement("table"); } - public void AddTableTitle(IFragmentWriter writer, string content) + public void AddTableTitle(IFragmentWriter writer, IFragment content) { var xw = ((XmlFragmentWriter)writer).Writer; xw.WriteStartElement("caption"); - xw.WriteRaw(content); + xw.WriteRaw(content.ToString()); xw.WriteEndElement(); // } @@ -791,7 +796,7 @@ public void StartTableRow(IFragmentWriter writer) /// Adds a <td> element (or <th> if isHead is true). /// If isRightAligned is true, adds the appropriate style element. /// - public void AddTableCell(IFragmentWriter writer, bool isHead, int colSpan, HorizontalAlign alignment, string content) + public void AddTableCell(IFragmentWriter writer, bool isHead, int colSpan, HorizontalAlign alignment, IFragment content) { var xw = ((XmlFragmentWriter)writer).Writer; xw.WriteStartElement(isHead ? "th" : "td"); @@ -815,7 +820,7 @@ public void AddTableCell(IFragmentWriter writer, bool isHead, int colSpan, Horiz default: throw new ArgumentOutOfRangeException(nameof(alignment), alignment, null); } - xw.WriteRaw(content); + xw.WriteRaw(content.ToString()); // WriteFullEndElement in case there is no content xw.WriteFullEndElement(); // or } @@ -844,9 +849,12 @@ public void StartEntry(IFragmentWriter writer, string className, Guid entryGuid, xw.WriteAttributeString("id", "g" + entryGuid); } - public void AddEntryData(IFragmentWriter writer, List pieces) + public void AddEntryData(IFragmentWriter writer, List pieces) { - pieces.ForEach(((XmlFragmentWriter)writer).Writer.WriteRaw); + foreach (IFragment frag in pieces) + { + ((XmlFragmentWriter)writer).Writer.WriteRaw(frag.ToString()); + } } public void EndEntry(IFragmentWriter writer) @@ -877,16 +885,17 @@ public void EndObject(IFragmentWriter writer) ((XmlFragmentWriter)writer).Writer.WriteEndElement(); // or } - public void WriteProcessedContents(IFragmentWriter writer, string contents) + public void WriteProcessedContents(IFragmentWriter writer, IFragment contents) { - ((XmlFragmentWriter)writer).Writer.WriteRaw(contents); + ((XmlFragmentWriter)writer).Writer.WriteRaw(contents.ToString()); } /// /// This is used as an id in the xhtml and must be unique. - public string AddImage(string classAttribute, string srcAttribute, string pictureGuid) + public IFragment AddImage(string classAttribute, string srcAttribute, string pictureGuid) { var bldr = new StringBuilder(); + var fragment = new StringFragment(bldr); using (var xw = XmlWriter.Create(bldr, new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment })) { xw.WriteStartElement("img"); @@ -895,13 +904,14 @@ public string AddImage(string classAttribute, string srcAttribute, string pictur xw.WriteAttributeString("id", "g" + pictureGuid); xw.WriteEndElement(); xw.Flush(); - return bldr.ToString(); + return fragment; } } - public string AddImageCaption(string captionContent) + public IFragment AddImageCaption(string captionContent) { var bldr = new StringBuilder(); + var fragment = new StringFragment(bldr); using (var xw = XmlWriter.Create(bldr, new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment })) { xw.WriteStartElement("div"); @@ -909,13 +919,14 @@ public string AddImageCaption(string captionContent) xw.WriteRaw(captionContent); xw.WriteEndElement(); xw.Flush(); - return bldr.ToString(); + return fragment; } } - public string GenerateSenseNumber(string formattedSenseNumber, string senseNumberWs) + public IFragment GenerateSenseNumber(string formattedSenseNumber, string senseNumberWs) { var bldr = new StringBuilder(); + var fragment = new StringFragment(bldr); using (var xw = XmlWriter.Create(bldr, new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment })) { xw.WriteStartElement("span"); @@ -924,14 +935,15 @@ public string GenerateSenseNumber(string formattedSenseNumber, string senseNumbe xw.WriteString(formattedSenseNumber); xw.WriteEndElement(); xw.Flush(); - return bldr.ToString(); + return fragment; } } - public string AddLexReferences(bool generateLexType, string lexTypeContent, string className, + public IFragment AddLexReferences(bool generateLexType, IFragment lexTypeContent, string className, string referencesContent, bool typeBefore) { var bldr = new StringBuilder(100); + var fragment = new StringFragment(bldr); // Generate the factored ref types element (if before). if (generateLexType && typeBefore) { @@ -945,7 +957,7 @@ public string AddLexReferences(bool generateLexType, string lexTypeContent, stri bldr.Append(WriteProcessedObject(false, lexTypeContent, className)); } - return bldr.ToString(); + return fragment; } public void BeginCrossReference(IFragmentWriter writer, bool isBlockProperty, string classAttribute) @@ -958,27 +970,31 @@ public void EndCrossReference(IFragmentWriter writer) EndObject(writer); } - public string WriteProcessedSenses(bool isBlock, string sensesContent, string classAttribute, string sharedGramInfo) + public IFragment WriteProcessedSenses(bool isBlock, IFragment sensesContent, string classAttribute, IFragment sharedGramInfo) { - return WriteProcessedObject(isBlock, sharedGramInfo + sensesContent, classAttribute); + sharedGramInfo.Append(sensesContent); + return WriteProcessedObject(isBlock, sharedGramInfo, classAttribute); } - public string AddAudioWsContent(string className, Guid linkTarget, string fileContent) + public IFragment AddAudioWsContent(string className, Guid linkTarget, IFragment fileContent) { // No additional wrapping required for the xhtml return fileContent; } - public string GenerateErrorContent(StringBuilder badStrBuilder) + public IFragment GenerateErrorContent(StringBuilder badStrBuilder) { - return $"\u0FFF\u0FFF\u0FFF"; + var fragment = new StringFragment(message); + return fragment; } - public string GenerateVideoLinkContent(string className, string mediaId, + public IFragment GenerateVideoLinkContent(string className, string mediaId, string srcAttribute, string caption) { var bldr = new StringBuilder(); + var fragment = new StringFragment(bldr); using (var xw = XmlWriter.Create(bldr, new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment })) { // This creates a link that will open the video in the same window as the dictionary view/preview @@ -993,27 +1009,29 @@ public string GenerateVideoLinkContent(string className, string mediaId, xw.WriteRaw(""); xw.WriteFullEndElement(); xw.Flush(); - return bldr.ToString(); + return fragment; } } - public string AddCollectionItem(bool isBlock, string collectionItemClass, string content) + public IFragment AddCollectionItem(bool isBlock, string collectionItemClass, IFragment content) { var bldr = new StringBuilder(); + var builder = new StringFragment(bldr); using (var xw = XmlWriter.Create(bldr, new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment })) { xw.WriteStartElement(isBlock ? "div" : "span"); xw.WriteAttributeString("class", collectionItemClass); - xw.WriteRaw(content); + xw.WriteRaw(content.ToString()); xw.WriteEndElement(); xw.Flush(); - return bldr.ToString(); + return builder; } } - public string AddProperty(string className, bool isBlockProperty, string content) + public IFragment AddProperty(string className, bool isBlockProperty, string content) { var bldr = new StringBuilder(); + var fragment = new StringFragment(bldr); using (var xw = XmlWriter.Create(bldr, new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment })) { @@ -1022,20 +1040,21 @@ public string AddProperty(string className, bool isBlockProperty, string content xw.WriteString(content); xw.WriteEndElement(); xw.Flush(); - return bldr.ToString(); + return fragment; } } - public string AddSenseData(string senseNumberSpan, bool isBlock, Guid ownerGuid, + public IFragment AddSenseData(IFragment senseNumberSpan, bool isBlock, Guid ownerGuid, string senseContent, string className) { var bldr = new StringBuilder(); + var fragment = new StringFragment(bldr); using (var xw = XmlWriter.Create(bldr, new XmlWriterSettings { ConformanceLevel = ConformanceLevel.Fragment })) { // Wrap the number and sense combination in a sensecontent span so that both can be affected by DisplayEachSenseInParagraph xw.WriteStartElement("span"); xw.WriteAttributeString("class", "sensecontent"); - xw.WriteRaw(senseNumberSpan); + xw.WriteRaw(senseNumberSpan?.ToString()); xw.WriteStartElement(isBlock ? "div" : "span"); xw.WriteAttributeString("class", className); xw.WriteAttributeString("entryguid", "g" + ownerGuid); @@ -1043,7 +1062,7 @@ public string AddSenseData(string senseNumberSpan, bool isBlock, Guid ownerGuid, xw.WriteEndElement(); // element name for property xw.WriteEndElement(); // xw.Flush(); - return bldr.ToString(); + return fragment; } } diff --git a/Src/xWorks/StringFragment.cs b/Src/xWorks/StringFragment.cs new file mode 100644 index 0000000000..eda4916f7d --- /dev/null +++ b/Src/xWorks/StringFragment.cs @@ -0,0 +1,70 @@ +using SIL.FieldWorks.XWorks; +using System; +using System.Text; + +public class StringFragment : IFragment +{ + public StringBuilder StrBuilder { get; set; } + + public StringFragment() + { + StrBuilder = new StringBuilder(); + } + + // Create a new string fragment linked to an existing string builder. + public StringFragment(StringBuilder bldr) + { + StrBuilder = bldr; + } + + // Create a new string fragment containing the given string. + public StringFragment(string str) : this() + { + // Add text to the fragment + StrBuilder.Append(str); + } + + public override string ToString() + { + if (StrBuilder == null) + return String.Empty; + return StrBuilder.ToString(); + } + + public int Length() + { + if (StrBuilder == null) + return 0; + return StrBuilder.Length; + } + + public void Append(IFragment frag) + { + if (frag != null) + StrBuilder.Append(frag.ToString()); + } + + public void AppendBreak() + { + StrBuilder.AppendLine(); + } + + public void TrimEnd(char c) + { + string curString = StrBuilder.ToString(); + StrBuilder.Clear(); + StrBuilder.Append(curString.TrimEnd(c)); + } + + public bool IsNullOrEmpty() + { + if ((StrBuilder != null) && (!String.IsNullOrEmpty(StrBuilder.ToString()))) + return false; + return true; + } + + public void Clear() + { + StrBuilder?.Clear(); + } +} diff --git a/Src/xWorks/XhtmlDocView.cs b/Src/xWorks/XhtmlDocView.cs index a222d2e0f0..6f28d26888 100644 --- a/Src/xWorks/XhtmlDocView.cs +++ b/Src/xWorks/XhtmlDocView.cs @@ -370,7 +370,7 @@ private void AddMoreEntriesToPage(bool goingUp, GeckoWebBrowser browser) foreach (var entry in entries) { var entryElement = browserElement.OwnerDocument.CreateHtmlElement("div"); - var entryDoc = XDocument.Parse(entry); + var entryDoc = XDocument.Parse(entry.ToString()); foreach (var attribute in entryDoc.Root.Attributes()) { entryElement.SetAttribute(attribute.Name.ToString(), attribute.Value); @@ -402,7 +402,7 @@ private void AddMoreEntriesToPage(bool goingUp, GeckoWebBrowser browser) // Load entries above the lower navigation buttons foreach (var entry in entries) { - var entryElement = browserElement.OwnerDocument.CreateHtmlElement("div"); var entryDoc = XDocument.Parse(entry); + var entryElement = browserElement.OwnerDocument.CreateHtmlElement("div"); var entryDoc = XDocument.Parse(entry.ToString()); foreach (var attribute in entryDoc.Root.Attributes()) { entryElement.SetAttribute(attribute.Name.ToString(), attribute.Value); diff --git a/Src/xWorks/xWorks.csproj b/Src/xWorks/xWorks.csproj index a4d4c12beb..909643cc39 100644 --- a/Src/xWorks/xWorks.csproj +++ b/Src/xWorks/xWorks.csproj @@ -429,6 +429,7 @@ + @@ -514,6 +515,7 @@ Form + diff --git a/Src/xWorks/xWorksTests/ConfiguredLcmUsfmGeneratorTests.cs b/Src/xWorks/xWorksTests/ConfiguredLcmUsfmGeneratorTests.cs index 5b5b060a16..ab07598513 100644 --- a/Src/xWorks/xWorksTests/ConfiguredLcmUsfmGeneratorTests.cs +++ b/Src/xWorks/xWorksTests/ConfiguredLcmUsfmGeneratorTests.cs @@ -140,7 +140,7 @@ public void NoUSFM_GeneratesPlainText() const string plainText = "Plain Text"; var entry = CreateInterestingLexEntry(plainText); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath( XPathToUSFMField + "/span[@lang='en' and text()='" + plainText + "']", 1); AssertThatXmlIn.String(result).HasNoMatchForXpath("//table"); @@ -153,7 +153,7 @@ public void NoLeadingUSFM_GeneratesPlainText() const string plainText = "Plain Text\n\\d ignore me"; var entry = CreateInterestingLexEntry(plainText); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings).ToString(); AssertThatXmlIn.String(result).HasNoMatchForXpath("//table"); AssertIsGood(result); } @@ -165,7 +165,7 @@ public void LeadingTitle_GeneratesTable() const string titleUSFM = @"\d " + title; var entry = CreateInterestingLexEntry(titleUSFM); - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath( XPathToTitle + "[@lang='en' and text()='" + title + "']", 1); AssertIsGood(result); @@ -176,7 +176,7 @@ public void LeadingTableRow_GeneratesTable() { var entry = CreateInterestingLexEntry("\\tr\n"); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(XPathToUSFMField + "/table", 1); AssertIsGood(result); } @@ -186,7 +186,7 @@ public void TitleAndTableRow_GeneratesBoth() { var entry = CreateInterestingLexEntry(@"\d title \tr \tc "); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(XPathToUSFMField + "/table", 1); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath( XPathToTitle + "[@lang='en' and text()='title']", 1); @@ -202,7 +202,7 @@ public void NoGapNoContentTitleAndRow_DoesNotThrow() var entry = CreateInterestingLexEntry(almostTable); var result = string.Empty; // SUT - Assert.DoesNotThrow(() => result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings)); + Assert.DoesNotThrow(() => result = (ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings)).ToString()); // Verify that the field is in the results AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(XPathToUSFMField, 1); @@ -218,7 +218,7 @@ public void WhitespaceOnlyBetweenTitleAndRow_DoesNotThrow() var entry = CreateInterestingLexEntry(almostTable); var result = string.Empty; // SUT - Assert.DoesNotThrow(() => result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings)); + Assert.DoesNotThrow(() => result = (ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings)).ToString()); // Verify that the partially-typed table is in the results AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(XPathToTitle + "[text()='" + TR + "']", 1); @@ -233,7 +233,7 @@ public void MissingSpaces_NoCells() var entry = CreateInterestingLexEntry(almostTable); var result = string.Empty; // SUT - Assert.DoesNotThrow(() => result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings)); + Assert.DoesNotThrow(() => result = (ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings)).ToString()); // Verify that the field is in the results AssertThatXmlIn.String(result).HasNoMatchForXpath("//th"); @@ -256,7 +256,7 @@ public void ManyRowsAndCells() var entry = CreateInterestingLexEntry($"{TR} {TC}1 {a1}\t{TC}2 {a2} \t \r\n{TC}3 {a3} {TR}" + $"\t{TC}1 {b1} {TC}2 {b2} {TC}3 {b3} {TR} {TC}1 {c1} {TC}2 {c2} {TC}3 {c3}"); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(XPathToUSFMField + "/table", 1); AssertThatXmlIn.String(result).HasNoMatchForXpath(XPathToTitle); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(XPathToRow, 3); @@ -287,7 +287,7 @@ public void EmptyCell() const string a3 = "ty"; var entry = CreateInterestingLexEntry($"{TR} {TC}1 {a1}\t{TC}2 {TC}3 {a3}"); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings).ToString(); const string xpathToA = XPathToRow + "/td[span[@lang='en' and text()='" + a1 + "']]/following-sibling::td[not(node())]/following-sibling::td[span[text()='" + a3 + "']]"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(xpathToA, 1); @@ -300,7 +300,7 @@ public void EmptyCells() { var entry = CreateInterestingLexEntry($"{TR} {TC}1 {TC}2\r\n{TC}3\t{TC}4 "); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings).ToString(); const string xpathToA = XPathToRow + "/td[not(node())]/following-sibling::td[not(node())]/following-sibling::td[not(node())]/following-sibling::td[not(node())]"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(xpathToA, 1); @@ -316,7 +316,7 @@ public void TableHeading_GeneratesTableHeader() const string b2h = "not normally expected, but not forbidden"; var entry = CreateInterestingLexEntry($"{TR} {TH}1 {a1h} {TC}2\r{a2c}\n{TR} {TC}1 {b1c} {TH}2 {b2h}"); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings).ToString(); const string xpathToA = XPathToRow + "/th[span[@lang='en' and text()='" + a1h + "']]/following-sibling::td[span[@lang='en' and text()='" + a2c + "']]"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(xpathToA, 1); @@ -340,7 +340,7 @@ public void TableCellAlignment() var entry = CreateInterestingLexEntry($"{TR} {TH}r1 {a1} {TH}2\r{a2}\n{TR} {TC}1 {b1} {TC}r2 {b2} " + $"{TR} {TH}c1 {c1} {TH}l2\r{c2}\n{TR} {TC}l1 {d1} {TC}c2 {d2}"); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings).ToString(); const string xpathToA = XPathToRow + "/th[@style='text-align: right;' and span[@lang='en' and text()='" + a1 + "']]/following-sibling::th[not(@style) and span[@lang='en' and text()='" + a2 + "']]"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(xpathToA, 1); @@ -371,7 +371,7 @@ public void MultipleTables() $@"\d {title1} {TR} {TC}1 {la1} \d {title2} {TR} {TH}1 {za1} {TH}2 {za2} {TR} {TC}1 {zb1} {TC}2 {zb2} \d {TR} {TH}1 {loneCell}"); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(XPathToUSFMField + "/table", 3); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(XPathToTitle, 2); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(XPathToRow, 4); // 1 + 2 + 1 = 4 @@ -385,7 +385,7 @@ public void CellRange(int min, int lim) { var entry = CreateInterestingLexEntry($"{TR} {TC}{min}-{lim} home on the range"); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings).ToString(); // +1 because the range includes both ends AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath($"{XPathToRow}/td[@colspan='{lim - min + 1}']", 1); AssertIsGood(result); @@ -401,7 +401,7 @@ public void CellRange_None_NoColSpan(string range) const string content = "seldom"; var entry = CreateInterestingLexEntry($"{TR} {TC}{range} {content}"); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath($"{XPathToRow}/td[not(@colspan)]/span[text()='{content}']", 1); AssertIsGood(result); } @@ -412,7 +412,7 @@ public void BadUSFM_RowWithoutCells() const string junk = "no cells"; var entry = CreateInterestingLexEntry($"{TR} {junk}"); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings).ToString(); var expected = string.Format(xWorksStrings.InvalidUSFM_TextAfterTR, junk); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(XPathToUSFMField + $"//span[{StyleBigRed} and text() = '{expected}']", 1); @@ -426,7 +426,7 @@ public void BadUSFM_RowWithTextBeforeCells() const string junk = "oops"; var entry = CreateInterestingLexEntry($"{TR} {junk} {TC}1 data"); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings).ToString(); var expected = string.Format(xWorksStrings.InvalidUSFM_TextAfterTR, junk); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(XPathToUSFMField + @@ -441,7 +441,7 @@ public void BadUSFM_PartiallyTyped_NoErrors(string usfm, string xpath) { var entry = CreateInterestingLexEntry(usfm); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(xpath, 1); Assert.That(result, Does.EndWith("")); AssertIsGood(result); @@ -452,7 +452,7 @@ public void BadUSFM_PartiallyTyped_NoErrors([Values(@"\t", TC, TH + "1", TH + "l { var entry = CreateInterestingLexEntry($@"{TR} {marker}\th2 exists"); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(XPathToUSFMField + $"//span[{StyleBigRed} and text()='{marker}']", 1); Assert.That(result, Does.EndWith("")); @@ -464,7 +464,7 @@ public void BadUSFM_PartiallyTyped_NoError() { var entry = CreateInterestingLexEntry(TR + @" \t"); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(XPathToUSFMField + "//span[" + StyleBigRed + @" and text()='\t']", 1); Assert.That(result, Does.EndWith("")); @@ -478,7 +478,7 @@ public void MistypedMarker([Values("tre", "tcp", "t", "td", "tl", "t2", "h", "th const string cellAfter = "after"; var cellWithBadMark = $@"in \{misMark} between"; var entry = CreateInterestingLexEntry($"{TR} {TC} {cellBefore} {TC} {cellWithBadMark} {TC} {cellAfter}"); - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, m_configNode, null, m_settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(XPathToCell, 3); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(XPathToCell + "[text()='" + cellBefore + "']", 1); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(XPathToCell + "[text()='" + cellAfter + "']", 1); diff --git a/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorReversalTests.cs b/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorReversalTests.cs index 1fa28b8559..e43d64b0c4 100644 --- a/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorReversalTests.cs +++ b/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorReversalTests.cs @@ -119,7 +119,7 @@ public void GenerateXHTMLForEntry_LexemeFormConfigurationGeneratesCorrectResult( CssGeneratorTests.PopulateFieldsForTesting(mainEntryNode); var entry = CreateInterestingEnglishReversalEntry(); //SUT - string result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, DefaultSettings); + string result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, DefaultSettings).ToString(); const string frenchLexForm = "/div[@class='reversalindexentry']/span[@class='reversalform']/span[@lang='en' and text()='ReversalForm']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(frenchLexForm, 1); } @@ -146,7 +146,7 @@ public void GenerateXHTMLForEntry_PrimaryEntryReferencesWork_ComplexFormOfEntry( paroleEntry.SummaryDefinition.SetAnalysisDefaultWritingSystem("summDefn"); CXGTests.CreateComplexForm(Cache, paroleEntry, sense.Owner as ILexEntry, true); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(reversalEntry, mainRevEntryNode, null, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(reversalEntry, mainRevEntryNode, null, DefaultSettings).ToString(); const string headwordXpath = senseXpath + "/span[@class='headword']/span[@lang='fr']//a[text()='porte-parole']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(headwordXpath, 1); const string refTypeXpath = entryRefTypeXpath + "/span[@class='abbreviation']/span[@lang='en' and text()='comp. of']"; @@ -165,7 +165,7 @@ public void GenerateXHTMLForEntry_PrimaryEntryReferencesWork_ComplexFormOfSense( var paroleEntry = CXGTests.CreateInterestingLexEntry(Cache, "parole", "speech"); CXGTests.CreateComplexForm(Cache, paroleEntry.SensesOS[0], sense.Owner as ILexEntry, true); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(reversalEntry, mainRevEntryNode, null, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(reversalEntry, mainRevEntryNode, null, DefaultSettings).ToString(); const string refTypeXpath = entryRefTypeXpath + "/span[@class='abbreviation']/span[@lang='en' and text()='comp. of']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(refTypeXpath, 1); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(refHeadwordXpath, 1); @@ -182,7 +182,7 @@ public void GenerateXHTMLForEntry_PrimaryEntryReferencesWork_VariantFormOfSense( var paroleEntry = CXGTests.CreateInterestingLexEntry(Cache, "parole", "speech"); CXGTests.CreateVariantForm(Cache, paroleEntry.SensesOS[0], variantEntry, "Spelling Variant"); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(reversalEntry, mainRevEntryNode, null, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(reversalEntry, mainRevEntryNode, null, DefaultSettings).ToString(); const string refTypeXpath = entryRefTypeXpath + "/span[@class='abbreviation']/span[@lang='en' and text()='sp. var. of']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(refTypeXpath, 1); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(refHeadwordXpath, 1); @@ -200,7 +200,7 @@ public void GenerateXHTMLForEntry_PrimaryEntryReferencesWork_VariantFormOfEntry( paroleEntry.SummaryDefinition.SetAnalysisDefaultWritingSystem("summDefn"); CXGTests.CreateVariantForm(Cache, paroleEntry, variantEntry, "Spelling Variant"); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(reversalEntry, mainRevEntryNode, null, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(reversalEntry, mainRevEntryNode, null, DefaultSettings).ToString(); const string refTypeXpath = entryRefTypeXpath + "/span[@class='abbreviation']/span[@lang='en' and text()='sp. var. of']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(refTypeXpath, 1); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(refHeadwordXpath, 1); @@ -226,7 +226,7 @@ public void GenerateXHTMLForEntry_PrimaryEntryReferences_Ordered() using (CXGTests.CreateVariantForm(Cache, refer4, primaryEntry, new Guid("00000000-0000-0000-dddd-000000000000"), null)) // no Variant Type using (CXGTests.CreateVariantForm(Cache, refer5, primaryEntry, new Guid("00000000-0000-0000-eeee-000000000000"), "Spelling Variant")) { - var result = ConfiguredLcmGenerator.GenerateContentForEntry(reversalEntry, mainRevEntryNode, null, DefaultSettings); // SUT + var result = ConfiguredLcmGenerator.GenerateContentForEntry(reversalEntry, mainRevEntryNode, null, DefaultSettings).ToString(); // SUT var assertIt = AssertThatXmlIn.String(result); assertIt.HasSpecifiedNumberOfMatchesForXpath(entryRefTypeXpath, 3); // should be one Complex Form Type and two Variant Types. const string headwordBit = "/span[@class='headword']/span[@lang='fr']/a[text()='{1}']"; @@ -401,7 +401,7 @@ public void GenerateXHTMLForEntry_ReversalStringGeneratesContent() var entryHeadWord = rie.SensesRS.First().Entry.HeadWord; //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(rie, reversalNode, null, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(rie, reversalNode, null, DefaultSettings).ToString(); var reversalFormDataPath = string.Format("/div[@class='reversalindexentry']/span[@class='reversalform']/span[text()='{0}']", TsStringUtils.Compose(rie.LongName)); var entryDataPath = string.Format("//span[text()='{0}']", entryHeadWord.get_NormalizedForm(FwNormalizationMode.knmNFC).Text); @@ -461,7 +461,7 @@ public void GenerateXHTMLForEntry_SenseNumbersGeneratedForMultipleReferencedSens var testEntry = CreateInterestingEnglishReversalEntry(); AddSenseToReversaEntry(testEntry, "second gloss", m_wsEn, Cache); //SUT - var xhtml = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, DefaultSettings); + var xhtml = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, DefaultSettings).ToString(); const string senseNumberOne = "/div[@class='reversalindexentry']/span[@class='sensesrs']/span[@class='sensecontent']/span[@class='sensesr' and preceding-sibling::span[@class='sensenumber' and text()='1']]//span[@lang='en' and text()='gloss']"; const string senseNumberTwo = "/div[@class='reversalindexentry']/span[@class='sensesrs']/span[@class='sensecontent']/span[@class='sensesr' and preceding-sibling::span[@class='sensenumber' and text()='2']]//span[@lang='en' and text()='second gloss']"; //This assert is dependent on the specific entry data created in CreateInterestingEnglishReversalEntry @@ -523,7 +523,7 @@ public void GenerateXHTMLForEntry_VernacularFormWithSubSenses() var testEntry = CreateInterestingEnglishReversalEntry(); AddSingleSubSenseToSense(testEntry, "second gloss", m_wsEn, Cache); //SUT - var xhtml = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, DefaultSettings); + var xhtml = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, DefaultSettings).ToString(); // REVIEW (Hasso) 2016.03: we should probably do something about the leading space in the Sense Number Run, as it is currently in addition to the "between" space. const string subSenseOneOne = "/div[@class='reversalindexentry']/span[@class='sensesrs']/span[@class='sensecontent']/span[@class='sensesr']/span[@class='headword']/span/span/a[text()='1.1']"; AssertThatXmlIn.String(xhtml).HasSpecifiedNumberOfMatchesForXpath(subSenseOneOne, 1); @@ -585,7 +585,7 @@ public void GenerateXHTMLForEntry_VernacularFormWithSubSensesinReversalSubEntry( CssGeneratorTests.PopulateFieldsForTesting(mainEntryNode); var testEntry = CreateInterestingEnglishSubReversalEntryWithSubSense(); //SUT - var xhtml = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, DefaultSettings); + var xhtml = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, DefaultSettings).ToString(); const string subSenseOneOne = "/div[@class='reversalindexentry']/span[@class='subentries']/span[@class='subentry']/span[@class='sensesrs']/span[@class='sensecontent']/span[@class='sensesr']/span[@class='headword']/span/span/a[text()='1.1']"; AssertThatXmlIn.String(xhtml).HasSpecifiedNumberOfMatchesForXpath(subSenseOneOne, 1); } @@ -703,7 +703,7 @@ public void GenerateXHTMLForEntry_SameGramInfoCollapsesOnDemand() testEntry.SensesRS.Add(entry1.SensesOS.First()); testEntry.SensesRS.Add(entry2.SensesOS.First()); - var xhtml = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var xhtml = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); // check that the sense gram info appears once before the rest of the sense information. Assert.That(xhtml, Is.Not.Null.Or.Empty); const string sharedGramInfo = "/div[@class='reversalindexentry']/span[@class='sensesrs']/span[@class='sharedgrammaticalinfo']/span[@class='morphosyntaxanalysis']/span[@class='partofspeech']/span[@lang='en' and text()='n']"; @@ -715,7 +715,7 @@ public void GenerateXHTMLForEntry_SameGramInfoCollapsesOnDemand() entry2.MorphoSyntaxAnalysesOC.Add(msa2a); msa2a.PartOfSpeechRA = verb; entry2.SensesOS.First().MorphoSyntaxAnalysisRA = msa2a; - xhtml = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + xhtml = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); // check that the sense gram info appears separately for both senses. AssertThatXmlIn.String(xhtml).HasSpecifiedNumberOfMatchesForXpath(sharedGramInfo, 0); AssertThatXmlIn.String(xhtml).HasSpecifiedNumberOfMatchesForXpath(separateGramInfo, 2); diff --git a/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorTests.cs b/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorTests.cs index 6c9a7fb08b..88a5ba1435 100644 --- a/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorTests.cs +++ b/Src/xWorks/xWorksTests/ConfiguredXHTMLGeneratorTests.cs @@ -189,7 +189,7 @@ public void GenerateContentForEntry_HeadwordConfigurationGeneratesCorrectResult( AddHeadwordToEntry(entry, "HeadWordTest", m_wsFr); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, new ReadOnlyPropertyTable(m_propertyTable), false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings).ToString(); const string frenchHeadwordOfHeadwordTest = "/div[@class='lexentry']/span[@class='headword']/span[@lang='fr']/a[text()='HeadWordTest']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(frenchHeadwordOfHeadwordTest, 1); } @@ -212,7 +212,7 @@ public void GenerateContentForEntry_InvalidUnicodeHeadword_GeneratesErrorResult( var entry = CreateInterestingLexEntry(Cache, "\uD900"); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, new ReadOnlyPropertyTable(m_propertyTable), false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings).ToString(); const string invalidCharsHeadwordTest = "/div[@class='lexentry']/span[@class='headword']/span[text()='\u0fff\u0fff\u0fff']"; // change Headword back to something legal so that we don't crash trying to save bad data into the cache. AddHeadwordToEntry(entry, "notbadanymore", Cache.DefaultVernWs); @@ -279,7 +279,7 @@ public void GenerateContentForEntry_LexemeFormConfigurationGeneratesCorrectResul morph.Form.set_String(wsFr, TsStringUtils.MakeString("LexemeFormTest", wsFr)); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, new ReadOnlyPropertyTable(m_propertyTable), false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings).ToString(); const string frenchLexForm = "/div[@class='lexentry']/span[@class='lexemeformoa']/span[@lang='fr']/a[text()='LexemeFormTest']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(frenchLexForm, 1); } @@ -324,7 +324,7 @@ public void GenerateContentForEntry_PronunciationLocationGeneratesCorrectResult( pronunciation.LocationRA = location; var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings).ToString(); const string hereLocation = "/div[@class='lexentry']/span[@class='pronunciations']/span[@class='pronunciation']/span[@class='location']/span[@class='name']/span[@lang='fr' and text()='Here!']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(hereLocation, 1); } @@ -402,7 +402,7 @@ public void GenerateContentForEntry_PronunciationVideoFileGeneratesAnchorTag() const string mediaFileAnchor2 = entryPart + variantsPart + varPronPart + mediaFilePart + movieCamSearch; //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings).ToString(); Assert.That(result, Contains.Substring(videoFileUrl1)); Assert.That(result, Contains.Substring(videoFileUrl2)); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(mediaFileAnchor1, 1); @@ -456,7 +456,7 @@ public void GenerateContentForEntry_NoEnabledConfigurationsWritesNothing() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings).ToString(); Assert.IsEmpty(result, "Should not have generated anything for a disabled node"); } @@ -476,9 +476,9 @@ public void GenerateContentForEntry_HomographNumbersGeneratesCorrectResult() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); XHTMLStringBuilder.AppendLine(""); //keep the xml valid (single root element) //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings).ToString(); XHTMLStringBuilder.Append(result); - result = ConfiguredLcmGenerator.GenerateContentForEntry(entryTwo, mainEntryNode, null, settings); + result = ConfiguredLcmGenerator.GenerateContentForEntry(entryTwo, mainEntryNode, null, settings).ToString(); XHTMLStringBuilder.Append(result); XHTMLStringBuilder.AppendLine(""); @@ -502,7 +502,7 @@ public void GenerateContentForEntry_HeadwordRefConfigurationGeneratesWithTwoWS() var mainEntryNode = ModelForCrossReferences(new[] { comRefType.Guid.ToString() }); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, DefaultSettings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(crossRefOwnerTypeXpath, 1); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(CrossRefOwnerTypeXpath(comRefTypeName), 1); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(HeadwordWsInCrossRefsXpath("en", "bEN"), 1); @@ -530,7 +530,7 @@ public void GenerateContentForEntry_OneSenseWithGlossGeneratesCorrectResult() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); const string oneSenseWithGlossOfGloss = xpathThruSense + "//span[@lang='en' and text()='gloss']"; // This assert is dependent on the specific entry data created in CreateInterestingLexEntry AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(oneSenseWithGlossOfGloss, 1); @@ -579,9 +579,9 @@ public void GenerateContentForEntry_OneEntryWithSenseAndOneWithoutWorks() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); XHTMLStringBuilder.AppendLine(""); //keep the xml valid (single root element) //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings).ToString(); XHTMLStringBuilder.Append(result); - result = ConfiguredLcmGenerator.GenerateContentForEntry(entryTwo, mainEntryNode, null, settings); + result = ConfiguredLcmGenerator.GenerateContentForEntry(entryTwo, mainEntryNode, null, settings).ToString(); XHTMLStringBuilder.Append(result); XHTMLStringBuilder.AppendLine(""); result = XHTMLStringBuilder.ToString(); @@ -602,7 +602,7 @@ public void GenerateContentForEntry_DefaultRootGeneratesResult() var dictionaryModel = new DictionaryConfigurationModel(defaultRoot, Cache); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, dictionaryModel.Parts[0], DefaultDecorator, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, dictionaryModel.Parts[0], DefaultDecorator, settings).ToString(); var entryExists = "/div[@class='entry' and @id='g" + entry.Guid + "']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(entryExists, 1); } @@ -639,7 +639,7 @@ public void GenerateContentForEntry_DoesNotDescendThroughDisabledNode() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings).ToString(); const string sensesThatShouldNotBe = "/div[@class='entry']/span[@class='senses']"; const string headwordThatShouldNotBe = "//span[@class='gloss']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(sensesThatShouldNotBe, 0); @@ -670,7 +670,7 @@ public void GenerateContentForEntry_ProduceNothingWithOnlyDisabledNode() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings).ToString(); Assert.IsEmpty(result, "With only one subnode that is disabled, there should be nothing generated!"); } @@ -728,7 +728,7 @@ public void GenerateContentForEntry_TwoSensesWithSameInfoShowGramInfoFirst() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); // SUT - var xhtmlString = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings); + var xhtmlString = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings).ToString(); const string sharedGramInfoPath = "//div[@class='lexentry']/span[@class='senses']/span[@class='sharedgrammaticalinfo']"; const string gramInfoPath = "//div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense']/span[@class='msas']/span[@class='mlpartofspeech']"; AssertThatXmlIn.String(xhtmlString).HasNoMatchForXpath(gramInfoPath); @@ -813,7 +813,7 @@ public void GenerateContentForEntry_TwoSensesWithSameInfo_ThirdSenseNotPublished Cache.ServiceLocator.GetInstance().LexDbEntries, mainDict); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); // SUT - var xhtmlString = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, mainDictionaryDecorator, settings); + var xhtmlString = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, mainDictionaryDecorator, settings).ToString(); const string sharedGramInfoPath = "//div[@class='lexentry']/span[@class='senses']/span[@class='sharedgrammaticalinfo']"; const string gramInfoPath = "//div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense']/span[@class='msa']/span[@class='mlpartofspeech']"; AssertThatXmlIn.String(xhtmlString).HasNoMatchForXpath(gramInfoPath); @@ -878,7 +878,7 @@ public void GenerateContentForEntry_TwoSensesWithDifferentGramInfoShowInfoInSens var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); // SUT - var xhtmlString = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings); + var xhtmlString = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings).ToString(); const string sharedGramInfoPath = "//div[@class='lexentry']/span[@class='sensesos']/span[@class='sharedgrammaticalinfo']"; const string gramInfoPath = "//div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense']/span[@class='msas']/span[@class='mlpartofspeech']"; AssertThatXmlIn.String(xhtmlString).HasSpecifiedNumberOfMatchesForXpath(gramInfoPath, 2); @@ -927,7 +927,7 @@ public void GenerateContentForEntry_TwoSensesWithNoGramInfoDisplaysNothingForSha var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); // SUT - var xhtmlString = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings); + var xhtmlString = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings).ToString(); const string sharedGramInfoPath = "//div[@class='lexentry']/span[@class='sensesos']/span[@class='sharedgrammaticalinfo']"; const string gramInfoPath = "//div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense']/span[@class='msas']/span[@class='mlpartofspeech']"; AssertThatXmlIn.String(xhtmlString).HasNoMatchForXpath(gramInfoPath); @@ -987,7 +987,7 @@ public void GenerateContentForEntry_MorphemeType() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings).ToString(); const string morphTypePath = "//span[@class='morphosyntaxanalysis']/span[@class='morphtypes']/span[@class='morphtype']/span[@class='abbreviation']/span[@lang='en' and text()='sfx']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(morphTypePath, 1); } @@ -1027,7 +1027,7 @@ public void GenerateContentForEntry_MakesSpanForRA() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings).ToString(); const string gramInfoPath = xpathThruSense + "/span[@class='morphosyntaxanalysis']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(gramInfoPath, 1); } @@ -1078,7 +1078,7 @@ public void GenerateContentForEntry_CmObjectWithNoEnabledChildrenSkipsSpan() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings).ToString(); const string gramInfoPath = xpathThruSense + "/span[@class='morphosyntaxanalysis']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(gramInfoPath, 0); } @@ -1112,7 +1112,7 @@ public void GenerateContentForEntry_DoesNotMakeSpanForRAIfNoData() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings).ToString(); const string gramInfoPath = xpathThruSense + "/span[@class='morphosyntaxanalysis']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(gramInfoPath, 0); } @@ -1146,7 +1146,7 @@ public void GenerateContentForEntry_DoesNotMakeSpanForTSStringIfNoData() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings).ToString(); const string scientificCatPath = xpathThruSense + "/span[@class='scientificname']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(scientificCatPath, 0); } @@ -1202,7 +1202,7 @@ public void GenerateContentForEntry_SupportsGramAbbrChildOfMSARA() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings).ToString(); const string gramAbbr1 = xpathThruSense + "/span[@class='morphosyntaxanalysis']/span[@class='interlinearabbrtss']/span[@lang='fr']/span[@lang='fr' and text()='Blah']"; const string gramAbbr2 = xpathThruSense + "/span[@class='morphosyntaxanalysis']/span[@class='interlinearabbrtss']/span[@lang='fr']/span[@lang='en' and text()=':Any']"; @@ -1265,7 +1265,7 @@ public void GenerateContentForEntry_DontDisplayNotSure() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings).ToString(); const string gramAbbr1 = xpathThruSense + "/span[@class='morphosyntaxanalysis']/span[@class='interlinearabbrtss']/span[@lang='fr']/span[@lang='fr' and text()='']"; const string gramAbbr2 = xpathThruSense + "/span[@class='morphosyntaxanalysis']/span[@class='interlinearabbrtss']/span[@lang='fr']/span[@lang='en' and text()=':Any']"; @@ -1314,7 +1314,7 @@ public void GenerateContentForEntry_CaptionOrHeadwordGetsCaption() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings).ToString(); const string captionOrHeadwordContainsCaption = "/div[@class='lexentry']/span[@class='pictures']/div[@class='picture']/div[@class='captionContent']/span[@class='captionorheadword']//span[text()='captionEn']"; //This assert is dependent on the specific entry data created in CreateInterestingLexEntry AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(captionOrHeadwordContainsCaption, 1); @@ -1357,7 +1357,7 @@ public void GenerateContentForEntry_CaptionOrHeadwordGetsHeadword() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings).ToString(); const string captionOrHeadwordContainsHeadword = "/div[@class='lexentry']/span[@class='pictures']/div[@class='picture']/div[@class='captionContent']/span[@class='captionorheadword']//span[text()='HeadwordEn']"; //This assert is dependent on the specific entry data created in CreateInterestingLexEntry AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(captionOrHeadwordContainsHeadword, 1); @@ -1401,7 +1401,7 @@ public void GenerateContentForEntry_CaptionOrHeadword_HandlePerWs() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings).ToString(); const string captionOrHeadwordContainsCaptionEn = "/div[@class='lexentry']/span[@class='pictures']/div[@class='picture']/div[@class='captionContent']/span[@class='captionorheadword']//span[@lang='en' and text()='captionEn']"; const string captionOrHeadwordContainsHeadwordFr = "/div[@class='lexentry']/span[@class='pictures']/div[@class='picture']/div[@class='captionContent']/span[@class='captionorheadword']//span[@lang='fr' and text()='HeadwordFr']"; //This assert is dependent on the specific entry data created in CreateInterestingLexEntry @@ -1432,7 +1432,7 @@ public void GenerateContentForEntry_DefinitionOrGlossWorks() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings).ToString(); const string senseWithdefinitionOrGloss = "//span[@class='sense']/span[@class='definitionorgloss']/span[text()='gloss']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(senseWithdefinitionOrGloss, 1); } @@ -1462,7 +1462,7 @@ public void GenerateContentForEntry_DefinitionOrGlossWorks_WithAbbrev() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings).ToString(); const string senseWithdefinitionOrGloss = "//span[@class='sense']/span[@class='definitionorgloss']/span[@class='writingsystemprefix' and normalize-space(text())='Eng']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(senseWithdefinitionOrGloss, 1); @@ -1491,7 +1491,7 @@ public void GenerateContentForEntry_DefinitionOrGloss_HandlePerWS() entryOne.SensesOS.First().Definition.set_String(wsEs, "definition"); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings).ToString(); const string senseWithdefinitionOrGlossTwoWs = "//span[@class='sense']/span[@class='definitionorgloss' and span[1]='gloss' and span[2]='definition']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(senseWithdefinitionOrGlossTwoWs, 1); } @@ -1550,7 +1550,7 @@ public void GenerateContentForEntry_ReferencedComplexFormDefinitionOrGloss_Handl CssGeneratorTests.PopulateFieldsForTesting(mainEntryNode); // SUT - var output = ConfiguredLcmGenerator.GenerateContentForEntry(entryEntry, mainEntryNode, pubMain, DefaultSettings); + var output = ConfiguredLcmGenerator.GenerateContentForEntry(entryEntry, mainEntryNode, pubMain, DefaultSettings).ToString(); // set of xpaths and required number of matches. var checkthis = new Dictionary() @@ -1623,7 +1623,7 @@ public void GenerateContentForEntry_OtherReferencedComplexForms() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings).ToString(); var fwdNameXpath = string.Format( "//span[@class='complexformsnotsubentries']/span[@class='complexformtypes']/span[@class='complexformtype']/span/span[@lang='en' and text()='{0}']", complexRefAbbr); @@ -1660,7 +1660,7 @@ public void GenerateContentForEntry_DuplicateConfigNodeWithSpaceWorks() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings).ToString(); const string senseWithHyphenSuffix = "//span[@class='senses_test-one']/span[@class='sense_test-one']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(senseWithHyphenSuffix, 1); } @@ -1691,7 +1691,7 @@ public void GenerateContentForEntry_DuplicateConfigNodeWithPuncWorks() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings).ToString(); const string senseWithHyphenSuffix = "//span[@class='senses_-test']/span[@class='sense_-test']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(senseWithHyphenSuffix, 1); } @@ -1722,7 +1722,7 @@ public void GenerateContentForEntry_DuplicateConfigNodeWithMultiPuncWorks() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings).ToString(); const string senseWithHyphenSuffix = "//span[@class='senses_-test-']/span[@class='sense_-test-']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(senseWithHyphenSuffix, 1); } @@ -1781,7 +1781,7 @@ public void GenerateContentForEntry_HeadWordRefVirtualPropWorks() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings).ToString(); var headwordMatch = string.Format("//span[@class='{0}']//span[@class='{1}']/span[text()='{2}']", nters, headWord, entryThreeForm); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(headwordMatch, 1); @@ -1835,7 +1835,7 @@ public void GenerateContentForEntry_EtymologyLanguageWorks() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings).ToString(); const string etymologyWithArabicSrcLanguage = "//span[@class='etymologies']/span[@class='etymology']/span[@class='languages']/span[@class='language']/span[@class='abbreviation']/span[@lang='en' and text()='ar']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(etymologyWithArabicSrcLanguage, 1); const string etymologyWithGeorgianNotes = "//span[@class='etymologies']/span[@class='etymology']/span[@class='languagenotes']/span[@lang='en' and text()='Georgian']"; @@ -2008,7 +2008,7 @@ public void GenerateContentForEntry_SenseNumbersGeneratedForMultipleSenses(strin AddSenseToEntry(testEntry, "10", m_wsEn, Cache); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings).ToString(); string senseNumberOne = $"/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and @lang='{homographWs}' and text()='1']]//span[@lang='en' and text()='gloss']"; string senseNumberTwo = $"/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and @lang='{homographWs}' and text()='2']]//span[@lang='en' and text()='second gloss']"; string senseNumberTen = $"//span[@class='sensecontent']/spansenses/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and @lang='{homographWs}' and text()='{tenthSenseNumber}']]//span[@lang='en' and text()='10']"; @@ -2170,7 +2170,7 @@ public void GenerateContentForEntry_SingleSenseGetsNoSenseNumber() Assert.That(testEntry.AllSenses.Count, Is.EqualTo(1), "Test set up incorrectly. There should just be one sense."); Assert.That(testEntry.AllSenses.First().AllSenses.Count, Is.EqualTo(1), "Test not set up correctly. There should be no subsenses."); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings).ToString(); const string senseNumberOne = "/div[@class='lexentry']/span[@class='senses']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='1']]/span[@lang='en' and text()='gloss']"; // This assert is dependent on the specific entry data created in CreateInterestingLexEntry AssertThatXmlIn.String(result).HasNoMatchForXpath(senseNumberOne); @@ -2238,7 +2238,7 @@ public void GenerateContentForEntry_TurnedOffSubsensesCausesSenseToBehaveLikeSin Assert.That(testEntry.AllSenses.Count, Is.EqualTo(3), "Test set up incorrectly."); Assert.That(testEntry.AllSenses.First().AllSenses.Count, Is.EqualTo(3), "Test not set up correctly."); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings).ToString(); const string senseNumberXpath = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sensenumber']"; AssertThatXmlIn.String(result).HasNoMatchForXpath(senseNumberXpath); // Should not have a sense number on top sense. @@ -2305,7 +2305,7 @@ public void GenerateContentForEntry_EmptyStyleSubsensesCausesSenseToBehaveLikeSi Assert.That(testEntry.AllSenses.Count, Is.EqualTo(3), "Test set up incorrectly."); Assert.That(testEntry.AllSenses.First().AllSenses.Count, Is.EqualTo(3), "Test not set up correctly."); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings).ToString(); const string senseNumberXpath = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sensenumber']"; AssertThatXmlIn.String(result).HasNoMatchForXpath(senseNumberXpath); // Should not have a sense number on top sense. @@ -2372,7 +2372,7 @@ public void GenerateContentForEntry_SubsenseStyleInfluencesSenseNumberShown() Assert.That(testEntry.AllSenses.Count, Is.EqualTo(3), "Test set up incorrectly."); Assert.That(testEntry.AllSenses.First().AllSenses.Count, Is.EqualTo(3), "Test not set up correctly."); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings).ToString(); const string senseNumberXpath = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sensenumber']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(senseNumberXpath, 1); // Should have sense number on top sense. @@ -2422,7 +2422,7 @@ public void GenerateContentForEntry_NumberingSingleSenseAlsoCountsSubSense() AddSingleSubSenseToSense("gloss", testEntry.SensesOS.First()); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings).ToString(); const string SenseOneSubSense = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='1']]/span[@class='senses']/span[@class='sensecontent']//span[@lang='en' and text()='gloss1.1']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(SenseOneSubSense, 1); } @@ -2479,7 +2479,7 @@ public void GenerateContentForEntry_SensesAndSubSensesWithDifferentNumberingStyl AddSenseAndTwoSubsensesToEntry(testEntry, "second gloss"); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings).ToString(); const string senseNumberOne = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='A']]//span[@lang='en' and text()='gloss']"; const string senseNumberTwo = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='B']]//span[@lang='en' and text()='second gloss']"; const string subSensesNumberTwoOne = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='I']]//span[@lang='en' and text()='second gloss2.1']"; @@ -2544,7 +2544,7 @@ public void GenerateContentForEntry_SensesAndSubSensesWithNumberingStyle() AddSenseAndTwoSubsensesToEntry(testEntry, "second gloss"); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings).ToString(); const string senseNumberOne = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='A']]//span[@lang='en' and text()='gloss']"; const string senseNumberTwo = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='B']]//span[@lang='en' and text()='second gloss']"; const string subSensesNumberTwoOne = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='1']]//span[@lang='en' and text()='second gloss2.1']"; @@ -2596,7 +2596,7 @@ public void GenerateContentForEntry_NoSenseNumberFIfStyleSaysNoNumbering() AddSenseToEntry(testEntry, "gloss", m_wsEn, Cache); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings).ToString(); const string senseNumberXpath = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sensenumber']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(senseNumberXpath, 0); // Should not have produced sense number if style said not to number it. @@ -2656,7 +2656,7 @@ public void GenerateContentForEntry_SensesNoneAndSubSensesWithNumberingStyle() AddSenseAndTwoSubsensesToEntry(testEntry, "second gloss"); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings).ToString(); const string subSensesNumberOne = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='1']]//span[@lang='en' and text()='second gloss2.1']"; const string subSenseNumberTwo = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='2']]//span[@lang='en' and text()='second gloss2.2']"; //This assert is dependent on the specific entry data created in CreateInterestingLexEntry @@ -2698,7 +2698,7 @@ public void GenerateContentForEntry_SensesGeneratedForMultipleSubSenses() AddSenseAndTwoSubsensesToEntry(testEntry, "second gloss"); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings).ToString(); const string senseNumberOne = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='1']]//span[@lang='en' and text()='gloss']"; const string senseNumberTwo = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='2']]//span[@lang='en' and text()='second gloss']"; const string subSensesNumberTwoOne = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='1']]//span[@lang='en' and text()='second gloss2.1']"; @@ -2753,7 +2753,7 @@ public void GenerateContentForEntry_SubSenseParentSenseNumberingStyleJoined() AddSenseAndTwoSubsensesToEntry(testEntry.SensesOS[1].SensesOS[0], "matte"); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings).ToString(); const string senseNumber = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='2']]"; const string subSenseNumber = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='2a']]"; const string subSubSenseNumber = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='2aA']]"; @@ -2807,7 +2807,7 @@ public void GenerateContentForEntry_SubSenseParentSenseNumberingStyleSeparatedBy AddSenseAndTwoSubsensesToEntry(testEntry.SensesOS[1].SensesOS[0], "matte"); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings).ToString(); const string senseNumber = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='2']]"; const string subSenseNumber = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='2.a']]"; const string subSubSenseNumber = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='2.a.A']]"; @@ -2861,7 +2861,7 @@ public void GenerateContentForEntry_SubSenseParentSenseNumberingStyleNone() AddSenseAndTwoSubsensesToEntry(testEntry.SensesOS[1].SensesOS[0], "matte"); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings).ToString(); const string senseNumber = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='2']]"; const string subSenseNumber = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='a']]"; const string subSubSenseNumber = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='A']]"; @@ -2906,7 +2906,7 @@ public void GenerateContentForEntry_SubSubSensesWithNumberingStyle() AddSenseAndTwoSubsensesToEntry(testEntry.SensesOS[1].SensesOS[0], "matte"); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings).ToString(); const string senseNumberOne = "//span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='1']]//span[@lang='en' and text()='gloss']"; const string senseNumberTwo = "//span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='2']]//span[@lang='en' and text()='second gloss']"; const string subSenseNumberTwoOne = "//span[@class='share sense' and preceding-sibling::span[@class='sensenumber' and text()='1']]//span[@lang='en' and text()='second gloss2.1']"; @@ -3047,7 +3047,7 @@ public void GenerateContentForEntry_SubSensesOfSingleSenses_GetFullNumbers() AddSenseAndTwoSubsensesToEntry(testEntry.SensesOS[0], "subGloss"); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings).ToString(); const string subSenseNumberOneOne = "//span[@class='share sense' and preceding-sibling::span[@class='sensenumber' and text()='a']]//span[@lang='en' and text()='subGloss']"; const string subosoSenseNumberOneOneOne = "//span[@lang='en' and text()='subGloss2.1']"; const string subosoSenseNumberOneOneTwo = "//span[@lang='en' and text()='subGloss2.2']"; @@ -3097,7 +3097,7 @@ public void GenerateContentForEntry_SubentriesSensesDontGetMainEntrySensesNumber CreateComplexForm(Cache, testEntry.SensesOS[0], subEntry, true); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, settings).ToString(); const string senseContent = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']"; const string senseNumberOne = senseContent + "/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='1']]//span[@lang='en' and text()='gloss']"; const string subentrySenseContent = senseContent + "/span[@class='sense']/span[@class='subentries']/span[@class='subentry']/span[@class='senses']/span[@class='sensecontent']"; @@ -3137,7 +3137,7 @@ public void GenerateContentForEntry_SingleSenseGetsNumberWithNumberEvenOneSenseO var testEntry = CreateInterestingLexEntry(Cache); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, DefaultSettings).ToString(); const string senseNumberOne = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and preceding-sibling::span[@class='sensenumber' and text()='1']]//span[@lang='en' and text()='gloss']"; // This assert is dependent on the specific entry data created in CreateInterestingLexEntry AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(senseNumberOne, 1); @@ -3167,7 +3167,7 @@ public void GenerateContentForEntry_SenseContentWithGuid() var testEntry = CreateInterestingLexEntry(Cache); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, DefaultSettings).ToString(); const string senseEntryGuid = "/div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense' and @entryguid]"; // This assert is dependent on the specific entry data created in CreateInterestingLexEntry AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(senseEntryGuid, 1); @@ -3220,7 +3220,7 @@ public void GenerateContentForEntry_ExampleAndTranslationAreGenerated() AddExampleToSense(testEntry.SensesOS[0], example, Cache, m_wsFr, m_wsEn, translation); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, DefaultSettings).ToString(); const string xpathThruExample = xpathThruSense + "/span[@class='examplescontents']/span[@class='examplescontent']"; var oneSenseWithExample = string.Format(xpathThruExample + "/span[@class='example']/span[@lang='fr' and text()='{0}']", example); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(oneSenseWithExample, 1); @@ -3274,7 +3274,7 @@ public void GenerateContentForEntry_ExampleSentenceAndTranslationAreGenerated() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings).ToString(); const string xpathThruExampleSentence = "/div[@class='lexentry']/span[@class='complexformsnotsubentries']/span[@class='complexformsnotsubentry']/span[@class='examplesentences']/span[@class='examplesentence']"; var oneSenseWithExample = string.Format(xpathThruExampleSentence + "//span[@lang='fr' and text()='{0}']", example); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(oneSenseWithExample, 1); @@ -3328,7 +3328,7 @@ public void GenerateContentForEntry_LineSeperatorUnicodeCharBecomesBrElement() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings).ToString(); const string xpathThruExampleSentence = "/div[@class='lexentry']/span[@class='complexformsnotsubentries']/span[@class='complexformsnotsubentry']/span[@class='examplesentences']/span[@class='examplesentence']"; var oneSenseWithExample = string.Format(xpathThruExampleSentence + "//span[@lang='fr']//br"); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(oneSenseWithExample, 1); @@ -3403,7 +3403,7 @@ public void GenerateContentForEntry_ExtendedNoteChildrenAreGenerated() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); const string extendedNote = xpathThruSense + "/span[@class='extendednotecontents']/span[@class='extendednotecontent']"; var xpathThruNoteType = string.Format(extendedNote + "/span[@class='extendednotetypera_name']/span[@lang='en' and text()='{0}']", noteType); @@ -3485,7 +3485,7 @@ public void GenerateContentForEntry_ExtendedNoteNoteTypeEmptyAreGenerated() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); const string extendedNote = xpathThruSense + "/span[@class='extendednotecontents']/span[@class='extendednotecontent']"; var xpathThruNoteType = string.Format(extendedNote + "/span[@class='extendednotetypera_name']/span[@lang='en' and text()='{0}']", noteType); @@ -3561,7 +3561,7 @@ public void GenerateContentForEntry_EnvironmentsAndAllomorphsAreGenerated() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings).ToString(); const string xPathThruAllomorph = "/div[@class='lexentry']/span[@class='alternateformsos']/span[@class='alternateformso']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath( xPathThruAllomorph + "/span[@class='form']/span[@lang='fr' and text()='Allomorph']", 1); @@ -3598,7 +3598,7 @@ public void GenerateContentForEntry_ReferencedComplexFormsIncludesSubentriesAndO var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath( "/div[@class='lexentry']/span[@class='visiblecomplexformbackrefs']/span[@class='visiblecomplexformbackref']//span[@lang='fr']/span[@lang='fr']", 4); } @@ -3650,7 +3650,7 @@ public void GenerateContentForEntry_GeneratesLinksForReferencedForms() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath( "//span[@class='visiblevariantentryrefs']/span[@class='visiblevariantentryref']/span[@class='referencedentries']/span[@class='referencedentry']/span[@class='headword']/span[@lang='fr']/span[@lang='fr']/a[@href]", 2); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath( @@ -3749,7 +3749,7 @@ public void GenerateContentForEntry_GeneratesLinksForPrimaryEntryReferences() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(otherMainEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(otherMainEntry, mainEntryNode, null, settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath( "//span[@class='minimallexreferences']/span[@class='minimallexreference']/span[@class='configtargets']/span[@class='configtarget']/span[@class='primaryentryrefs']/span[@class='primaryentryref']/span[@class='referencedentries']/span[@class='referencedentry']/span[@class='headword']/span[@lang='fr']/a[@href]", 1); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath( @@ -3796,7 +3796,7 @@ public void GenerateContentForEntry_GeneratesLinksForCrossReferences() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath( "//span[@class='minimallexreferences']/span[@class='minimallexreference']/span[@class='configtargets']/span[@class='configtarget']/span[@class='headword']/span[@lang='fr']/span[@lang='fr']/a[@href]", 4); } @@ -3848,7 +3848,7 @@ public void GenerateContentForEntry_GeneratesLinksForCrossReferencesWithReferenc var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath( "//span[@class='configtargets']/span[@class='configtarget']/span[@class='headword']/span[@lang='fr']//a[@href]", 4); } @@ -3904,7 +3904,7 @@ public void GenerateContentForEntry_GeneratesCrossReferencesOnUnCheckConfigTarge var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT- - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings).ToString(); AssertThatXmlIn.String(result).HasNoMatchForXpath( "//span[@class='minimallexreferences']/span[@class='minimallexreference']/span[@class='configtargets']"); } @@ -3944,7 +3944,7 @@ public void GenerateContentForEntry_GeneratesForwardNameForSymmetricCrossReferen var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(referencedEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(referencedEntry, mainEntryNode, null, settings).ToString(); var fwdNameXpath = string.Format( "//span[@class='minimallexreferences']/span[@class='minimallexreference']/span[@class='ownertype_name']/span[@lang='en' and text()='{0}']", refTypeName); const string anyNameXpath = @@ -3989,7 +3989,7 @@ public void GenerateContentForEntry_GeneratesForwardNameForForwardCrossReference var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings).ToString(); var fwdNameXpath = string.Format( "//span[@class='minimallexreferences']/span[@class='minimallexreference']/span[@class='ownertype_name']/span[@lang='en' and text()='{0}']", refTypeName); var revNameXpath = string.Format( @@ -4034,7 +4034,7 @@ public void GenerateContentForEntry_GeneratesReverseNameForReverseCrossReference var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(referencedEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(referencedEntry, mainEntryNode, null, settings).ToString(); var fwdNameXpath = string.Format( "//span[@class='minimallexreferences']/span[@class='minimallexreference']/span[@class='ownertype_name']/span[@lang='en' and text()='{0}']", refTypeName); var revNameXpath = string.Format( @@ -4084,7 +4084,7 @@ public void GenerateContentForEntry_GeneratesForwardNameForForwardLexicalRelatio var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings).ToString(); var fwdNameXpath = string.Format( "//span[@class='lexsensereferences']/span[@class='lexsensereference']/span[@class='ownertype_name']/span[@lang='en' and text()='{0}']", refTypeName); var revNameXpath = string.Format( @@ -4135,7 +4135,7 @@ public void GenerateContentForEntry_GeneratesLexicalRelationsLabelWithNoRepetiti var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings).ToString(); var fwdNameXpath = string.Format( "//span[@class='lexsensereferences']/span[@class='lexsensereference']/span[@class='ownertype_name']/span[@lang='en' and text()='{0}']", refTypeName); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(fwdNameXpath, 1); @@ -4182,7 +4182,7 @@ public void GenerateContentForEntry_GeneratesReverseNameForReverseLexicalRelatio var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(referencedEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(referencedEntry, mainEntryNode, null, settings).ToString(); var fwdNameXpath = string.Format( "//span[@class='lexsensereferences']/span[@class='lexsensereference']/span[@class='ownertype_name']/span[@lang='en' and text()='{0}']", refTypeName); var revNameXpath = string.Format( @@ -4237,7 +4237,7 @@ public void GenerateContentForEntry_LexicalRelationsSortbyNodeOptionsOrder() CssGeneratorTests.PopulateFieldsForTesting(mainEntryNode); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings).ToString(); const string NameXpath = "//span[@class='minimallexreferences']/span[@class='minimallexreference' and position()='{0}']/span[@class='ownertype_name']/span[@lang='en' and text()='{1}']"; var fwdNameFirstXpath = string.Format(NameXpath, "1", etyRefTypeName); var fwdNameSecondXpath = string.Format(NameXpath, "2", comRefTypeName); @@ -4249,7 +4249,7 @@ public void GenerateContentForEntry_LexicalRelationsSortbyNodeOptionsOrder() Options = DictionaryDetailsControllerTests.ListOfEnabledDNOsFromStrings(new[] { comRefType.Guid + ":f", etyRefType.Guid + ":f" }) }; - var resultAfterChange = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings); + var resultAfterChange = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings).ToString(); var fwdNameChangedFirstXpath = string.Format(NameXpath, "1", comRefTypeName); var fwdNameChangedSecondXpath = string.Format(NameXpath, "2", etyRefTypeName); AssertThatXmlIn.String(resultAfterChange).HasSpecifiedNumberOfMatchesForXpath(fwdNameChangedFirstXpath, 1); @@ -4316,7 +4316,7 @@ public void GenerateContentForEntry_GeneratesAsymmetricRelationsProperly() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var output = ConfiguredLcmGenerator.GenerateContentForEntry(armEntry, mainEntryNode, null, settings); + var output = ConfiguredLcmGenerator.GenerateContentForEntry(armEntry, mainEntryNode, null, settings).ToString(); var fwdNameXpath = string.Format( "//span[@class='lexsensereferences']/span[@class='lexsensereference']/span[@class='ownertype_name']/span[@lang='en' and text()='{0}']", refTypeName); AssertThatXmlIn.String(output).HasNoMatchForXpath(fwdNameXpath); @@ -4394,7 +4394,7 @@ public void GenerateContentForEntry_GeneratesConfigTargetsForSubSenseProperly() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var output = ConfiguredLcmGenerator.GenerateContentForEntry(firstEntry, mainEntryNode, null, settings); + var output = ConfiguredLcmGenerator.GenerateContentForEntry(firstEntry, mainEntryNode, null, settings).ToString(); var goodTarget = string.Format( "//span[@class='lexsensereferences']/span[@class='lexsensereference']/span[@class='configtargets']/span[@class='configtarget']/span[@class='headword']/span[@lang='fr' and text()='{0}']", firstHeadword); AssertThatXmlIn.String(output).HasSpecifiedNumberOfMatchesForXpath(goodTarget, 1); @@ -4463,7 +4463,7 @@ public void GenerateContentForEntry_GeneratesConfigTargetsForTreeBetweenSenses() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(firstEntry, mainEntryNode, DefaultDecorator, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(firstEntry, mainEntryNode, DefaultDecorator, settings).ToString(); var goodTarget1 = "//span[@class='lexsensereferences']/span[@class='lexsensereference']/span[@class='ownertype_name']/span[text()='Part']/ancestor::span[1]/following-sibling::node()//span[@class='configtarget']/span[@class='gloss']/span[@lang='en' and text()='b2']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(goodTarget1, 1); @@ -4896,7 +4896,7 @@ public void GenerateContentForEntry_NoncheckedListItemsAreNotGenerated() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath( "/div[@class='lexentry']/span[@class='variantformentrybackrefs']/span[@class='variantformentrybackref']/span[@lang='fr']", 0); } @@ -4947,7 +4947,7 @@ public void GenerateContentForEntry_CheckedListItemsAreGenerated() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath( "/div[@class='lexentry']/span[@class='variantformentrybackrefs']/span[@class='variantformentrybackref']//span[@lang='fr']/span[@lang='fr']", 2); } @@ -5004,7 +5004,7 @@ public void GenerateContentForEntry_VariantTypeIsUncheckedAndHeadwordIsChecked() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath( "/div[@class='lexentry']/span[@class='variantformentrybackrefs']/span[@class='variantformentrybackref']/span[@class='referencedentries']" + "/span[@class='referencedentry']/span[@class='headword']/span[@lang='fr']/span[@lang='fr' and text()='Citation']", 1); @@ -5046,7 +5046,7 @@ public void GenerateContentForEntry_ReferencedComplexFormsUnderSensesIncludesSub var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath( xpathThruSense + "/span[@class='visiblecomplexformbackrefs']/span[@class='visiblecomplexformbackref']//span[@lang='fr']/span[@lang='fr']", 4); } @@ -5249,7 +5249,7 @@ public void GenerateContentForEntry_OneSenseWithSinglePicture() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); const string oneSenseWithPicture = "/div[@class='lexentry']/span[@class='pictures']/div[@class='picture']/img[@class='photo' and @id]"; const string oneSenseWithPictureCaption = "/div[@class='lexentry']/span[@class='pictures']/div[@class='picture']/div[@class='captionContent']/span[@class='caption']//span[text()='caption']"; //This assert is dependent on the specific entry data created in CreateInterestingLexEntry @@ -5287,8 +5287,8 @@ public void GenerateContentForEntry_PictureFileMissing() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); - Assert.That(result, Is.Empty); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); + Assert.IsEmpty(result); } /// LT-21573: PictureFileRA can be null after an incomplete SFM import @@ -5323,7 +5323,7 @@ public void GenerateContentForEntry_PictureFileRAMissing() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); Assert.That(result, Is.Empty); } @@ -5356,7 +5356,7 @@ public void GenerateContentForEntry_PictureWithCreator() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); const string oneSenseWithPicture = "/div[@class='lexentry']/span[@class='pictures']/div[@class='picture']/img[@class='photo' and @id]"; const string oneSenseWithPictureCaption = "/div[@class='lexentry']/span[@class='pictures']/div[@class='picture']/div[@class='captionContent']/span[@class='creator' and text()='Jason Naylor']"; //This assert is dependent on the specific entry data created in CreateInterestingLexEntry @@ -5388,7 +5388,7 @@ public void GenerateContentForEntry_PictureWithNonUnicodePathLinksCorrectly() // generates a src attribute with an absolute file path var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); var pictureWithComposedPath = "/div[@class='lexentry']/span[@class='pictures']/span[@class='picture']/img[contains(@src, '" + composedPath + "')]"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(pictureWithComposedPath, 1); } @@ -5416,7 +5416,7 @@ public void GenerateContentForEntry_PictureCopiedAndRelativePathUsed() try { //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); var pictureRelativePath = Path.Combine("pictures", Path.GetFileName(filePath)); var pictureWithComposedPath = "/div[@class='lexentry']/span[@class='pictures']/span[@class='picture']/img[starts-with(@src, '" + pictureRelativePath + "')]"; if (!Platform.IsUnix) @@ -5453,7 +5453,7 @@ public void GenerateContentForEntry_MissingPictureFileDoesNotCrashOnCopy() try { //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); var pictureRelativePath = Path.Combine("pictures", Path.GetFileName(filePath)); var pictureWithComposedPath = "/div[@class='lexentry']/span[@class='pictures']/span[@class='picture']/img[starts-with(@src, '" + pictureRelativePath + "')]"; if (!Platform.IsUnix) @@ -5508,7 +5508,7 @@ public void GenerateContentForEntry_TwoDifferentFilesGetTwoDifferentResults() { var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, true, true, tempFolder.FullName); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); var pictureRelativePath = Path.Combine("pictures", Path.GetFileName(fileName)); var pictureWithComposedPath = "/div[@class='lexentry']/span[@class='pictures']/span[@class='picture']/img[contains(@src, '" + pictureRelativePath + "')]"; if (!Platform.IsUnix) @@ -5563,7 +5563,7 @@ public void GenerateContentForEntry_UniqueIdsForSameFile() { var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, true, true, tempFolder.FullName); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); var pictureRelativePath = Path.Combine("pictures", Path.GetFileName(fileName)); const string pictureXPath = "/div[@class='lexentry']/span[@class='pictures']/span[@class='picture']/img"; var pictureWithComposedPath = pictureXPath + "[contains(@src, '" + pictureRelativePath + "')]"; @@ -5705,7 +5705,7 @@ public void GenerateContentForEntry_TwoDifferentLinksToTheSamefileWorks() { var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, true, true, tempFolder.FullName); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); var pictureRelativePath = Path.Combine("pictures", Path.GetFileName(fileName)); var pictureWithComposedPath = "/div[@class='lexentry']/span[@class='pictures']/span[@class='picture']/img[contains(@src, '" + pictureRelativePath + "')]"; if (!Platform.IsUnix) @@ -5747,7 +5747,7 @@ public void GenerateContentForEntry_StringCustomFieldGeneratesContent() Cache.MainCacheAccessor.SetString(testEntry.Hvo, customField.Flid, TsStringUtils.MakeString(customData, wsEn)); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); var customDataPath = string.Format("/div[@class='lexentry']/span[@class='customstring']/span[text()='{0}']", customData); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(customDataPath, 1); } @@ -5784,7 +5784,7 @@ public void GenerateContentForEntry_CustomFieldInGroupingNodeGeneratesContent() Cache.MainCacheAccessor.SetString(testEntry.Hvo, customField.Flid, TsStringUtils.MakeString(customData, wsEn)); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); var customDataPath = $"/div[@class='lexentry']/span[@class='grouping_customgroup']/span[@class='customstring']/span[text()='" + customData + "']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(customDataPath, 1); } @@ -5833,7 +5833,7 @@ public void GenerateContentForEntry_CustomFieldInNestedGroupingNodeGeneratesCont Cache.MainCacheAccessor.SetString(testEntry.Hvo, customField.Flid, TsStringUtils.MakeString(customData, wsEn)); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); const string grpXPath = "/span[@class='grouping_customgroup']"; var customDataPath = $"/div[@class='lexentry']{grpXPath}{grpXPath}{grpXPath}/span[@class='customstring']/span[text()='{customData}']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(customDataPath, 1); @@ -5901,7 +5901,7 @@ public void GenerateContentForEntry_StringCustomFieldOnSenseGeneratesContent() Cache.MainCacheAccessor.SetString(testSence.Hvo, customField.Flid, TsStringUtils.MakeString(customData, wsEn)); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); var customDataPath = string.Format("/div[@class='l']/span[@class='es']/span[@class='e']/span[@class='customstring']/span[text()='{0}']", customData); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(customDataPath, 1); } @@ -5947,7 +5947,7 @@ public void GenerateContentForEntry_StringCustomFieldOnExampleGeneratesContent() Cache.MainCacheAccessor.SetString(exampleSentence.Hvo, customField.Flid, TsStringUtils.MakeString(customData, wsEn)); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); var customDataPath = string.Format( "/div[@class='l']/span[@class='es']//span[@class='xs']/span[@class='x']/span[@class='customstring']/span[text()='{0}']", customData); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(customDataPath, 1); @@ -5986,7 +5986,7 @@ public void GenerateContentForEntry_StringCustomFieldOnAllomorphGeneratesContent Cache.MainCacheAccessor.SetString(allomorph.Hvo, customField.Flid, TsStringUtils.MakeString(customData, m_wsEn)); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); var customDataPath = string.Format( "/div[@class='l']/span[@class='as']/span[@class='a']/span[@class='customstring']/span[text()='{0}']", customData); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(customDataPath, 1); @@ -6018,7 +6018,7 @@ public void GenerateContentForEntry_MultiStringCustomFieldGeneratesContent() Cache.MainCacheAccessor.SetMultiStringAlt(testEntry.Hvo, customField.Flid, m_wsEn, TsStringUtils.MakeString(customData, m_wsEn)); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); var customDataPath = string.Format("/div[@class='lexentry']/span[@class='customstring']/span[text()='{0}']", customData); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(customDataPath, 1); } @@ -6080,7 +6080,7 @@ public void GenerateContentForEntry_CustomFieldOnISenseOrEntryGeneratesContentFo TsStringUtils.MakeString(senseCustomData, m_wsEn)); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); var entryDataPath = string.Format("/div[@class='lexentry']/span[@class='mlrs']/span[@class='mlr']/span[@class='configtargets']/span[@class='configtarget']/span[@class='entrycstring']/span[text()='{0}']", entryCustomData); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(entryDataPath, 1); var senseDataPath = string.Format("/div[@class='lexentry']/span[@class='mlrs']/span[@class='mlr']/span[@class='configtargets']/span[@class='configtarget']/span[@class='sensecstring']/span[text()='{0}']", senseCustomData); @@ -6144,7 +6144,7 @@ public void GenerateContentForEntry_CustomFieldOnISenseOrEntryGeneratesContentFo TsStringUtils.MakeString(senseCustomData, m_wsEn)); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); var entryDataPath = string.Format("/div[@class='lexentry']/span[@class='mlrs']/span[@class='mlr']/span[@class='configtargets']/span[@class='configtarget']/span[@class='entrycstring']/span[text()='{0}']", entryCustomData); AssertThatXmlIn.String(result).HasNoMatchForXpath(entryDataPath, message: "Ref is to Sense; should be no Entry Custom Data"); var senseDataPath = string.Format("/div[@class='lexentry']/span[@class='mlrs']/span[@class='mlr']/span[@class='configtargets']/span[@class='configtarget']/span[@class='sensecstring']/span[text()='{0}']", senseCustomData); @@ -6184,7 +6184,7 @@ public void GenerateContentForEntry_CustomFieldOnRefdLexEntryGeneratesContent() Cache.MainCacheAccessor.SetMultiStringAlt(refdEntry.Hvo, customField.Flid, m_wsEn, TsStringUtils.MakeString(customData, m_wsEn)); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); var customDataPath = string.Format("/div[@class='lexentry']/span[@class='vars']/span[@class='var']/span[@class='owningentry_customstring']/span[text()='{0}']", customData); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(customDataPath, 1); } @@ -6220,7 +6220,7 @@ public void GenerateContentForEntry_MultiStringDefinition_GeneratesMultilingualS var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); var definitionXpath = "//div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense']/span[@class='definition']/span[@lang='en']"; var str1Xpath = string.Format(definitionXpath + "/span[@lang='en' and text()='{0}']", multirunContent[0]); var str2Xpath = string.Format(definitionXpath + "/span[@lang='fr' and text()='{0}']", multirunContent[1]); @@ -6268,7 +6268,7 @@ public void GenerateContentForEntry_ListItemCustomFieldGeneratesContent() Cache.MainCacheAccessor.SetObjProp(testEntry.Hvo, customField.Flid, possibilityItem.Hvo); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); const string customDataPath = "/div[@class='lexentry']/span[@class='customlistitem']/span[@class='name']/span[text()='Djbuti']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(customDataPath, 1); } @@ -6309,7 +6309,7 @@ public void GenerateContentForEntry_MultiListItemCustomFieldGeneratesContent() Cache.MainCacheAccessor.Replace(testEntry.Hvo, customField.Flid, 0, 0, new[] { possibilityItem1.Hvo, possibilityItem2.Hvo }, 2); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); const string customDataPath1 = "/div[@class='lexentry']/span[@class='customlistitems']/span[@class='customlistitem']/span[@class='name']/span[text()='Dallas']"; const string customDataPath2 = "/div[@class='lexentry']/span[@class='customlistitems']/span[@class='customlistitem']/span[@class='name']/span[text()='Barcelona']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(customDataPath1, 1); @@ -6341,7 +6341,7 @@ public void GenerateContentForEntry_DateCustomFieldGeneratesContent() SilTime.SetTimeProperty(Cache.MainCacheAccessor, testEntry.Hvo, customField.Flid, customData); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); var customDataPath = string.Format("/div[@class='lexentry']/span[@class='customdate' and text()='{0}']", customData.ToLongDateString()); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(customDataPath, 1); } @@ -6371,7 +6371,7 @@ public void GenerateContentForEntry_IntegerCustomFieldGeneratesContent() Cache.MainCacheAccessor.SetInt(testEntry.Hvo, customField.Flid, customData); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, settings).ToString(); var customDataPath = string.Format("/div[@class='lexentry']/span[@class='custominteger' and text()='{0}']", customData); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(customDataPath, 1); } @@ -6402,7 +6402,7 @@ public void GenerateContentForEntry_MultiLineCustomFieldGeneratesContent() Cache.MainCacheAccessor.SetObjProp(testEntry.Hvo, customField.Flid, text.Hvo); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, rootNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, rootNode, null, settings).ToString(); const string customDataPath = "/div[@class='lexentry']/div/span[text()='First para Custom string'] | /div[@class='lexentry']/div/span[text()='Second para Custom string']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(customDataPath, 2); @@ -6440,7 +6440,7 @@ public void GenerateContentForEntry_VariantOfReferencedHeadWord() CreateVariantForm(Cache, variantForm, mainEntry); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, settings).ToString(); const string referencedEntries = "//span[@class='visiblevariantentryrefs']/span[@class='visiblevariantentryref']/span[@class='referencedentries']/span[@class='referencedentry']/span[@class='headword']/span[@lang='fr']/span[@lang='fr']"; AssertThatXmlIn.String(result) @@ -6471,7 +6471,7 @@ public void GenerateContentForEntry_WsAudiowithHyperlink() senseaudio.Form.set_String(wsEnAudio.Handle, TsStringUtils.MakeString(audioFileName, wsEnAudio.Handle)); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings).ToString(); const string audioTagwithSource = "//audio/source/@src"; AssertThatXmlIn.String(result) @@ -6560,7 +6560,7 @@ public void GenerateContentForEntry_AudioConversionDestinationDoesNotExist(bool File.Copy(path, Path.Combine(destination, Path.GetFileName(path)), true); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings).ToString(); if (isWebExport) { Assert.That(result, Contains.Substring("abu2.mp3"), "The automatic audio conversion in the CopyFileSafely method failed"); @@ -6649,7 +6649,7 @@ public void GenerateContentForEntry_AudioConversionIdenticalFileExists(bool isWe File.Copy(path, Path.Combine(destination, Path.GetFileName(path)), true); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings).ToString(); if (isWebExport) { Assert.That(result, Contains.Substring("abu2.mp3"), "The automatic audio conversion in the CopyFileSafely method failed"); @@ -6745,7 +6745,7 @@ public void GenerateContentForEntry_AudioConversionNonIdenticalFileExists(bool i } //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings).ToString(); if (isWebExport) { Assert.That(result, Contains.Substring("abu21.mp3"), "The automatic audio conversion code in the CopyFileSafely method did not change the file name as it should have since a file with the same name but different contents already exists"); @@ -6781,7 +6781,7 @@ public void GenerateContentForEntry_WsAudiowithRelativePaths() senseaudio.Form.set_String(wsEnAudio.Handle, TsStringUtils.MakeString(audioFileName, wsEnAudio.Handle)); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, true, true, "//audio/source/@src"); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, settings).ToString(); const string safeAudioId = "gTest_Audi_o"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath("//audio[contains(@id," + safeAudioId + ")]", 1); @@ -6917,7 +6917,7 @@ public void GenerateContentForEntry_GeneratesComplexFormTypeForSubentryUnderSens var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings).ToString(); var fwdNameXpath = string.Format( "//span[@class='sense']/span[@class='subentries']/span[@class='subentry']/span[@class='complexformtypes']/span[@class='complexformtype']/span/span[@lang='en' and text()='{0}']", complexRefAbbr); @@ -6965,7 +6965,7 @@ public void GenerateContentForEntry_GeneratesComplexFormTypeForSubentry() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings).ToString(); var fwdNameXpath = string.Format( "//span[@class='subentries']/span[@class='subentry']/span[@class='complexformtypes']/span[@class='complexformtype']/span/span[@lang='en' and text()='{0}']", complexRefAbbr); @@ -7036,7 +7036,7 @@ public void GenerateContentForEntry_GeneratesComplexFormTypeForSubsubentry([Valu var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings).ToString(); const string fwdNameXpath = "//span[@class='subentries']/span[@class='subentry subentry']/span[@class='subentries']/span[@class='subentry subentry']" + "/span[@class='complexformtypes']/span[@class='complexformtype']/span/span[@lang='en' and text()='{0}']"; @@ -7097,7 +7097,7 @@ public void GenerateContentForEntry_DoesntGeneratesComplexFormType_WhenDisabled( var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings).ToString(); const string refTypeXpath = "//span[@class='subentries']/span[@class='subentry']/span[@class='complexformtypes']"; AssertThatXmlIn.String(result).HasNoMatchForXpath(refTypeXpath); StringAssert.DoesNotContain(complexRefAbbr, result); @@ -7184,7 +7184,7 @@ public void GenerateContentForEntry_GeneratesComplexForm_NoTypeSpecified() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings).ToString(); const string refTypeXpath = "//span[@class='visiblecomplexformbackrefs']/span[@class='visiblecomplexformbackref']/span[@class='complexformtypes']/span[@class='complexformtype']"; AssertThatXmlIn.String(result).HasNoMatchForXpath(refTypeXpath); const string headwordXpath = "//span[@class='visiblecomplexformbackrefs']/span[@class='visiblecomplexformbackref']/span[@class='headword']"; @@ -7226,7 +7226,7 @@ public void GenerateContentForEntry_GeneratesSubentry_NoTypeSpecified() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings).ToString(); const string refTypeXpath = "//span[@class='subentries']/span[@class='subentry']/span[@class='complexformtypes']/span[@class='complexformtype']"; AssertThatXmlIn.String(result).HasNoMatchForXpath(refTypeXpath); const string headwordXpath = "//span[@class='subentries']/span[@class='subentry']/span[@class='headword']"; @@ -7272,13 +7272,13 @@ public void GenerateContentForEntry_ComplexFormDontGenerateReference() // When hiding minor entries this should still generate the reference (if not publishing to Webonary). var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null, false, false); - var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(withReference, 2); //SUT // When hiding minor entries and publishing to Webonary this should NOT generate the reference. settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null, false, true); - result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings); + result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(withReference, 0); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(withoutReference, 2); } @@ -7371,7 +7371,7 @@ public void GenerateContentForEntry_GeneratesVariant_NoTypeSpecified() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings).ToString(); const string refTypeXpath = "//span[@class='variantformentrybackrefs']/span[@class='variantformentrybackref']/span[@class='variantentrytypesrs']/span[@class='variantentrytypesr']"; AssertThatXmlIn.String(result).HasNoMatchForXpath(refTypeXpath); const string headwordXpath = "//span[@class='variantformentrybackrefs']/span[@class='variantformentrybackref']/span[@class='headword']"; @@ -7409,19 +7409,19 @@ public void GenerateContentForEntry_VariantShowsIfNotHideMinorEntry_ViewDoesntMa var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(variantEntry, model, null, settings); - Assert.That(result, Is.Null.Or.Empty); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(variantEntry, model, null, settings).ToString(); + Assert.IsEmpty(result); // try with HideMinorEntry off variantEntryRef.HideMinorEntry = 0; - result = ConfiguredLcmGenerator.GenerateContentForEntry(variantEntry, model, null, settings); + result = ConfiguredLcmGenerator.GenerateContentForEntry(variantEntry, model, null, settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath("/div[@class='lexentry']/span[@class='headword']", 1); // Should get the same results if in Root based view model.IsRootBased = true; - result = ConfiguredLcmGenerator.GenerateContentForEntry(variantEntry, model, null, settings); + result = ConfiguredLcmGenerator.GenerateContentForEntry(variantEntry, model, null, settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath("/div[@class='lexentry']/span[@class='headword']", 1); variantEntryRef.HideMinorEntry = 1; - result = ConfiguredLcmGenerator.GenerateContentForEntry(variantEntry, model, null, settings); - Assert.That(result, Is.Null.Or.Empty); + result = ConfiguredLcmGenerator.GenerateContentForEntry(variantEntry, model, null, settings).ToString(); + Assert.IsEmpty(result); } // Variant: Continue to generate the reference even if we are hiding the minor entry (useful for preview). @@ -7477,13 +7477,13 @@ public void GenerateContentForEntry_VariantGenerateReferenceForHiddenEntry() // When not hiding minor entries this should generate the reference. variantEntryRef.HideMinorEntry = 0; - var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, model, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, model, null, settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(withReference, 2); //SUT // When hiding minor entries this should still generate the reference. variantEntryRef.HideMinorEntry = 1; - result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, model, null, settings); + result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, model, null, settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(withReference, 2); } @@ -7541,13 +7541,13 @@ public void GenerateContentForEntry_VariantDontGenerateReference() // When hiding minor entries this should still generate the reference (if not publishing to Webonary). var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null, false, false); - var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, model, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, model, null, settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(withReference, 2); //SUT // When hiding minor entries and publishing to Webonary this should NOT generate the reference. settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null, false, true); - result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, model, null, settings); + result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, model, null, settings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(withReference, 0); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(withoutReference, 2); } @@ -7590,7 +7590,7 @@ public void GenerateContentForEntry_ReferencedNode_GeneratesBothClasses() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings).ToString(); const string headwordXpath = "//span[@class='reffingsubs']/span[@class='reffingsub sharedsubentry']/span[@class='headword']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(headwordXpath, 1); } @@ -7624,7 +7624,7 @@ public void GenerateContentForEntry_GeneratesCorrectMainAndMinorEntries() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForMainEntry(idiom, mainEntryNode, null, settings, 0); + var result = ConfiguredLcmGenerator.GenerateContentForMainEntry(idiom, mainEntryNode, null, settings, 0).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath("/div[@class='lexentry']/span[@class='headword']", 1); var css = ((CssGenerator)settings.StylesGenerator).GetStylesString(); // verify that the flow reset css is generated @@ -7632,7 +7632,7 @@ public void GenerateContentForEntry_GeneratesCorrectMainAndMinorEntries() Assert.That(css, Contains.Substring("clear:both")); var complexOptions = (DictionaryNodeListOptions)mainEntryNode.DictionaryNodeOptions; complexOptions.Options[0].IsEnabled = false; - result = ConfiguredLcmGenerator.GenerateContentForMainEntry(idiom, mainEntryNode, null, settings, 1); + result = ConfiguredLcmGenerator.GenerateContentForMainEntry(idiom, mainEntryNode, null, settings, 1).ToString(); Assert.IsEmpty(result); } @@ -7696,7 +7696,7 @@ public void GenerateContentForEntry_GeneratesCorrectMinorEntries( var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(minorEntry, model, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(minorEntry, model, null, settings).ToString(); var isComplexFormShowing = complexForm == FormType.Unspecified && isUnspecifiedComplexTypeEnabled; var isVariantFormShowing = variantForm == FormType.Unspecified && isUnspecifiedVariantTypeEnabled; @@ -7942,7 +7942,7 @@ public void GenerateContentForEntry_FilterByPublication() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var output = ConfiguredLcmGenerator.GenerateContentForEntry(entryCorps, mainEntryNode, pubEverything, settings); + var output = ConfiguredLcmGenerator.GenerateContentForEntry(entryCorps, mainEntryNode, pubEverything, settings).ToString(); Console.WriteLine(output); Assert.That(output, Is.Not.Null.Or.Empty); // Verify that the unfiltered output displays everything. @@ -7954,7 +7954,7 @@ public void GenerateContentForEntry_FilterByPublication() AssertThatXmlIn.String(output).HasSpecifiedNumberOfMatchesForXpath(matchFrenchPictureCaption, 1); //SUT - output = ConfiguredLcmGenerator.GenerateContentForEntry(entryCorps, mainEntryNode, pubMain, settings); + output = ConfiguredLcmGenerator.GenerateContentForEntry(entryCorps, mainEntryNode, pubMain, settings).ToString(); Assert.That(output, Is.Not.Null.Or.Empty); // Verify that the main publication output displays what it should. AssertThatXmlIn.String(output).HasSpecifiedNumberOfMatchesForXpath(matchFrenchEntry, 1); @@ -7968,7 +7968,7 @@ public void GenerateContentForEntry_FilterByPublication() AssertThatXmlIn.String(output).HasSpecifiedNumberOfMatchesForXpath(matchBodyIsBig, 1); //SUT - output = ConfiguredLcmGenerator.GenerateContentForEntry(entryCorps, mainEntryNode, pubTest, settings); + output = ConfiguredLcmGenerator.GenerateContentForEntry(entryCorps, mainEntryNode, pubTest, settings).ToString(); Assert.That(output, Is.Not.Null.Or.Empty); // Verify that the test publication output displays what it should. AssertThatXmlIn.String(output).HasSpecifiedNumberOfMatchesForXpath(matchFrenchEntry, 1); @@ -7982,7 +7982,7 @@ public void GenerateContentForEntry_FilterByPublication() AssertThatXmlIn.String(output).HasSpecifiedNumberOfMatchesForXpath(matchCorpseIsDead, 1); //SUT - output = ConfiguredLcmGenerator.GenerateContentForEntry(entryBras, mainEntryNode, pubEverything, settings); + output = ConfiguredLcmGenerator.GenerateContentForEntry(entryBras, mainEntryNode, pubEverything, settings).ToString(); Assert.That(output, Is.Not.Null.Or.Empty); // Verify that the unfiltered output displays everything. AssertThatXmlIn.String(output).HasSpecifiedNumberOfMatchesForXpath(matchFrenchEntry, 1); @@ -7991,7 +7991,7 @@ public void GenerateContentForEntry_FilterByPublication() AssertThatXmlIn.String(output).HasSpecifiedNumberOfMatchesForXpath(matchEnglishTranslation, 2); //SUT - output = ConfiguredLcmGenerator.GenerateContentForEntry(entryBras, mainEntryNode, pubMain, settings); + output = ConfiguredLcmGenerator.GenerateContentForEntry(entryBras, mainEntryNode, pubMain, settings).ToString(); Assert.That(output, Is.Not.Null.Or.Empty); // Verify that the main publication output displays everything. AssertThatXmlIn.String(output).HasSpecifiedNumberOfMatchesForXpath(matchFrenchEntry, 1); @@ -8002,7 +8002,7 @@ public void GenerateContentForEntry_FilterByPublication() //SUT // We can still produce test publication output for the entry since we have a copy of it. Its senses and // examples should not be displayed because the senses are separately hidden. - output = ConfiguredLcmGenerator.GenerateContentForEntry(entryBras, mainEntryNode, pubTest, settings); + output = ConfiguredLcmGenerator.GenerateContentForEntry(entryBras, mainEntryNode, pubTest, settings).ToString(); Assert.That(output, Is.Not.Null.Or.Empty); // Verify that the test output doesn't display the senses and examples. AssertThatXmlIn.String(output).HasSpecifiedNumberOfMatchesForXpath(matchFrenchEntry, 1); @@ -8011,7 +8011,7 @@ public void GenerateContentForEntry_FilterByPublication() AssertThatXmlIn.String(output).HasSpecifiedNumberOfMatchesForXpath(matchEnglishTranslation, 0); //SUT - output = ConfiguredLcmGenerator.GenerateContentForEntry(entryOreille, mainEntryNode, pubEverything, settings); + output = ConfiguredLcmGenerator.GenerateContentForEntry(entryOreille, mainEntryNode, pubEverything, settings).ToString(); Assert.That(output, Is.Not.Null.Or.Empty); // Verify that the unfiltered output displays everything. AssertThatXmlIn.String(output).HasSpecifiedNumberOfMatchesForXpath(matchFrenchEntry, 1); @@ -8022,7 +8022,7 @@ public void GenerateContentForEntry_FilterByPublication() //SUT // We can still produce main publication output for the entry since we have a copy of it. Its sense and // example should not be displayed because the sense is separately hidden. - output = ConfiguredLcmGenerator.GenerateContentForEntry(entryOreille, mainEntryNode, pubMain, settings); + output = ConfiguredLcmGenerator.GenerateContentForEntry(entryOreille, mainEntryNode, pubMain, settings).ToString(); Assert.That(output, Is.Not.Null.Or.Empty); // Verify that the test output doesn't display the sense and example. AssertThatXmlIn.String(output).HasSpecifiedNumberOfMatchesForXpath(matchFrenchEntry, 1); @@ -8031,7 +8031,7 @@ public void GenerateContentForEntry_FilterByPublication() AssertThatXmlIn.String(output).HasSpecifiedNumberOfMatchesForXpath(matchEnglishTranslation, 0); //SUT - output = ConfiguredLcmGenerator.GenerateContentForEntry(entryOreille, mainEntryNode, pubTest, settings); + output = ConfiguredLcmGenerator.GenerateContentForEntry(entryOreille, mainEntryNode, pubTest, settings).ToString(); Assert.That(output, Is.Not.Null.Or.Empty); // Verify that the test publication output displays everything. AssertThatXmlIn.String(output).HasSpecifiedNumberOfMatchesForXpath(matchFrenchEntry, 1); @@ -8045,7 +8045,7 @@ public void GenerateContentForEntry_FilterByPublication() const string matchVariantRef = "//span[@class='variantentrytypes']/span[@class='variantentrytype']/span[@class='name']/span[@lang='en']"; //SUT - output = ConfiguredLcmGenerator.GenerateContentForEntry(entryEntry, mainEntryNode, pubMain, settings); + output = ConfiguredLcmGenerator.GenerateContentForEntry(entryEntry, mainEntryNode, pubMain, settings).ToString(); Assert.That(output, Is.Not.Null.Or.Empty); // Verify that the main publication output displays what it should. AssertThatXmlIn.String(output).HasSpecifiedNumberOfMatchesForXpath(matchFrenchEntry, 1); @@ -8056,7 +8056,7 @@ public void GenerateContentForEntry_FilterByPublication() AssertThatXmlIn.String(output).HasSpecifiedNumberOfMatchesForXpath(matchVariantRef, 1); //SUT - output = ConfiguredLcmGenerator.GenerateContentForEntry(entryEntry, mainEntryNode, pubTest, settings); + output = ConfiguredLcmGenerator.GenerateContentForEntry(entryEntry, mainEntryNode, pubTest, settings).ToString(); Assert.That(output, Is.Not.Null.Or.Empty); // Verify that the test publication output displays what it should. AssertThatXmlIn.String(output).HasSpecifiedNumberOfMatchesForXpath(matchFrenchEntry, 1); @@ -8120,7 +8120,7 @@ public void GenerateContentForEntry_GeneratesVariantEntryTypesLabelWithNoRepetit using (CreateVariantForm(Cache, entryEntry, ve4, new Guid("00000000-0000-0000-0000-000000000004"), null)) // no Type; none generated { var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); - var output = ConfiguredLcmGenerator.GenerateContentForEntry(entryEntry, mainEntryNode, null, settings); + var output = ConfiguredLcmGenerator.GenerateContentForEntry(entryEntry, mainEntryNode, null, settings).ToString(); const string matchVariantRef = "//span[@class='variantentrytypes']/span[@class='variantentrytype']/span[@class='name']/span[@lang='en']"; AssertThatXmlIn.String(output).HasSpecifiedNumberOfMatchesForXpath(matchVariantRef, 2); } @@ -8183,7 +8183,7 @@ public void GenerateContentForEntry_GeneratesVariantEntryTypesShowOnlySelectedLi CreateVariantForm(Cache, entryEntry, ve1, "Free Variant"); // unique Type; CreateVariantForm(Cache, entryEntry, ve2, "Spelling Variant"); // unique Type; UnChecked var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); - var output = ConfiguredLcmGenerator.GenerateContentForEntry(entryEntry, mainEntryNode, null, settings); + var output = ConfiguredLcmGenerator.GenerateContentForEntry(entryEntry, mainEntryNode, null, settings).ToString(); const string matchFreeVariantRef = "//span[@class='variantentrytypes']/span[@class='variantentrytype']/span[@class='name']/span[@lang='en' and text()='Free Variant']"; AssertThatXmlIn.String(output).HasSpecifiedNumberOfMatchesForXpath(matchFreeVariantRef, 1); const string matchSpellingVariantRef = "//span[@class='variantentrytypes']/span[@class='variantentrytype']/span[@class='name']/span[@lang='en' and text()='Spelling Variant']"; @@ -8246,7 +8246,7 @@ public void GenerateContentForEntry_GeneratesComplexFormEntryTypesLabelWithNoRep }; CssGeneratorTests.PopulateFieldsForTesting(mainEntryNode); - var output = ConfiguredLcmGenerator.GenerateContentForEntry(entryEntry, mainEntryNode, pubMain, DefaultSettings); + var output = ConfiguredLcmGenerator.GenerateContentForEntry(entryEntry, mainEntryNode, pubMain, DefaultSettings).ToString(); const string matchComplexFormRef = "//span[@class='complexformtypes']/span[@class='complexformtype']/span[@class='name']/span[@lang='en']"; AssertThatXmlIn.String(output).HasSpecifiedNumberOfMatchesForXpath(matchComplexFormRef, 1); } @@ -8309,7 +8309,7 @@ public void GenerateContentForEntry_GeneratesComplexFormEntryTypesAndNamesGroup( }; CssGeneratorTests.PopulateFieldsForTesting(mainEntryNode); - var output = ConfiguredLcmGenerator.GenerateContentForEntry(entryEntry, mainEntryNode, pubMain, DefaultSettings); + var output = ConfiguredLcmGenerator.GenerateContentForEntry(entryEntry, mainEntryNode, pubMain, DefaultSettings).ToString(); const string matchComplexFormTypeCompound = "//span[@class='complexformtypes']/span[@class='complexformtype']/span[@class='name']/span[@lang='en' and text()='Compound']"; AssertThatXmlIn.String(output).HasSpecifiedNumberOfMatchesForXpath(matchComplexFormTypeCompound, 1); const string matchComplexFormTypeIdiom = "//span[@class='complexformtypes']/span[@class='complexformtype']/span[@class='name']/span[@lang='en' and text()='Idiom']"; @@ -8362,7 +8362,7 @@ public void GenerateContentForEntry_ComplexFormAndSenseInPara() CssGeneratorTests.PopulateFieldsForTesting(mainEntryNode); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, DefaultSettings).ToString(); const string senseXpath = "div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense']/span[@class='gloss']/span[@lang='en' and text()='gloss']"; var paracontinuationxpath = string.Format( "div[@class='lexentry']//span[@class='subentries']/span[@class='subentry']/span[@class='complexformtypes']/span[@class='complexformtype']/span[@class='reverseabbr']/span[@lang='en' and text()='{0}']", @@ -8421,19 +8421,19 @@ public void GenerateContentForEntry_MinorComplexForm_GeneratesGlossOrSummaryDefi var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(subentry1, minorEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(subentry1, minorEntryNode, null, settings).ToString(); const string complexFormEntryRefXpath = "div[@class='minorentrycomplex']/span[@class='complexformentryrefs']/span[@class='complexformentryref']"; const string referencedEntriesXpath = "/span[@class='referencedentries']/span[@class='referencedentry']"; const string glossOrSummXpath1 = complexFormEntryRefXpath + referencedEntriesXpath + "/span[@class='glossorsummary']/span[@lang='en' and text()='MainEntrySummaryDefn']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(glossOrSummXpath1, 1); //SUT - var result2 = ConfiguredLcmGenerator.GenerateContentForEntry(subentry2, minorEntryNode, null, settings); + var result2 = ConfiguredLcmGenerator.GenerateContentForEntry(subentry2, minorEntryNode, null, settings).ToString(); const string glossOrSummXpath2 = complexFormEntryRefXpath + referencedEntriesXpath + "/span[@class='glossorsummary']/span[@lang='en' and text()='gloss2']"; AssertThatXmlIn.String(result2).HasSpecifiedNumberOfMatchesForXpath(glossOrSummXpath2, 1); //SUT - var result3 = ConfiguredLcmGenerator.GenerateContentForEntry(subentry3, minorEntryNode, null, settings); + var result3 = ConfiguredLcmGenerator.GenerateContentForEntry(subentry3, minorEntryNode, null, settings).ToString(); const string glossOrSummXpath3 = complexFormEntryRefXpath + referencedEntriesXpath + "/span[@class='glossorsummary']/span[@lang='en' and text()='MainEntryS3Defn']"; AssertThatXmlIn.String(result3).HasSpecifiedNumberOfMatchesForXpath(glossOrSummXpath3, 1); } @@ -8464,7 +8464,7 @@ public void GenerateContentForEntry_ContinuationParagraphWithEmtpyContentDoesNot var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings).ToString(); const string senseXpath = "div[@class='lexentry']/span[@class='senses']/span[@class='sensecontent']/span[@class='sense']/span[@class='gloss']/span[@lang='en' and text()='gloss']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(senseXpath, 1); Assert.That(result, Does.Not.Match(@"
"), @@ -9050,7 +9050,7 @@ public void GenerateContentForEntry_EmbeddedWritingSystemGeneratesCorrectResult( var multiRunString = frenchString.Insert(12, englishStr); entry.Bibliography.set_String(m_wsFr, multiRunString); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, DefaultSettings).ToString(); const string nestedEn = "/div[@class='lexentry']/span[@class='bib']/span[@lang='fr']/span[@lang='en']"; const string nestedFr = "/div[@class='lexentry']/span[@class='bib']/span[@lang='fr']/span[@lang='fr']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(nestedEn, 1); @@ -9077,7 +9077,7 @@ public void GenerateContentForEntry_EmbeddedWritingSystemOfOppositeDirectionGene var wsHe = Cache.ServiceLocator.WritingSystemManager.GetWsFromStr("he"); entry.Bibliography.set_String(wsHe, multiRunString); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, DefaultSettings).ToString(); const string nestedEn = "/div[@class='lexentry']/span[@class='bib']/span[@lang='he']/span[@dir='rtl']/span[@lang='en']/span[@dir='ltr']"; const string nestedHe = "/div[@class='lexentry']/span[@class='bib']/span[@lang='he']/span[@dir='rtl']/span[@lang='he']"; const string extraDirection = "/div[@class='lexentry']/span[@class='bib']/span[@lang='he']/span[@dir='rtl']/span[@lang='he']/span[@dir='rtl']"; @@ -9107,7 +9107,7 @@ public void GenerateContentForEntry_WritingSystemOfSameDirectionGeneratesNoExtra entry.Bibliography.set_String(wsHe, multiRunString); var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null, true); // Right-to-Left //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings).ToString(); const string nestedEn = "/div[@class='lexentry']/span[@class='bib']/span[@lang='he']/span[@lang='en']/span[@dir='ltr']"; const string nestedHe = "/div[@class='lexentry']/span[@class='bib']/span[@lang='he']/span[@lang='he']"; const string extraDirection0 = "/div[@class='lexentry']/span[@class='bib']/span[@lang='he']/span[@dir='rtl']"; @@ -9145,7 +9145,7 @@ public void GenerateContentForEntry_EmbeddedHyperlinkGeneratesAnchor() (char)FwObjDataTypes.kodtExternalPathName + testUrl); entry.Bibliography.set_String(m_wsFr, stringBldr.GetString()); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, DefaultSettings).ToString(); string nestedLink = $"/div[@class='lexentry']/span[@class='bib']/span/span/a[@href='{testUrl}']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(nestedLink, 1); } @@ -9198,7 +9198,7 @@ public void GenerateContentForEntry_CompareRelations_SimpleSituations_SortByHead var mainEntryNode = ModelForCrossReferences(new[] { comRefType.Guid.ToString() }); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, DefaultSettings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(crossRefOwnerTypeXpath, 1); // ensure there is only one AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(CrossRefOwnerTypeXpath(comRefTypeName), 1); // ...the *correct* one AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(HeadwordOrderInCrossRefsXpath(1, "a"), 1); @@ -9223,7 +9223,7 @@ public void GenerateContentForEntry_CompareRelations_ComplexSituation_SortByHead var mainEntryNode = ModelForCrossReferences(new[] { comRefType.Guid.ToString() }); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, DefaultSettings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(CrossRefOwnerTypeXpath(comRefTypeName), 1); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(HeadwordOrderInCrossRefsXpath(1, "a"), 1); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(HeadwordOrderInCrossRefsXpath(2, "b"), 1); @@ -9249,7 +9249,7 @@ public void GenerateContentForEntry_CrossRefs_Sequences_SequencePreserved() var mainEntryNode = ModelForCrossReferences(new[] { colorType.Guid.ToString(), greekType.Guid.ToString() }); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(alphaEntry, mainEntryNode, null, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(alphaEntry, mainEntryNode, null, DefaultSettings).ToString(); // first sequence: colors: ARGB AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(CrossRefOwnerTypeXpath(colorTypeName), 1); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(HeadwordOrderInCrossRefsXpath(1, "alpha"), 2); // the first in both @@ -9274,7 +9274,7 @@ public void GenerateContentForEntry_CrossRefs_Unidirectional_SequencePreserved() var mainEntryNode = ModelForCrossReferences(new[] { characterType.Guid + ":f" }); // SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(stoogesEntry, mainEntryNode, null, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(stoogesEntry, mainEntryNode, null, DefaultSettings).ToString(); // sequence of Stooges: AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(CrossRefOwnerTypeXpath(characterTypeName), 1); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(HeadwordOrderInCrossRefsXpath(1, "Larry"), 1); @@ -9436,7 +9436,7 @@ public void GenerateContentForEntry_LexicalReferencesOrderedCorrectly([Values(tr //SUT //Console.WriteLine(LcmXhtmlGenerator.SavePreviewHtmlWithStyles(new[] { manEntry.Hvo, familyEntry.Hvo, girlEntry.Hvo, individualEntry.Hvo }, null, // new DictionaryConfigurationModel { Parts = new List { mainEntryNode } }, m_mediator)); // full output for diagnostics - var manResult = ConfiguredLcmGenerator.GenerateContentForEntry(manEntry, mainEntryNode, null, settings); + var manResult = ConfiguredLcmGenerator.GenerateContentForEntry(manEntry, mainEntryNode, null, settings).ToString(); AssertThatXmlIn.String(manResult).HasSpecifiedNumberOfMatchesForXpath(xpathLexRef, 2); // antonyms are grouped into one span var idxAntonymAbbr = manResult.IndexOf(antAbbrSpan, StringComparison.Ordinal); var idxWhole = manResult.IndexOf(whSpan, StringComparison.Ordinal); @@ -9462,7 +9462,7 @@ public void GenerateContentForEntry_LexicalReferencesOrderedCorrectly([Values(tr // Ignore if usingSubfield. Justification: Part-Whole direction is miscalculated for field=Entry, subfield=MinimalLexReferences (LT-17571) if (!usingSubfield) { - var familyResult = ConfiguredLcmGenerator.GenerateContentForEntry(familyEntry, mainEntryNode, null, settings); + var familyResult = ConfiguredLcmGenerator.GenerateContentForEntry(familyEntry, mainEntryNode, null, settings).ToString(); AssertThatXmlIn.String(familyResult).HasSpecifiedNumberOfMatchesForXpath(xpathLexRef, 2); idxAntonymAbbr = familyResult.IndexOf(antAbbrSpan, StringComparison.Ordinal); idxWhole = familyResult.IndexOf(whSpan, StringComparison.Ordinal); @@ -9475,7 +9475,7 @@ public void GenerateContentForEntry_LexicalReferencesOrderedCorrectly([Values(tr Assert.Less(idxAntonymAbbr, idxAntonymName, "Antonym name should come after Antonym abbreviation"); // SUT: Ensure that both directions of part-whole are kept separate - var girlResult = ConfiguredLcmGenerator.GenerateContentForEntry(girlEntry, mainEntryNode, null, settings); + var girlResult = ConfiguredLcmGenerator.GenerateContentForEntry(girlEntry, mainEntryNode, null, settings).ToString(); AssertThatXmlIn.String(girlResult).HasSpecifiedNumberOfMatchesForXpath(xpathLexRef, 2); // whole and part idxAntonymAbbr = girlResult.IndexOf(antAbbrSpan, StringComparison.Ordinal); idxWhole = girlResult.IndexOf(whSpan, StringComparison.Ordinal); @@ -9488,7 +9488,7 @@ public void GenerateContentForEntry_LexicalReferencesOrderedCorrectly([Values(tr Assert.AreEqual(-1, idxAntonymName, "Antonym name relation should not exist for fille (girl)"); } - var individualResult = ConfiguredLcmGenerator.GenerateContentForEntry(individualEntry, mainEntryNode, null, settings); + var individualResult = ConfiguredLcmGenerator.GenerateContentForEntry(individualEntry, mainEntryNode, null, settings).ToString(); AssertThatXmlIn.String(individualResult).HasSpecifiedNumberOfMatchesForXpath(xpathLexRef, 1); idxAntonymAbbr = individualResult.IndexOf(antAbbrSpan, StringComparison.Ordinal); idxWhole = individualResult.IndexOf(whSpan, StringComparison.Ordinal); @@ -9548,7 +9548,7 @@ public void GenerateContentForEntry_VariantsOfEntryAreOrdered() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings).ToString(); // Test that variantformentrybackref items are in alphabetical order Assert.That(result.IndexOf("headwordA", StringComparison.InvariantCulture), @@ -9610,7 +9610,7 @@ public void GenerateContentForEntry_TypeBeforeForm() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings).ToString(); // Test that variantentrytypes is before variantformentrybackref Assert.That(result.IndexOf("variantentrytypes", StringComparison.InvariantCulture), @@ -9678,7 +9678,7 @@ public void GenerateContentForEntry_ComplexFormsAreOrderedAsUserSpecified( var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(lexentry, mainEntryNode, null, settings).ToString(); // Test that variantformentrybackref items are in (alphabetical or) virtual order Assert.That(result.IndexOf(headwords[0], StringComparison.InvariantCulture), @@ -9742,7 +9742,7 @@ public void GenerateContentForFieldByReflection_VariantFormTypesAreOrderedBasedO CreateInterestingLexEntry(Cache, "headwordB").MakeVariantOf(lexentry, (ILexEntryType)finalTypeInOptionsList); // SUT1 - var result = ConfiguredLcmGenerator.GenerateContentForFieldByReflection(lexentry, variantFormNode, null, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForFieldByReflection(lexentry, variantFormNode, null, DefaultSettings).ToString(); Assert.That(result.IndexOf("headwordA", StringComparison.InvariantCulture), Is.LessThan(result.IndexOf("headwordB", StringComparison.InvariantCulture)), "variant forms not appearing in an order corresponding to their type sorting"); @@ -9751,7 +9751,7 @@ public void GenerateContentForFieldByReflection_VariantFormTypesAreOrderedBasedO ((DictionaryNodeListOptions)variantFormNode.DictionaryNodeOptions).Options.Reverse(); // SUT2 - result = ConfiguredLcmGenerator.GenerateContentForFieldByReflection(lexentry, variantFormNode, null, DefaultSettings); + result = ConfiguredLcmGenerator.GenerateContentForFieldByReflection(lexentry, variantFormNode, null, DefaultSettings).ToString(); Assert.That(result.IndexOf("headwordB", StringComparison.InvariantCulture), Is.LessThan(result.IndexOf("headwordA", StringComparison.InvariantCulture)), "variant forms not appearing in an order corresponding to their type sorting"); @@ -9802,7 +9802,7 @@ public void GenerateContentForFieldByReflection_SubentryTypesAreOrderedBasedOnOp CreateComplexForm(Cache, lexentry, CreateInterestingLexEntry(Cache, "headwordB"), true, finalTypeInOptionsListGuid); // SUT1 - var result = ConfiguredLcmGenerator.GenerateContentForFieldByReflection(lexentry, subentryNode, null, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForFieldByReflection(lexentry, subentryNode, null, DefaultSettings).ToString(); Assert.That(result.IndexOf("headwordA", StringComparison.InvariantCulture), Is.LessThan(result.IndexOf("headwordB", StringComparison.InvariantCulture)), "Subentries should be sorted by Type"); @@ -9811,7 +9811,7 @@ public void GenerateContentForFieldByReflection_SubentryTypesAreOrderedBasedOnOp ((DictionaryNodeListOptions)subentryNode.DictionaryNodeOptions).Options.Reverse(); // SUT2 - result = ConfiguredLcmGenerator.GenerateContentForFieldByReflection(lexentry, subentryNode, null, DefaultSettings); + result = ConfiguredLcmGenerator.GenerateContentForFieldByReflection(lexentry, subentryNode, null, DefaultSettings).ToString(); Assert.That(result.IndexOf("headwordB", StringComparison.InvariantCulture), Is.LessThan(result.IndexOf("headwordA", StringComparison.InvariantCulture)), "Subentries should be sorted by Type"); @@ -10052,7 +10052,7 @@ public void GenerateNextFewEntries_UpReturnsRequestedEntries() var entries = LcmXhtmlGenerator.GenerateNextFewEntries(pubEverything, new[] { firstEntry.Hvo, secondEntry.Hvo, thirdEntry.Hvo, fourthEntry.Hvo }, configPath, settings, currentPage, adjPage, 1, out current, out adjacent); Assert.AreEqual(1, entries.Count, "No entries generated"); - Assert.That(entries[0], Does.Contain(thirdEntry.HeadWord.Text)); + Assert.That(entries[0].ToString(), Does.Contain(thirdEntry.HeadWord.Text)); } finally { @@ -10107,8 +10107,8 @@ public void GenerateNextFewEntries_DownReturnsRequestedEntries() var entries = LcmXhtmlGenerator.GenerateNextFewEntries(pubEverything, new[] { firstEntry.Hvo, secondEntry.Hvo, thirdEntry.Hvo, fourthEntry.Hvo }, configPath, settings, currentPage, adjPage, 2, out current, out adjacent); Assert.AreEqual(2, entries.Count, "Not enough entries generated"); - Assert.That(entries[0], Does.Contain(thirdEntry.HeadWord.Text)); - Assert.That(entries[1], Does.Contain(fourthEntry.HeadWord.Text)); + Assert.That(entries[0].ToString(), Does.Contain(thirdEntry.HeadWord.Text)); + Assert.That(entries[1].ToString(), Does.Contain(fourthEntry.HeadWord.Text)); Assert.That(adjacent, Is.Null); } finally @@ -10142,7 +10142,7 @@ public void GenerateContentForEntry_GroupingNodeGeneratesSpanAndInnerContentWork var testEntry = CreateInterestingLexEntry(Cache); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, DefaultSettings).ToString(); const string oneSenseWithGlossOfGloss = "/div[@class='lexentry']/span[@class='grouping_sensegroup']" + "/span[@class='senses']/span[@class='sense']//span[@lang='en' and text()='gloss']"; @@ -10175,7 +10175,7 @@ public void GenerateContentForEntry_GeneratesNFC() entry.CitationForm.set_String(wsKo, headword); Assert.That(entry.CitationForm.get_String(wsKo).get_IsNormalizedForm(FwNormalizationMode.knmNFD), "Should be NFDecomposed in memory"); Assert.AreEqual(6, headword.Text.Length); - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, node, null, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, node, null, DefaultSettings).ToString(); var tsResult = TsStringUtils.MakeString(result, Cache.DefaultAnalWs); Assert.False(TsStringUtils.IsNullOrEmpty(tsResult), "Results should have been generated"); Assert.That(tsResult.get_IsNormalizedForm(FwNormalizationMode.knmNFC), "Resulting XHTML should be NFComposed"); @@ -10204,7 +10204,7 @@ public void GenerateContentForEntry_CompareRelations_ComplexSituation_CustomSort Assert.That(comRefType, Is.Not.Null); var mainEntryNode = ModelForCrossReferences(new[] { comRefType.Guid.ToString() }); - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, DefaultSettings).ToString(); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(CrossRefOwnerTypeXpath(comRefTypeName), 1); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(HeadwordOrderInCrossRefsXpath(1, "atest"), 1); AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(HeadwordOrderInCrossRefsXpath(2, "ctest"), 1); diff --git a/Src/xWorks/xWorksTests/CssGeneratorTests.cs b/Src/xWorks/xWorksTests/CssGeneratorTests.cs index 6bb1391a9f..6af2b10723 100644 --- a/Src/xWorks/xWorksTests/CssGeneratorTests.cs +++ b/Src/xWorks/xWorksTests/CssGeneratorTests.cs @@ -1289,7 +1289,7 @@ public void ClassMappingOverrides_ApplyAtRoot() using (var XHTMLWriter = XmlWriter.Create(xhtmResult)) { XHTMLWriter.WriteStartElement("body"); - var content = ConfiguredLcmGenerator.GenerateContentForEntry(entry, testNode, null, DefaultSettings); + var content = ConfiguredLcmGenerator.GenerateContentForEntry(entry, testNode, null, DefaultSettings).ToString(); XHTMLWriter.WriteRaw(content); XHTMLWriter.WriteEndElement(); XHTMLWriter.Flush(); @@ -1331,7 +1331,7 @@ public void ClassMappingOverrides_ApplyToChildren() Assert.That(cssResult, Does.Not.Contain(".headword")); Assert.That(cssResult, Contains.Substring(".tailwind")); - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, testParentNode, null, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, testParentNode, null, DefaultSettings).ToString(); const string positiveTest = "//*[@class='tailwind']"; const string negativeTest = "//*[@class='headword']"; AssertThatXmlIn.String(result).HasNoMatchForXpath(negativeTest); @@ -1372,7 +1372,7 @@ public void CssAndXhtmlMatchOnSenseCollectionItems() var cssResult = CssGenerator.GenerateCssFromConfiguration(model, m_propertyTable); Assert.That(cssResult, Contains.Substring(".gloss")); - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, testEntryNode, null, DefaultSettings); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, testEntryNode, null, DefaultSettings).ToString(); const string positiveTest = "/*[@class='lexentry']/span[@class='senses']/span[@class='sense']/span[@class='gloss']"; AssertThatXmlIn.String(result).HasSpecifiedNumberOfMatchesForXpath(positiveTest, 1); } diff --git a/Src/xWorks/xWorksTests/LcmJsonGeneratorTests.cs b/Src/xWorks/xWorksTests/LcmJsonGeneratorTests.cs index 52e1e68f99..6241df3c96 100644 --- a/Src/xWorks/xWorksTests/LcmJsonGeneratorTests.cs +++ b/Src/xWorks/xWorksTests/LcmJsonGeneratorTests.cs @@ -156,7 +156,7 @@ public void GenerateJsonForEntry_OneSenseWithGlossGeneratesCorrectResult() var entry = ConfiguredXHTMLGeneratorTests.CreateInterestingLexEntry(Cache); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, DefaultSettings, 0); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, DefaultSettings, 0).ToString(); Console.WriteLine(result); var expectedResult = @"{""xhtmlTemplate"": ""lexentry"",""guid"":""g" + entry.Guid + @""",""letterHead"": ""c"",""sortIndex"": 0, ""senses"": [{""guid"":""g" + entry.Guid + @""",""gloss"": [{""lang"":""en"",""value"":""gloss""}]},]}"; @@ -189,7 +189,7 @@ public void GenerateJsonForEntry_DefinitionOrGloss_HandlePerWS() var wsEs = ConfiguredXHTMLGeneratorTests.EnsureWritingSystemSetup(Cache, "es", false); entry.SensesOS.First().Definition.set_String(wsEs, "definition"); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, DefaultSettings, 0); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, DefaultSettings, 0).ToString(); var expectedResults = @"{""xhtmlTemplate"": ""lexentry"",""guid"":""g" + entry.Guid + @""",""letterHead"": ""c"",""sortIndex"": 0, ""senses"": [{""guid"":""g" + entry.Guid + @""", ""definitionorgloss"": [{""lang"":""en"",""value"":""gloss""}, @@ -255,7 +255,7 @@ public void GenerateJsonForEntry_TwoSensesWithSameInfoShowGramInfoFirst_Json() var settings = new ConfiguredLcmGenerator.GeneratorSettings(Cache, m_propertyTable, false, false, null) { ContentGenerator = new LcmJsonGenerator(Cache) }; //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings, 0); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings, 0).ToString(); Console.WriteLine(result); var expectedResults = @"{""xhtmlTemplate"": ""lexentry"",""guid"":""g" + entry.Guid + @""",""letterHead"": ""c"",""sortIndex"": 0, ""msas"": {""mlpartofspeech"": [{""lang"":""en"",""value"":""Blah""}]}, ""senses"": [{""guid"":""g" + entry.Guid + @""",""gloss"": [{""lang"":""en"",""value"":""gloss""}]}, @@ -296,7 +296,7 @@ public void GenerateJsonForEntry_OneSenseWithSinglePicture() var settings = DefaultSettings; //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings, 0); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, settings, 0).ToString(); var expectedResults = @"{""xhtmlTemplate"": ""lexentry"",""guid"":""g" + entry.Guid + @""",""letterHead"": ""c"",""sortIndex"": 0, ""pictures"": [{""guid"":""g" + sensePic.Guid + @""",""src"":""pictures/test_auth_copy_license.jpg"", ""sensenumber"": [{""lang"":""en"",""value"":""1""}],""caption"": [{""lang"":""en"",""value"":""caption""}]}]}"; @@ -510,7 +510,7 @@ public void GenerateJsonForEntry_FilterByPublication() CssGeneratorTests.PopulateFieldsForTesting(mainEntryNode); //SUT - var output = ConfiguredLcmGenerator.GenerateContentForEntry(entryEntry, mainEntryNode, pubMain, DefaultSettings, 0); + var output = ConfiguredLcmGenerator.GenerateContentForEntry(entryEntry, mainEntryNode, pubMain, DefaultSettings, 0).ToString(); Assert.That(output, Is.Not.Null.Or.Empty); var expectedResults = "{\"xhtmlTemplate\": \"lexentry\",\"guid\":\"g" + entryEntry.Guid + "\",\"letterHead\": \"e\",\"sortIndex\": 0," + "\"entry\": [{\"lang\":\"fr\",\"value\":\"entry\"}],\"senses\": [{\"guid\":\"g" + @@ -580,7 +580,7 @@ public void GenerateJsonForEntry_TypeAfterForm() CssGeneratorTests.PopulateFieldsForTesting(mainEntryNodeTypeAfter); //SUT - var outputTypeAfter = ConfiguredLcmGenerator.GenerateContentForEntry(entryEntry, mainEntryNodeTypeAfter, pubMain, DefaultSettings, 0); + var outputTypeAfter = ConfiguredLcmGenerator.GenerateContentForEntry(entryEntry, mainEntryNodeTypeAfter, pubMain, DefaultSettings, 0).ToString(); Assert.That(outputTypeAfter, Is.Not.Null.Or.Empty); var expectedResultsTypeAfter = "{\"xhtmlTemplate\": \"lexentry\",\"guid\":\"g" + entryEntry.Guid + "\",\"letterHead\": \"e\",\"sortIndex\": 0," + "\"entry\": [{\"lang\":\"fr\",\"value\":\"entry\"}]," + @@ -615,7 +615,7 @@ public void GenerateJsonForEntry_WsAudiowithHyperlink() const string audioFileName = "Test Audi'o.wav"; senseaudio.Form.set_String(wsEnAudio.Handle, TsStringUtils.MakeString(audioFileName, wsEnAudio.Handle)); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, DefaultSettings, 0); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, DefaultSettings, 0).ToString(); var expectedResults = @"{""xhtmlTemplate"": ""lexentry"",""guid"": ""g" + entryOne.Guid + @""",""letterHead"": ""c"",""sortIndex"": 0, ""headword"": [{""guid"": ""g" + entryOne.Guid + @""", ""lang"":""en-Zxxx-x-audio"", ""value"": {""id"": ""gTest_Audi_o"", ""src"": ""AudioVisual/Test Audi'o.wav""}}]}"; @@ -672,7 +672,7 @@ public void GenerateJsonForEntry_SensibleJsonForVideoFiles() CssGeneratorTests.PopulateFieldsForTesting(mainEntryNode); //SUT - var output = ConfiguredLcmGenerator.GenerateContentForEntry(entryCorps, mainEntryNode, DefaultDecorator, DefaultSettings, 0); + var output = ConfiguredLcmGenerator.GenerateContentForEntry(entryCorps, mainEntryNode, DefaultDecorator, DefaultSettings, 0).ToString(); Assert.That(output, Is.Not.Null.Or.Empty); var expectedResults = "{\"xhtmlTemplate\":\"lexentry\",\"guid\":\"g" + entryCorps.Guid + "\",\"letterHead\":\"c\",\"sortIndex\":0," + "\"entry\": [{\"lang\":\"fr\",\"value\":\"corps\"}]," + @@ -702,7 +702,7 @@ public void GenerateJsonForEntry_SenseNumbersGeneratedForMultipleSenses() var testEntry = ConfiguredXHTMLGeneratorTests.CreateInterestingLexEntry(Cache); ConfiguredXHTMLGeneratorTests.AddSenseToEntry(testEntry, "second gloss", m_wsEn, Cache); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, DefaultSettings, 0); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, DefaultDecorator, DefaultSettings, 0).ToString(); var expectedResults = @"{""xhtmlTemplate"": ""lexentry"",""guid"":""g" + testEntry.Guid + @""",""letterHead"": ""c"",""sortIndex"": 0,""senses"":[{""senseNumber"":""1"", ""guid"":""g" + testEntry.Guid + @""",""gloss"":[{""lang"":""en"",""value"":""gloss""}]}, {""senseNumber"":""2"",""guid"":""g" + testEntry.Guid + @""",""gloss"":[{""lang"":""en"",""value"":""second gloss""}]}]}"; @@ -731,7 +731,7 @@ public void GenerateJsonForEntry_EmbeddedWritingSystemGeneratesCorrectResult() var multiRunString = frenchString.Insert(12, englishStr); entry.Bibliography.set_String(m_wsFr, multiRunString); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, DefaultSettings, 0); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, DefaultSettings, 0).ToString(); var expectedResults = @"{""xhtmlTemplate"": ""lexentry"",""guid"":""g" + entry.Guid + @""",""letterHead"": ""c"",""sortIndex"": 0,""bib"": [{""lang"":""fr"",""value"":""French with ""}, {""lang"":""en"",""value"":""English""},{""lang"":""fr"",""value"":"" embedded""}]}"; var expected = (JObject)JsonConvert.DeserializeObject(expectedResults, new JsonSerializerSettings { Formatting = Formatting.None }); @@ -757,7 +757,7 @@ public void GenerateJsonForEntry_UnicodeLineBreak_GeneratesValidJson() var englishStr = TsStringUtils.MakeString("English\u2028with line break", m_wsEn); entry.Bibliography.set_String(m_wsFr, englishStr); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, DefaultSettings, 0); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, DefaultSettings, 0).ToString(); var expectedResults = @"{""xhtmlTemplate"": ""lexentry"",""guid"":""g" + entry.Guid + @""",""letterHead"": ""c"",""sortIndex"": 0, ""bib"": [{""lang"":""en"",""value"":""English\nwith line break""}]}"; var expected = (JObject)JsonConvert.DeserializeObject(expectedResults, new JsonSerializerSettings { Formatting = Formatting.None }); @@ -804,7 +804,7 @@ public void GenerateJsonForEntry_GeneratesForwardNameForForwardLexicalRelations( CssGeneratorTests.PopulateFieldsForTesting(mainEntryNode); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, DefaultSettings, 0); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, DefaultSettings, 0).ToString(); var expectedResults = @"{""xhtmlTemplate"": ""lexentry"",""guid"":""g" + mainEntry.Guid + @""",""letterHead"": ""c"",""sortIndex"": 0, ""sensesos"": [{""lexsensereferences"": [{""ownertype_name"": [{""lang"":""en"",""value"":""TestRefType""}]}]}]}"; var expected = (JObject)JsonConvert.DeserializeObject(expectedResults, new JsonSerializerSettings { Formatting = Formatting.None }); @@ -847,7 +847,7 @@ public void GenerateJsonForEntry_EmptyNameOnLexicalRelation_GeneratesEmptyButVal CssGeneratorTests.PopulateFieldsForTesting(mainEntryNode); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, DefaultSettings, 0); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, DefaultSettings, 0).ToString(); var expectedResults = @"{""xhtmlTemplate"": ""lexentry"",""guid"":""g" + mainEntry.Guid + @""",""letterHead"": ""c"",""sortIndex"": 0, ""sensesos"": [{""lexsensereferences"": [{}]}]}"; var expected = (JObject)JsonConvert.DeserializeObject(expectedResults, new JsonSerializerSettings { Formatting = Formatting.None }); @@ -874,12 +874,12 @@ public void GenerateJsonForEntry_HomographNumbersGeneratesCorrectResult() var entryTwo = ConfiguredXHTMLGeneratorTests.CreateInterestingLexEntry(Cache); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, DefaultSettings, 0); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, DefaultSettings, 0).ToString(); var expectedResults = @"{""xhtmlTemplate"": ""lexentry"",""guid"":""g" + entryOne.Guid + @""",""letterHead"": ""c"",""sortIndex"": 0,""homographnumber"": ""1"", ""citationform"": [{""lang"":""fr"",""value"":""Citation""}]}"; var expected = (JObject)JsonConvert.DeserializeObject(expectedResults, new JsonSerializerSettings { Formatting = Formatting.None }); VerifyJson(result, expected); - result = ConfiguredLcmGenerator.GenerateContentForEntry(entryTwo, mainEntryNode, null, DefaultSettings, 0); + result = ConfiguredLcmGenerator.GenerateContentForEntry(entryTwo, mainEntryNode, null, DefaultSettings, 0).ToString(); expectedResults = @"{""xhtmlTemplate"": ""lexentry"",""guid"":""g" + entryTwo.Guid + @""",""letterHead"": ""c"",""sortIndex"": 0,""homographnumber"": ""2"", ""citationform"": [{""lang"":""fr"",""value"":""Citation""}]}"; expected = (JObject)JsonConvert.DeserializeObject(expectedResults, new JsonSerializerSettings { Formatting = Formatting.None }); @@ -905,13 +905,13 @@ public void GenerateJsonForEntry_GeneratesSpecifiedSortIndex() var entryOne = ConfiguredXHTMLGeneratorTests.CreateInterestingLexEntry(Cache); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, DefaultSettings, 36); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, DefaultSettings, 36).ToString(); var expectedResults = @"{""xhtmlTemplate"": ""lexentry"",""guid"":""g" + entryOne.Guid + @""",""letterHead"": ""c"",""sortIndex"": 36, ""citationform"": [{""lang"":""fr"",""value"":""Citation""}]}"; var expected = (JObject)JsonConvert.DeserializeObject(expectedResults, new JsonSerializerSettings { Formatting = Formatting.None }); VerifyJson(result, expected); // default value of -1 - result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, DefaultSettings); + result = ConfiguredLcmGenerator.GenerateContentForEntry(entryOne, mainEntryNode, null, DefaultSettings).ToString(); expectedResults = @"{""xhtmlTemplate"": ""lexentry"",""guid"":""g" + entryOne.Guid + @""",""letterHead"": ""c"",""sortIndex"": -1, ""citationform"": [{""lang"":""fr"",""value"":""Citation""}]}"; expected = (JObject)JsonConvert.DeserializeObject(expectedResults, new JsonSerializerSettings { Formatting = Formatting.None }); @@ -958,7 +958,7 @@ public void GenerateJsonForEntry_TwoDifferentPicturesGetUniqueWebFriendlyPaths() try { //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, DefaultSettings, 0); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, DefaultSettings, 0).ToString(); // Bug: The second filename should be different after the export with relative path settings (fix later) var expectedResults = @"{""xhtmlTemplate"": ""lexentry"",""guid"":""g" + testEntry.Guid + @""",""letterHead"": ""c"",""sortIndex"": 0, @@ -1024,21 +1024,21 @@ public void GenerateJsonForEntry_MinorComplexForm_TemplateTypeCorrect_GeneratesG CssGeneratorTests.PopulateFieldsForTesting(minorEntryNode); ; //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(subentry1, minorEntryNode, null, DefaultSettings, 0); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(subentry1, minorEntryNode, null, DefaultSettings, 0).ToString(); var expectedResults = @"{""xhtmlTemplate"":""minorentrycomplex"",""guid"":""g" + subentry1.Guid + @""",""letterHead"": ""c"",""sortIndex"": 0, ""complexformentryrefs"": [{""referencedentries"": [{""glossorsummary"": [{""lang"":""en"",""value"":""MainEntrySummaryDefn""}]}]}]}"; var expected = (JObject)JsonConvert.DeserializeObject(expectedResults, new JsonSerializerSettings { Formatting = Formatting.None }); VerifyJson(result, expected); //SUT - var result2 = ConfiguredLcmGenerator.GenerateContentForEntry(subentry2, minorEntryNode, null, DefaultSettings, 0); + var result2 = ConfiguredLcmGenerator.GenerateContentForEntry(subentry2, minorEntryNode, null, DefaultSettings, 0).ToString(); expectedResults = @"{""xhtmlTemplate"":""minorentrycomplex"",""guid"":""g" + subentry2.Guid + @""",""letterHead"": ""c"",""sortIndex"": 0, ""complexformentryrefs"": [{""referencedentries"": [{""glossorsummary"": [{""lang"":""en"",""value"":""gloss2""}]}]}]}"; expected = (JObject)JsonConvert.DeserializeObject(expectedResults, new JsonSerializerSettings { Formatting = Formatting.None }); VerifyJson(result2, expected); //SUT - var result3 = ConfiguredLcmGenerator.GenerateContentForEntry(subentry3, minorEntryNode, null, DefaultSettings, 0); + var result3 = ConfiguredLcmGenerator.GenerateContentForEntry(subentry3, minorEntryNode, null, DefaultSettings, 0).ToString(); expectedResults = @"{""xhtmlTemplate"": ""minorentrycomplex"",""guid"":""g" + subentry3.Guid + @""",""letterHead"": ""c"",""sortIndex"": 0, ""complexformentryrefs"": [{""referencedentries"": [{""glossorsummary"": [{""lang"":""en"",""value"":""MainEntryS3Defn""}]}]}]}"; expected = (JObject)JsonConvert.DeserializeObject(expectedResults, new JsonSerializerSettings { Formatting = Formatting.None }); @@ -1200,7 +1200,7 @@ public void GenerateXHTMLForEntry_EmbeddedWritingSystemOfOppositeDirectionGenera entry.Bibliography.set_String(wsHe, multiRunString); //SUT - var json = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, DefaultSettings); + var json = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, DefaultSettings).ToString(); var expectedResults = @"{""xhtmlTemplate"":""lexentry"",""guid"":""g" + entry.Guid + @""",""letterHead"":""c"",""sortIndex"":-1, ""bib"": [{""lang"":""he"",""value"":""דוד""},{""lang"":""en"",""value"":"" et ""},{""lang"":""he"",""value"":""דניאל""}],}"; var expected = (JObject)JsonConvert.DeserializeObject(expectedResults, new JsonSerializerSettings { Formatting = Formatting.None }); @@ -1229,7 +1229,7 @@ public void GenerateXHTMLForEntry_MultiLineCustomFieldGeneratesContent() var text = ConfiguredXHTMLGeneratorTests.CreateMultiParaText("Custom string", Cache); Cache.MainCacheAccessor.SetObjProp(testEntry.Hvo, customField.Flid, text.Hvo); //SUT - var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, rootNode, null, DefaultSettings, 0); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, rootNode, null, DefaultSettings, 0).ToString(); var expectedResults = @"{""xhtmlTemplate"":""lexentry"", ""guid"":""g" + testEntry.Guid + @""", From 08e60c761eed7bfa024898b2626386aab910e5fe Mon Sep 17 00:00:00 2001 From: Jason Naylor Date: Wed, 10 Jan 2024 09:39:56 -0800 Subject: [PATCH 017/285] Possible fix to the root cause of LT-21371 * Change the copy flag on the Clipboard api call to eliminate any possible issues related to memory being released and reused User debugging showed that a COMException with additional info: Invalid FORMATETC structure. Copying data from our c++ views and switching windows could make it unsafe to have clipboard contents that isn't copied. We tested this change with a user who was encountering the issue frequently and they observed a significant reduction in crashes Change-Id: I331e80177eabaacbc8e61c469351212e079adb22 --- Src/Common/SimpleRootSite/EditingHelper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/Common/SimpleRootSite/EditingHelper.cs b/Src/Common/SimpleRootSite/EditingHelper.cs index 8b53bf2011..67167ed574 100644 --- a/Src/Common/SimpleRootSite/EditingHelper.cs +++ b/Src/Common/SimpleRootSite/EditingHelper.cs @@ -3246,7 +3246,7 @@ internal void CopyTssToClipboard(ITsString tss) // the user selected a footnote marker but the TextRepOfObj() method isn't // implemented. - SetTsStringOnClipboard(tss, false, WritingSystemFactory); + SetTsStringOnClipboard(tss, true, WritingSystemFactory); } /// From ae7b2dde4b9d860ae95973f30b1c280277aac2ea Mon Sep 17 00:00:00 2001 From: Jason Naylor Date: Wed, 10 Jan 2024 10:12:08 -0800 Subject: [PATCH 018/285] Fix LT-21666 - Default complex form type for phrase to N/A * Previous default of Unspecified Complex Form caused many problems for users who didn't notice or desire that behavior Change-Id: Ie79b223a246e089d68c94e01e7e8f7085c6b5233 --- Src/LexText/LexTextControls/InsertEntryDlg.cs | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Src/LexText/LexTextControls/InsertEntryDlg.cs b/Src/LexText/LexTextControls/InsertEntryDlg.cs index 1a7e32bd69..7f81715219 100644 --- a/Src/LexText/LexTextControls/InsertEntryDlg.cs +++ b/Src/LexText/LexTextControls/InsertEntryDlg.cs @@ -1570,14 +1570,7 @@ private void EnableComplexFormTypeCombo() case MoMorphTypeTags.kMorphDiscontiguousPhrase: case MoMorphTypeTags.kMorphPhrase: m_cbComplexFormType.Enabled = true; - // default to "Unspecified Complex Form" if found, else set to "0" for "phrase" - if (m_cbComplexFormType.SelectedIndex == m_idxNotComplex) - { - int unSpecCompFormIndex = m_cbComplexFormType.FindStringExact(UnSpecifiedComplex); - m_cbComplexFormType.SelectedIndex = unSpecCompFormIndex != -1 - ? unSpecCompFormIndex - : 0; - } + // Do not attempt to change index. Should default to "Not Applicable" - At request of LT-21666 break; default: m_cbComplexFormType.SelectedIndex = 0; From 05703eaf68e304ebe3d23fa4e1f96924e70db5c3 Mon Sep 17 00:00:00 2001 From: mark-sil Date: Wed, 17 Jan 2024 11:27:15 -0500 Subject: [PATCH 019/285] =?UTF-8?q?LT-21443:=20=E2=80=98Approve=20All?= =?UTF-8?q?=E2=80=99=20-=20mark=20as=20user-approved?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Marking the analysis as user-approved is a partial fix for this Defect. The following problems still exist when testing with the project ‘Parser-Test2 Approve all 2024-01-16’: Step 5: There are 2 analysis, both in User Approved. The first has *** for Lex Gloss, the second has ‘I + NOM’ for Lex Gloss. (Note: This may be an issue because the capital word Sayana is also in the text.) Step 7: Both Analysis are shown as ‘User Opinion Unknown’ Step 9: Both Analysis are displayed in the correct section, but the one in ‘User Approved’ displays ‘***’ instead of displaying ‘he’ for the Lex Gloss. (Note: If in the ‘Word Analyses’ view you left click on the Lex Gloss that is displaying the ***, then it populates with ‘he + NOM’ and if you go back to the text, then ‘he + NOM’ is displayed in the Lex Gloss field for both ‘diana’ words, even if it’s not selected.) Step 10: When not selected Lex Gloss is displayed as ‘***’, Except for the first word in row 1. On row 1 the capital word ‘Sayana’ shows the Lex Gloss value even when not selected (I + NOM). NOTE: If I Use ‘Approve Throughout this Text’ for all the words, then none of these problems exist. Everything displays as described in the defects Acceptance Tests. Change-Id: Ic19d9391aa364016c63e2ab766ba4b2eab69965f --- Src/LexText/Interlinear/InterlinDocForAnalysis.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Src/LexText/Interlinear/InterlinDocForAnalysis.cs b/Src/LexText/Interlinear/InterlinDocForAnalysis.cs index 9313c12f54..aafa827de4 100644 --- a/Src/LexText/Interlinear/InterlinDocForAnalysis.cs +++ b/Src/LexText/Interlinear/InterlinDocForAnalysis.cs @@ -2331,11 +2331,22 @@ public void ApproveAllSuggestedAnalyses(Command cmd) // 2) A parser result - not sure which gets picked if multiple. // #2 May take a while to "percolate" through to become a "guess". var guess = Cache.ServiceLocator.ObjectRepository.GetObject(hvo); + IWfiAnalysis workingAnalysis; if (guess != null && guess is IAnalysis) - occ.Segment.AnalysesRS[occ.Index] = (IAnalysis) guess; + { + occ.Segment.AnalysesRS[occ.Index] = (IAnalysis)guess; + workingAnalysis = guess as IWfiAnalysis; + } else { - occ.Segment.AnalysesRS[occ.Index] = occAn.Wordform.AnalysesOC.FirstOrDefault(); + workingAnalysis = occAn.Wordform.AnalysesOC.FirstOrDefault(); + occ.Segment.AnalysesRS[occ.Index] = workingAnalysis; + } + + if (workingAnalysis != null) + { + // Make sure this analysis is marked as user-approved. + Cache.LangProject.DefaultUserAgent.SetEvaluation(workingAnalysis, Opinions.approves); } } /* else if (occAn.HasWordform && occAn.Wordform.ParserCount > 0) From cdf578134a7b2a0a7b61fa3c7aa3b30960a2dc4d Mon Sep 17 00:00:00 2001 From: mark-sil Date: Wed, 31 Jan 2024 16:23:50 -0500 Subject: [PATCH 020/285] =?UTF-8?q?LT-21443:=20=E2=80=98Approve=20All?= =?UTF-8?q?=E2=80=99=20=E2=80=93=20Fix=20Approve=20errors?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In addition to fixing the problems in LT-21443 this change also uses the same approval code for the following approval workflows: 1. Approve All 2. Approve Throughout this Text 3. Approve and Move Next 4. Select a Morphemes then press Enter 5. Select a Morphemes then Left Click on a Different Word 6. Select a Morphemes then Select somewhere else in the GUI (a different Tab, Texts, Texts & Words, Bottom Left List). Change-Id: Id5be16e36d8986a30cda8f4566b6cde38cf76dc0 --- .../FocusBoxController.ApproveAndMove.cs | 359 +++++------------- .../InterlinDocForAnalysisTests.cs | 51 ++- .../Interlinear/InterlinDocForAnalysis.cs | 95 ++--- Src/LexText/Interlinear/SandboxBase.cs | 2 +- 4 files changed, 160 insertions(+), 347 deletions(-) diff --git a/Src/LexText/Interlinear/FocusBoxController.ApproveAndMove.cs b/Src/LexText/Interlinear/FocusBoxController.ApproveAndMove.cs index 95c62f3aea..23b4054e1b 100644 --- a/Src/LexText/Interlinear/FocusBoxController.ApproveAndMove.cs +++ b/Src/LexText/Interlinear/FocusBoxController.ApproveAndMove.cs @@ -25,7 +25,7 @@ public partial class FocusBoxController internal void ApproveAndStayPut(ICommandUndoRedoText undoRedoText) { // don't navigate, just save. - UpdateRealFromSandbox(undoRedoText, true, SelectedOccurrence); + UpdateRealFromSandbox(undoRedoText, true); } /// @@ -35,78 +35,21 @@ internal void ApproveAndStayPut(ICommandUndoRedoText undoRedoText) /// or clicking the "Approve and Move Next" green check in an analysis. /// /// - internal virtual void ApproveAndMoveNext(ICommandUndoRedoText undoRedoText) + internal virtual void ApproveAndMoveNext(ICommandUndoRedoText cmd) { - ApproveAndMoveNextRecursive(undoRedoText); - } + if (!PreCheckApprove()) + return; - /// - /// Approves an analysis and moves the selection to the next wordform or the - /// next Interlinear line. An Interlinear line is one of the configurable - /// "lines" in the Tools->Configure->Interlinear Lines dialog, not a segement. - /// The list of lines is collected in choices[] below. - /// WordLevel is true for word or analysis lines. The non-word lines are translation and note lines. - /// Normally, this is invoked as a result of pressing the key in an analysis. - /// - /// - /// true if IP moved on, false otherwise - internal virtual bool ApproveAndMoveNextRecursive(ICommandUndoRedoText undoRedoText) - { - if (!SelectedOccurrence.IsValid) - { - // Can happen (at least) when the text we're analyzing got deleted in another window - SelectedOccurrence = null; - InterlinDoc.TryHideFocusBoxAndUninstall(); - return false; - } - var navigator = new SegmentServices.StTextAnnotationNavigator(SelectedOccurrence); - var nextWordform = navigator.GetNextWordformOrDefault(SelectedOccurrence); - if (nextWordform == null || nextWordform.Segment != SelectedOccurrence.Segment || - nextWordform == SelectedOccurrence) - { - // We're at the end of a segment...try to go to an annotation of SelectedOccurrence.Segment - // or possibly (See LT-12229:If the nextWordform is the same as SelectedOccurrence) - // at the end of the text. - UpdateRealFromSandbox(undoRedoText, true, null); // save work done in sandbox - // try to select the first configured annotation (not a null note) in this segment - if (InterlinDoc.SelectFirstTranslationOrNote()) - { // IP should now be on an annotation line. - return true; - } - } - if (nextWordform != null) - { - bool dealtWith = false; - if (nextWordform.Segment != SelectedOccurrence.Segment) - { // Is there another segment before the next wordform? - // It would have no analyses or just punctuation. - // It could have "real" annotations. - AnalysisOccurrence realAnalysis; - ISegment nextSeg = InterlinDoc.GetNextSegment - (SelectedOccurrence.Segment.Owner.IndexInOwner, - SelectedOccurrence.Segment.IndexInOwner, false, out realAnalysis); // downward move - if (nextSeg != null && nextSeg != nextWordform.Segment) - { // This is a segment before the one contaning the next wordform. - if (nextSeg.AnalysesRS.Where(an => an.HasWordform).Count() > 0) - { // Set it as the current segment and recurse - SelectedOccurrence = new AnalysisOccurrence(nextSeg, 0); // set to first analysis - dealtWith = ApproveAndMoveNextRecursive(undoRedoText); - } - else - { // only has annotations: focus on it and set the IP there. - InterlinDoc.SelectFirstTranslationOrNote(nextSeg); - return true; // IP should now be on an annotation line. - } - } - } - if (!dealtWith) - { // If not dealt with continue on to the next wordform. - UpdateRealFromSandbox(undoRedoText, true, nextWordform); - // do the move. - InterlinDoc.SelectOccurrence(nextWordform); - } - } - return true; + UndoableUnitOfWorkHelper.Do(cmd.UndoText, cmd.RedoText, Cache.ActionHandlerAccessor, + () => + { + ApproveAnalysis(SelectedOccurrence, false, true); + }); + + // This should not make any data changes, since we're telling it not to save and anyway + // we already saved the current annotation. And it can't correctly place the focus box + // until the change we just did are completed and PropChanged sent. So keep this outside the UOW. + OnNextBundle(cmd, false, false, false, true); } /// @@ -115,9 +58,7 @@ internal virtual bool ApproveAndMoveNextRecursive(ICommandUndoRedoText undoRedoT /// Approving the state of the FocusBox can be associated with /// different user actions (ie. UOW) /// - /// - internal void UpdateRealFromSandbox(ICommandUndoRedoText undoRedoText, bool fSaveGuess, - AnalysisOccurrence nextWordform) + internal void UpdateRealFromSandbox(ICommandUndoRedoText undoRedoText, bool fSaveGuess) { if (!ShouldCreateAnalysisFromSandbox(fSaveGuess)) return; @@ -136,7 +77,7 @@ internal void UpdateRealFromSandbox(ICommandUndoRedoText undoRedoText, bool fSav // But we don't want it to happen as an automatic side effect of the PropChanged. InterlinDoc.SuspendResettingAnalysisCache = true; UndoableUnitOfWorkHelper.Do(undoText, redoText, - Cache.ActionHandlerAccessor, () => ApproveAnalysisAndMove(fSaveGuess, nextWordform)); + Cache.ActionHandlerAccessor, () => ApproveAnalysis(SelectedOccurrence, false, fSaveGuess)); } finally { @@ -158,31 +99,9 @@ protected virtual bool ShouldCreateAnalysisFromSandbox(bool fSaveGuess) return true; } - - protected virtual void ApproveAnalysisAndMove(bool fSaveGuess, AnalysisOccurrence nextWordform) - { - using (new UndoRedoApproveAndMoveHelper(this, SelectedOccurrence, nextWordform)) - ApproveAnalysis(fSaveGuess); - } - - /// - /// - /// - /// - protected virtual void ApproveAnalysis(bool fSaveGuess) + private void FinishSettingAnalysis(AnalysisTree newAnalysisTree, IAnalysis oldAnalysis) { - IWfiAnalysis obsoleteAna; - AnalysisTree newAnalysisTree = InterlinWordControl.GetRealAnalysis(fSaveGuess, out obsoleteAna); - // if we've made it this far, might as well try to go the whole way through the UOW. - SaveAnalysisForAnnotation(SelectedOccurrence, newAnalysisTree); - FinishSettingAnalysis(newAnalysisTree, InitialAnalysis); - if (obsoleteAna != null) - obsoleteAna.Delete(); - } - - private void FinishSettingAnalysis(AnalysisTree newAnalysisTree, AnalysisTree oldAnalysisTree) - { - if (newAnalysisTree.Analysis == oldAnalysisTree.Analysis) + if (newAnalysisTree.Analysis == oldAnalysis) return; List msaHvoList = new List(); // Collecting for the new analysis is probably overkill, since the MissingEntries combo will only have MSAs @@ -209,150 +128,12 @@ private void SaveAnalysisForAnnotation(AnalysisOccurrence occurrence, AnalysisTr // analysis of the word. occurrence.Analysis = newAnalysisTree.Analysis; - // In case the wordform we point at has a form that doesn't match, we may need to set up an overidden form for the annotation. - IWfiWordform targetWordform = newAnalysisTree.Wordform; - if (targetWordform != null) - { - TryCacheRealWordForm(occurrence); - } - // It's possible if the new analysis is a different case form that the old wordform is now // unattested and should be removed. if (wfToTryDeleting != null && wfToTryDeleting != occurrence.Analysis.Wordform) wfToTryDeleting.DeleteIfSpurious(); } - private static bool BaselineFormDiffersFromAnalysisWord(AnalysisOccurrence occurrence, out ITsString baselineForm) - { - baselineForm = occurrence.BaselineText; // Review JohnT: does this work if the text might have changed?? - var wsBaselineForm = TsStringUtils.GetWsAtOffset(baselineForm, 0); - // We've updated the annotation to have InstanceOf set to the NEW analysis, so what we now derive from - // that is the NEW wordform. - var wfNew = occurrence.Analysis as IWfiWordform; - if (wfNew == null) - return false; // punctuation variations not significant. - var tssWfNew = wfNew.Form.get_String(wsBaselineForm); - return !baselineForm.Equals(tssWfNew); - } - - private void TryCacheRealWordForm(AnalysisOccurrence occurrence) - { - ITsString tssBaselineCbaForm; - if (BaselineFormDiffersFromAnalysisWord(occurrence, out tssBaselineCbaForm)) - { - //m_cache.VwCacheDaAccessor.CacheStringProp(hvoAnnotation, - // InterlinVc.TwficRealFormTag(m_cache), - // tssBaselineCbaForm); - } - } - - internal class UndoRedoApproveAndMoveHelper : DisposableBase - { - internal UndoRedoApproveAndMoveHelper(FocusBoxController focusBox, - AnalysisOccurrence occBeforeApproveAndMove, AnalysisOccurrence occAfterApproveAndMove) - { - Cache = focusBox.Cache; - FocusBox = focusBox; - OccurrenceBeforeApproveAndMove = occBeforeApproveAndMove; - OccurrenceAfterApproveAndMove = occAfterApproveAndMove; - - // add the undo action - AddUndoRedoAction(OccurrenceBeforeApproveAndMove, null); - } - - LcmCache Cache { get; set; } - FocusBoxController FocusBox { get; set; } - AnalysisOccurrence OccurrenceBeforeApproveAndMove { get; set; } - AnalysisOccurrence OccurrenceAfterApproveAndMove { get; set; } - - private UndoRedoApproveAnalysis AddUndoRedoAction(AnalysisOccurrence currentAnnotation, AnalysisOccurrence newAnnotation) - { - if (Cache.ActionHandlerAccessor != null && currentAnnotation != newAnnotation) - { - var undoRedoAction = new UndoRedoApproveAnalysis(FocusBox.InterlinDoc, - currentAnnotation, newAnnotation); - Cache.ActionHandlerAccessor.AddAction(undoRedoAction); - return undoRedoAction; - } - return null; - } - - protected override void DisposeManagedResources() - { - // add the redo action - if (OccurrenceBeforeApproveAndMove != OccurrenceAfterApproveAndMove) - AddUndoRedoAction(null, OccurrenceAfterApproveAndMove); - } - - protected override void DisposeUnmanagedResources() - { - FocusBox = null; - OccurrenceBeforeApproveAndMove = null; - OccurrenceAfterApproveAndMove = null; - } - - protected override void Dispose(bool disposing) - { - Debug.WriteLineIf(!disposing, "****** Missing Dispose() call for " + GetType().Name + " ******"); - base.Dispose(disposing); - } - } - - /// - /// This class allows smarter UndoRedo for ApproveAnalysis, so that the FocusBox can move appropriately. - /// - internal class UndoRedoApproveAnalysis : UndoActionBase - { - readonly InterlinDocForAnalysis m_interlinDoc; - readonly AnalysisOccurrence m_oldOccurrence; - AnalysisOccurrence m_newOccurrence; - - internal UndoRedoApproveAnalysis(InterlinDocForAnalysis interlinDoc, AnalysisOccurrence oldAnnotation, - AnalysisOccurrence newAnnotation) - { - m_interlinDoc = interlinDoc; - m_oldOccurrence = oldAnnotation; - m_newOccurrence = newAnnotation; - } - - #region Overrides of UndoActionBase - - private bool IsUndoable() - { - return m_oldOccurrence != null && m_oldOccurrence.IsValid && m_interlinDoc.IsFocusBoxInstalled; - } - - public override bool Redo() - { - if (m_newOccurrence != null && m_newOccurrence.IsValid) - { - m_interlinDoc.SelectOccurrence(m_newOccurrence); - } - else - { - m_interlinDoc.TryHideFocusBoxAndUninstall(); - } - - return true; - } - - public override bool Undo() - { - if (IsUndoable()) - { - m_interlinDoc.SelectOccurrence(m_oldOccurrence); - } - else - { - m_interlinDoc.TryHideFocusBoxAndUninstall(); - } - - return true; - } - - #endregion - } - /// /// We can navigate from one bundle to another if the focus box controller is /// actually visible. (Earlier versions of this method also checked it was in the right tool, but @@ -471,6 +252,30 @@ private static bool CheckPropSetForAllMorphs(IWfiAnalysis wa, int flid) return wa.MorphBundlesOS.All(bundle => wa.Cache.DomainDataByFlid.get_ObjectProp(bundle.Hvo, flid) != 0); } + /// + /// Common pre-checks used for some of the Approve workflows. + /// + /// true: passed all pre-checks. + public bool PreCheckApprove() + { + if (SelectedOccurrence == null) + return false; + + if (!SelectedOccurrence.IsValid) + { + // Can happen (at least) when the text we're analyzing got deleted in another window + SelectedOccurrence = null; + InterlinDoc.TryHideFocusBoxAndUninstall(); + return false; + } + + var stText = SelectedOccurrence.Paragraph.Owner as IStText; + if (stText == null || stText.ParagraphsOS.Count == 0) + return false; // paranoia, we should be in one of its paragraphs. + + return true; + } + /// /// Using the current focus box content, approve it and apply it to all unanalyzed matching /// wordforms in the text. See LT-8833. @@ -478,14 +283,12 @@ private static bool CheckPropSetForAllMorphs(IWfiAnalysis wa, int flid) /// public void ApproveGuessOrChangesForWholeTextAndMoveNext(Command cmd) { + if (!PreCheckApprove()) + return; + // Go through the entire text looking for matching analyses that can be set to the new // value. - if (SelectedOccurrence == null) - return; - var oldWf = SelectedOccurrence.Analysis.Wordform; - var stText = SelectedOccurrence.Paragraph.Owner as IStText; - if (stText == null || stText.ParagraphsOS.Count == 0) - return; // paranoia, we should be in one of its paragraphs. + // We don't need to discard existing guesses, even though we will modify Segment.Analyses, // since guesses for other wordforms will not be affected, and there will be no remaining // guesses for the word we're confirming everywhere. (This needs to be outside the block @@ -496,29 +299,9 @@ public void ApproveGuessOrChangesForWholeTextAndMoveNext(Command cmd) // Needs to include GetRealAnalysis, since it might create a new one. UndoableUnitOfWorkHelper.Do(cmd.UndoText, cmd.RedoText, Cache.ActionHandlerAccessor, () => - { - IWfiAnalysis obsoleteAna; - AnalysisTree newAnalysisTree = InterlinWordControl.GetRealAnalysis(true, out obsoleteAna); - var wf = newAnalysisTree.Wordform; - if (newAnalysisTree.Analysis == wf) - { - // nothing significant to confirm, so move on - // (return means get out of this lambda expression, not out of the method). - return; - } - SaveAnalysisForAnnotation(SelectedOccurrence, newAnalysisTree); - if (wf != null) - { - ApplyAnalysisToInstancesOfWordform(newAnalysisTree.Analysis, oldWf, wf); - } - // don't try to clean up the old analysis until we've finished walking through - // the text and applied all our changes, otherwise we could delete a wordform - // that is referenced by dummy annotations in the text, and thus cause the display - // to treat them like pronunciations, and just show an unanalyzable text (LT-9953) - FinishSettingAnalysis(newAnalysisTree, InitialAnalysis); - if (obsoleteAna != null) - obsoleteAna.Delete(); - }); + { + ApproveAnalysis(SelectedOccurrence, true, true); + }); }); // This should not make any data changes, since we're telling it not to save and anyway // we already saved the current annotation. And it can't correctly place the focus box @@ -526,10 +309,50 @@ public void ApproveGuessOrChangesForWholeTextAndMoveNext(Command cmd) OnNextBundle(cmd, false, false, false, true); } + /// + /// Common code intended to be used for all analysis approval workflows. + /// + /// The occurrence to approve. + /// if true, approve all occurrences; if false, only approve occ + /// if true, saves guesses; if false, skips guesses but still saves edits. + public virtual void ApproveAnalysis(AnalysisOccurrence occ, bool allOccurrences, bool fSaveGuess) + { + IAnalysis oldAnalysis = occ.Analysis; + IWfiWordform oldWf = occ.Analysis.Wordform; + + IWfiAnalysis obsoleteAna; + AnalysisTree newAnalysisTree = InterlinWordControl.GetRealAnalysis(fSaveGuess, out obsoleteAna); + var wf = newAnalysisTree.Wordform; + if (newAnalysisTree.Analysis == wf) + { + // nothing significant to confirm, so move on + return; + } + SaveAnalysisForAnnotation(occ, newAnalysisTree); + if (wf != null) + { + if (allOccurrences) + { + ApplyAnalysisToInstancesOfWordform(occ, newAnalysisTree.Analysis, oldWf, wf); + } + else + { + occ.Segment.AnalysesRS[occ.Index] = newAnalysisTree.Analysis; + } + } + // don't try to clean up the old analysis until we've finished walking through + // the text and applied all our changes, otherwise we could delete a wordform + // that is referenced by dummy annotations in the text, and thus cause the display + // to treat them like pronunciations, and just show an unanalyzable text (LT-9953) + FinishSettingAnalysis(newAnalysisTree, oldAnalysis); + if (obsoleteAna != null) + obsoleteAna.Delete(); + } + // Caller must create UOW - private void ApplyAnalysisToInstancesOfWordform(IAnalysis newAnalysis, IWfiWordform oldWordform, IWfiWordform newWordform) + private void ApplyAnalysisToInstancesOfWordform(AnalysisOccurrence occurrence, IAnalysis newAnalysis, IWfiWordform oldWordform, IWfiWordform newWordform) { - var navigator = new SegmentServices.StTextAnnotationNavigator(SelectedOccurrence); + var navigator = new SegmentServices.StTextAnnotationNavigator(occurrence); foreach (var occ in navigator.GetAnalysisOccurrencesAdvancingInStText().ToList()) { // We certainly want to update any occurrence that exactly matches the wordform of the analysis we are confirming. diff --git a/Src/LexText/Interlinear/ITextDllTests/InterlinDocForAnalysisTests.cs b/Src/LexText/Interlinear/ITextDllTests/InterlinDocForAnalysisTests.cs index 38476f999c..3ee0bd5280 100644 --- a/Src/LexText/Interlinear/ITextDllTests/InterlinDocForAnalysisTests.cs +++ b/Src/LexText/Interlinear/ITextDllTests/InterlinDocForAnalysisTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2015 SIL International +// Copyright (c) 2015 SIL International // This software is licensed under the LGPL, version 2.1 or later // (http://www.gnu.org/licenses/lgpl-2.1.html) @@ -148,6 +148,13 @@ public void ApproveAndStayPut_NewWordGloss() [Test] public void ApproveAndMoveNext_NoChange() { + // Override the InterlinVc for this test, but not other tests. + var origVc = m_interlinDoc.InterlinVc; + m_interlinDoc.InterlinVc = new InterlinDocForAnalysisVc(Cache); + + ISegment seg = m_para0_0.SegmentsOS[0]; + SetUpMocksForTest(seg); + var occurrences = SegmentServices.GetAnalysisOccurrences(m_para0_0).ToList(); m_interlinDoc.SelectOccurrence(occurrences[0]); var initialAnalysisTree = m_focusBox.InitialAnalysis; @@ -161,6 +168,9 @@ public void ApproveAndMoveNext_NoChange() // nothing to undo. Assert.AreEqual(0, Cache.ActionHandlerAccessor.UndoableSequenceCount); + + // Restore the InterlinVc for other tests. + m_interlinDoc.InterlinVc = origVc; } /// @@ -204,7 +214,7 @@ public void ApproveAndMoveNext_NewWordGloss() public void OnAddWordGlossesToFreeTrans_Simple() { ISegment seg = m_para0_0.SegmentsOS[0]; - SetUpMocksForOnAddWordGlossesToFreeTransTest(seg); + SetUpMocksForTest(seg); SetUpGlosses(seg, "hope", "this", "works"); m_interlinDoc.OnAddWordGlossesToFreeTrans(null); @@ -232,7 +242,7 @@ public void OnAddWordGlossesToFreeTrans_ORCs() m_para0_0.Contents = strBldr.GetString(); }); - SetUpMocksForOnAddWordGlossesToFreeTransTest(seg); + SetUpMocksForTest(seg); SetUpGlosses(seg, "hope", null, "this", "works"); m_interlinDoc.OnAddWordGlossesToFreeTrans(null); @@ -247,7 +257,7 @@ public void OnAddWordGlossesToFreeTrans_ORCs() #endregion #region Helper methods - private void SetUpMocksForOnAddWordGlossesToFreeTransTest(ISegment seg) + private void SetUpMocksForTest(ISegment seg) { IVwRootBox rootb = MockRepository.GenerateMock(); m_interlinDoc.MockedRootBox = rootb; @@ -298,6 +308,23 @@ internal MockInterlinDocForAnalyis(IStText testText) m_testText = testText; Vc = new InterlinVc(Cache); Vc.RootSite = this; + m_mediator = new Mediator(); + m_propertyTable = new PropertyTable(m_mediator); + + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (m_mediator != null) + m_mediator.Dispose(); + if (m_propertyTable != null) + m_propertyTable.Dispose(); + } + m_mediator = null; + m_propertyTable = null; + base.Dispose(disposing); } protected override FocusBoxController CreateFocusBoxInternal() @@ -311,6 +338,16 @@ public override void SelectOccurrence(AnalysisOccurrence target) FocusBox.SelectOccurrence(target); } + internal InterlinVc InterlinVc + { + get => Vc; + set + { + Vc = value; + Vc.RootSite = this; + } + } + internal override void UpdateGuesses(HashSet wordforms) { // for now, don't update guesses in these tests. @@ -393,11 +430,11 @@ protected override bool ShouldCreateAnalysisFromSandbox(bool fSaveGuess) return base.ShouldCreateAnalysisFromSandbox(fSaveGuess); } - protected override void ApproveAnalysis(bool fSaveGuess) + public override void ApproveAnalysis(AnalysisOccurrence occ, bool allOccurrences, bool fSaveGuess) { if (DoDuringUnitOfWork != null) NewAnalysisTree.Analysis = DoDuringUnitOfWork().Analysis; - base.ApproveAnalysis(fSaveGuess); + base.ApproveAnalysis(occ, allOccurrences, fSaveGuess); } internal AnalysisTree NewAnalysisTree @@ -489,7 +526,7 @@ AnalysisTree IAnalysisControlInternal.GetRealAnalysis(bool fSaveGuess, out IWfiA public int GetLineOfCurrentSelection() { - throw new NotImplementedException(); + return -1; } public bool SelectOnOrBeyondLine(int startLine, int increment) diff --git a/Src/LexText/Interlinear/InterlinDocForAnalysis.cs b/Src/LexText/Interlinear/InterlinDocForAnalysis.cs index aafa827de4..0358115149 100644 --- a/Src/LexText/Interlinear/InterlinDocForAnalysis.cs +++ b/Src/LexText/Interlinear/InterlinDocForAnalysis.cs @@ -289,7 +289,7 @@ public virtual void TriggerAnalysisSelected(AnalysisOccurrence target, bool fSav return; } if (IsFocusBoxInstalled) - FocusBox.UpdateRealFromSandbox(null, fSaveGuess, target); + FocusBox.UpdateRealFromSandbox(null, fSaveGuess); TryHideFocusBoxAndUninstall(); RecordGuessIfNotKnown(target); InstallFocusBox(); @@ -2073,7 +2073,7 @@ protected virtual bool HandleClickSelection(IVwSelection vwselNew, bool fBundleO if (!fBundleOnly) { if (IsFocusBoxInstalled) - FocusBox.UpdateRealFromSandbox(null, fSaveGuess, null); + FocusBox.UpdateRealFromSandbox(null, fSaveGuess); TryHideFocusBoxAndUninstall(); } @@ -2117,7 +2117,7 @@ protected virtual bool HandleClickSelection(IVwSelection vwselNew, bool fBundleO if (!fBundleOnly) { if (IsFocusBoxInstalled) - FocusBox.UpdateRealFromSandbox(null, fSaveGuess, null); + FocusBox.UpdateRealFromSandbox(null, fSaveGuess); TryHideFocusBoxAndUninstall(); } @@ -2251,7 +2251,7 @@ internal IAnalysis GetGuessForWordform(IWfiWordform wf, int ws) internal bool PrepareToGoAway() { if (IsFocusBoxInstalled) - FocusBox.UpdateRealFromSandbox(null, false, null); + FocusBox.UpdateRealFromSandbox(null, false); return true; } @@ -2272,43 +2272,20 @@ public void ApproveAllSuggestedAnalyses(Command cmd) var helper = SelectionHelper.Create(RootBox.Site); // only helps restore translation and note line selections AnalysisOccurrence focusedWf = SelectedOccurrence; // need to restore focus box if selected - // find the very first analysis - ISegment firstRealSeg = null; - IAnalysis firstRealOcc = null; - int occInd = 0; - foreach (IStPara p in RootStText.ParagraphsOS) + if (!FocusBox.PreCheckApprove()) + return; + + var sandbox = FocusBox.InterlinWordControl as Sandbox; + if (sandbox == null) { - var para = (IStTxtPara) p; - foreach (ISegment seg in para.SegmentsOS) - { - firstRealSeg = seg; - occInd = 0; - foreach(IAnalysis an in seg.AnalysesRS) - { - if (an.HasWordform && an.IsValidObject) - { - firstRealOcc = an; - break; - } - occInd++; - } - if (firstRealOcc != null) break; - } - if (firstRealOcc != null) break; - } - // Set it as the current segment and recurse - if (firstRealOcc == null) - return; // punctuation only or nothing to analyze - AnalysisOccurrence ao = null; - if (focusedWf != null && focusedWf.Analysis == firstRealOcc) - ao = new AnalysisOccurrence(focusedWf.Segment, focusedWf.Index); - else - ao = new AnalysisOccurrence(firstRealSeg, occInd); - TriggerAnalysisSelected(ao, true, true, false); - var navigator = new SegmentServices.StTextAnnotationNavigator(ao); + throw new Exception("Not expecting sandbox to ever be null."); + } + + var navigator = new SegmentServices.StTextAnnotationNavigator(SelectedOccurrence); // This needs to be outside the block for the UOW, since what we are suppressing // happens at the completion of the UOW. + FocusBox.Hide(); SuppressResettingGuesses( () => { @@ -2316,8 +2293,6 @@ public void ApproveAllSuggestedAnalyses(Command cmd) UndoableUnitOfWorkHelper.Do(cmd.UndoText, cmd.RedoText, Cache.ActionHandlerAccessor, () => { - var nav = new SegmentServices.StTextAnnotationNavigator(SelectedOccurrence); - AnalysisOccurrence lastOccurrence; var analyses = navigator.GetAnalysisOccurrencesAdvancingInStText().ToList(); foreach (var occ in analyses) { // This could be punctuation or any kind of analysis. @@ -2326,42 +2301,20 @@ public void ApproveAllSuggestedAnalyses(Command cmd) { // this is an analysis or a wordform int hvo = Vc.GetGuess(occAn); if (occAn.Hvo != hvo) - { // this is a guess, so approve it - // 1) A second occurence of a word that has had a lexicon entry or sense created for it. - // 2) A parser result - not sure which gets picked if multiple. - // #2 May take a while to "percolate" through to become a "guess". - var guess = Cache.ServiceLocator.ObjectRepository.GetObject(hvo); - IWfiAnalysis workingAnalysis; - if (guess != null && guess is IAnalysis) - { - occ.Segment.AnalysesRS[occ.Index] = (IAnalysis)guess; - workingAnalysis = guess as IWfiAnalysis; - } - else - { - workingAnalysis = occAn.Wordform.AnalysesOC.FirstOrDefault(); - occ.Segment.AnalysesRS[occ.Index] = workingAnalysis; - } - - if (workingAnalysis != null) - { - // Make sure this analysis is marked as user-approved. - Cache.LangProject.DefaultUserAgent.SetEvaluation(workingAnalysis, Opinions.approves); - } + { + // Move the sandbox to the next AnalysisOccurrence, then do the approval (using the sandbox data). + sandbox.SwitchWord(occ); + FocusBox.ApproveAnalysis(occ, true, true); } - /* else if (occAn.HasWordform && occAn.Wordform.ParserCount > 0) - { // this doesn't seem to be needed (and may not be correct) - always caught above - bool isHumanNoOpinion = occAn.Wordform.HumanNoOpinionParses.Cast().Any(wf => wf.Hvo == occAn.Hvo); - if (isHumanNoOpinion) - { - occ.Segment.AnalysesRS[occ.Index] = occAn.Wordform.AnalysesOC.FirstOrDefault(); - } - } */ } } + + // Restore the sandbox. + sandbox.SwitchWord(focusedWf); }); - } - ); + }); + FocusBox.Show(); + // MoveFocusBoxIntoPlace(); if (focusedWf != null) SelectOccurrence(focusedWf); diff --git a/Src/LexText/Interlinear/SandboxBase.cs b/Src/LexText/Interlinear/SandboxBase.cs index 6b68ac4605..32daddbdb6 100644 --- a/Src/LexText/Interlinear/SandboxBase.cs +++ b/Src/LexText/Interlinear/SandboxBase.cs @@ -4527,7 +4527,7 @@ public virtual bool OnJumpToTool(object commandObject) // not what we started with. We would save anyway as we switched views, so do it now. var parent = Controller; if (parent != null) - parent.UpdateRealFromSandbox(null, false, null); + parent.UpdateRealFromSandbox(null, false); // This leaves the parent in a bad state, but maybe it would be good if all this is // happening in some other parent, such as the words analysis view? //m_hvoAnalysisGuess = GetRealAnalysis(false); From 551c6bacd218ee9c5c2276a3a1485e61c2a32491 Mon Sep 17 00:00:00 2001 From: Jason Naylor Date: Mon, 5 Feb 2024 12:22:36 -0800 Subject: [PATCH 021/285] Fix regression LT-21721 - Enter should move to trans or note * Using the enter key on the last occurence of a segment should move focust to the first translation or note line Change-Id: Id2623ac4b9e5d23f50064f498676aa304d1b44c1 --- .../Interlinear/FocusBoxController.ApproveAndMove.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Src/LexText/Interlinear/FocusBoxController.ApproveAndMove.cs b/Src/LexText/Interlinear/FocusBoxController.ApproveAndMove.cs index 23b4054e1b..851c7fe146 100644 --- a/Src/LexText/Interlinear/FocusBoxController.ApproveAndMove.cs +++ b/Src/LexText/Interlinear/FocusBoxController.ApproveAndMove.cs @@ -160,6 +160,15 @@ public void OnNextBundle(ICommandUndoRedoText undoRedoText, bool fSaveGuess, boo if (InterlinWordControl!= null) currentLineIndex = InterlinWordControl.GetLineOfCurrentSelection(); var nextOccurrence = GetNextOccurrenceToAnalyze(fForward, skipFullyAnalyzedWords); + // If we are at the end of a segment we should move to the first Translation or note line (if any) + if(nextOccurrence.Segment != SelectedOccurrence.Segment || nextOccurrence == SelectedOccurrence) + { + if (InterlinDoc.SelectFirstTranslationOrNote()) + { + // We moved to a translation or note line, exit + return; + } + } InterlinDoc.TriggerAnalysisSelected(nextOccurrence, fSaveGuess, fMakeDefaultSelection); if (!fMakeDefaultSelection && currentLineIndex >= 0 && InterlinWordControl != null) InterlinWordControl.SelectOnOrBeyondLine(currentLineIndex, 1); From 48837ac946517c3689b75643039374452cf6c238 Mon Sep 17 00:00:00 2001 From: mark-sil Date: Thu, 1 Feb 2024 15:14:58 -0500 Subject: [PATCH 022/285] LT-21424: Fix Approve undo issue Fixed the undo problem described in Scenario 4 in the defect. The undo problem only existed after selecting a Morphemes and then clicking on another word to do the approval. (The other approval workflows did not have this issue.) The problem was resolved by executing the code in a way that is similar to what the other approval workflows do. Change-Id: Ia62e5a8be4c503ce4a4b972b9ec32e335b96b24b --- .../FocusBoxController.ApproveAndMove.cs | 58 ++++++++++++++++--- .../Interlinear/InterlinDocForAnalysis.cs | 5 +- Src/LexText/Interlinear/SandboxBase.cs | 2 +- 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/Src/LexText/Interlinear/FocusBoxController.ApproveAndMove.cs b/Src/LexText/Interlinear/FocusBoxController.ApproveAndMove.cs index 851c7fe146..f2bd5ef76b 100644 --- a/Src/LexText/Interlinear/FocusBoxController.ApproveAndMove.cs +++ b/Src/LexText/Interlinear/FocusBoxController.ApproveAndMove.cs @@ -34,8 +34,7 @@ internal void ApproveAndStayPut(ICommandUndoRedoText undoRedoText) /// Normally, this is invoked as a result of pressing the key /// or clicking the "Approve and Move Next" green check in an analysis. /// - /// - internal virtual void ApproveAndMoveNext(ICommandUndoRedoText cmd) + internal void ApproveAndMoveNext(ICommandUndoRedoText cmd) { if (!PreCheckApprove()) return; @@ -52,6 +51,36 @@ internal virtual void ApproveAndMoveNext(ICommandUndoRedoText cmd) OnNextBundle(cmd, false, false, false, true); } + /// + /// Approves an analysis (if there are edits or if fSaveGuess is true and there is a guess) and + /// moves the selection to target. + /// + /// The occurrence to move to. + /// If the FocusBox parent is not set, then use this value to set it. + /// if true, saves guesses; if false, skips guesses but still saves edits. + /// true to make the default selection within the new sandbox. + internal void ApproveAndMoveTarget(AnalysisOccurrence target, InterlinDocForAnalysis parent, bool fSaveGuess, bool fMakeDefaultSelection) + { + if (!PreCheckApprove()) + return; + + if (Parent == null) + { + Parent = parent; + } + + UndoableUnitOfWorkHelper.Do(ITextStrings.ksUndoApproveAnalysis, ITextStrings.ksRedoApproveAnalysis, Cache.ActionHandlerAccessor, + () => + { + ApproveAnalysis(SelectedOccurrence, false, fSaveGuess); + }); + + // This should not make any data changes, since we're telling it not to save and anyway + // we already saved the current annotation. And it can't correctly place the focus box + // until the change we just did are completed and PropChanged sent. So keep this outside the UOW. + TargetBundle(target, false, fMakeDefaultSelection); + } + /// /// /// @@ -149,16 +178,13 @@ protected bool CanNavigateBundles } /// - /// Move to the next bundle in the direction indicated by fForward. If fSaveGuess is true, save guesses in the current position, - /// using Undo text from the command. If skipFullyAnalyzedWords is true, move to the next item needing analysis, otherwise, the immediate next. + /// Move to the next bundle in the direction indicated by fForward. If fSaveGuess is true, save guesses in the current position. + /// If skipFullyAnalyzedWords is true, move to the next item needing analysis, otherwise, the immediate next. /// If fMakeDefaultSelection is true, make the default selection within the moved focus box. /// - public void OnNextBundle(ICommandUndoRedoText undoRedoText, bool fSaveGuess, bool skipFullyAnalyzedWords, + public void OnNextBundle(ICommandUndoRedoText _, bool fSaveGuess, bool skipFullyAnalyzedWords, bool fMakeDefaultSelection, bool fForward) { - int currentLineIndex = -1; - if (InterlinWordControl!= null) - currentLineIndex = InterlinWordControl.GetLineOfCurrentSelection(); var nextOccurrence = GetNextOccurrenceToAnalyze(fForward, skipFullyAnalyzedWords); // If we are at the end of a segment we should move to the first Translation or note line (if any) if(nextOccurrence.Segment != SelectedOccurrence.Segment || nextOccurrence == SelectedOccurrence) @@ -169,7 +195,21 @@ public void OnNextBundle(ICommandUndoRedoText undoRedoText, bool fSaveGuess, boo return; } } - InterlinDoc.TriggerAnalysisSelected(nextOccurrence, fSaveGuess, fMakeDefaultSelection); + TargetBundle(nextOccurrence, fSaveGuess, fMakeDefaultSelection); + } + + /// + /// Move to the target bundle. + /// + /// The occurrence to move to. + /// if true, saves guesses in the current position; if false, skips guesses but still saves edits. + /// true to make the default selection within the moved focus box. + public void TargetBundle(AnalysisOccurrence target, bool fSaveGuess, bool fMakeDefaultSelection) + { + int currentLineIndex = -1; + if (InterlinWordControl != null) + currentLineIndex = InterlinWordControl.GetLineOfCurrentSelection(); + InterlinDoc.TriggerAnalysisSelected(target, fSaveGuess, fMakeDefaultSelection); if (!fMakeDefaultSelection && currentLineIndex >= 0 && InterlinWordControl != null) InterlinWordControl.SelectOnOrBeyondLine(currentLineIndex, 1); } diff --git a/Src/LexText/Interlinear/InterlinDocForAnalysis.cs b/Src/LexText/Interlinear/InterlinDocForAnalysis.cs index 0358115149..dd66ac1729 100644 --- a/Src/LexText/Interlinear/InterlinDocForAnalysis.cs +++ b/Src/LexText/Interlinear/InterlinDocForAnalysis.cs @@ -2042,7 +2042,7 @@ public override void OriginalWndProc(ref Message msg) /// /// if true, saves guesses; if false, skips guesses but still saves edits. /// - protected virtual bool HandleClickSelection(IVwSelection vwselNew, bool fBundleOnly, bool fSaveGuess) + protected bool HandleClickSelection(IVwSelection vwselNew, bool fBundleOnly, bool fSaveGuess) { if (vwselNew == null) return false; // couldn't select a bundle! @@ -2138,7 +2138,8 @@ protected virtual bool HandleClickSelection(IVwSelection vwselNew, bool fBundleO TryHideFocusBoxAndUninstall(); return false; } - TriggerAnnotationSelected(new AnalysisOccurrence(seg, ianalysis), fSaveGuess); + + FocusBox.ApproveAndMoveTarget(new AnalysisOccurrence(seg, ianalysis), this, fSaveGuess, true); return true; } diff --git a/Src/LexText/Interlinear/SandboxBase.cs b/Src/LexText/Interlinear/SandboxBase.cs index 32daddbdb6..e866a8e9cc 100644 --- a/Src/LexText/Interlinear/SandboxBase.cs +++ b/Src/LexText/Interlinear/SandboxBase.cs @@ -3789,7 +3789,7 @@ internal void ClearAllGlosses() /// public bool ShouldSave(bool fSaveGuess) { - return m_caches.DataAccess.IsDirty() || fSaveGuess && UsingGuess; + return m_caches.DataAccess.IsDirty() || (fSaveGuess && UsingGuess); } /// From a1e13f73bb917c5511df56b6c5d3586f63da6403 Mon Sep 17 00:00:00 2001 From: mark-sil Date: Tue, 6 Feb 2024 13:37:59 -0500 Subject: [PATCH 023/285] =?UTF-8?q?LT-21424:=20Code=20Cleanup=20=E2=80=93?= =?UTF-8?q?=20remove=20unused=20parameter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the unused parameter on OnNextBundle() Change-Id: Ifd665d40738cd94b2a55e231332bcec51181bdbf --- .../FocusBoxController.ApproveAndMove.cs | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/Src/LexText/Interlinear/FocusBoxController.ApproveAndMove.cs b/Src/LexText/Interlinear/FocusBoxController.ApproveAndMove.cs index f2bd5ef76b..1b882d5669 100644 --- a/Src/LexText/Interlinear/FocusBoxController.ApproveAndMove.cs +++ b/Src/LexText/Interlinear/FocusBoxController.ApproveAndMove.cs @@ -48,7 +48,7 @@ internal void ApproveAndMoveNext(ICommandUndoRedoText cmd) // This should not make any data changes, since we're telling it not to save and anyway // we already saved the current annotation. And it can't correctly place the focus box // until the change we just did are completed and PropChanged sent. So keep this outside the UOW. - OnNextBundle(cmd, false, false, false, true); + OnNextBundle(false, false, false, true); } /// @@ -182,8 +182,7 @@ protected bool CanNavigateBundles /// If skipFullyAnalyzedWords is true, move to the next item needing analysis, otherwise, the immediate next. /// If fMakeDefaultSelection is true, make the default selection within the moved focus box. /// - public void OnNextBundle(ICommandUndoRedoText _, bool fSaveGuess, bool skipFullyAnalyzedWords, - bool fMakeDefaultSelection, bool fForward) + public void OnNextBundle(bool fSaveGuess, bool skipFullyAnalyzedWords, bool fMakeDefaultSelection, bool fForward) { var nextOccurrence = GetNextOccurrenceToAnalyze(fForward, skipFullyAnalyzedWords); // If we are at the end of a segment we should move to the first Translation or note line (if any) @@ -355,7 +354,7 @@ public void ApproveGuessOrChangesForWholeTextAndMoveNext(Command cmd) // This should not make any data changes, since we're telling it not to save and anyway // we already saved the current annotation. And it can't correctly place the focus box // until the change we just did are completed and PropChanged sent. So keep this outside the UOW. - OnNextBundle(cmd, false, false, false, true); + OnNextBundle(false, false, false, true); } /// @@ -453,7 +452,7 @@ public bool OnDisplayApproveAndMoveNextSameLine(object commandObject, ref UIItem public bool OnApproveAndMoveNextSameLine(object cmd) { - OnNextBundle(cmd as Command, true, false, false, true); + OnNextBundle(true, true, true, true); return true; } @@ -484,7 +483,7 @@ public bool OnDisplayBrowseMoveNextSameLine(object commandObject, ref UIItemDisp public bool OnBrowseMoveNextSameLine(object cmd) { - OnNextBundle(cmd as Command, false, false, false, true); + OnNextBundle(false, false, false, true); return true; } @@ -497,7 +496,7 @@ public bool OnDisplayBrowseMoveNext(object commandObject, ref UIItemDisplayPrope public bool OnBrowseMoveNext(object cmd) { - OnNextBundle(cmd as Command, false, false, true, true); + OnNextBundle(false, false, true, true); return true; } @@ -566,7 +565,7 @@ public bool OnMoveFocusBoxRight(object cmd) public void OnMoveFocusBoxRight(ICommandUndoRedoText undoRedoText, bool fSaveGuess) { // Move in the literal direction (LT-3706) - OnNextBundle(undoRedoText, fSaveGuess, false, true, !m_fRightToLeft); + OnNextBundle(fSaveGuess, false, true, !m_fRightToLeft); } /// @@ -598,7 +597,7 @@ public bool OnMoveFocusBoxRightNc(object cmd) /// public bool OnMoveFocusBoxLeft(object cmd) { - OnNextBundle(cmd as ICommandUndoRedoText, true, false, true, m_fRightToLeft); + OnNextBundle(true, false, true, m_fRightToLeft); return true; } @@ -621,7 +620,7 @@ public virtual bool OnDisplayMoveFocusBoxLeftNc(object commandObject, ref UIItem /// public bool OnMoveFocusBoxLeftNc(object cmd) { - OnNextBundle(cmd as ICommandUndoRedoText, false, false, true, m_fRightToLeft); + OnNextBundle(false, false, true, m_fRightToLeft); return true; } @@ -660,7 +659,7 @@ public virtual bool OnDisplayNextIncompleteBundleNc(object commandObject, ref UI /// public bool OnNextIncompleteBundle(object cmd) { - OnNextBundle(cmd as ICommandUndoRedoText, true, true, true, true); + OnNextBundle(true, true, true, true); return true; } @@ -671,7 +670,7 @@ public bool OnNextIncompleteBundle(object cmd) /// public bool OnNextIncompleteBundleNc(object cmd) { - OnNextBundle(cmd as ICommandUndoRedoText, false, true, true, true); + OnNextBundle(false, true, true, true); return true; } From 7c26c48aa8dcef55bd8c2eb8e382c69bd80dfb90 Mon Sep 17 00:00:00 2001 From: mark-sil Date: Thu, 8 Feb 2024 15:42:46 -0500 Subject: [PATCH 024/285] LT-21634: Add senses missing from Pathway The Pathway code cares about what the class name starts with so instead of pre-pending the parent node to the name, we now append the parent node to the name. Also cleaned up the code that generates unique names to avoid getting names like this: ".headword-referencedentries-1-2-3-4-5" Instead we now get: ".headword-referencedentries-5" Change-Id: I84a68cfcbd2dfc87e6094881b74949202350b9f1 --- Src/xWorks/CssGenerator.cs | 9 ++++++--- Src/xWorks/xWorksTests/CssGeneratorTests.cs | 14 +++++++------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Src/xWorks/CssGenerator.cs b/Src/xWorks/CssGenerator.cs index de049a4492..b36814af35 100644 --- a/Src/xWorks/CssGenerator.cs +++ b/Src/xWorks/CssGenerator.cs @@ -120,12 +120,15 @@ public static string GetBestUniqueNameForNode(Dictionary ConfigurableDictionaryNode node) { Guard.AgainstNull(node.Parent, "There should not be duplicate class names at the top of tree."); - // first try pre-pending the parent node classname - var className = $".{GetClassAttributeForConfig(node.Parent)}-{GetClassAttributeForConfig(node)}"; + // First try appending the parent node classname. Pathway has code that cares about what + // the className starts with, so keep the 'node' name first. + var className = $".{GetClassAttributeForConfig(node)}-{GetClassAttributeForConfig(node.Parent)}"; + + string classNameBase = className; int counter = 0; while (styles.ContainsKey(className)) { - className = $"{className}-{++counter}"; + className = $"{classNameBase}-{++counter}"; } return className; } diff --git a/Src/xWorks/xWorksTests/CssGeneratorTests.cs b/Src/xWorks/xWorksTests/CssGeneratorTests.cs index 6af2b10723..aaec341336 100644 --- a/Src/xWorks/xWorksTests/CssGeneratorTests.cs +++ b/Src/xWorks/xWorksTests/CssGeneratorTests.cs @@ -185,7 +185,7 @@ public void GenerateCssForConfiguration_SharedConfigurationGeneratesValidCss() cssGenerator.AddStyles(subEntryHeadwordNode); var cssResult = cssGenerator.GetStylesString(); // verify that the css result contains a line similar to: .sharedsubentries .sharedsubentry .headword span{ - VerifyRegex(cssResult, @"^\s*\.sharedsubentries-mainheadword>\s*span\s*{.*", + VerifyRegex(cssResult, @"^\s*\.mainheadword-sharedsubentries>\s*span\s*{.*", "Css for child node(headword) did not generate a match"); } @@ -338,9 +338,9 @@ public void GenerateCssForConfiguration_BeforeAfterGroupingSpanWorks() Assert.IsTrue(Regex.Match(cssResult, @"\.grouping_hwg\s*:after\s*{\s*content\s*:\s*'}';\s*}").Success, "css after rule for the grouping node was not generated"); // Check result for before and after rules equivalent to .headword span:first-child{content:'Z';} and .headword span:last-child{content:'A'} - Assert.IsTrue(Regex.Match(cssResult, @"\.grouping_hwg-mh>\s*span\s*:\s*first-child:before\s*{\s*content\s*:\s*'Z';\s*}").Success, + Assert.IsTrue(Regex.Match(cssResult, @"\.mh-grouping_hwg>\s*span\s*:\s*first-child:before\s*{\s*content\s*:\s*'Z';\s*}").Success, "css before rule with Z content not found on headword"); - Assert.IsTrue(Regex.Match(cssResult, @"\.grouping_hwg-mh>\s*span\s*:\s*last-child:after\s*{\s*content\s*:\s*'A';\s*}").Success, + Assert.IsTrue(Regex.Match(cssResult, @"\.mh-grouping_hwg>\s*span\s*:\s*last-child:after\s*{\s*content\s*:\s*'A';\s*}").Success, "css after rule with A content not found on headword"); } @@ -438,10 +438,10 @@ public void GenerateCssForConfiguration_BeforeAfterConfigGeneratesBeforeAfterCss cssGenerator.AddStyles(mainEntryHeadword); cssGenerator.AddStyles(headwordNode); var cssResult = cssGenerator.GetStylesString(); - // Check result for before and after rules equivalent to .subentries-headword span:first-child{content:'Z';} and .headword span:last-child{content:'A'} - VerifyRegex(cssResult, @"\.subentries-headword>\s*span\s*:\s*first-child:before\s*{\s*content\s*:\s*'Z';\s*}", + // Check result for before and after rules equivalent to .headword-subentries span:first-child{content:'Z';} and .headword span:last-child{content:'A'} + VerifyRegex(cssResult, @"\.headword-subentries>\s*span\s*:\s*first-child:before\s*{\s*content\s*:\s*'Z';\s*}", "css before rule with Z content not found on headword"); - VerifyRegex(cssResult, @"\.subentries-headword>\s*span\s*:\s*last-child:after\s*{\s*content\s*:\s*'A';\s*}", + VerifyRegex(cssResult, @"\.headword-subentries>\s*span\s*:\s*last-child:after\s*{\s*content\s*:\s*'A';\s*}", "css after rule with A content not found on headword"); } @@ -1936,7 +1936,7 @@ public void GenerateCssForConfiguration_SenseComplexFormsNotSubEntriesHeadWord() cssGenerator.AddStyles(headwordMain); cssGenerator.AddStyles(form); var cssResult = cssGenerator.GetStylesString(); - VerifyRegex(cssResult, @"^\s*\.otherreferencedcomplexforms-headword", "Headword node not generated for non subentry headword"); + VerifyRegex(cssResult, @"^\s*\.headword-otherreferencedcomplexforms", "Headword node not generated for non subentry headword"); } [Test] From bb111a20c0d060c3a0810779cdef086f29f8602c Mon Sep 17 00:00:00 2001 From: mark-sil Date: Tue, 20 Feb 2024 16:24:25 -0500 Subject: [PATCH 025/285] =?UTF-8?q?WIP:=20LT-21634:=20Fix=20=E2=80=98After?= =?UTF-8?q?=E2=80=99=20text=20being=20applied=20twice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The text in the dictionary configuration field: Senses->Grammatical Info.->After was being applied twice to the output that was displayed. Change-Id: I0fc97a3e28af73ea0bd8cd93ae7ede639b2f8544 --- Src/xWorks/CssGenerator.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Src/xWorks/CssGenerator.cs b/Src/xWorks/CssGenerator.cs index b36814af35..7ec6b930af 100644 --- a/Src/xWorks/CssGenerator.cs +++ b/Src/xWorks/CssGenerator.cs @@ -968,6 +968,7 @@ private static string SelectClassName(ConfigurableDictionaryNode configNode, str case ConfiguredLcmGenerator.PropertyType.CollectionType: { // for collections we generate a css selector to match each item e.g '.senses .sense' + // TODO - Should we do the same here? The old code was calling GetClassAttributeForConfig(). return string.Format("{0} .{1}", adjustedClassName, GetClassAttributeForCollectionItem(configNode)); } case ConfiguredLcmGenerator.PropertyType.CmPictureType: @@ -985,11 +986,26 @@ private static string SelectClassName(ConfigurableDictionaryNode configNode, str { spanStyle = "> span"; } + // TODO - Should we do the same here? The old code was calling GetClassAttributeForConfig(). return adjustedClassName + spanStyle; } goto default; } default: + // TODO - Do we just want CSSClassNameOverride or do we want the additional + // name generation that is done in GetClassAttributeForConfig(). + + // Only append the CSSClassNameOverride if it is not already part of the adjustedClassName. + if (configNode.CSSClassNameOverride != null) + { + string classAttName = CustomIcu.GetIcuNormalizer(FwNormalizationMode.knmNFC) + .Normalize(configNode.CSSClassNameOverride.ToLower()); + + if (!adjustedClassName.ToLower().Contains(classAttName)) + { + return string.Format("{0} > .{1}", adjustedClassName, classAttName); + } + } return adjustedClassName; } } From 6a61f16774d50d3f5ec30e8eb1f49d843ddcbf19 Mon Sep 17 00:00:00 2001 From: Jason Naylor Date: Mon, 19 Feb 2024 16:16:38 -0800 Subject: [PATCH 026/285] Improve buildLocalLibraries script * Ensure Download and Output directores exist * Clean old packages before calling dotnet pack Change-Id: I5626e729d94bf77308c97ad47fcdf9d87a0e1787 --- Build/buildLocalLibraries.sh | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Build/buildLocalLibraries.sh b/Build/buildLocalLibraries.sh index 925db030e3..be836b7384 100755 --- a/Build/buildLocalLibraries.sh +++ b/Build/buildLocalLibraries.sh @@ -39,8 +39,11 @@ function delete_and_pack_liblcm { echo "Deleting files starting with 'SIL.LCModel' in $packages_dir" find "$packages_dir" -name 'SIL.LCModel*' -exec rm -f -r {} \; + echo "Removing liblcm output packages so that dotnet pack will run and output the version" + (cd "$liblcm_dir/artifacts" && rm *nupkg) + echo "Running 'dotnet pack' in the liblcm directory: $liblcm_dir" - pack_output=$(cd "$liblcm_dir" && dotnet pack) + pack_output=$(cd "$liblcm_dir" && dotnet pack -c Debug) # Extract version number using regex if [[ $pack_output =~ $version_regex ]]; then @@ -80,8 +83,11 @@ function delete_and_pack_chorus { find "$packages_dir" -name "${prefix}*" -exec rm -f -r {} \; + echo "Removing chorus output packages so that dotnet pack will run and output the version" + (cd "$chorus_dir/output" && rm *nupkg) + echo "Running 'dotnet pack' in the chorus directory: $chorus_dir" - pack_output=$(cd "$chorus_dir" && dotnet pack) + pack_output=$(cd "$chorus_dir" && dotnet pack -c Debug) # Extract version number using regex if [[ $pack_output =~ $version_regex ]]; then @@ -120,8 +126,11 @@ function delete_and_pack_libpalaso { find "$packages_dir" -name "${prefix}*" -exec rm -f -r {} \; done + echo "Removing palaso output packages so that dotnet pack will run and output the version" + (cd "$libpalaso_dir/output" && rm *nupkg) + echo "Running 'dotnet pack' in the libpalaso directory: $libpalaso_dir" - pack_output=$(cd "$libpalaso_dir" && dotnet pack) + pack_output=$(cd "$libpalaso_dir" && dotnet pack -c Debug) # Extract version number using regex if [[ $pack_output =~ $version_regex ]]; then @@ -234,6 +243,9 @@ if [ -z "$libpalaso_dir" ] && [ -z "$liblcm_dir" ] && [ -z "$chorus_dir" ]; then display_usage fi +mkdir ../Output/Debug +mkdir ../Downloads + # Display the provided directory paths echo "libpalaso directory: $libpalaso_dir" echo "liblcm directory: $liblcm_dir" From 3df052d794b636389d93bd8e2d2ee0514d0a72bd Mon Sep 17 00:00:00 2001 From: Jason Naylor Date: Tue, 20 Feb 2024 14:54:48 -0800 Subject: [PATCH 027/285] =?UTF-8?q?Revert=20"WIP:=20LT-21634:=20Fix=20?= =?UTF-8?q?=E2=80=98After=E2=80=99=20text=20being=20applied..."?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit bb111a20c0d060c3a0810779cdef086f29f8602c. Change-Id: Ie541029ddbf71bbb70d2bbc91849be226d6e1579 --- Src/xWorks/CssGenerator.cs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/Src/xWorks/CssGenerator.cs b/Src/xWorks/CssGenerator.cs index 7ec6b930af..b36814af35 100644 --- a/Src/xWorks/CssGenerator.cs +++ b/Src/xWorks/CssGenerator.cs @@ -968,7 +968,6 @@ private static string SelectClassName(ConfigurableDictionaryNode configNode, str case ConfiguredLcmGenerator.PropertyType.CollectionType: { // for collections we generate a css selector to match each item e.g '.senses .sense' - // TODO - Should we do the same here? The old code was calling GetClassAttributeForConfig(). return string.Format("{0} .{1}", adjustedClassName, GetClassAttributeForCollectionItem(configNode)); } case ConfiguredLcmGenerator.PropertyType.CmPictureType: @@ -986,26 +985,11 @@ private static string SelectClassName(ConfigurableDictionaryNode configNode, str { spanStyle = "> span"; } - // TODO - Should we do the same here? The old code was calling GetClassAttributeForConfig(). return adjustedClassName + spanStyle; } goto default; } default: - // TODO - Do we just want CSSClassNameOverride or do we want the additional - // name generation that is done in GetClassAttributeForConfig(). - - // Only append the CSSClassNameOverride if it is not already part of the adjustedClassName. - if (configNode.CSSClassNameOverride != null) - { - string classAttName = CustomIcu.GetIcuNormalizer(FwNormalizationMode.knmNFC) - .Normalize(configNode.CSSClassNameOverride.ToLower()); - - if (!adjustedClassName.ToLower().Contains(classAttName)) - { - return string.Format("{0} > .{1}", adjustedClassName, classAttName); - } - } return adjustedClassName; } } From 57f53639bd3b28436fda17898a154accdd1c4461 Mon Sep 17 00:00:00 2001 From: mark-sil Date: Wed, 21 Feb 2024 14:58:52 -0500 Subject: [PATCH 028/285] =?UTF-8?q?LT-21634:=20Fix=20=E2=80=98After?= =?UTF-8?q?=E2=80=99=20text=20being=20applied=20twice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The text in the dictionary configuration field: Senses->Grammatical Info.->After was being applied twice to the output that was displayed. The sharedgrammaticalinfo class is no longer needed in this Case , and was resulting in the duplicate ‘After’ text, so removed it. Change-Id: I4bc90e33cd3a9e49959516a1a396feafce273be8 --- Src/xWorks/CssGenerator.cs | 9 ---- Src/xWorks/xWorksTests/CssGeneratorTests.cs | 56 +++++++++++++++++++-- 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/Src/xWorks/CssGenerator.cs b/Src/xWorks/CssGenerator.cs index b36814af35..cc2e49caa5 100644 --- a/Src/xWorks/CssGenerator.cs +++ b/Src/xWorks/CssGenerator.cs @@ -494,15 +494,6 @@ private static List GenerateCssForSenses(ConfigurableDictionaryNode c if (!IsEmptyRule(senseContentRule)) styleRules.Add(senseContentRule); } - - if (senseOptions.ShowSharedGrammarInfoFirst) - { - foreach (var gramInfoNode in configNode.Children.Where(node => node.FieldDescription == "MorphoSyntaxAnalysisRA" && node.IsEnabled)) - { - styleRules.AddRange(GenerateCssFromConfigurationNode(gramInfoNode, collectionSelector + "> .sharedgrammaticalinfo", propertyTable)); - } - } - return styleRules; } diff --git a/Src/xWorks/xWorksTests/CssGeneratorTests.cs b/Src/xWorks/xWorksTests/CssGeneratorTests.cs index aaec341336..66c9d3cc4a 100644 --- a/Src/xWorks/xWorksTests/CssGeneratorTests.cs +++ b/Src/xWorks/xWorksTests/CssGeneratorTests.cs @@ -2009,10 +2009,58 @@ public void GenerateCssForConfiguration_SenseShowGramInfoFirstWorks() cssGenerator.AddStyles(senses); cssGenerator.AddStyles(gramInfo); var cssResult = cssGenerator.GetStylesString(); - VerifyRegex(cssResult, @"^\s*\.morphosyntaxanalysisra", "Style for non-shared grammatical info not generated"); - VerifyRegex(cssResult, - @"^\s*\.senses\s*>\s*\.sharedgrammaticalinfo\s*{.*font-family\s*:\s*'foofoo'\,serif.*}", - "Style for sharedgrammaticalinfo not placed correctly"); + VerifyRegex(cssResult, @"^\s*\.morphosyntaxanalysisra", "Style for morphosyntaxanalysisra not generated"); + VerifyRegex(cssResult, @"^\s*\.morphosyntaxanalysisra\s*{.*font-family\s*:\s*'foofoo'\,serif.*}", + "Style for morphosyntaxanalysisra not placed correctly"); + } + + [Test] + public void GenerateCssForConfiguration_GramInfoAfterText() + { + GenerateStyle("Dictionary-Contrasting"); + var pos = new ConfigurableDictionaryNode { FieldDescription = "MLPartOfSpeech" }; + var inflectionClass = new ConfigurableDictionaryNode { FieldDescription = "MLInflectionClass" }; + var afterText = "ExactlyOnce"; + var gramInfo = new ConfigurableDictionaryNode + { + FieldDescription = "MorphoSyntaxAnalysisRA", + Label = "Gram. Info.", + Children = new List { pos, inflectionClass }, + Style = "Dictionary-Contrasting", + After = afterText + }; + var gloss = new ConfigurableDictionaryNode { FieldDescription = "Gloss", Style = "FooStyle" }; + var senses = new ConfigurableDictionaryNode + { + FieldDescription = "SensesOS", + CSSClassNameOverride = "Senses", + DictionaryNodeOptions = new DictionaryNodeSenseOptions { ShowSharedGrammarInfoFirst = true }, + Children = new List { gramInfo, gloss } + }; + var entry = new ConfigurableDictionaryNode + { + FieldDescription = "LexEntry", + CSSClassNameOverride = "lexentry", + Children = new List { senses } + }; + + var model = new DictionaryConfigurationModel(); + model.Parts = new List { entry }; + PopulateFieldsForTesting(entry); + var cssGenerator = new CssGenerator(); + cssGenerator.Init(m_propertyTable); + cssGenerator.AddGlobalStyles(model, m_propertyTable); + + //SUT + cssGenerator.AddStyles(senses); + cssGenerator.AddStyles(gramInfo); + var cssResult = cssGenerator.GetStylesString(); + + // Check that the after text is included once, not more or less. + var firstIndex = cssResult.IndexOf(afterText); + var lastIndex = cssResult.LastIndexOf(afterText); + Assert.IsTrue(firstIndex != -1 && firstIndex == lastIndex, + string.Format("After text \'{0}\' was not included exactly one time.", afterText)); } [Test] From ebbf5a64f3d53d28c89fa4cc8549b0beb7687d76 Mon Sep 17 00:00:00 2001 From: mark-sil Date: Wed, 21 Feb 2024 15:55:31 -0500 Subject: [PATCH 029/285] =?UTF-8?q?LT-21634:=20Fix=20=E2=80=98After?= =?UTF-8?q?=E2=80=99=20text=20not=20displaying=20for=20Pictures?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pictures Before, Between, and After content should not be in .captionContent. Removed the code that added this to the collectionSelector. Was: .captionContent .pictures> div:first-child:before{ content:'PBefore'; } Now is: .pictures> div:first-child:before{ content:'PBefore'; } Change-Id: I5d80a5b60d938a1abb6459f6e183fc14ea2bee1f --- Src/xWorks/CssGenerator.cs | 1 - Src/xWorks/xWorksTests/CssGeneratorTests.cs | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/Src/xWorks/CssGenerator.cs b/Src/xWorks/CssGenerator.cs index cc2e49caa5..78d34c524f 100644 --- a/Src/xWorks/CssGenerator.cs +++ b/Src/xWorks/CssGenerator.cs @@ -840,7 +840,6 @@ private static List GenerateSelectorsFromNode(ConfigurableDictionaryN } case DictionaryNodePictureOptions _: { - collectionSelector = pictCaptionContent + "." + GetClassAttributeForConfig(configNode); betweenSelector = string.Format("{0}> {1}+{1}:before", collectionSelector, " div"); break; } diff --git a/Src/xWorks/xWorksTests/CssGeneratorTests.cs b/Src/xWorks/xWorksTests/CssGeneratorTests.cs index 66c9d3cc4a..9bec87275c 100644 --- a/Src/xWorks/xWorksTests/CssGeneratorTests.cs +++ b/Src/xWorks/xWorksTests/CssGeneratorTests.cs @@ -2871,6 +2871,24 @@ public void GenerateCssForConfiguration_PictureBeforeBetweenAfterIsAreGenerated( var pictureBetween = @".*\.pictures>\s*div\s*\+\s*div:before\{\s*content:', ';"; VerifyRegex(cssResult, pictureBetween, "expected Picture between rule is generated"); + + // Verify that the before/after/between picture content is not nested in 'captionContent'. + RegexOptions options = RegexOptions.Singleline | RegexOptions.Multiline; + var captionContentPictureBefore = @".captionContent .pictures> div:first-child:before\{\s*content:'\[';"; + string message = "did not expect Picture before rule to be nested in captionContent."; + Assert.IsFalse(Regex.Match(cssResult, captionContentPictureBefore, options).Success, + string.Format("{3}Expected{0}{1}{0}but got{0}{2}", Environment.NewLine, pictureBefore, cssResult, message + Environment.NewLine)); + + var captionContentPictureAfter = @".captionContent .pictures> div:last-child:after\{\s*content:'\]';"; + message = "did not expect Picture after rule to be nested in captionContent."; + Assert.IsFalse(Regex.Match(cssResult, captionContentPictureAfter, options).Success, + string.Format("{3}Expected{0}{1}{0}but got{0}{2}", Environment.NewLine, pictureAfter, cssResult, message + Environment.NewLine)); + + var captionContentPictureBetween = @".captionContent .*\.pictures>\s*div\s*\+\s*div:before\{\s*content:', ';"; + VerifyRegex(cssResult, pictureBetween, "expected Picture between rule is generated"); + message = "did not expect Picture between rule to be nested in captionContent."; + Assert.IsFalse(Regex.Match(cssResult, captionContentPictureBetween, options).Success, + string.Format("{3}Expected{0}{1}{0}but got{0}{2}", Environment.NewLine, pictureBetween, cssResult, message + Environment.NewLine)); } From 5eec01d790d1abb5ef544f9b88ce715cc32a0900 Mon Sep 17 00:00:00 2001 From: mark-sil Date: Thu, 29 Feb 2024 14:17:49 -0500 Subject: [PATCH 030/285] Use lcm 11.0.0-beta0087 Change-Id: Iaa780ee8aaf43ce90465ffc98fcfdd5848630ee1 --- Build/mkall.targets | 2 +- Build/nuget-common/packages.config | 18 +++++++++--------- .../ITextDllTests/InterlinLineChoicesTests.cs | 8 ++++---- .../LexTextControlsTests/LiftMergerTests.cs | 12 ++++++------ 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Build/mkall.targets b/Build/mkall.targets index e7c267f83f..5d9d4a5aa8 100644 --- a/Build/mkall.targets +++ b/Build/mkall.targets @@ -285,7 +285,7 @@ 5.2.0-beta0003 13.0.0-beta0076 9.4.0.1-beta - 11.0.0-beta0083 + 11.0.0-beta0087 70.1.123 2.5.13 diff --git a/Build/nuget-common/packages.config b/Build/nuget-common/packages.config index be5a41057b..74b142cd1c 100644 --- a/Build/nuget-common/packages.config +++ b/Build/nuget-common/packages.config @@ -51,15 +51,15 @@ - - - - - - - - - + + + + + + + + + diff --git a/Src/LexText/Interlinear/ITextDllTests/InterlinLineChoicesTests.cs b/Src/LexText/Interlinear/ITextDllTests/InterlinLineChoicesTests.cs index d5687409ad..beba2dffe2 100644 --- a/Src/LexText/Interlinear/ITextDllTests/InterlinLineChoicesTests.cs +++ b/Src/LexText/Interlinear/ITextDllTests/InterlinLineChoicesTests.cs @@ -533,13 +533,13 @@ public void AddCustomSpecsForAnalAndVern() var wsFrn = frWs.Handle; using (var cFirstAnal = new CustomFieldForTest(Cache, - "Candy Apple Red", + "Candy Apple Red Anal", Cache.MetaDataCacheAccessor.GetClassId("Segment"), WritingSystemServices.kwsAnal, CellarPropertyType.String, Guid.Empty)) using (var cFirstVern = new CustomFieldForTest(Cache, - "Candy Apple Red", + "Candy Apple Red Vern", Cache.MetaDataCacheAccessor.GetClassId("Segment"), WritingSystemServices.kwsVern, CellarPropertyType.String, @@ -571,13 +571,13 @@ public void CreateSpecForCustomAlwaysUsesDefaultWS() var wsGer = deWs.Handle; using (var cFirstAnal = new CustomFieldForTest(Cache, - "Candy Apple Red", + "Candy Apple Red Anal", Cache.MetaDataCacheAccessor.GetClassId("Segment"), WritingSystemServices.kwsAnal, CellarPropertyType.String, Guid.Empty)) using (var cFirstVern = new CustomFieldForTest(Cache, - "Candy Apple Red", + "Candy Apple Red Vern", Cache.MetaDataCacheAccessor.GetClassId("Segment"), WritingSystemServices.kwsVern, CellarPropertyType.String, diff --git a/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs b/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs index 486ff161c2..006899e51b 100644 --- a/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs +++ b/Src/LexText/LexTextControls/LexTextControlsTests/LiftMergerTests.cs @@ -2765,7 +2765,7 @@ public void TestLiftImport9AMergingStTextKeepBoth() SetWritingSystems("fr"); CreateNeededStyles(); - var flidCustom = CreateFirstEntryWithConflictingData(); + var flidCustom = CreateFirstEntryWithConflictingData("Long Text1"); var repoEntry = Cache.ServiceLocator.GetInstance(); var repoSense = Cache.ServiceLocator.GetInstance(); @@ -2789,7 +2789,7 @@ public void TestLiftImport9BMergingStTextKeepOld() SetWritingSystems("fr"); CreateNeededStyles(); - var flidCustom = CreateFirstEntryWithConflictingData(); + var flidCustom = CreateFirstEntryWithConflictingData("Long Text2"); var repoEntry = Cache.ServiceLocator.GetInstance(); var repoSense = Cache.ServiceLocator.GetInstance(); @@ -2813,7 +2813,7 @@ public void TestLiftImport9CMergingStTextKeepNew() SetWritingSystems("fr"); CreateNeededStyles(); - var flidCustom = CreateFirstEntryWithConflictingData(); + var flidCustom = CreateFirstEntryWithConflictingData("Long Text3"); var repoEntry = Cache.ServiceLocator.GetInstance(); var repoSense = Cache.ServiceLocator.GetInstance(); @@ -2855,7 +2855,7 @@ public void TestLiftImport9DMergingStTextKeepOnlyNew() SetWritingSystems("fr"); CreateNeededStyles(); - var flidCustom = CreateFirstEntryWithConflictingData(); + var flidCustom = CreateFirstEntryWithConflictingData("Long Text4"); var repoEntry = Cache.ServiceLocator.GetInstance(); var repoSense = Cache.ServiceLocator.GetInstance(); @@ -2990,7 +2990,7 @@ private void VerifyFirstEntryStTextDataImportExact(ILexEntryRepository repoEntry Assert.IsTrue(tss.Equals(para.Contents), "The third paragraph contents should have all its formatting."); } - private int CreateFirstEntryWithConflictingData() + private int CreateFirstEntryWithConflictingData(string customFieldName) { var entry0 = Cache.ServiceLocator.GetInstance().Create( new Guid("494616cc-2f23-4877-a109-1a6c1db0887e"), Cache.LangProject.LexDbOA); @@ -3007,7 +3007,7 @@ private int CreateFirstEntryWithConflictingData() var mdc = Cache.MetaDataCacheAccessor as IFwMetaDataCacheManaged; Assert.That(mdc, Is.Not.Null); - var flidCustom = mdc.AddCustomField("LexEntry", "Long Text", CellarPropertyType.OwningAtomic, StTextTags.kClassId); + var flidCustom = mdc.AddCustomField("LexEntry", customFieldName, CellarPropertyType.OwningAtomic, StTextTags.kClassId); var hvoText = Cache.DomainDataByFlid.MakeNewObject(StTextTags.kClassId, entry0.Hvo, flidCustom, -2); var text = Cache.ServiceLocator.GetInstance().GetObject(hvoText); From a527f42479e52a8da75942b3e497cddc0c4b4c52 Mon Sep 17 00:00:00 2001 From: Ariel Rorabaugh Date: Fri, 12 Jan 2024 13:55:11 -0500 Subject: [PATCH 031/285] LT-21707: Initial styling for word export Styles that are expected to be working: * Normal (this is created as a default style, so everything inherits from it unless overridden) * Dictionary-LetterHeading * Dictionary-Normal * Dictionary-Headword * Dictionary-POS * Dictionary-SenseNumber Formatting changes made to these styles in a FieldWorks project should be reflected in the word export unless they relate to below items that are not implemented yet. Things that are not implemented: * before/after/between content * display in separate paragraphs * custom bullets & numbering * subscript/superscript * underline * strikethrough Change-Id: I01a82514fcbacf127a3bc48a08a5906ef29c300f --- Src/xWorks/ConfiguredLcmGenerator.cs | 107 ++-- Src/xWorks/CssGenerator.cs | 6 +- Src/xWorks/ILcmContentGenerator.cs | 30 +- Src/xWorks/LcmJsonGenerator.cs | 41 +- Src/xWorks/LcmWordGenerator.cs | 622 ++++++++++++------ Src/xWorks/LcmXhtmlGenerator.cs | 42 +- Src/xWorks/WordStylesGenerator.cs | 911 +++++++++++++++++++++++++++ Src/xWorks/xWorks.csproj | 1 + 8 files changed, 1470 insertions(+), 290 deletions(-) create mode 100644 Src/xWorks/WordStylesGenerator.cs diff --git a/Src/xWorks/ConfiguredLcmGenerator.cs b/Src/xWorks/ConfiguredLcmGenerator.cs index 6c4a9a5059..047a475ce6 100644 --- a/Src/xWorks/ConfiguredLcmGenerator.cs +++ b/Src/xWorks/ConfiguredLcmGenerator.cs @@ -34,7 +34,6 @@ using System.Threading; using System.Web.UI.WebControls; using XCore; -using FileUtils = SIL.LCModel.Utils.FileUtils; using UnitType = ExCSS.UnitType; namespace SIL.FieldWorks.XWorks @@ -333,12 +332,10 @@ internal static IFragment GenerateContentForEntry(ICmObject entry, ConfigurableD return settings.ContentGenerator.CreateFragment(); } - var pieces = configuration.ReferencedOrDirectChildren - .Select(config => - GenerateContentForFieldByReflection(entry, config, publicationDecorator, - settings)) - .Where(content => content!=null && !string.IsNullOrEmpty(content.ToString())).ToList(); + .Select(config => new ConfigFragment(config, GenerateContentForFieldByReflection(entry, config, publicationDecorator, + settings))) + .Where(content => content.Frag!=null && !string.IsNullOrEmpty(content.Frag.ToString())).ToList(); if (pieces.Count == 0) return settings.ContentGenerator.CreateFragment(); var bldr = settings.ContentGenerator.CreateFragment(); @@ -346,7 +343,7 @@ internal static IFragment GenerateContentForEntry(ICmObject entry, ConfigurableD { var clerk = settings.PropertyTable.GetValue("ActiveClerk", null); var entryClassName = settings.StylesGenerator.AddStyles(configuration).Trim('.'); - settings.ContentGenerator.StartEntry(xw, + settings.ContentGenerator.StartEntry(xw, configuration, entryClassName, entry.Guid, index, clerk); settings.ContentGenerator.AddEntryData(xw, pieces); settings.ContentGenerator.EndEntry(xw); @@ -807,7 +804,7 @@ private static IFragment GenerateContentForPossibility(object propertyValue, Con if (bldr.Length() > 0) { var className = settings.StylesGenerator.AddStyles(config).Trim('.'); - return settings.ContentGenerator.WriteProcessedObject(false, bldr, className); + return settings.ContentGenerator.WriteProcessedObject(false, bldr, config, className); } // bldr is a fragment that is empty of text, since length = 0 @@ -825,7 +822,7 @@ private static IFragment GenerateContentForPictureCaption(object propertyValue, if (!content.IsNullOrEmpty()) { var className = settings.StylesGenerator.AddStyles(config).Trim('.'); - return settings.ContentGenerator.WriteProcessedObject(true, content, className); + return settings.ContentGenerator.WriteProcessedObject(true, content, config,className); } return settings.ContentGenerator.CreateFragment(); } @@ -893,7 +890,7 @@ private static IFragment GenerateContentForDefOrGloss(ILexSense sense, Configura { var wsOption = config.DictionaryNodeOptions as DictionaryNodeWritingSystemOptions; if (wsOption == null) - throw new ArgumentException(@"Configuration nodes for MultiString fields whould have WritingSystemOptions", "config"); + throw new ArgumentException(@"Configuration nodes for MultiString fields would have WritingSystemOptions", "config"); var bldr = settings.ContentGenerator.CreateFragment(); foreach (var option in wsOption.Options) { @@ -911,8 +908,8 @@ private static IFragment GenerateContentForDefOrGloss(ILexSense sense, Configura if (bldr.Length() > 0) { - var className = settings.StylesGenerator.AddStyles(config).Trim('.'); ; - return settings.ContentGenerator.WriteProcessedCollection(false, bldr, className); + var className = settings.StylesGenerator.AddStyles(config).Trim('.'); + return settings.ContentGenerator.WriteProcessedCollection(false, bldr, config,className); } // bldr is a fragment that is empty of text, since length = 0 return bldr; @@ -938,7 +935,7 @@ private static IFragment GenerateContentForCaptionOrHeadword(ICmPicture picture, } } if (bldr.Length() > 0) - return settings.ContentGenerator.WriteProcessedCollection(false, bldr, GetClassNameAttributeForConfig(config)); + return settings.ContentGenerator.WriteProcessedCollection(false, bldr, config, GetClassNameAttributeForConfig(config)); // bldr is a fragment that is empty of text, since length = 0 return bldr; } @@ -1445,10 +1442,10 @@ private static IFragment GenerateContentForCollection(object collectionField, Co if (bldr.Length() > 0 || sharedCollectionInfo.Length() > 0) { - var className = settings.StylesGenerator.AddStyles(config).Trim('.'); ; + var className = settings.StylesGenerator.AddStyles(config).Trim('.'); return config.DictionaryNodeOptions is DictionaryNodeSenseOptions ? - settings.ContentGenerator.WriteProcessedSenses(false, bldr, className, sharedCollectionInfo) : - settings.ContentGenerator.WriteProcessedCollection(false, bldr, className); + settings.ContentGenerator.WriteProcessedSenses(false, bldr, config, className, sharedCollectionInfo) : + settings.ContentGenerator.WriteProcessedCollection(false, bldr, config, className); } return settings.ContentGenerator.CreateFragment(); } @@ -1583,7 +1580,7 @@ private static void GenerateContentForLexEntryRefsByType(ConfigurableDictionaryN : null; var className = generateLexType ? settings.StylesGenerator.AddStyles(typeNode).Trim('.') : null; var refsByType = settings.ContentGenerator.AddLexReferences(generateLexType, - lexTypeContent, className, innerBldr.ToString(), IsTypeBeforeForm(config)); + lexTypeContent, config, className, innerBldr.ToString(), IsTypeBeforeForm(config)); bldr.Append(refsByType); } } @@ -1704,9 +1701,35 @@ private static IFragment GenerateContentForSenses(ConfigurableDictionaryNode con // So calculating outside the loop for performance. var isThisSenseNumbered = ShouldThisSenseBeNumbered(filteredSenseCollection[0], config, filteredSenseCollection); var bldr = settings.ContentGenerator.CreateFragment(); + + // TODO: Can handle separate sense paragraph styling here; likely will make the most sense to be handled wherever we deal with before/after content for senses. + // TODO: If handled here (or elsewhere w/in LcmGenerator), remove sense paragraph handling from the CssGenerator, otherwise sense paragraphs will be separated by two lines in the xhtml export. + /*// Only need to check once whether DisplayEachSenseInAParagraph is true -- value will be the same for each item in the loop + bool newParagraphPerSense; + if (senseNode?.DisplayEachSenseInAParagraph == true) + newParagraphPerSense = true; + else + newParagraphPerSense = false; + + //If the first sense must be inline, we append the first sense without a preceding line break + int startSense = 0; + if (senseNode?.DisplayFirstSenseInline == true) + { + bldr.Append(GenerateSenseContent(config, publicationDecorator, filteredSenseCollection[startSense], isThisSenseNumbered, settings, isSameGrammaticalInfo, info)); + startSense++; + } + + for (int i = startSense; i < filteredSenseCollection.Count; i++)*/ + foreach (var item in filteredSenseCollection) { info.SenseCounter++; + + // TODO: sense paragraphs + /*// If each sense belongs in a new paragraph, append a line break before the sense content. + if (newParagraphPerSense) + bldr.AppendBreak();*/ + bldr.Append(GenerateSenseContent(config, publicationDecorator, item, isThisSenseNumbered, settings, isSameGrammaticalInfo, info)); } settings.StylesGenerator.AddStyles(config); @@ -1767,7 +1790,7 @@ private static IFragment InsertGramInfoBeforeSenses(ILexSense item, Configurable var content = GenerateContentForFieldByReflection(item, gramInfoNode, publicationDecorator, settings); if (content.IsNullOrEmpty()) return settings.ContentGenerator.CreateFragment(); - return settings.ContentGenerator.GenerateGramInfoBeforeSensesContent(content); + return settings.ContentGenerator.GenerateGramInfoBeforeSensesContent(content, gramInfoNode); } private static bool IsAllGramInfoTheSame(ConfigurableDictionaryNode config, IEnumerable collection, bool isSubsense, @@ -1869,9 +1892,9 @@ private static IFragment GenerateSenseContent(ConfigurableDictionaryNode config, } if (bldr.Length() == 0) return bldr; - var senseContent = bldr.ToString(); + return settings.ContentGenerator.AddSenseData(senseNumberSpan, IsBlockProperty(config), ((ICmObject)item).Owner.Guid, - senseContent, GetCollectionItemClassAttribute(config)); + bldr, GetCollectionItemClassAttribute(config)); } private static IFragment GeneratePictureContent(ConfigurableDictionaryNode config, DictionaryPublicationDecorator publicationDecorator, @@ -1957,7 +1980,7 @@ private static IFragment GenerateCollectionItemContent(ConfigurableDictionaryNod if (bldr.Length() == 0) return bldr; var collectionContent = bldr; - return settings.ContentGenerator.AddCollectionItem(IsBlockProperty(config), GetCollectionItemClassAttribute(config), collectionContent); + return settings.ContentGenerator.AddCollectionItem(IsBlockProperty(config), GetCollectionItemClassAttribute(config), config, collectionContent); } private static void GenerateContentForLexRefCollection(ConfigurableDictionaryNode config, @@ -2081,7 +2104,7 @@ private static IFragment GenerateCrossReferenceChildren(ConfigurableDictionaryNo { // targets settings.ContentGenerator.AddCollection(xw, IsBlockProperty(child), - CssGenerator.GetClassAttributeForConfig(child), contentBldr.ToString()); + CssGenerator.GetClassAttributeForConfig(child), config, contentBldr.ToString()); } break; case "OwnerType": @@ -2146,7 +2169,7 @@ private static IFragment GenerateSenseNumberSpanIfNeeded(ConfigurableDictionaryN var senseNumberWs = string.IsNullOrEmpty(info.HomographConfig.WritingSystem) ? "en" : info.HomographConfig.WritingSystem; if (string.IsNullOrEmpty(formattedSenseNumber)) return settings.ContentGenerator.CreateFragment(); - return settings.ContentGenerator.GenerateSenseNumber(formattedSenseNumber, senseNumberWs); + return settings.ContentGenerator.GenerateSenseNumber(formattedSenseNumber, senseNumberWs, senseConfigNode); } private static string GetSenseNumber(string numberingStyle, ref SenseInfo info) @@ -2224,7 +2247,7 @@ private static IFragment GenerateContentForICmObject(ICmObject propertyValue, Co if (bldr.Length() > 0) { var className = settings.StylesGenerator.AddStyles(config).Trim('.'); ; - return settings.ContentGenerator.WriteProcessedObject(false, bldr, className); + return settings.ContentGenerator.WriteProcessedObject(false, bldr, config, className); } return bldr; } @@ -2452,7 +2475,7 @@ private static IFragment GenerateContentForValue(object field, object propertyVa if (!content.IsNullOrEmpty()) { var className = settings.StylesGenerator.AddStyles(config).Trim('.'); ; - return settings.ContentGenerator.WriteProcessedCollection(false, content, className); + return settings.ContentGenerator.WriteProcessedCollection(false, content, config, className); } } return settings.ContentGenerator.CreateFragment(); @@ -2505,7 +2528,7 @@ private static IFragment GenerateContentForValue(object field, object propertyVa } } if (bldr.Length() > 0) - return settings.ContentGenerator.WriteProcessedCollection(true, bldr, GetClassNameAttributeForConfig(config)); + return settings.ContentGenerator.WriteProcessedCollection(true, bldr, config, GetClassNameAttributeForConfig(config)); // bldr is empty of text return bldr; } @@ -2592,7 +2615,7 @@ private static IFragment GenerateContentForStrings(IMultiStringAccessor multiStr if (bldr.Length() > 0) { var className = settings.StylesGenerator.AddStyles(config).Trim('.'); ; - return settings.ContentGenerator.WriteProcessedCollection(false, bldr, className); + return settings.ContentGenerator.WriteProcessedCollection(false, bldr, config, className); } // bldr is empty of text return bldr; @@ -2637,7 +2660,7 @@ private static IFragment GenerateContentForVirtualStrings(ICmObject owningObject if (bldr.Length() > 0) { var className = settings.StylesGenerator.AddStyles(config).Trim('.'); - return settings.ContentGenerator.WriteProcessedCollection(false, bldr, className); + return settings.ContentGenerator.WriteProcessedCollection(false, bldr, config, className); } // bldr is empty of text return bldr; @@ -2654,7 +2677,7 @@ private static IFragment GenerateWsPrefixAndString(ConfigurableDictionaryNode co var content = GenerateContentForString(requestedString, config, settings, guid, wsName); if (String.IsNullOrEmpty(content.ToString())) return settings.ContentGenerator.CreateFragment(); - return settings.ContentGenerator.GenerateWsPrefixWithString(settings, wsOptions.DisplayWritingSystemAbbreviations, wsId, content); + return settings.ContentGenerator.GenerateWsPrefixWithString(config, settings, wsOptions.DisplayWritingSystemAbbreviations, wsId, content); } private static IFragment GenerateContentForString(ITsString fieldValue, ConfigurableDictionaryNode config, @@ -2678,7 +2701,7 @@ private static IFragment GenerateContentForString(ITsString fieldValue, Configur var fileContent = GenerateContentForAudioFile(writingSystem, audioId, srcAttr, string.Empty, settings); var content = GenerateAudioWsContent(writingSystem, linkTarget, fileContent, settings); if (!content.IsNullOrEmpty()) - return settings.ContentGenerator.WriteProcessedObject(false, content, null); + return settings.ContentGenerator.WriteProcessedObject(false, content, config,null); } } else if (config.IsCustomField && IsUSFM(fieldValue.Text)) @@ -2725,7 +2748,7 @@ private static IFragment GenerateContentForString(ITsString fieldValue, Configur externalLink = props.GetStrPropValue((int)FwTextPropType.ktptObjData); } writingSystem = settings.Cache.WritingSystemFactory.GetStrFromWs(fieldValue.get_WritingSystem(i)); - GenerateRunWithPossibleLink(settings, writingSystem, writer, style, text, linkTarget, rightToLeft, externalLink); + GenerateRunWithPossibleLink(settings, writingSystem, writer, style, text, linkTarget, rightToLeft, config, externalLink); } if (fieldValue.RunCount > 1) @@ -2771,7 +2794,7 @@ private static IFragment GenerateAudioWsContent(string wsId, } private static void GenerateRunWithPossibleLink(GeneratorSettings settings, string writingSystem, IFragmentWriter writer, string style, - string text, Guid linkDestination, bool rightToLeft, string externalLink = null) + string text, Guid linkDestination, bool rightToLeft, ConfigurableDictionaryNode config, string externalLink = null) { settings.ContentGenerator.StartRun(writer, writingSystem); var wsRtl = settings.Cache.WritingSystemFactory.get_Engine(writingSystem).RightToLeftScript; @@ -2785,15 +2808,15 @@ private static void GenerateRunWithPossibleLink(GeneratorSettings settings, stri settings.Cache.WritingSystemFactory.GetWsFromStr(writingSystem), settings.PropertyTable); var css = cssStyle.ToString(); if (!String.IsNullOrEmpty(css)) - settings.ContentGenerator.SetRunStyle(writer, css); + settings.ContentGenerator.SetRunStyle(writer, config, css); } if (linkDestination != Guid.Empty) { - settings.ContentGenerator.StartLink(writer, linkDestination); + settings.ContentGenerator.StartLink(writer, config, linkDestination); } if (!string.IsNullOrEmpty(externalLink)) { - settings.ContentGenerator.StartLink(writer, externalLink.TrimStart((char)FwObjDataTypes.kodtExternalPathName)); + settings.ContentGenerator.StartLink(writer, config, externalLink.TrimStart((char)FwObjDataTypes.kodtExternalPathName)); } if (text.Contains(TxtLineSplit)) { @@ -3008,7 +3031,7 @@ private static void GenerateError(string text, IFragmentWriter writer, Generator new ExCSS.Property("color") { Term = new HtmlColor(222, 0, 0) }, new ExCSS.Property("font-size") { Term = new PrimitiveTerm(UnitType.Ems, 1.5f) } }; - settings.ContentGenerator.SetRunStyle(writer, css.ToString()); + settings.ContentGenerator.SetRunStyle(writer, null, css.ToString()); if (text.Contains(TxtLineSplit)) { var txtContents = text.Split(TxtLineSplit); @@ -3120,6 +3143,18 @@ private static bool IsTypeBeforeForm(ConfigurableDictionaryNode config) return typeBefore; } + public class ConfigFragment + { + public ConfigurableDictionaryNode Config { get; } + public IFragment Frag { get; } + + public ConfigFragment(ConfigurableDictionaryNode config, IFragment frag) + { + Config = config; + Frag = frag; + } + } + public class GeneratorSettings { public ILcmContentGenerator ContentGenerator = new LcmXhtmlGenerator(); diff --git a/Src/xWorks/CssGenerator.cs b/Src/xWorks/CssGenerator.cs index 78d34c524f..1cbd1eadf1 100644 --- a/Src/xWorks/CssGenerator.cs +++ b/Src/xWorks/CssGenerator.cs @@ -1442,9 +1442,9 @@ private static void AddFontInfoCss(BaseStyleInfo projectStyle, StyleDeclaration // empty span (ie span[lang="en"]{}. If not included, font-family will be added to many more spans. if (fontName == null && projectStyle.Name == "Normal") { - var lgWritingSysytem = cache.ServiceLocator.WritingSystemManager.get_EngineOrNull(wsId); - if(lgWritingSysytem != null) - fontName = lgWritingSysytem.DefaultFontName; + var lgWritingSystem = cache.ServiceLocator.WritingSystemManager.get_EngineOrNull(wsId); + if(lgWritingSystem != null) + fontName = lgWritingSystem.DefaultFontName; } if (fontName != null) diff --git a/Src/xWorks/ILcmContentGenerator.cs b/Src/xWorks/ILcmContentGenerator.cs index 0a75535915..e9b9301ee3 100644 --- a/Src/xWorks/ILcmContentGenerator.cs +++ b/Src/xWorks/ILcmContentGenerator.cs @@ -14,15 +14,15 @@ namespace SIL.FieldWorks.XWorks /// public interface ILcmContentGenerator { - IFragment GenerateWsPrefixWithString(ConfiguredLcmGenerator.GeneratorSettings settings, bool displayAbbreviation, int wsId, IFragment content); + IFragment GenerateWsPrefixWithString(ConfigurableDictionaryNode config, ConfiguredLcmGenerator.GeneratorSettings settings, bool displayAbbreviation, int wsId, IFragment content); IFragment GenerateAudioLinkContent(string classname, string srcAttribute, string caption, string safeAudioId); - IFragment WriteProcessedObject(bool isBlock, IFragment elementContent, string className); - IFragment WriteProcessedCollection(bool isBlock, IFragment elementContent, string className); - IFragment GenerateGramInfoBeforeSensesContent(IFragment content); + IFragment WriteProcessedObject(bool isBlock, IFragment elementContent, ConfigurableDictionaryNode config, string className); + IFragment WriteProcessedCollection(bool isBlock, IFragment elementContent, ConfigurableDictionaryNode config, string className); + IFragment GenerateGramInfoBeforeSensesContent(IFragment content, ConfigurableDictionaryNode config); IFragment GenerateGroupingNode(object field, string className, ConfigurableDictionaryNode config, DictionaryPublicationDecorator publicationDecorator, ConfiguredLcmGenerator.GeneratorSettings settings, Func childContentGenerator); - IFragment AddSenseData(IFragment senseNumberSpan, bool isBlockProperty, Guid ownerGuid, string senseContent, string className); - IFragment AddCollectionItem(bool isBlock, string collectionItemClass, IFragment content); + IFragment AddSenseData(IFragment senseNumberSpan, bool isBlockProperty, Guid ownerGuid, IFragment senseContent, string className); + IFragment AddCollectionItem(bool isBlock, string collectionItemClass, ConfigurableDictionaryNode config,IFragment content); IFragment AddProperty(string className, bool isBlockProperty, string content); IFragment CreateFragment(); IFragment CreateFragment(string str); @@ -33,9 +33,9 @@ IFragment GenerateGroupingNode(object field, string className, ConfigurableDicti void EndBiDiWrapper(IFragmentWriter writer); void StartRun(IFragmentWriter writer, string writingSystem); void EndRun(IFragmentWriter writer); - void SetRunStyle(IFragmentWriter writer, string css); - void StartLink(IFragmentWriter writer, Guid destination); - void StartLink(IFragmentWriter writer, string externalDestination); + void SetRunStyle(IFragmentWriter writer, ConfigurableDictionaryNode config, string css); + void StartLink(IFragmentWriter writer, ConfigurableDictionaryNode config, Guid destination); + void StartLink(IFragmentWriter writer, ConfigurableDictionaryNode config, string externalDestination); void EndLink(IFragmentWriter writer); void AddToRunContent(IFragmentWriter writer, string txtContent); void AddLineBreakInRunContent(IFragmentWriter writer); @@ -47,20 +47,20 @@ IFragment GenerateGroupingNode(object field, string className, ConfigurableDicti void EndTableRow(IFragmentWriter writer); void EndTableBody(IFragmentWriter writer); void EndTable(IFragmentWriter writer); - void StartEntry(IFragmentWriter writer, string className, Guid entryGuid, int index, RecordClerk clerk); - void AddEntryData(IFragmentWriter writer, List pieces); + void StartEntry(IFragmentWriter writer, ConfigurableDictionaryNode config, string className, Guid entryGuid, int index, RecordClerk clerk); + void AddEntryData(IFragmentWriter writer, List pieces); void EndEntry(IFragmentWriter writer); - void AddCollection(IFragmentWriter writer, bool isBlockProperty, string className, string content); + void AddCollection(IFragmentWriter writer, bool isBlockProperty, string className, ConfigurableDictionaryNode config, string content); void BeginObjectProperty(IFragmentWriter writer, bool isBlockProperty, string getCollectionItemClassAttribute); void EndObject(IFragmentWriter writer); void WriteProcessedContents(IFragmentWriter writer, IFragment contents); IFragment AddImage(string classAttribute, string srcAttribute, string pictureGuid); IFragment AddImageCaption(string captionContent); - IFragment GenerateSenseNumber(string formattedSenseNumber, string senseNumberWs); - IFragment AddLexReferences(bool generateLexType, IFragment lexTypeContent, string className, string referencesContent, bool typeBefore); + IFragment GenerateSenseNumber(string formattedSenseNumber, string senseNumberWs, ConfigurableDictionaryNode config); + IFragment AddLexReferences(bool generateLexType, IFragment lexTypeContent, ConfigurableDictionaryNode config, string className, string referencesContent, bool typeBefore); void BeginCrossReference(IFragmentWriter writer, bool isBlockProperty, string className); void EndCrossReference(IFragmentWriter writer); - IFragment WriteProcessedSenses(bool isBlock, IFragment senseContent, string className, IFragment sharedCollectionInfo); + IFragment WriteProcessedSenses(bool isBlock, IFragment senseContent, ConfigurableDictionaryNode config, string className, IFragment sharedCollectionInfo); IFragment AddAudioWsContent(string wsId, Guid linkTarget, IFragment fileContent); IFragment GenerateErrorContent(StringBuilder badStrBuilder); IFragment GenerateVideoLinkContent(string className, string mediaId, string srcAttribute, diff --git a/Src/xWorks/LcmJsonGenerator.cs b/Src/xWorks/LcmJsonGenerator.cs index 1b16b330b4..a033b42400 100644 --- a/Src/xWorks/LcmJsonGenerator.cs +++ b/Src/xWorks/LcmJsonGenerator.cs @@ -38,7 +38,7 @@ public LcmJsonGenerator(LcmCache cache) Cache = cache; } - public IFragment GenerateWsPrefixWithString(ConfiguredLcmGenerator.GeneratorSettings settings, + public IFragment GenerateWsPrefixWithString(ConfigurableDictionaryNode config, ConfiguredLcmGenerator.GeneratorSettings settings, bool displayAbbreviation, int wsId, IFragment content) { return content; @@ -55,7 +55,7 @@ public IFragment GenerateAudioLinkContent(string classname, string srcAttribute, dynamic audioObject = new JObject(); audioObject.id = safeAudioId; audioObject.src = srcAttribute.Replace("\\", "/"); // expecting relative paths only - return WriteProcessedObject(false, new StringFragment(audioObject.ToString()), "value"); + return WriteProcessedObject(false, new StringFragment(audioObject.ToString()), null,"value"); } public IFragment GenerateVideoLinkContent(string className, string mediaId, @@ -66,10 +66,10 @@ public IFragment GenerateVideoLinkContent(string className, string mediaId, dynamic videoObject = new JObject(); videoObject.id = mediaId; videoObject.src = srcAttribute.Replace("\\", "/"); // expecting relative paths only - return WriteProcessedObject(false, new StringFragment(videoObject.ToString()), "value"); + return WriteProcessedObject(false, new StringFragment(videoObject.ToString()), null, "value"); } - public IFragment WriteProcessedObject(bool isBlock, IFragment elementContent, string className) + public IFragment WriteProcessedObject(bool isBlock, IFragment elementContent, ConfigurableDictionaryNode config, string className) { if (elementContent.ToString().StartsWith("{")) return WriteProcessedContents(elementContent, className, string.Empty, ","); @@ -78,7 +78,7 @@ public IFragment WriteProcessedObject(bool isBlock, IFragment elementContent, st return WriteProcessedContents(elementContent, className, "{", "},"); } - public IFragment WriteProcessedCollection(bool isBlock, IFragment elementContent, string className) + public IFragment WriteProcessedCollection(bool isBlock, IFragment elementContent, ConfigurableDictionaryNode config, string className) { ((StringFragment)elementContent).TrimEnd(','); return WriteProcessedContents(elementContent, className, "[", "],"); @@ -103,7 +103,7 @@ private IFragment WriteProcessedContents(IFragment elementContent, string classN return fragment; } - public IFragment GenerateGramInfoBeforeSensesContent(IFragment content) + public IFragment GenerateGramInfoBeforeSensesContent(IFragment content, ConfigurableDictionaryNode config) { // The grammatical info is generated as a json property on 'senses' return content; @@ -117,7 +117,7 @@ public IFragment GenerateGroupingNode(object field, string className, Configurab return new StringFragment(); } - public IFragment AddCollectionItem(bool isBlock, string className, IFragment content) + public IFragment AddCollectionItem(bool isBlock, string className, ConfigurableDictionaryNode config,IFragment content) { var fragment = new StringFragment(); fragment.StrBuilder.Append(content.IsNullOrEmpty() ? string.Empty : $"{{{content}}},"); @@ -181,18 +181,18 @@ public void EndRun(IFragmentWriter writer) m_runBuilder.Value.Clear(); } - public void SetRunStyle(IFragmentWriter writer, string css) + public void SetRunStyle(IFragmentWriter writer, ConfigurableDictionaryNode config, string css) { if(!string.IsNullOrEmpty(css)) ((JsonFragmentWriter)writer).InsertJsonProperty("style", css); } - public void StartLink(IFragmentWriter writer, Guid destination) + public void StartLink(IFragmentWriter writer, ConfigurableDictionaryNode config, Guid destination) { ((JsonFragmentWriter)writer).InsertJsonProperty("guid", "g" + destination); } - public void StartLink(IFragmentWriter writer, string externalLink) + public void StartLink(IFragmentWriter writer, ConfigurableDictionaryNode config, string externalLink) { ((JsonFragmentWriter)writer).InsertJsonProperty("linkUrl", externalLink); } @@ -246,7 +246,7 @@ public void EndTable(IFragmentWriter writer) // TODO: decide on a useful json representation for tables } - public void StartEntry(IFragmentWriter xw, string className, Guid entryGuid, int index, RecordClerk clerk) + public void StartEntry(IFragmentWriter xw, ConfigurableDictionaryNode config, string className, Guid entryGuid, int index, RecordClerk clerk) { var jsonWriter = (JsonFragmentWriter)xw; jsonWriter.StartObject(); @@ -274,9 +274,10 @@ public void StartEntry(IFragmentWriter xw, string className, Guid entryGuid, int jsonWriter.InsertRawJson(","); } - public void AddEntryData(IFragmentWriter xw, List pieces) + public void AddEntryData(IFragmentWriter xw, List pieces) { - pieces.ForEach(((JsonFragmentWriter)xw).InsertRawJson); + foreach (ConfiguredLcmGenerator.ConfigFragment piece in pieces) + ((JsonFragmentWriter)xw).InsertRawJson(piece.Frag); } public void EndEntry(IFragmentWriter xw) @@ -284,7 +285,7 @@ public void EndEntry(IFragmentWriter xw) ((JsonFragmentWriter)xw).EndObject(); } - public void AddCollection(IFragmentWriter writer, bool isBlockProperty, string className, string content) + public void AddCollection(IFragmentWriter writer, bool isBlockProperty, string className, ConfigurableDictionaryNode config, string content) { ((JsonFragmentWriter)writer).InsertPropertyName(className); BeginArray(writer); @@ -350,12 +351,12 @@ public IFragment AddImageCaption(string captionContent) return new StringFragment(captionContent); } - public IFragment GenerateSenseNumber(string formattedSenseNumber, string wsId) + public IFragment GenerateSenseNumber(string formattedSenseNumber, string wsId, ConfigurableDictionaryNode config) { return new StringFragment(formattedSenseNumber); } - public IFragment AddLexReferences(bool generateLexType, IFragment lexTypeContent, string className, + public IFragment AddLexReferences(bool generateLexType, IFragment lexTypeContent, ConfigurableDictionaryNode config, string className, string referencesContent, bool typeBefore) { var bldr = new StringBuilder(); @@ -406,9 +407,9 @@ public void EndCrossReference(IFragmentWriter writer) /// /// Generates data for all senses of an entry. For better processing of json add sharedGramInfo as a separate property object /// - public IFragment WriteProcessedSenses(bool isBlock, IFragment sensesContent, string classAttribute, IFragment sharedGramInfo) + public IFragment WriteProcessedSenses(bool isBlock, IFragment sensesContent, ConfigurableDictionaryNode config, string classAttribute, IFragment sharedGramInfo) { - return new StringFragment($"{sharedGramInfo.ToString()}{WriteProcessedCollection(isBlock, sensesContent, classAttribute)}"); + return new StringFragment($"{sharedGramInfo.ToString()}{WriteProcessedCollection(isBlock, sensesContent, config, classAttribute)}"); } public IFragment AddAudioWsContent(string wsId, Guid linkTarget, IFragment fileContent) @@ -424,7 +425,7 @@ public IFragment GenerateErrorContent(StringBuilder badStrBuilder) } public IFragment AddSenseData(IFragment senseNumberSpan, bool isBlock, Guid ownerGuid, - string senseContent, string className) + IFragment senseContent, string className) { var bldr = new StringBuilder(); var fragment = new StringFragment(bldr); @@ -440,7 +441,7 @@ public IFragment AddSenseData(IFragment senseNumberSpan, bool isBlock, Guid owne } xw.WritePropertyName("guid"); xw.WriteValue("g" + ownerGuid); - xw.WriteRaw("," + senseContent.TrimEnd(',')); + xw.WriteRaw("," + senseContent.ToString().TrimEnd(',')); xw.WriteEndObject(); xw.WriteRaw(","); xw.Flush(); diff --git a/Src/xWorks/LcmWordGenerator.cs b/Src/xWorks/LcmWordGenerator.cs index 31b1ec3635..615b7477b4 100644 --- a/Src/xWorks/LcmWordGenerator.cs +++ b/Src/xWorks/LcmWordGenerator.cs @@ -2,6 +2,17 @@ // This software is licensed under the LGPL, version 2.1 or later // (http://www.gnu.org/licenses/lgpl-2.1.html) +using DocumentFormat.OpenXml; +using DocumentFormat.OpenXml.Packaging; +using DocumentFormat.OpenXml.Wordprocessing; +using Icu.Collation; +using SIL.Code; +using SIL.FieldWorks.Common.FwUtils; +using SIL.FieldWorks.Common.Widgets; +using SIL.LCModel; +using SIL.LCModel.DomainServices; +using SIL.LCModel.Utils; +using Style = DocumentFormat.OpenXml.Wordprocessing.Style; using System; using System.Collections.Generic; using System.Diagnostics; @@ -9,16 +20,7 @@ using System.Linq; using System.Text; using System.Web.UI.WebControls; -using DocumentFormat.OpenXml; -using Icu.Collation; -using SIL.FieldWorks.Common.Controls; -using SIL.FieldWorks.Common.FwUtils; -using SIL.LCModel; -using SIL.LCModel.Core.Text; -using SIL.LCModel.Utils; using XCore; -using DocumentFormat.OpenXml.Packaging; -using DocumentFormat.OpenXml.Wordprocessing; namespace SIL.FieldWorks.XWorks { @@ -29,6 +31,10 @@ namespace SIL.FieldWorks.XWorks public class LcmWordGenerator : ILcmContentGenerator, ILcmStylesGenerator { private LcmCache Cache { get; } + private static Styles _styleSheet { get; set; } = new Styles(); + private static Dictionary _styleDictionary = new Dictionary(); + private ReadOnlyPropertyTable _propertyTable; + public LcmWordGenerator(LcmCache cache) { Cache = cache; @@ -45,12 +51,14 @@ public static void SavePublishedDocx(int[] entryHvos, DictionaryPublicationDecor var cssPath = System.IO.Path.ChangeExtension(filePath, "css"); var clerk = propertyTable.GetValue("ActiveClerk", null); var cache = propertyTable.GetValue("cache", null); - - + var generator = new LcmWordGenerator(cache); var readOnlyPropertyTable = new ReadOnlyPropertyTable(propertyTable); + + generator.Init(readOnlyPropertyTable); var settings = new ConfiguredLcmGenerator.GeneratorSettings(cache, readOnlyPropertyTable, true, true, System.IO.Path.GetDirectoryName(filePath), ConfiguredLcmGenerator.IsEntryStyleRtl(readOnlyPropertyTable, configuration), System.IO.Path.GetFileName(cssPath) == "configured.css") - { ContentGenerator = new LcmWordGenerator(cache) }; + { ContentGenerator = generator, StylesGenerator = generator}; + settings.StylesGenerator.AddGlobalStyles(configuration, readOnlyPropertyTable); string lastHeader = null; var entryContents = new Tuple[entryCount]; var entryActions = new List(); @@ -85,18 +93,19 @@ public static void SavePublishedDocx(int[] entryHvos, DictionaryPublicationDecor var wsString = entryContents.Length > 0 ? ConfiguredLcmGenerator.GetWsForEntryType(entryContents[0].Item1, settings.Cache) : null; var col = FwUtils.GetCollatorForWs(wsString); + var propStyleSheet = FontHeightAdjuster.StyleSheetFromPropertyTable(propertyTable); + foreach (var entry in entryContents) { if (!entry.Item2.IsNullOrEmpty()) { IFragment letterHeader = GenerateLetterHeaderIfNeeded(entry.Item1, - ref lastHeader, col, settings, clerk); + ref lastHeader, col, settings, readOnlyPropertyTable, propStyleSheet, clerk); // If needed, append letter header to the word doc if (!letterHeader.IsNullOrEmpty()) fragment.Append(letterHeader); - // TODO: when/how are styles applied to the letter headers? // Append the entry to the word doc fragment.Append(entry.Item2); } @@ -106,7 +115,29 @@ public static void SavePublishedDocx(int[] entryHvos, DictionaryPublicationDecor if (progress != null) progress.Message = xWorksStrings.ksGeneratingStyleInfo; - // TODO: Generate styles + // Generate styles + StyleDefinitionsPart stylePart = fragment.mainDocPart.StyleDefinitionsPart; + if (stylePart == null) + { + // Initialize word doc's styles xml + stylePart = AddStylesPartToPackage(fragment.DocFrag); + + // Add generated styles into the stylesheet from the dictionary + foreach (var stylesItem in _styleDictionary.Values) + { + foreach (var style in stylesItem.Descendants + - + - + - + - + - + diff --git a/Src/LexText/ParserUI/ParserReportViewModel.cs b/Src/LexText/ParserUI/ParserReportViewModel.cs index 9aac28d4cb..58374c63c2 100644 --- a/Src/LexText/ParserUI/ParserReportViewModel.cs +++ b/Src/LexText/ParserUI/ParserReportViewModel.cs @@ -14,12 +14,25 @@ public class ParserReportViewModel { public ParserReport ParserReport { get; set; } + private FileTimeToDateTimeConverter m_FileTimeToDateTimeConverter = new FileTimeToDateTimeConverter(); + public string Title { get { - string time = ParserReport.IsDiff ? new TimeSpan(ParserReport.Timestamp).ToString() : new DateTime(ParserReport.Timestamp).ToString(); - return (ParserReport.IsDiff ? "Diff " : "") + ParserReport.ProjectName + ", " + ParserReport.SourceText + ", " + time + "," + ParserReport.MachineName; + string time = ParserReport.IsDiff + ? TimeSpan.FromTicks(ParserReport.Timestamp).ToString() + : m_FileTimeToDateTimeConverter.Convert(ParserReport.Timestamp, null, null, null).ToString(); + return (ParserReport.IsDiff ? ParserUIStrings.ksDiffHeader + " " : "") + ParserReport.ProjectName + ", " + ParserReport.SourceText + ", " + time + ", " + ParserReport.MachineName; + } + } + + public IEnumerable ParseReports + { + get + { + // Use ToList so that sorting the reports doesn't change the data model. + return ParserReport.ParseReports.Values.ToList(); } } diff --git a/Src/LexText/ParserUI/ParserReportsDialog.xaml b/Src/LexText/ParserUI/ParserReportsDialog.xaml index 8a2934b480..9cc22b49ed 100644 --- a/Src/LexText/ParserUI/ParserReportsDialog.xaml +++ b/Src/LexText/ParserUI/ParserReportsDialog.xaml @@ -6,11 +6,12 @@ xmlns:local="clr-namespace:SIL.FieldWorks.LexText.Controls" d:DataContext="{d:DesignInstance Type=local:ParserReportsViewModel, IsDesignTimeCreatable=True}" mc:Ignorable="d" - Title="Parser Test Reports" WindowStartupLocation="CenterScreen" + Title="{x:Static local:ParserUIStrings.ksParserTestReports}" WindowStartupLocation="CenterScreen" SizeToContent="WidthAndHeight" MinHeight="300" MaxHeight="500" WindowStyle="ThreeDBorderWindow"> + @@ -19,8 +20,8 @@ - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { + public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SIL.FieldWorks.LexText.Controls.ParserUIStrings", typeof(ParserUIStrings).Assembly); @@ -51,7 +51,7 @@ internal ParserUIStrings() { /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { + public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -63,7 +63,7 @@ internal ParserUIStrings() { /// /// Looks up a localized string similar to Changed value for {0} from {1} to {2}. The value must be between {3} and {4}, inclusive.. /// - internal static string ksChangedValueReport { + public static string ksChangedValueReport { get { return ResourceManager.GetString("ksChangedValueReport", resourceCulture); } @@ -72,7 +72,7 @@ internal static string ksChangedValueReport { /// /// Looks up a localized string similar to Changed a Value. /// - internal static string ksChangeValueDialogTitle { + public static string ksChangeValueDialogTitle { get { return ResourceManager.GetString("ksChangeValueDialogTitle", resourceCulture); } @@ -81,25 +81,88 @@ internal static string ksChangeValueDialogTitle { /// /// Looks up a localized string similar to -. /// - internal static string ksDash { + public static string ksDash { get { return ResourceManager.GetString("ksDash", resourceCulture); } } + /// + /// Looks up a localized string similar to Delete this test report from the disk. + /// + public static string ksDeleteToolTip { + get { + return ResourceManager.GetString("ksDeleteToolTip", resourceCulture); + } + } + /// /// Looks up a localized string similar to Parse was not attempted because of errors in the lexical data. /// - internal static string ksDidNotParse { + public static string ksDidNotParse { get { return ResourceManager.GetString("ksDidNotParse", resourceCulture); } } + /// + /// Looks up a localized string similar to diff. + /// + public static string ksDiffButton { + get { + return ResourceManager.GetString("ksDiffButton", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show the difference between this test report and the report selected by the radio button (older report is subtracted from newer report). + /// + public static string ksDiffButtonToolTip { + get { + return ResourceManager.GetString("ksDiffButtonToolTip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Diff With. + /// + public static string ksDiffHeader { + get { + return ResourceManager.GetString("ksDiffHeader", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The other argument to diff. + /// + public static string ksDiffHeaderToolTip { + get { + return ResourceManager.GetString("ksDiffHeaderToolTip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error Message. + /// + public static string ksErrorMessage { + get { + return ResourceManager.GetString("ksErrorMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The error message reported by the parser. + /// + public static string ksErrorMessageToolTip { + get { + return ResourceManager.GetString("ksErrorMessageToolTip", resourceCulture); + } + } + /// /// Looks up a localized string similar to . /// - internal static string ksIdle_ { + public static string ksIdle_ { get { return ResourceManager.GetString("ksIdle_", resourceCulture); } @@ -108,7 +171,7 @@ internal static string ksIdle_ { /// /// Looks up a localized string similar to Created by importing the words from files: {0}. /// - internal static string ksImportedFromFilesX { + public static string ksImportedFromFilesX { get { return ResourceManager.GetString("ksImportedFromFilesX", resourceCulture); } @@ -117,7 +180,7 @@ internal static string ksImportedFromFilesX { /// /// Looks up a localized string similar to Created by importing the words from file: {0}. /// - internal static string ksImportedFromFileX { + public static string ksImportedFromFileX { get { return ResourceManager.GetString("ksImportedFromFileX", resourceCulture); } @@ -126,16 +189,34 @@ internal static string ksImportedFromFileX { /// /// Looks up a localized string similar to Loading Files for Word Set {0}. /// - internal static string ksLoadingFilesForWordSetX { + public static string ksLoadingFilesForWordSetX { get { return ResourceManager.GetString("ksLoadingFilesForWordSetX", resourceCulture); } } + /// + /// Looks up a localized string similar to Machine Name. + /// + public static string ksMachineName { + get { + return ResourceManager.GetString("ksMachineName", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The machine that the text was parsed on. + /// + public static string ksMachineNameToolTip { + get { + return ResourceManager.GetString("ksMachineNameToolTip", resourceCulture); + } + } + /// /// Looks up a localized string similar to No files. /// - internal static string ksNoFiles { + public static string ksNoFiles { get { return ResourceManager.GetString("ksNoFiles", resourceCulture); } @@ -144,7 +225,7 @@ internal static string ksNoFiles { /// /// Looks up a localized string similar to No files to import! Please choose at least one file.. /// - internal static string ksNoFilesToImport { + public static string ksNoFilesToImport { get { return ResourceManager.GetString("ksNoFilesToImport", resourceCulture); } @@ -153,25 +234,178 @@ internal static string ksNoFilesToImport { /// /// Looks up a localized string similar to No Parser Loaded. /// - internal static string ksNoParserLoaded { + public static string ksNoParserLoaded { get { return ResourceManager.GetString("ksNoParserLoaded", resourceCulture); } } + /// + /// Looks up a localized string similar to Num Analyses. + /// + public static string ksNumAnalyses { + get { + return ResourceManager.GetString("ksNumAnalyses", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The number of analyses produced by the parser. + /// + public static string ksNumAnalysesToolTip { + get { + return ResourceManager.GetString("ksNumAnalysesToolTip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Disapproved Analyses. + /// + public static string ksNumDisapprovedAnalyses { + get { + return ResourceManager.GetString("ksNumDisapprovedAnalyses", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The number of analyses produced by the parser that were disapproved by the user. + /// + public static string ksNumDisapprovedAnalysesToolTip { + get { + return ResourceManager.GetString("ksNumDisapprovedAnalysesToolTip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed Analyses. + /// + public static string ksNumMissingAnalyses { + get { + return ResourceManager.GetString("ksNumMissingAnalyses", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The number of analyses approved by the user that the parser failed to produce. + /// + public static string ksNumMissingAnalysesToolTip { + get { + return ResourceManager.GetString("ksNumMissingAnalysesToolTip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unknown Analyses. + /// + public static string ksNumNoOpinionAnalyses { + get { + return ResourceManager.GetString("ksNumNoOpinionAnalyses", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The number of analyses produced by the parser that were neither approved nor disapproved by the user. + /// + public static string ksNumNoOpinionAnalysesToolTip { + get { + return ResourceManager.GetString("ksNumNoOpinionAnalysesToolTip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Error Messages. + /// + public static string ksNumParseErrors { + get { + return ResourceManager.GetString("ksNumParseErrors", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The number of error messages in the words parsed. + /// + public static string ksNumParseErrorsToolTip { + get { + return ResourceManager.GetString("ksNumParseErrorsToolTip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Words Parsed. + /// + public static string ksNumWordsParsed { + get { + return ResourceManager.GetString("ksNumWordsParsed", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The number of distinct words parsed in the text. + /// + public static string ksNumWordsParsedToolTip { + get { + return ResourceManager.GetString("ksNumWordsParsedToolTip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No Parses. + /// + public static string ksNumZeroParses { + get { + return ResourceManager.GetString("ksNumZeroParses", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The number of words that got no parse. + /// + public static string ksNumZeroParsesToolTip { + get { + return ResourceManager.GetString("ksNumZeroParsesToolTip", resourceCulture); + } + } + /// /// Looks up a localized string similar to Parser Parameters. /// - internal static string ksParserParameters { + public static string ksParserParameters { get { return ResourceManager.GetString("ksParserParameters", resourceCulture); } } + /// + /// Looks up a localized string similar to Parser Test Reports. + /// + public static string ksParserTestReports { + get { + return ResourceManager.GetString("ksParserTestReports", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Parse Time. + /// + public static string ksParseTime { + get { + return ResourceManager.GetString("ksParseTime", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The time it took to parse the word. + /// + public static string ksParseTimeToolTip { + get { + return ResourceManager.GetString("ksParseTimeToolTip", resourceCulture); + } + } + /// /// Looks up a localized string similar to Queue: ({0}/{1}/{2}). /// - internal static string ksQueueXYZ { + public static string ksQueueXYZ { get { return ResourceManager.GetString("ksQueueXYZ", resourceCulture); } @@ -180,7 +414,7 @@ internal static string ksQueueXYZ { /// /// Looks up a localized string similar to Redo Clear Selected Word Parser Analyses. /// - internal static string ksRedoClearParserAnalyses { + public static string ksRedoClearParserAnalyses { get { return ResourceManager.GetString("ksRedoClearParserAnalyses", resourceCulture); } @@ -189,25 +423,214 @@ internal static string ksRedoClearParserAnalyses { /// /// Looks up a localized string similar to Redo Editing Parser Parameters. /// - internal static string ksRedoEditingParserParameters { + public static string ksRedoEditingParserParameters { get { return ResourceManager.GetString("ksRedoEditingParserParameters", resourceCulture); } } + /// + /// Looks up a localized string similar to reparse. + /// + public static string ksReparse { + get { + return ResourceManager.GetString("ksReparse", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Reparse this word using Try A Word. + /// + public static string ksReparseToolTip { + get { + return ResourceManager.GetString("ksReparseToolTip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Please select a second report other than this report using the radio button in the column labelled 'Diff With'.. + /// + public static string ksSelectDiffReport { + get { + return ResourceManager.GetString("ksSelectDiffReport", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to show. + /// + public static string ksShowAnalyses { + get { + return ResourceManager.GetString("ksShowAnalyses", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show the analyses of this word. + /// + public static string ksShowAnalysesToolTip { + get { + return ResourceManager.GetString("ksShowAnalysesToolTip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to show. + /// + public static string ksShowReport { + get { + return ResourceManager.GetString("ksShowReport", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show this test report. + /// + public static string ksShowReportToolTip { + get { + return ResourceManager.GetString("ksShowReportToolTip", resourceCulture); + } + } + /// /// Looks up a localized string similar to , . /// - internal static string ksSlotNameSeparator { + public static string ksSlotNameSeparator { get { return ResourceManager.GetString("ksSlotNameSeparator", resourceCulture); } } + /// + /// Looks up a localized string similar to Text. + /// + public static string ksText { + get { + return ResourceManager.GetString("ksText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The text that was parsed. + /// + public static string ksTextToolTip { + get { + return ResourceManager.GetString("ksTextToolTip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Timestamp. + /// + public static string ksTimestamp { + get { + return ResourceManager.GetString("ksTimestamp", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to When the text was parsed. + /// + public static string ksTimestampToolTip { + get { + return ResourceManager.GetString("ksTimestampToolTip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Num Analyses. + /// + public static string ksTotalAnalyses { + get { + return ResourceManager.GetString("ksTotalAnalyses", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The total number of analyses in the words parsed. + /// + public static string ksTotalAnalysesToolTip { + get { + return ResourceManager.GetString("ksTotalAnalysesToolTip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Disapproved Analyses. + /// + public static string ksTotalDisapprovedAnalyses { + get { + return ResourceManager.GetString("ksTotalDisapprovedAnalyses", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The total number of disapproved analyses in the words parsed. + /// + public static string ksTotalDisapprovedAnalysesToolTip { + get { + return ResourceManager.GetString("ksTotalDisapprovedAnalysesToolTip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Failed Analyses. + /// + public static string ksTotalMissingAnalyses { + get { + return ResourceManager.GetString("ksTotalMissingAnalyses", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The total number of approved analyses that the parser failed to produce in the words parsed. + /// + public static string ksTotalMissingAnalysesToolTip { + get { + return ResourceManager.GetString("ksTotalMissingAnalysesToolTip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unknown Analyses. + /// + public static string ksTotalNoOpinionAnalyses { + get { + return ResourceManager.GetString("ksTotalNoOpinionAnalyses", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The total number of analyses that were neither approved not disapproved in the words parsed. + /// + public static string ksTotalNoOpinionAnalysesToolTip { + get { + return ResourceManager.GetString("ksTotalNoOpinionAnalysesToolTip", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Parse Time. + /// + public static string ksTotalParseTime { + get { + return ResourceManager.GetString("ksTotalParseTime", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The total time it took to parse the words. + /// + public static string ksTotalParseTimeToolTip { + get { + return ResourceManager.GetString("ksTotalParseTimeToolTip", resourceCulture); + } + } + /// /// Looks up a localized string similar to Undo Clear Selected Word Parser Analyses. /// - internal static string ksUndoClearParserAnalyses { + public static string ksUndoClearParserAnalyses { get { return ResourceManager.GetString("ksUndoClearParserAnalyses", resourceCulture); } @@ -216,7 +639,7 @@ internal static string ksUndoClearParserAnalyses { /// /// Looks up a localized string similar to Undo Editing Parser Parameters. /// - internal static string ksUndoEditingParserParameters { + public static string ksUndoEditingParserParameters { get { return ResourceManager.GetString("ksUndoEditingParserParameters", resourceCulture); } @@ -225,7 +648,7 @@ internal static string ksUndoEditingParserParameters { /// /// Looks up a localized string similar to Unknown. /// - internal static string ksUnknown { + public static string ksUnknown { get { return ResourceManager.GetString("ksUnknown", resourceCulture); } @@ -234,10 +657,28 @@ internal static string ksUnknown { /// /// Looks up a localized string similar to Update. /// - internal static string ksUpdate { + public static string ksUpdate { get { return ResourceManager.GetString("ksUpdate", resourceCulture); } } + + /// + /// Looks up a localized string similar to Word. + /// + public static string ksWord { + get { + return ResourceManager.GetString("ksWord", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The word that was parsed. + /// + public static string ksWordToolTip { + get { + return ResourceManager.GetString("ksWordToolTip", resourceCulture); + } + } } } diff --git a/Src/LexText/ParserUI/ParserUIStrings.resx b/Src/LexText/ParserUI/ParserUIStrings.resx index 1f3706898b..933b652375 100644 --- a/Src/LexText/ParserUI/ParserUIStrings.resx +++ b/Src/LexText/ParserUI/ParserUIStrings.resx @@ -1,4 +1,4 @@ - + diff --git a/Build/nuget-common/packages.config b/Build/nuget-common/packages.config index 7cbd3c92e4..4fd40b7ee3 100644 --- a/Build/nuget-common/packages.config +++ b/Build/nuget-common/packages.config @@ -52,15 +52,15 @@ - - - - - - - - - + + + + + + + + + From ca731933c124d2548570e3b4c669a84ff12d1f2f Mon Sep 17 00:00:00 2001 From: John T Maxwell III Date: Fri, 2 Aug 2024 12:09:09 -0700 Subject: [PATCH 159/285] LT-21586: Export/import Phonology (#120) * Add code to export phonology * Add import interface * Switch to using PhonologyServices. This depends on the latest liblcm * Fix UI for importing phonology * Improve warning message --- .../Language Explorer/Configuration/Main.xml | 8 +- .../Export Templates/Phonology.xml | 4 + Src/xWorks/ExportDialog.cs | 29 ++++++- Src/xWorks/FwXWindow.cs | 78 +++++++++++++++++++ Src/xWorks/xWorksStrings.Designer.cs | 11 ++- Src/xWorks/xWorksStrings.resx | 3 + 6 files changed, 126 insertions(+), 7 deletions(-) create mode 100644 DistFiles/Language Explorer/Export Templates/Phonology.xml diff --git a/DistFiles/Language Explorer/Configuration/Main.xml b/DistFiles/Language Explorer/Configuration/Main.xml index c7f0a26380..1fecd5759c 100644 --- a/DistFiles/Language Explorer/Configuration/Main.xml +++ b/DistFiles/Language Explorer/Configuration/Main.xml @@ -67,8 +67,9 @@ - - + + + @@ -425,7 +426,8 @@ - + + diff --git a/DistFiles/Language Explorer/Export Templates/Phonology.xml b/DistFiles/Language Explorer/Export Templates/Phonology.xml new file mode 100644 index 0000000000..e762f03275 --- /dev/null +++ b/DistFiles/Language Explorer/Export Templates/Phonology.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/Src/xWorks/ExportDialog.cs b/Src/xWorks/ExportDialog.cs index 7f63aedc80..078d49bee5 100644 --- a/Src/xWorks/ExportDialog.cs +++ b/Src/xWorks/ExportDialog.cs @@ -27,6 +27,7 @@ using SIL.FieldWorks.Common.RootSites; using SIL.LCModel; using SIL.LCModel.DomainImpl; +using SIL.LCModel.DomainServices; using SIL.FieldWorks.FdoUi; using SIL.FieldWorks.LexText.Controls; using SIL.FieldWorks.Resources; @@ -38,6 +39,7 @@ using XCore; using PropertyTable = XCore.PropertyTable; using ReflectionHelper = SIL.LCModel.Utils.ReflectionHelper; +using Newtonsoft.Json; namespace SIL.FieldWorks.XWorks { @@ -84,7 +86,8 @@ protected internal enum FxtTypes kftClassifiedDict, kftSemanticDomains, kftWebonary, - kftWordOpenXml + kftWordOpenXml, + kftPhonology } // ReSharper restore InconsistentNaming protected internal struct FxtType @@ -840,6 +843,13 @@ protected void DoExport(string outPath, bool fLiftOutput) progressDlg.Restartable = true; progressDlg.RunTask(true, ExportGrammarSketch, outPath, ft.m_sDataType, ft.m_sXsltFiles); break; + case FxtTypes.kftPhonology: + progressDlg.Minimum = 0; + progressDlg.Maximum = 1000; + progressDlg.AllowCancel = true; + progressDlg.Restartable = true; + progressDlg.RunTask(true, ExportPhonology, outPath, ft.m_sDataType, ft.m_sXsltFiles); + break; case FxtTypes.kftWordOpenXml: progressDlg.Minimum = 0; progressDlg.Maximum = 1000; @@ -916,8 +926,8 @@ private object ExportConfiguredXhtml(IThreadedProgress progress, object[] args) private object ExportGrammarSketch(IThreadedProgress progress, object[] args) { var outPath = (string)args[0]; - var sDataType = (string) args[1]; - var sXslts = (string) args[2]; + var sDataType = (string)args[1]; + var sXslts = (string)args[2]; m_progressDlg = progress; var parameter = new Tuple(sDataType, outPath, sXslts); m_mediator.SendMessage("SaveAsWebpage", parameter); @@ -925,6 +935,16 @@ private object ExportGrammarSketch(IThreadedProgress progress, object[] args) return null; } + private object ExportPhonology(IThreadedProgress progress, object[] args) + { + var outPath = (string)args[0]; + m_progressDlg = progress; + var phonologyServices = new PhonologyServices(m_cache); + phonologyServices.ExportPhonologyAsXml(outPath); + m_progressDlg.Step(1000); + return null; + } + /// ------------------------------------------------------------------------------------ /// /// Exports as a LIFT file (possibly with one or more range files. @@ -1297,6 +1317,9 @@ protected virtual void ConfigureItem(XmlDocument document, ListViewItem item, Xm case "semanticDomains": ft.m_ft = FxtTypes.kftSemanticDomains; break; + case "phonology": + ft.m_ft = FxtTypes.kftPhonology; + break; default: Debug.Fail("Invalid type attribute value for the template element"); ft.m_ft = FxtTypes.kftFxt; diff --git a/Src/xWorks/FwXWindow.cs b/Src/xWorks/FwXWindow.cs index 6baa1892d4..04141608a8 100644 --- a/Src/xWorks/FwXWindow.cs +++ b/Src/xWorks/FwXWindow.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Reflection; using System.Text; +using System.Threading; using System.Windows.Forms; using System.Xml; using Microsoft.Win32; @@ -39,6 +40,8 @@ using SIL.Reporting; using SIL.Utils; using XCore; +using SIL.LCModel.Application.ApplicationServices; +using NAudio.Utils; namespace SIL.FieldWorks.XWorks { @@ -1921,6 +1924,81 @@ public override IxCoreColleague[] GetMessageTargets() return new IxCoreColleague[]{this}; } + public bool OnImportPhonology(object commandObject) + { + string filename = null; + // ActiveForm can go null (see FWNX-731), so cache its value, and check whether + // we need to use 'this' instead (which might be a better idea anyway). + var form = ActiveForm; + if (form == null) + form = this; + Command command = (Command)commandObject; + string caption = command.ToolTip; + using (var dlg = new OpenFileDialogAdapter()) + { + dlg.CheckFileExists = true; + dlg.RestoreDirectory = true; + dlg.Title = ResourceHelper.GetResourceString("kstidOpenTranslatedLists"); + dlg.ValidateNames = true; + dlg.Multiselect = false; + dlg.Filter = ResourceHelper.FileFilter(FileFilterType.FieldWorksTranslatedLists); + if (dlg.ShowDialog(form) != DialogResult.OK) + return true; + filename = dlg.FileName; + } + DialogResult result = MessageBox.Show(xWorksStrings.DeletePhonology, caption, MessageBoxButtons.YesNo, MessageBoxIcon.Question); + if (result != DialogResult.Yes) + return true; + DeletePhonology(); + using (new WaitCursor(form, true)) + { + using (var dlg = new ProgressDialogWithTask(this)) + { + dlg.AllowCancel = true; + dlg.Maximum = 200; + dlg.Message = filename; + dlg.RunTask(true, ImportPhonology, filename, caption); + } + } + return true; + } + + private object ImportPhonology(IThreadedProgress dlg, object[] parameters) + { + try + { + var phonologyServices = new PhonologyServices(Cache); + phonologyServices.ImportPhonologyFromXml((string)parameters[0]); + dlg.Step(200); + // Let the dialog update. + Thread.Sleep(100); + } + catch (Exception ex) + { + Console.WriteLine("Error: " + ex.Message); + MessageBox.Show(ex.Message, (string)parameters[1]); + } + return false; + } + + + private void DeletePhonology() + { + NonUndoableUnitOfWorkHelper.Do(Cache.ServiceLocator.GetInstance(), () => + { + IPhPhonData phonData = Cache.LangProject.PhonologicalDataOA; + // Delete what is covered by ImportPhonology. + phonData.ContextsOS.Clear(); + phonData.EnvironmentsOS.Clear(); + phonData.FeatConstraintsOS.Clear(); + phonData.NaturalClassesOS.Clear(); + phonData.PhonemeSetsOS.Clear(); + phonData.PhonRulesOS.Clear(); + Cache.LanguageProject.PhFeatureSystemOA.TypesOC.Clear(); + Cache.LanguageProject.PhFeatureSystemOA.FeaturesOC.Clear(); + }); + } + /// /// Display this import command everywhere. /// diff --git a/Src/xWorks/xWorksStrings.Designer.cs b/Src/xWorks/xWorksStrings.Designer.cs index 7f5c7624d3..12b4a5ec1c 100644 --- a/Src/xWorks/xWorksStrings.Designer.cs +++ b/Src/xWorks/xWorksStrings.Designer.cs @@ -258,6 +258,15 @@ internal static string DeletedObjectDetected { } } + /// + /// Looks up a localized string similar to This will permanently delete the existing phonology and all references to it. Do you want to continue?. + /// + internal static string DeletePhonology { + get { + return ResourceManager.GetString("DeletePhonology", resourceCulture); + } + } + /// /// Looks up a localized string similar to Delete this custom view. /// @@ -2981,7 +2990,7 @@ internal static string UploadToWebonaryController_ExportDictionaryContent_Export } /// - /// Looks up a localized string similar to Initial Connection failed retrying.... + /// Looks up a localized string similar to Initial connection failed retrying.... /// internal static string UploadToWebonaryController_RetryAfterFailedConnection { get { diff --git a/Src/xWorks/xWorksStrings.resx b/Src/xWorks/xWorksStrings.resx index 7b4dc3fee7..560763d324 100644 --- a/Src/xWorks/xWorksStrings.resx +++ b/Src/xWorks/xWorksStrings.resx @@ -1262,4 +1262,7 @@ See USFM documentation for help. Initial connection failed retrying... + + This will permanently delete the existing phonology and all references to it. Do you want to continue? + \ No newline at end of file From ec55db296a133f9f62842a8646aa1d1efca80565 Mon Sep 17 00:00:00 2001 From: Jason Naylor Date: Fri, 2 Aug 2024 15:02:21 -0700 Subject: [PATCH 160/285] Make some parser tester UX improvements (#124) * Move buttons from columns to button row at the bottm * Change button text to clearer options --- Src/LexText/ParserCore/ParserReport.cs | 4 +- Src/LexText/ParserUI/ParserListener.cs | 8 +- Src/LexText/ParserUI/ParserReportViewModel.cs | 25 ++- Src/LexText/ParserUI/ParserReportsDialog.xaml | 206 ++++++++---------- .../ParserUI/ParserReportsDialog.xaml.cs | 79 +++++-- .../ParserUI/ParserReportsViewModel.cs | 70 +++++- .../ParserUI/ParserUIStrings.Designer.cs | 28 ++- Src/LexText/ParserUI/ParserUIStrings.resx | 17 +- 8 files changed, 281 insertions(+), 156 deletions(-) diff --git a/Src/LexText/ParserCore/ParserReport.cs b/Src/LexText/ParserCore/ParserReport.cs index 8cb63c7231..22324182e9 100644 --- a/Src/LexText/ParserCore/ParserReport.cs +++ b/Src/LexText/ParserCore/ParserReport.cs @@ -84,13 +84,13 @@ public class ParserReport: IEquatable /// Whether this report is selected /// [JsonIgnore] - public Boolean IsSelected { get; set; } + public bool IsSelected { get; set; } /// /// Is this report the result of DiffParserReports? /// [JsonIgnore] - public Boolean IsDiff { get; set; } + public bool IsDiff { get; set; } /// /// The filename that the report came from. diff --git a/Src/LexText/ParserUI/ParserListener.cs b/Src/LexText/ParserUI/ParserListener.cs index 03629a2175..c899351c26 100644 --- a/Src/LexText/ParserUI/ParserListener.cs +++ b/Src/LexText/ParserUI/ParserListener.cs @@ -64,7 +64,7 @@ public class ParserListener : IxCoreColleague, IDisposable, IVwNotifyChange private Dictionary m_checkParserResults = null; private int m_checkParserResultsCount = 0; private string m_sourceText = null; - private ObservableCollection m_parserReports = null; + private ObservableCollection m_parserReports = null; private ParserReportsDialog m_parserReportsDialog = null; public void Init(Mediator mediator, PropertyTable propertyTable, XmlNode configurationParameters) @@ -742,12 +742,12 @@ private void ReadParserReports() { if (m_parserReports == null) { - m_parserReports = new ObservableCollection(); + m_parserReports = new ObservableCollection(); var reportDir = ParserReport.GetProjectReportsDirectory(m_cache); foreach (string filename in Directory.EnumerateFiles(reportDir, "*.json")) { var parserReport = ParserReport.ReadJsonFile(filename); - m_parserReports.Add(parserReport); + m_parserReports.Add(new ParserReportViewModel { ParserReport = parserReport}); } } } @@ -761,7 +761,7 @@ private void AddParserReport(ParserReport parserReport) // m_parserReportsDialog's window updates when m_parserReports changes // because m_parserReports is an ObservableCollection. // Add at front so that newest reports appear first. - m_parserReports.Insert(0, parserReport); + m_parserReports.Insert(0, new ParserReportViewModel { ParserReport = parserReport }); } /// diff --git a/Src/LexText/ParserUI/ParserReportViewModel.cs b/Src/LexText/ParserUI/ParserReportViewModel.cs index 58374c63c2..fd1e94e873 100644 --- a/Src/LexText/ParserUI/ParserReportViewModel.cs +++ b/Src/LexText/ParserUI/ParserReportViewModel.cs @@ -1,16 +1,13 @@ using SIL.FieldWorks.WordWorks.Parser; using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.ComponentModel; using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Windows; namespace SIL.FieldWorks.LexText.Controls { - public class ParserReportViewModel + public class ParserReportViewModel : INotifyPropertyChanged { public ParserReport ParserReport { get; set; } @@ -36,6 +33,21 @@ public IEnumerable ParseReports } } + public DateTime Timestamp => DateTime.FromFileTime(ParserReport.Timestamp); + + public bool IsSelected + { + get => ParserReport.IsSelected; + set + { + if (ParserReport.IsSelected != value) + { + ParserReport.IsSelected = value; + OnPropertyChanged(nameof(IsSelected)); + } + } + } + public ParserReportViewModel() { ParserReport = new ParserReport(); @@ -48,5 +60,10 @@ public ParserReportViewModel() ParserReport.AddParseReport("error", new ParseReport(null, new ParseResult("error"))); } } + public event PropertyChangedEventHandler PropertyChanged; + protected virtual void OnPropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } } } diff --git a/Src/LexText/ParserUI/ParserReportsDialog.xaml b/Src/LexText/ParserUI/ParserReportsDialog.xaml index 9cc22b49ed..31a03ec9c3 100644 --- a/Src/LexText/ParserUI/ParserReportsDialog.xaml +++ b/Src/LexText/ParserUI/ParserReportsDialog.xaml @@ -7,124 +7,104 @@ d:DataContext="{d:DesignInstance Type=local:ParserReportsViewModel, IsDesignTimeCreatable=True}" mc:Ignorable="d" Title="{x:Static local:ParserUIStrings.ksParserTestReports}" WindowStartupLocation="CenterScreen" - SizeToContent="WidthAndHeight" MinHeight="300" MaxHeight="500" + SizeToContent="Width" MinHeight="300" WindowStyle="ThreeDBorderWindow"> + + - - - - - - public partial class ParserReportsDialog : Window { - public ObservableCollection ParserReports { get; } + public ObservableCollection ParserReports { get; } public Mediator Mediator { get; set; } @@ -25,7 +28,7 @@ public ParserReportsDialog() InitializeComponent(); } - public ParserReportsDialog(ObservableCollection parserReports, Mediator mediator, LcmCache cache) + public ParserReportsDialog(ObservableCollection parserReports, Mediator mediator, LcmCache cache) { InitializeComponent(); parserReports.Sort((x, y) => y.Timestamp.CompareTo(x.Timestamp)); @@ -37,45 +40,87 @@ public ParserReportsDialog(ObservableCollection parserReports, Med public void ShowParserReport(object sender, RoutedEventArgs e) { - var button = sender as Button; - var parserReport = button.CommandParameter as ParserReport; - ParserListener.ShowParserReport(parserReport, Mediator, Cache); + foreach (var report in ParserReports) + { + if (report.IsSelected) + { + ParserListener.ShowParserReport(report.ParserReport, Mediator, Cache); + break; + } + } } public void DeleteParserReport(object sender, RoutedEventArgs e) { - var button = sender as Button; - var parserReport = button.CommandParameter as ParserReport; - parserReport.DeleteJsonFile(); - ParserReports.Remove(parserReport); + foreach (var report in ParserReports.ToArray()) // ToArray to avoid modifying the collection while iterating + { + if (report.IsSelected) + { + report.ParserReport.DeleteJsonFile(); + ParserReports.Remove(report); + } + } } public void DiffParserReports(object sender, RoutedEventArgs e) { var button = sender as Button; - var parserReport = button.CommandParameter as ParserReport; - ParserReport parserReport2 = null; + ParserReportViewModel parserReport = null; + ParserReportViewModel parserReport2 = null; foreach (var report in ParserReports) { - if (report.IsSelected && report != parserReport) + if (report.ParserReport.IsSelected) { - parserReport2 = report; + if (parserReport == null) + { + parserReport = report; + } + else if(parserReport2 == null) + { + parserReport2 = report; + } + else + { + // other logic should prevent this case, but if we break that logic just throw an exception. + throw new System.Exception("Only two reports can be selected for diffing."); + } } } if (parserReport2 == null) { - MessageBox.Show(ParserUIStrings.ksSelectDiffReport); - return; + throw new System.Exception("Two reports must be selected for diffing."); } if (parserReport.Timestamp < parserReport2.Timestamp) { // swap the two variables. - ParserReport temp = parserReport; + ParserReportViewModel temp = parserReport; parserReport = parserReport2; parserReport2 = temp; } - var diff = parserReport.DiffParserReports(parserReport2); + var diff = parserReport.ParserReport.DiffParserReports(parserReport2.ParserReport); ParserListener.ShowParserReport(diff, Mediator, Cache); } + private void DataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e) + { + if (sender is DataGrid dataGrid) + { + if(dataGrid.SelectedItem is ParserReportViewModel selectedItem) + ParserListener.ShowParserReport(selectedItem.ParserReport, Mediator, Cache); + else + Debug.Fail("Type of Contents of DataGrid changed, adjust double click code."); + } + } + private void CheckBox_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + if (sender is CheckBox checkbox) + { + checkbox.Focus(); // Focus the checkbox to handle the click event + e.Handled = true; // Prevent the row from being selected + var newValue = !checkbox.IsChecked ?? true; // Toggle the checkbox value + checkbox.IsChecked = newValue; // Set the new value + var bindingExpression = checkbox.GetBindingExpression(ToggleButton.IsCheckedProperty); + bindingExpression?.UpdateSource(); // Update the binding source + } + } } } diff --git a/Src/LexText/ParserUI/ParserReportsViewModel.cs b/Src/LexText/ParserUI/ParserReportsViewModel.cs index 68742b1923..eeaad00d40 100644 --- a/Src/LexText/ParserUI/ParserReportsViewModel.cs +++ b/Src/LexText/ParserUI/ParserReportsViewModel.cs @@ -1,6 +1,7 @@ using System; using System.Collections.ObjectModel; using System.ComponentModel; +using System.Linq; using System.Windows; using SIL.FieldWorks.WordWorks.Parser; @@ -8,17 +9,54 @@ namespace SIL.FieldWorks.LexText.Controls { public class ParserReportsViewModel : INotifyPropertyChanged { - public ObservableCollection ParserReports { get; set; } + private ObservableCollection _parserReports; + public ObservableCollection ParserReports + { + get => _parserReports; + set + { + if (_parserReports != value) + { + // Unsubscribe from PropertyChanged events of old collection items + if (_parserReports != null) + { + foreach (var report in _parserReports) + { + report.PropertyChanged -= OnReportPropertyChanged; + } + } + + _parserReports = value; + + // Subscribe to PropertyChanged events of new collection items + if (_parserReports != null) + { + foreach (var report in _parserReports) + { + report.PropertyChanged += OnReportPropertyChanged; + } + } + + OnPropertyChanged(nameof(ParserReports)); + UpdateButtonStates(); // Update button states when the collection changes + } + } + } + public bool CanShowReport => ParserReports.Count(report => report.IsSelected) == 1; + public bool CanDiffReports => ParserReports.Count(report => report.IsSelected) == 2; + public bool CanDeleteReports => ParserReports.Any(report => report.IsSelected); + public string DiffButtonContent => string.Format(ParserUIStrings.ksDelete, + ParserReports.Count(report => report.IsSelected)); public ParserReportsViewModel() { - ParserReports = new ObservableCollection(); + ParserReports = new ObservableCollection(); // Check if we're in design mode if (DesignerProperties.GetIsInDesignMode(new DependencyObject())) { // Populate with design-time data - ParserReports.Add(new ParserReport + ParserReports.Add(new ParserReportViewModel { ParserReport = new ParserReport { ProjectName = "Example Project 1", MachineName = "DevMachine1", @@ -27,8 +65,8 @@ public ParserReportsViewModel() NumWords = 1000, NumParseErrors = 5, NumZeroParses = 3 - }); - ParserReports.Add(new ParserReport + }}); + ParserReports.Add(new ParserReportViewModel { ParserReport = new ParserReport { ProjectName = "Example Project 2", MachineName = "DevMachine2", @@ -37,15 +75,35 @@ public ParserReportsViewModel() NumWords = 1500, NumParseErrors = 2, NumZeroParses = 1 - }); + }}); } } public event PropertyChangedEventHandler PropertyChanged; + + private void OnReportPropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == nameof(ParserReportViewModel.IsSelected)) + { + // Notify changes to button state properties + OnPropertyChanged(nameof(CanShowReport)); + OnPropertyChanged(nameof(CanDiffReports)); + OnPropertyChanged(nameof(CanDeleteReports)); + OnPropertyChanged(nameof(DiffButtonContent)); + } + } protected virtual void OnPropertyChanged(string propertyName) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } + + // Call this method whenever the IsSelected property of any ParserReport changes + public void UpdateButtonStates() + { + OnPropertyChanged(nameof(CanShowReport)); + OnPropertyChanged(nameof(CanDiffReports)); + OnPropertyChanged(nameof(CanDeleteReports)); + } } } diff --git a/Src/LexText/ParserUI/ParserUIStrings.Designer.cs b/Src/LexText/ParserUI/ParserUIStrings.Designer.cs index 4c611d34d1..fe85bcee0f 100644 --- a/Src/LexText/ParserUI/ParserUIStrings.Designer.cs +++ b/Src/LexText/ParserUI/ParserUIStrings.Designer.cs @@ -87,6 +87,15 @@ public static string ksDash { } } + /// + /// Looks up a localized string similar to Delete {0} Reports. + /// + public static string ksDelete { + get { + return ResourceManager.GetString("ksDelete", resourceCulture); + } + } + /// /// Looks up a localized string similar to Delete this test report from the disk. /// @@ -106,7 +115,7 @@ public static string ksDidNotParse { } /// - /// Looks up a localized string similar to diff. + /// Looks up a localized string similar to _Compare. /// public static string ksDiffButton { get { @@ -115,7 +124,7 @@ public static string ksDiffButton { } /// - /// Looks up a localized string similar to Show the difference between this test report and the report selected by the radio button (older report is subtracted from newer report). + /// Looks up a localized string similar to Show the difference between two selected reports (older report is subtracted from newer report) [Alt-C]. /// public static string ksDiffButtonToolTip { get { @@ -430,7 +439,7 @@ public static string ksRedoEditingParserParameters { } /// - /// Looks up a localized string similar to reparse. + /// Looks up a localized string similar to Try A Word.... /// public static string ksReparse { get { @@ -447,6 +456,15 @@ public static string ksReparseToolTip { } } + /// + /// Looks up a localized string similar to Select. + /// + public static string ksSelect { + get { + return ResourceManager.GetString("ksSelect", resourceCulture); + } + } + /// /// Looks up a localized string similar to Please select a second report other than this report using the radio button in the column labelled 'Diff With'.. /// @@ -457,7 +475,7 @@ public static string ksSelectDiffReport { } /// - /// Looks up a localized string similar to show. + /// Looks up a localized string similar to Show. /// public static string ksShowAnalyses { get { @@ -475,7 +493,7 @@ public static string ksShowAnalysesToolTip { } /// - /// Looks up a localized string similar to show. + /// Looks up a localized string similar to Show Report. /// public static string ksShowReport { get { diff --git a/Src/LexText/ParserUI/ParserUIStrings.resx b/Src/LexText/ParserUI/ParserUIStrings.resx index 933b652375..37a83af411 100644 --- a/Src/LexText/ParserUI/ParserUIStrings.resx +++ b/Src/LexText/ParserUI/ParserUIStrings.resx @@ -186,10 +186,10 @@ Delete this test report from the disk - diff + _Compare - Show the difference between this test report and the report selected by the radio button (older report is subtracted from newer report) + Show the difference between two selected reports (older report is subtracted from newer report) [Alt-C] Diff With @@ -261,19 +261,19 @@ The time it took to parse the word - reparse + Try A Word... Reparse this word using Try A Word - show + Show Show the analyses of this word - show + Show Report Show this test report @@ -329,4 +329,11 @@ Please select a second report other than this report using the radio button in the column labelled 'Diff With'. + + Delete {0} Reports + Deleting parser reports {0} is the number selected + + + Select + \ No newline at end of file From 7b42dadfd0627769e9181a29b954d61a57b8916b Mon Sep 17 00:00:00 2001 From: Ariel Rorabaugh Date: Fri, 2 Aug 2024 17:13:16 -0400 Subject: [PATCH 161/285] LT-21807: WordExport - Fix pictures in subentries Images that are in subentries are associated with a Subentries node instead of a Pictures node. When constructing paragraphs for the runs in AddEntryData, we need to add a check for image descendents. This will catch images that aren't in a Pictures node. Change-Id: Ib4b544add3057b948078ed0ee418a4ab87fbc9c4 --- Src/xWorks/LcmWordGenerator.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Src/xWorks/LcmWordGenerator.cs b/Src/xWorks/LcmWordGenerator.cs index 8948252eac..e7479be6e2 100644 --- a/Src/xWorks/LcmWordGenerator.cs +++ b/Src/xWorks/LcmWordGenerator.cs @@ -1299,7 +1299,13 @@ public void AddEntryData(IFragmentWriter writer, List().Any(); + // Image captions have a Pictures node as their parent. + // For a main entry, an image will have the "Pictures" ConfigurableDictionaryNode associated with it. + // For subentries, however, the image is a descendant of a "Subentries" ConfigurableDictionaryNode. + // Thus, to know if we're dealing with an image and/or caption, + // we check if the node or its parent is a picture Node, or if the run contains a descendant that is a picture. + if (config.Label == "Pictures" || config.Parent?.Label == "Pictures" || containsDrawing) { // Runs containing pictures or captions need to be in separate paragraphs // from whatever precedes and follows them because they will be added into textframes, @@ -1321,7 +1327,7 @@ public void AddEntryData(IFragmentWriter writer, List().Any()) + if (containsDrawing) { if (pieceHasImage) { From ccd1781c6814aed0b31159244717fb370295db0c Mon Sep 17 00:00:00 2001 From: John T Maxwell III Date: Mon, 5 Aug 2024 09:51:23 -0700 Subject: [PATCH 162/285] Fix LT-21824 by changing error message (#125) --- Src/xWorks/xWorksStrings.Designer.cs | 2 +- Src/xWorks/xWorksStrings.resx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/xWorks/xWorksStrings.Designer.cs b/Src/xWorks/xWorksStrings.Designer.cs index 12b4a5ec1c..04b988f3e0 100644 --- a/Src/xWorks/xWorksStrings.Designer.cs +++ b/Src/xWorks/xWorksStrings.Designer.cs @@ -2664,7 +2664,7 @@ internal static string PunctInFieldNameError { } /// - /// Looks up a localized string similar to The custom field name "{0}" includes punctuation characters. This may cause export to fail or make the exported file difficult for other programs to use. We recommend that you rename your custom field and then do the export again. + /// Looks up a localized string similar to The custom field name "{0}" includes punctuation characters. This may cause export to fail or make the exported file difficult for other programs to use. Please contact flex_errors@sil.org for help modifying your project. ///Do you want to continue with the export?. /// internal static string PunctInFieldNameWarning { diff --git a/Src/xWorks/xWorksStrings.resx b/Src/xWorks/xWorksStrings.resx index 560763d324..f3d8440873 100644 --- a/Src/xWorks/xWorksStrings.resx +++ b/Src/xWorks/xWorksStrings.resx @@ -822,7 +822,7 @@ Are you sure you want to delete this list? Please remove the punctuation from the custom field name ("{0}") to allow export and some other functions to work. - The custom field name "{0}" includes punctuation characters. This may cause export to fail or make the exported file difficult for other programs to use. We recommend that you rename your custom field and then do the export again. + The custom field name "{0}" includes punctuation characters. This may cause export to fail or make the exported file difficult for other programs to use. Please contact flex_errors@sil.org for help modifying your project. Do you want to continue with the export? From 8e0fe208c956f45ed3a1a041270c968fbccb764b Mon Sep 17 00:00:00 2001 From: John T Maxwell III Date: Mon, 5 Aug 2024 17:11:41 -0700 Subject: [PATCH 163/285] Fix LT-21854: Delete reports button not working properly (#126) * Fix LT-21854: Delete reports button not working properly * Improve how new items are added to ParserReports --- Src/LexText/ParserUI/ParserListener.cs | 6 ++-- .../ParserUI/ParserReportsDialog.xaml.cs | 1 + .../ParserUI/ParserReportsViewModel.cs | 32 +++++++++---------- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/Src/LexText/ParserUI/ParserListener.cs b/Src/LexText/ParserUI/ParserListener.cs index c899351c26..2a1567da1b 100644 --- a/Src/LexText/ParserUI/ParserListener.cs +++ b/Src/LexText/ParserUI/ParserListener.cs @@ -758,10 +758,10 @@ private void ReadParserReports() private void AddParserReport(ParserReport parserReport) { ReadParserReports(); - // m_parserReportsDialog's window updates when m_parserReports changes - // because m_parserReports is an ObservableCollection. - // Add at front so that newest reports appear first. m_parserReports.Insert(0, new ParserReportViewModel { ParserReport = parserReport }); + if (m_parserReportsDialog != null) + // Reset ParserReports so that the window gets notified when the new report is selected. + ((ParserReportsViewModel)m_parserReportsDialog.DataContext).ParserReports = m_parserReports; } /// diff --git a/Src/LexText/ParserUI/ParserReportsDialog.xaml.cs b/Src/LexText/ParserUI/ParserReportsDialog.xaml.cs index 09c24aa762..1ec9ecd8b6 100644 --- a/Src/LexText/ParserUI/ParserReportsDialog.xaml.cs +++ b/Src/LexText/ParserUI/ParserReportsDialog.xaml.cs @@ -57,6 +57,7 @@ public void DeleteParserReport(object sender, RoutedEventArgs e) if (report.IsSelected) { report.ParserReport.DeleteJsonFile(); + report.IsSelected = false; ParserReports.Remove(report); } } diff --git a/Src/LexText/ParserUI/ParserReportsViewModel.cs b/Src/LexText/ParserUI/ParserReportsViewModel.cs index eeaad00d40..609dbfb768 100644 --- a/Src/LexText/ParserUI/ParserReportsViewModel.cs +++ b/Src/LexText/ParserUI/ParserReportsViewModel.cs @@ -15,31 +15,29 @@ public ObservableCollection ParserReports get => _parserReports; set { - if (_parserReports != value) + // Do this even if value == _parserReports because it may have new items. + // Unsubscribe from PropertyChanged events of old collection items + if (_parserReports != null) { - // Unsubscribe from PropertyChanged events of old collection items - if (_parserReports != null) + foreach (var report in _parserReports) { - foreach (var report in _parserReports) - { - report.PropertyChanged -= OnReportPropertyChanged; - } + report.PropertyChanged -= OnReportPropertyChanged; } + } - _parserReports = value; + _parserReports = value; - // Subscribe to PropertyChanged events of new collection items - if (_parserReports != null) + // Subscribe to PropertyChanged events of new collection items + if (_parserReports != null) + { + foreach (var report in _parserReports) { - foreach (var report in _parserReports) - { - report.PropertyChanged += OnReportPropertyChanged; - } + report.PropertyChanged += OnReportPropertyChanged; } - - OnPropertyChanged(nameof(ParserReports)); - UpdateButtonStates(); // Update button states when the collection changes } + + OnPropertyChanged(nameof(ParserReports)); + UpdateButtonStates(); // Update button states when the collection changes } } public bool CanShowReport => ParserReports.Count(report => report.IsSelected) == 1; From b6fb5b2b1d300ba58dc52ee41fa8397fa89a23f1 Mon Sep 17 00:00:00 2001 From: John T Maxwell III Date: Mon, 5 Aug 2024 18:02:24 -0700 Subject: [PATCH 164/285] Fix 21855: Scrolling function in Parse Test Reports (#128) --- Src/LexText/ParserUI/ParserReportsDialog.xaml | 2 +- Src/LexText/ParserUI/ParserReportsDialog.xaml.cs | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Src/LexText/ParserUI/ParserReportsDialog.xaml b/Src/LexText/ParserUI/ParserReportsDialog.xaml index 31a03ec9c3..6caef14ed4 100644 --- a/Src/LexText/ParserUI/ParserReportsDialog.xaml +++ b/Src/LexText/ParserUI/ParserReportsDialog.xaml @@ -24,7 +24,7 @@ - + parserRep DataContext = new ParserReportsViewModel { ParserReports = parserReports }; } + private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e) + { + ScrollViewer scrollViewer = sender as ScrollViewer; + if (scrollViewer != null) + if (e.Delta > 0) + scrollViewer.LineUp(); + else + scrollViewer.LineDown(); + e.Handled = true; + } + public void ShowParserReport(object sender, RoutedEventArgs e) { foreach (var report in ParserReports) From c636e34b64ff24f8c4a562f421fc649e73bad0ca Mon Sep 17 00:00:00 2001 From: Mark Kidder <83427558+mark-sil@users.noreply.github.com> Date: Tue, 6 Aug 2024 10:19:58 -0400 Subject: [PATCH 165/285] Word Export: Fix indenting problem (#129) We need to use the config.DisplayLabel instead of the config.Style for the style reference. Some cases were previously working because these values were the same. The change to the AddStyle() call was to fix a problem with the way paragraph styles were organized in the collection. Change-Id: I2db26f07716407afd7728bc10b6f7eeddb1954e1 --- Src/xWorks/LcmWordGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Src/xWorks/LcmWordGenerator.cs b/Src/xWorks/LcmWordGenerator.cs index e7479be6e2..004f535b02 100644 --- a/Src/xWorks/LcmWordGenerator.cs +++ b/Src/xWorks/LcmWordGenerator.cs @@ -865,7 +865,7 @@ config.DictionaryNodeOptions is DictionaryNodeGroupingOptions && if (!string.IsNullOrEmpty(config.Style)) { WP.ParagraphProperties paragraphProps = - new WP.ParagraphProperties(new ParagraphStyleId() { Val = config.Style }); + new WP.ParagraphProperties(new ParagraphStyleId() { Val = config.DisplayLabel }); groupPara.PrependChild(paragraphProps); } groupData.DocBody.AppendChild(groupPara); @@ -1678,7 +1678,7 @@ public string AddStyles(ConfigurableDictionaryNode node) if (style.Type == StyleValues.Paragraph) { string oldName = style.StyleId; - string newName = s_styleCollection.AddStyle(style, style.StyleId, style.StyleId); + string newName = s_styleCollection.AddStyle(style, node.Style, style.StyleId); Debug.Assert(oldName == newName, "Not expecting the name for a paragraph style to ever change!"); } else From 6d91975dc961e7dab423e2c14f4c72d7df38629a Mon Sep 17 00:00:00 2001 From: John T Maxwell III Date: Wed, 7 Aug 2024 15:23:48 -0700 Subject: [PATCH 166/285] Fix LT-21858: Parser test reports pane highlights inaccurate report (#130) --- Src/LexText/ParserUI/ParserReportsDialog.xaml | 3 ++- Src/LexText/ParserUI/ParserReportsDialog.xaml.cs | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/Src/LexText/ParserUI/ParserReportsDialog.xaml b/Src/LexText/ParserUI/ParserReportsDialog.xaml index 6caef14ed4..bd57e6f295 100644 --- a/Src/LexText/ParserUI/ParserReportsDialog.xaml +++ b/Src/LexText/ParserUI/ParserReportsDialog.xaml @@ -25,7 +25,8 @@ - + diff --git a/Src/LexText/ParserUI/ParserReportsDialog.xaml.cs b/Src/LexText/ParserUI/ParserReportsDialog.xaml.cs index ce7d838ca4..d3bca91982 100644 --- a/Src/LexText/ParserUI/ParserReportsDialog.xaml.cs +++ b/Src/LexText/ParserUI/ParserReportsDialog.xaml.cs @@ -1,6 +1,7 @@ using SIL.Extensions; using SIL.FieldWorks.WordWorks.Parser; using SIL.LCModel; +using System; using System.Collections.ObjectModel; using System.Diagnostics; using System.Linq; @@ -8,6 +9,7 @@ using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Input; +using System.Windows.Threading; using XCore; namespace SIL.FieldWorks.LexText.Controls @@ -118,8 +120,16 @@ private void DataGrid_MouseDoubleClick(object sender, MouseButtonEventArgs e) { if(dataGrid.SelectedItem is ParserReportViewModel selectedItem) ParserListener.ShowParserReport(selectedItem.ParserReport, Mediator, Cache); - else - Debug.Fail("Type of Contents of DataGrid changed, adjust double click code."); + } + else + Debug.Fail("Type of Contents of DataGrid changed, adjust double click code."); + } + private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (sender is DataGrid dataGrid) + { + // Turn off selection in favor of the check box. + Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() => dataGrid.UnselectAll())); } } private void CheckBox_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) From f939a7c64447dc6007173b7362e953653427f2ae Mon Sep 17 00:00:00 2001 From: "Ariel Ror." Date: Thu, 8 Aug 2024 15:19:02 -0400 Subject: [PATCH 167/285] Fix 2-column display by correcting image anchor (#133) The horizontal anchor type for the textframe should be set to text rather than page. A text type anchor promts the textframe to move with the text it is anchored to, while a page type anchor would keep the textframe fixed in a particular location on the page. If the horizontal anchor type is set to page, images can appear in the wrong column when the document layout is set to two columns. Change-Id: I28c1644d0ce7bdf33d80d908d3615f12b8a059ab --- Src/xWorks/WordStylesGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/xWorks/WordStylesGenerator.cs b/Src/xWorks/WordStylesGenerator.cs index e85fa1e767..66b8279ac2 100644 --- a/Src/xWorks/WordStylesGenerator.cs +++ b/Src/xWorks/WordStylesGenerator.cs @@ -465,7 +465,7 @@ private static Styles GenerateParagraphStyleFromPictureOptions(ConfigurableDicti // A paragraph is turned into a textframe simply by adding a frameproperties object inside the paragraph properties. // We leave a 4-pt border around the textframe--80 twentieths of a point. var textFrameBorder = "80"; - var textFrameProps = new FrameProperties() { Width = textFrameWidth.ToString(), HeightType = HeightRuleValues.Auto, HorizontalSpace = textFrameBorder, VerticalSpace = textFrameBorder, Wrap = TextWrappingValues.NotBeside, VerticalPosition = VerticalAnchorValues.Text, HorizontalPosition = HorizontalAnchorValues.Margin, XAlign = HorizontalAlignmentValues.Right }; + var textFrameProps = new FrameProperties() { Width = textFrameWidth.ToString(), HeightType = HeightRuleValues.Auto, HorizontalSpace = textFrameBorder, VerticalSpace = textFrameBorder, Wrap = TextWrappingValues.NotBeside, VerticalPosition = VerticalAnchorValues.Text, HorizontalPosition = HorizontalAnchorValues.Text, XAlign = HorizontalAlignmentValues.Right }; var parProps = new ParagraphProperties(); frameStyle.StyleId = PictureAndCaptionTextframeStyle; frameStyle.StyleName = new StyleName(){Val = PictureAndCaptionTextframeStyle}; From d8301c774136b34a2078cd750f65d212c657c32a Mon Sep 17 00:00:00 2001 From: John T Maxwell III Date: Fri, 9 Aug 2024 09:38:26 -0700 Subject: [PATCH 168/285] Fix LT-21852: Import Phonology command mistakenly suggests SFM file (#134) --- DistFiles/Language Explorer/Configuration/Main.xml | 2 +- Src/xWorks/FwXWindow.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DistFiles/Language Explorer/Configuration/Main.xml b/DistFiles/Language Explorer/Configuration/Main.xml index 1fecd5759c..f356e34e46 100644 --- a/DistFiles/Language Explorer/Configuration/Main.xml +++ b/DistFiles/Language Explorer/Configuration/Main.xml @@ -68,7 +68,7 @@ - + diff --git a/Src/xWorks/FwXWindow.cs b/Src/xWorks/FwXWindow.cs index 04141608a8..8859e5610c 100644 --- a/Src/xWorks/FwXWindow.cs +++ b/Src/xWorks/FwXWindow.cs @@ -1938,10 +1938,10 @@ public bool OnImportPhonology(object commandObject) { dlg.CheckFileExists = true; dlg.RestoreDirectory = true; - dlg.Title = ResourceHelper.GetResourceString("kstidOpenTranslatedLists"); + dlg.Title = ResourceHelper.GetResourceString("kstidXML"); dlg.ValidateNames = true; dlg.Multiselect = false; - dlg.Filter = ResourceHelper.FileFilter(FileFilterType.FieldWorksTranslatedLists); + dlg.Filter = ResourceHelper.FileFilter(FileFilterType.XML); if (dlg.ShowDialog(form) != DialogResult.OK) return true; filename = dlg.FileName; From 9f9fdae228b6476100274e3d5f98279b311d0869 Mon Sep 17 00:00:00 2001 From: John T Maxwell III Date: Fri, 9 Aug 2024 11:14:51 -0700 Subject: [PATCH 169/285] LT-21859: Fix minor problems in parser test reports (#131) * Fix problems 1-4 of LT-21859 * Switch to On Genre; Fix Beth's height problem * Fix compiler error --- .../Configuration/Words/areaConfiguration.xml | 4 +- Src/LexText/ParserCore/ParserReport.cs | 12 +++- Src/LexText/ParserUI/ParserListener.cs | 54 +++++++++++++---- Src/LexText/ParserUI/ParserReportDialog.xaml | 2 +- .../ParserUI/ParserReportDialog.xaml.cs | 9 ++- Src/LexText/ParserUI/ParserReportViewModel.cs | 6 +- Src/LexText/ParserUI/ParserReportsDialog.xaml | 2 +- Src/LexText/ParserUI/ParserUI.csproj | 10 ++++ .../ParserUI/ParserUIStrings.Designer.cs | 60 +++++++------------ Src/LexText/ParserUI/ParserUIStrings.resx | 29 ++++----- 10 files changed, 109 insertions(+), 79 deletions(-) diff --git a/DistFiles/Language Explorer/Configuration/Words/areaConfiguration.xml b/DistFiles/Language Explorer/Configuration/Words/areaConfiguration.xml index 59dd6f034c..3a39e8b1d1 100644 --- a/DistFiles/Language Explorer/Configuration/Words/areaConfiguration.xml +++ b/DistFiles/Language Explorer/Configuration/Words/areaConfiguration.xml @@ -18,7 +18,7 @@ - + @@ -277,8 +277,8 @@ - + diff --git a/Src/LexText/ParserCore/ParserReport.cs b/Src/LexText/ParserCore/ParserReport.cs index 22324182e9..ec6db14851 100644 --- a/Src/LexText/ParserCore/ParserReport.cs +++ b/Src/LexText/ParserCore/ParserReport.cs @@ -25,7 +25,7 @@ public class ParserReport: IEquatable public string MachineName { get; set; } /// - /// Either "Testbed Texts", "All Texts", or the name of the text parsed + /// Either the name of the text parsed, the name of the genre parsed, or "All Texts" /// public string SourceText { get; set; } @@ -35,6 +35,11 @@ public class ParserReport: IEquatable /// public long Timestamp { get; set; } + /// + /// Timestamp of report that this report was diffed with (only relevant for IsDiff). + /// + public long DiffTimestamp { get; set; } + /// /// Number of words parsed /// @@ -227,7 +232,8 @@ public ParserReport DiffParserReports(ParserReport other) diff.ProjectName = DiffNames(ProjectName, other.ProjectName); diff.SourceText = DiffNames(SourceText, other.SourceText); diff.MachineName = DiffNames(MachineName, other.MachineName); - diff.Timestamp = Timestamp - other.Timestamp; + diff.Timestamp = Timestamp; + diff.DiffTimestamp = other.Timestamp; diff.NumWords = NumWords - other.NumWords; diff.NumParseErrors = NumParseErrors - other.NumParseErrors; diff.NumZeroParses = NumZeroParses - other.NumZeroParses; @@ -244,7 +250,7 @@ string DiffNames(string name, string otherName) { if (name == otherName) return name; - return name + " - " + otherName; + return otherName + " => " + name; } /// diff --git a/Src/LexText/ParserUI/ParserListener.cs b/Src/LexText/ParserUI/ParserListener.cs index 2a1567da1b..fa69cd0b02 100644 --- a/Src/LexText/ParserUI/ParserListener.cs +++ b/Src/LexText/ParserUI/ParserListener.cs @@ -32,6 +32,9 @@ using XCore; using SIL.ObjectModel; using SIL.LCModel.Core.Text; +using System.Runtime.Remoting.Contexts; +using SIL.FieldWorks.Common.Framework.DetailControls; +using SIL.FieldWorks.Common.Controls; namespace SIL.FieldWorks.LexText.Controls { @@ -518,26 +521,56 @@ public bool OnCheckParserOnCurrentText(object argument) return true; //we handled this. } - public bool OnCheckParserOnTestbed(object argument) + public bool OnCheckParserOnGenre(object argument) { CheckDisposed(); if (ConnectToParser()) { - // Get all of the wordforms in the Testbed texts. - IEnumerable wordforms = new HashSet(); - foreach (var text in m_cache.LanguageProject.InterlinearTexts) - foreach (var genre in text.GenreCategories) - if (genre.ShortName == "Testbed") - wordforms = wordforms.Union(text.UniqueWordforms()); + // Get the selected genre from the user. + string displayWs = "analysis vernacular"; + var labels = ObjectLabel.CreateObjectLabels(m_cache, m_cache.LanguageProject.GenreListOA.PossibilitiesOS, "", displayWs); + var chooser = new SimpleListChooser(null, labels, ParserUIStrings.ksGenre, m_propertyTable.GetValue("HelpTopicProvider")); + // chooser.SetHelpTopic("FLExHelpFile"); + ExpandTreeViewNodes(chooser.TreeView.Nodes); + chooser.ShowDialog(); + ICmPossibility selectedGenre = (ICmPossibility)chooser.SelectedObject; + if (chooser.ChosenOne == null || selectedGenre == null) + return false; + // Get all of the wordforms in the genre's texts. + IEnumerable wordforms = new HashSet(); + foreach (var text in m_cache.LanguageProject.InterlinearTexts.Where(t => t.GenreCategories.Any(genre => ContainsGenre(selectedGenre, genre)))) + { + wordforms = wordforms.Union(text.UniqueWordforms()); + } // Check all of the wordforms. - UpdateWordforms(wordforms, ParserPriority.Medium, checkParser: true, "Testbed Texts"); + var genreName = String.Format(ParserUIStrings.ksXGenre, selectedGenre.Name.AnalysisDefaultWritingSystem.Text); + UpdateWordforms(wordforms, ParserPriority.Medium, checkParser: true, genreName); } return true; //we handled this. } + private void ExpandTreeViewNodes(TreeNodeCollection nodes) + { + foreach (TreeNode node in nodes) + { + node.Expand(); + ExpandTreeViewNodes(node.Nodes); + } + } + + private bool ContainsGenre(ICmPossibility genre1, ICmPossibility genre2) + { + while (genre2 != null) + { + if (genre1 == genre2) return true; + genre2 = genre2.Owner as ICmPossibility; + } + return false; + } + public bool OnCheckParserOnAll(object argument) { CheckDisposed(); @@ -575,6 +608,7 @@ private void UpdateWordforms(IEnumerable wordforms, ParserPriority InitCheckParserResults(wordforms, sourceText); if (wordforms.Count() == 0) { + ReadParserReports(); // Write an empty parser report. var parserReport = WriteParserReport(); AddParserReport(parserReport); @@ -624,8 +658,9 @@ private void WordformUpdatedEventHandler(object sender, WordformUpdatedEventArgs if (m_checkParserResults[key] == null) return; } + // Read parser reports before writing and adding a parser report to avoid duplicates. + ReadParserReports(); // Convert parse results into ParserReport. - var parserReport = WriteParserReport(); AddParserReport(parserReport); ShowParserReport(parserReport, m_mediator, m_cache); @@ -757,7 +792,6 @@ private void ReadParserReports() /// private void AddParserReport(ParserReport parserReport) { - ReadParserReports(); m_parserReports.Insert(0, new ParserReportViewModel { ParserReport = parserReport }); if (m_parserReportsDialog != null) // Reset ParserReports so that the window gets notified when the new report is selected. diff --git a/Src/LexText/ParserUI/ParserReportDialog.xaml b/Src/LexText/ParserUI/ParserReportDialog.xaml index 8328ef6bd9..be56f75bba 100644 --- a/Src/LexText/ParserUI/ParserReportDialog.xaml +++ b/Src/LexText/ParserUI/ParserReportDialog.xaml @@ -7,7 +7,7 @@ d:DataContext="{d:DesignInstance Type=local:ParserReportViewModel, IsDesignTimeCreatable=True}" mc:Ignorable="d" Title="{Binding Title}" WindowStartupLocation="CenterScreen" - SizeToContent="WidthAndHeight" MinHeight="300" MaxHeight="1000" + SizeToContent="Width" Height="500" WindowStyle="ThreeDBorderWindow"> diff --git a/Src/LexText/ParserUI/ParserReportDialog.xaml.cs b/Src/LexText/ParserUI/ParserReportDialog.xaml.cs index 1883419466..1334d2d326 100644 --- a/Src/LexText/ParserUI/ParserReportDialog.xaml.cs +++ b/Src/LexText/ParserUI/ParserReportDialog.xaml.cs @@ -30,14 +30,14 @@ public void ReparseWord(object sender, RoutedEventArgs e) { var button = sender as Button; var parseReport = button.CommandParameter as ParseReport; - Mediator.SendMessage("TryThisWord", parseReport.Word); + Mediator.SendMessage("TryThisWord", RemoveArrow(parseReport.Word)); } public void ShowWordAnalyses(object sender, RoutedEventArgs e) { var button = sender as Button; var parseReport = button.CommandParameter as ParseReport; - var tsString = TsStringUtils.MakeString(parseReport.Word, Cache.DefaultVernWs); + var tsString = TsStringUtils.MakeString(RemoveArrow(parseReport.Word), Cache.DefaultVernWs); IWfiWordform wordform; if (Cache.ServiceLocator.GetInstance().TryGetObject(tsString, out wordform)) { @@ -49,5 +49,10 @@ public void ShowWordAnalyses(object sender, RoutedEventArgs e) MessageBox.Show("Unknown word " + parseReport.Word); } } + + private string RemoveArrow(string word) + { + return word.Replace(" => ", string.Empty); + } } } diff --git a/Src/LexText/ParserUI/ParserReportViewModel.cs b/Src/LexText/ParserUI/ParserReportViewModel.cs index fd1e94e873..d9aaf09b5a 100644 --- a/Src/LexText/ParserUI/ParserReportViewModel.cs +++ b/Src/LexText/ParserUI/ParserReportViewModel.cs @@ -17,9 +17,9 @@ public string Title { get { - string time = ParserReport.IsDiff - ? TimeSpan.FromTicks(ParserReport.Timestamp).ToString() - : m_FileTimeToDateTimeConverter.Convert(ParserReport.Timestamp, null, null, null).ToString(); + string time = m_FileTimeToDateTimeConverter.Convert(ParserReport.Timestamp, null, null, null).ToString(); + if (ParserReport.IsDiff) + time = m_FileTimeToDateTimeConverter.Convert(ParserReport.DiffTimestamp, null, null, null).ToString() + " => " + time; return (ParserReport.IsDiff ? ParserUIStrings.ksDiffHeader + " " : "") + ParserReport.ProjectName + ", " + ParserReport.SourceText + ", " + time + ", " + ParserReport.MachineName; } } diff --git a/Src/LexText/ParserUI/ParserReportsDialog.xaml b/Src/LexText/ParserUI/ParserReportsDialog.xaml index bd57e6f295..9941d38385 100644 --- a/Src/LexText/ParserUI/ParserReportsDialog.xaml +++ b/Src/LexText/ParserUI/ParserReportsDialog.xaml @@ -7,7 +7,7 @@ d:DataContext="{d:DesignInstance Type=local:ParserReportsViewModel, IsDesignTimeCreatable=True}" mc:Ignorable="d" Title="{x:Static local:ParserUIStrings.ksParserTestReports}" WindowStartupLocation="CenterScreen" - SizeToContent="Width" MinHeight="300" + SizeToContent="Width" Height="300" WindowStyle="ThreeDBorderWindow"> diff --git a/Src/LexText/ParserUI/ParserUI.csproj b/Src/LexText/ParserUI/ParserUI.csproj index dc7c17b96a..d77a354945 100644 --- a/Src/LexText/ParserUI/ParserUI.csproj +++ b/Src/LexText/ParserUI/ParserUI.csproj @@ -362,6 +362,16 @@ MSBuild:Compile + + + {C65D2B3D-543D-4F63-B35D-5859F5ECDE1E} + DetailControls + + + {BC490547-D278-4442-BD34-3580DBEFC405} + XMLViews + + diff --git a/Src/LexText/ParserUI/ParserUIStrings.Designer.cs b/Src/LexText/ParserUI/ParserUIStrings.Designer.cs index fe85bcee0f..388890d55a 100644 --- a/Src/LexText/ParserUI/ParserUIStrings.Designer.cs +++ b/Src/LexText/ParserUI/ParserUIStrings.Designer.cs @@ -97,7 +97,7 @@ public static string ksDelete { } /// - /// Looks up a localized string similar to Delete this test report from the disk. + /// Looks up a localized string similar to Delete the selected test reports from the disk. /// public static string ksDeleteToolTip { get { @@ -133,7 +133,7 @@ public static string ksDiffButtonToolTip { } /// - /// Looks up a localized string similar to Diff With. + /// Looks up a localized string similar to Compare. /// public static string ksDiffHeader { get { @@ -141,15 +141,6 @@ public static string ksDiffHeader { } } - /// - /// Looks up a localized string similar to The other argument to diff. - /// - public static string ksDiffHeaderToolTip { - get { - return ResourceManager.GetString("ksDiffHeaderToolTip", resourceCulture); - } - } - /// /// Looks up a localized string similar to Error Message. /// @@ -168,6 +159,15 @@ public static string ksErrorMessageToolTip { } } + /// + /// Looks up a localized string similar to Genre. + /// + public static string ksGenre { + get { + return ResourceManager.GetString("ksGenre", resourceCulture); + } + } + /// /// Looks up a localized string similar to . /// @@ -448,7 +448,7 @@ public static string ksReparse { } /// - /// Looks up a localized string similar to Reparse this word using Try A Word. + /// Looks up a localized string similar to Parse this word using Try A Word. /// public static string ksReparseToolTip { get { @@ -465,15 +465,6 @@ public static string ksSelect { } } - /// - /// Looks up a localized string similar to Please select a second report other than this report using the radio button in the column labelled 'Diff With'.. - /// - public static string ksSelectDiffReport { - get { - return ResourceManager.GetString("ksSelectDiffReport", resourceCulture); - } - } - /// /// Looks up a localized string similar to Show. /// @@ -663,24 +654,6 @@ public static string ksUndoEditingParserParameters { } } - /// - /// Looks up a localized string similar to Unknown. - /// - public static string ksUnknown { - get { - return ResourceManager.GetString("ksUnknown", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Update. - /// - public static string ksUpdate { - get { - return ResourceManager.GetString("ksUpdate", resourceCulture); - } - } - /// /// Looks up a localized string similar to Word. /// @@ -698,5 +671,14 @@ public static string ksWordToolTip { return ResourceManager.GetString("ksWordToolTip", resourceCulture); } } + + /// + /// Looks up a localized string similar to {0} genre. + /// + public static string ksXGenre { + get { + return ResourceManager.GetString("ksXGenre", resourceCulture); + } + } } } diff --git a/Src/LexText/ParserUI/ParserUIStrings.resx b/Src/LexText/ParserUI/ParserUIStrings.resx index 37a83af411..52a54f8155 100644 --- a/Src/LexText/ParserUI/ParserUIStrings.resx +++ b/Src/LexText/ParserUI/ParserUIStrings.resx @@ -156,13 +156,6 @@ Queue: ({0}/{1}/{2}) {0}, {1}, and {2} are numbers (or value of ksDash) - - Unknown - - - Update - related to ParserCoreStrings.ksUpdateX - Undo Editing Parser Parameters @@ -182,9 +175,6 @@ Parse was not attempted because of errors in the lexical data - - Delete this test report from the disk - _Compare @@ -192,10 +182,7 @@ Show the difference between two selected reports (older report is subtracted from newer report) [Alt-C] - Diff With - - - The other argument to diff + Compare Error Message @@ -264,7 +251,7 @@ Try A Word... - Reparse this word using Try A Word + Parse this word using Try A Word Show @@ -326,9 +313,6 @@ The word that was parsed - - Please select a second report other than this report using the radio button in the column labelled 'Diff With'. - Delete {0} Reports Deleting parser reports {0} is the number selected @@ -336,4 +320,13 @@ Select + + Delete the selected test reports from the disk + + + Genre + + + {0} genre + \ No newline at end of file From 22e58c7365d6c1739c11852b4b291dcdea19bc84 Mon Sep 17 00:00:00 2001 From: Jason Naylor Date: Fri, 9 Aug 2024 12:36:57 -0700 Subject: [PATCH 170/285] Fix LT-21701: Add basic find dialog for Dictionary views (#132) Co-authored-by: Jake Oliver --- Src/FwCoreDlgs/BasicFindDialog.Designer.cs | 12 +- Src/FwCoreDlgs/BasicFindDialog.cs | 31 +++-- Src/FwCoreDlgs/BasicFindDialog.resx | 35 +++++- Src/xWorks/XhtmlDocView.cs | 127 ++++++++++++++++++++- 4 files changed, 191 insertions(+), 14 deletions(-) diff --git a/Src/FwCoreDlgs/BasicFindDialog.Designer.cs b/Src/FwCoreDlgs/BasicFindDialog.Designer.cs index 5cddfb310d..5ef3e44081 100644 --- a/Src/FwCoreDlgs/BasicFindDialog.Designer.cs +++ b/Src/FwCoreDlgs/BasicFindDialog.Designer.cs @@ -40,6 +40,7 @@ private void InitializeComponent() this._searchTextbox = new System.Windows.Forms.TextBox(); this._notificationLabel = new System.Windows.Forms.Label(); this._findNext = new System.Windows.Forms.Button(); + this._findPrev = new System.Windows.Forms.Button(); this.SuspendLayout(); // // _searchTextbox @@ -61,10 +62,18 @@ private void InitializeComponent() this._findNext.UseVisualStyleBackColor = true; this._findNext.Click += new System.EventHandler(this._findNext_Click); // + // _findPrev + // + resources.ApplyResources(this._findPrev, "_findPrev"); + this._findPrev.Name = "_findPrev"; + this._findPrev.UseVisualStyleBackColor = true; + this._findPrev.Click += new System.EventHandler(this._findPrev_Click); + // // BasicFindDialog // resources.ApplyResources(this, "$this"); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this._findPrev); this.Controls.Add(this._findNext); this.Controls.Add(this._notificationLabel); this.Controls.Add(this._searchTextbox); @@ -85,5 +94,6 @@ private void InitializeComponent() private TextBox _searchTextbox; private System.Windows.Forms.Label _notificationLabel; private System.Windows.Forms.Button _findNext; - } + private Button _findPrev; + } } \ No newline at end of file diff --git a/Src/FwCoreDlgs/BasicFindDialog.cs b/Src/FwCoreDlgs/BasicFindDialog.cs index c9977fc510..86caf70278 100644 --- a/Src/FwCoreDlgs/BasicFindDialog.cs +++ b/Src/FwCoreDlgs/BasicFindDialog.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2016 SIL International +// Copyright (c) 2016 SIL International // This software is licensed under the LGPL, version 2.1 or later // (http://www.gnu.org/licenses/lgpl-2.1.html) @@ -18,6 +18,18 @@ public partial class BasicFindDialog : Form, IBasicFindView /// public event FindNextDelegate FindNext; + /// + public delegate void FindPrevDelegate(object sender, IBasicFindView view); + + /// + public event FindPrevDelegate FindPrev; + + /// + public delegate void SearchTextChangeDelegate(object sender, IBasicFindView view); + + /// + public event SearchTextChangeDelegate SearchTextChanged; + /// /// Basic constructor (for the designer) /// @@ -28,8 +40,7 @@ public BasicFindDialog() private void _findNext_Click(object sender, EventArgs e) { - if(FindNext != null) - FindNext(this, this); + FindNext?.Invoke(this, this); } /// @@ -48,7 +59,8 @@ public string StatusText private void _searchTextbox_TextChanged(object sender, EventArgs e) { - _findNext.Enabled = !string.IsNullOrEmpty(_searchTextbox.Text); + _findNext.Enabled = _findPrev.Enabled = !string.IsNullOrEmpty(_searchTextbox.Text); + SearchTextChanged?.Invoke(this, this); } /// @@ -64,10 +76,15 @@ private void _searchTextbox_KeyDown(object sender, KeyEventArgs e) e.SuppressKeyPress = true; } } - } - /// - public interface IBasicFindView + private void _findPrev_Click(object sender, EventArgs e) + { + FindPrev?.Invoke(this, this); + } + } + + /// + public interface IBasicFindView { /// /// Text to display to the user in the dialog diff --git a/Src/FwCoreDlgs/BasicFindDialog.resx b/Src/FwCoreDlgs/BasicFindDialog.resx index 1bfc79d9fb..7d948ad93f 100644 --- a/Src/FwCoreDlgs/BasicFindDialog.resx +++ b/Src/FwCoreDlgs/BasicFindDialog.resx @@ -138,7 +138,7 @@ $this - 2 + 3 True @@ -162,7 +162,7 @@ $this - 1 + 2 False @@ -189,6 +189,37 @@ $this + 1 + + + False + + + + NoControl + + + 159, 51 + + + 75, 23 + + + 3 + + + Previous + + + _findPrev + + + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + $this + + 0 diff --git a/Src/xWorks/XhtmlDocView.cs b/Src/xWorks/XhtmlDocView.cs index fc59880891..8266cf3154 100644 --- a/Src/xWorks/XhtmlDocView.cs +++ b/Src/xWorks/XhtmlDocView.cs @@ -1187,13 +1187,132 @@ public bool OnFindAndReplaceText(object argument) { if (m_mainView != null) { - var geckoBrowser = m_mainView.NativeBrowser as GeckoWebBrowser; - if (geckoBrowser != null) + var findDialog = new FindDialog(this); + findDialog.Show(m_mainView); + } + return true; + } + + private class FindDialog : BasicFindDialog + { + private string[] results = null; + private int resultIndex = 0; + private XhtmlDocView docView; + public FindDialog(XhtmlDocView doc) + { + docView = doc; + FindNext += FindNextInBrowser; + FindPrev += FindPrevInBrowser; + SearchTextChanged += (sender, args) => { + var lastId = Guid.Empty.ToString(); + if (results != null && results.Length > 0) + lastId = results[resultIndex]; + StatusText = ""; + results = null; + ClearCurrentFindResult(docView.GeckoBrowser, lastId); + }; + } + + private void FindPrevInBrowser(object sender, IBasicFindView view) + { + var geckoBrowser = docView.m_mainView.NativeBrowser as GeckoWebBrowser; + if (geckoBrowser == null) + return; + string lastId = Guid.Empty.ToString(); + if (!InitResults(view.SearchText)) { - geckoBrowser.Window.Find(string.Empty, false, false, true, false, true, true); + lastId = results[resultIndex]; + if (resultIndex - 1 >= 0) + --resultIndex; + else // wrap around + resultIndex = results.Length - 1; } + ScrollAndHighlightResult(geckoBrowser, view, lastId); + } + private void FindNextInBrowser(object sender, IBasicFindView view) + { + var geckoBrowser = docView.m_mainView.NativeBrowser as GeckoWebBrowser; + if (geckoBrowser == null) + return; + string lastId = Guid.Empty.ToString(); + if(!InitResults(view.SearchText)) + { + lastId = results[resultIndex]; + if (resultIndex + 1 < results.Length) + ++resultIndex; + else // wrap around + resultIndex = 0; + } + ScrollAndHighlightResult(geckoBrowser, view, lastId); + } + + private void ScrollAndHighlightResult(GeckoWebBrowser geckoBrowser, IBasicFindView view, string lastId) + { + if (results != null && results.Length > 0) + { + view.StatusText = $"{resultIndex + 1} of {results.Length} Results"; + ClearCurrentFindResult(geckoBrowser, lastId); + var element = geckoBrowser.Document.GetHtmlElementById(results[resultIndex]); + element.ScrollIntoView(true); + docView.AddClassToHtmlElement(element, CurrentSelectedEntryClass); + } + else + { + view.StatusText = "0 Results"; + } + + } + + private void ClearCurrentFindResult(GeckoWebBrowser geckoBrowser, string lastId) + { + var currentElement = geckoBrowser.Document.GetHtmlElementById(lastId); + if (currentElement != null) + docView.RemoveClassFromHtmlElement(currentElement, CurrentSelectedEntryClass); + } + + private bool InitResults(string searchText) + { + var geckoBrowser = docView.m_mainView.NativeBrowser as GeckoWebBrowser; + if (geckoBrowser == null) + throw new ApplicationException(); + if (results == null || results.Length == 0) + { + string newResults = string.Empty; + geckoBrowser.RemoveMessageEventListener("find"); + geckoBrowser.AddMessageEventListener("find", r => newResults = r); + using(var executor = new AutoJSContext(geckoBrowser.Window)) + { + // Javascript query to execute in the browser + // finds every text element matching the search string and returns the id of the first parent element with an id + var browserJsQuery = + "var ids=[];" + + "var containsText = (containsText === undefined)" + // function for finding text in a node (make sure it isn't redefined) + " ? (el, searchText) => Array.from(el.childNodes).some(c => c.nodeType === Node.TEXT_NODE && c.textContent.includes(searchText))" + + " : containsText;" + + "Array.prototype.forEach.call(document.querySelectorAll('span, div, a'), function(element) { if (containsText(element, '" + + searchText + + "')) {" + + " let id = element.id;" + + " if (!id) {" + + " var parent = element.parentElement;" + + " while (parent && !parent.id) {" + + " parent = parent.parentElement;" + + " }" + + " id = parent ? parent.id : null;" + + " }" + + "if (id && !ids.includes(id)) { ids.push(id); } } });" + + "var idsString = ids.join(';');" + + "var event = new MessageEvent('find', { view: window, bubbles: true, cancelable: false, data: idsString });" + // send the results back to the C# code + "document.dispatchEvent(event);"; + executor.EvaluateScript(browserJsQuery); + } + results = newResults.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); + resultIndex = 0; + return true; + } + + return false; } - return true; } /// From e78a35829cdff465817d5ef8e0dd236343a09669 Mon Sep 17 00:00:00 2001 From: John T Maxwell III Date: Fri, 9 Aug 2024 12:51:37 -0700 Subject: [PATCH 171/285] Fix LT-21869: Run Tests report columns don't expand (#136) --- Src/LexText/ParserUI/ParserReportDialog.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Src/LexText/ParserUI/ParserReportDialog.xaml b/Src/LexText/ParserUI/ParserReportDialog.xaml index be56f75bba..8197811047 100644 --- a/Src/LexText/ParserUI/ParserReportDialog.xaml +++ b/Src/LexText/ParserUI/ParserReportDialog.xaml @@ -127,7 +127,7 @@ - + + + /// The style to add it's basedOn style. (It's BasedOn value might get modified.) - /// Can be null, but if it is then the paragraph margin is not generated in context. + /// Can be null, but if it is then the only option for getting a basedOnStyle is from + /// the style, not the parent node. private void AddBasedOnStyle(Style style, ConfigurableDictionaryNode node, ReadOnlyPropertyTable propertyTable) { + Debug.Assert(style.Type == StyleValues.Paragraph); + + // No based on styles for pictures. + if (style.StyleId == WordStylesGenerator.PictureAndCaptionTextframeStyle) + return; + + string basedOnStyleName = null; + string basedOnDisplayName = null; + ConfigurableDictionaryNode parentNode = null; if (style.BasedOn != null && !string.IsNullOrEmpty(style.BasedOn.Val)) + { + basedOnStyleName = style.BasedOn.Val; + } + + // If there is no basedOn style, or the basedOn style is "Normal" then use the + // parent node's style for the basedOn style. + if (string.IsNullOrEmpty(basedOnStyleName) || + basedOnStyleName == WordStylesGenerator.NormalParagraphStyleName) + { + if (node?.Parent != null && !string.IsNullOrEmpty(node.Parent.Style) && + (node.Parent.StyleType == ConfigurableDictionaryNode.StyleTypes.Paragraph)) + { + parentNode = node.Parent; + basedOnStyleName = node.Parent.Style; + basedOnDisplayName = node.Parent.DisplayLabel; + } + } + + if (!string.IsNullOrEmpty(basedOnStyleName)) { // If this is a continuation style then base it on a continuation style. bool continuationStyle = style.StyleId.Value.EndsWith(WordStylesGenerator.EntryStyleContinue); + // Currently this method does not work (and should not be used) for continuation styles. The problem is + // that the basedOn name of the regular style has already been changed to the display name. We would + // need a way to get the FLEX name from the display name. + if (continuationStyle) + { + Debug.Assert(!continuationStyle, "Currently this method does not support continuation styles."); + return; + } lock (s_styleCollection) { // If the basedOn style already exists, then update the reference to the basedOn styles unique name. - if (s_styleCollection.TryGetStyle(style.BasedOn.Val, out Style basedOnStyle)) + if (s_styleCollection.TryGetStyle(basedOnStyleName, out Style basedOnStyle)) { style.BasedOn.Val = basedOnStyle.StyleId; if(continuationStyle && style.BasedOn.Val != WordStylesGenerator.NormalParagraphStyleName) @@ -1719,22 +1782,31 @@ private void AddBasedOnStyle(Style style, ConfigurableDictionaryNode node, ReadO // it's basedOn style, then add this basedOn style to the collection. else { - basedOnStyle = WordStylesGenerator.GenerateWordStyleFromLcmStyleSheet(style.BasedOn.Val, 0, node, propertyTable, !continuationStyle); + basedOnStyle = WordStylesGenerator.GenerateWordStyleFromLcmStyleSheet(basedOnStyleName, 0, propertyTable, !continuationStyle); // Check if the style is based on itself. This happens with the 'Normal' style and could possibly happen with others. bool basedOnIsDifferent = basedOnStyle.BasedOn?.Val != null && basedOnStyle.StyleId != basedOnStyle.BasedOn?.Val; + + if (!string.IsNullOrEmpty(basedOnDisplayName)) + { + basedOnStyle.StyleId = basedOnDisplayName; + basedOnStyle.StyleName.Val = basedOnStyle.StyleId; + style.BasedOn.Val = basedOnStyle.StyleId; + } if (continuationStyle) { basedOnStyle.StyleId += WordStylesGenerator.EntryStyleContinue; basedOnStyle.StyleName.Val = basedOnStyle.StyleId; - style.BasedOn.Val += WordStylesGenerator.EntryStyleContinue; - + style.BasedOn.Val = basedOnStyle.StyleId; } if (basedOnIsDifferent) { - AddBasedOnStyle(basedOnStyle, node, propertyTable); + // If the parentNode is not null then the basedOnStyle came from the parentNode. + // If the parentNode is null then the basedOnStyle came from the style.BasedOn.Val and + // we should pass null to AddBasedOnStyle since no node is associated with the basedOnStyle. + AddBasedOnStyle(basedOnStyle, parentNode, propertyTable); } - s_styleCollection.AddStyle(basedOnStyle, basedOnStyle.StyleId, basedOnStyle.StyleId); + s_styleCollection.AddStyle(basedOnStyle, basedOnStyleName, basedOnStyle.StyleId); } } } @@ -1783,33 +1855,23 @@ public string AddStyles(ConfigurableDictionaryNode node) // The css className isn't important for the Word export. var className = $".{CssGenerator.GetClassAttributeForConfig(node)}"; - Styles styleContent = null; - styleContent = WordStylesGenerator.CheckRangeOfStylesForEmpties(WordStylesGenerator.GenerateParagraphStylesFromConfigurationNode(node, _propertyTable)); + Style style = WordStylesGenerator.GenerateParagraphStyleFromConfigurationNode(node, _propertyTable); - if (styleContent == null) - return className; - if (!styleContent.Any()) + if (style == null) return className; - foreach (Style style in styleContent.Descendants - - - - + + + + + + + /// Letter header string. /// Letter header style name to display in Word. - internal static DocFragment GenerateLetterHeaderDocFragment(string str, string styleDisplayName) + /// True if this is the first header being written. + internal static DocFragment GenerateLetterHeaderDocFragment(string str, string styleDisplayName, bool firstHeader) { var docFrag = new DocFragment(); // Only create paragraph, run, and text objects if string is nonempty if (!string.IsNullOrEmpty(str)) { - // Everything other than the Letter Header should be 2 columns. Create a empty - // paragraph with two columns for the last paragraph in the section that uses 2 - // columns. (The section is all the entries after the previous letter header.) - var sectProps2 = new SectionProperties( - new HeaderReference() { Id = WordStylesGenerator.PageHeaderIdEven, Type = HeaderFooterValues.Even }, - new HeaderReference() { Id = WordStylesGenerator.PageHeaderIdOdd, Type = HeaderFooterValues.Default }, - new Columns() { EqualWidth = true, ColumnCount = 2 }, - new SectionType() { Val = SectionMarkValues.Continuous } - ); - docFrag.DocBody.AppendChild(new WP.Paragraph(new WP.ParagraphProperties(sectProps2))); + // Don't add this paragraph before the first letter header. It results in an extra blank line. + if (!firstHeader) + { + // Everything other than the Letter Header should be 2 columns. Create a empty + // paragraph with two columns for the last paragraph in the section that uses 2 + // columns. (The section is all the entries after the previous letter header.) + var sectProps2 = new SectionProperties( + new HeaderReference() { Id = WordStylesGenerator.PageHeaderIdEven, Type = HeaderFooterValues.Even }, + new HeaderReference() { Id = WordStylesGenerator.PageHeaderIdOdd, Type = HeaderFooterValues.Default }, + new Columns() { EqualWidth = true, ColumnCount = 2 }, + new SectionType() { Val = SectionMarkValues.Continuous } + ); + docFrag.DocBody.AppendChild(new WP.Paragraph(new WP.ParagraphProperties(sectProps2))); + } // Create the letter header in a paragraph. WP.ParagraphProperties paragraphProps = new WP.ParagraphProperties(new ParagraphStyleId() { Val = styleDisplayName }); From 8de3f0dd97c416b40c1f5c45aa5521e3b233154d Mon Sep 17 00:00:00 2001 From: John T Maxwell III Date: Tue, 10 Dec 2024 12:07:22 -0800 Subject: [PATCH 270/285] Fix LT-21999: Disable Save Report on comparison reports (#232) Co-authored-by: Jason Naylor --- Src/LexText/ParserCore/ParserReport.cs | 1 + Src/LexText/ParserUI/ParserReportDialog.xaml | 1 + Src/LexText/ParserUI/ParserReportViewModel.cs | 5 ++++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Src/LexText/ParserCore/ParserReport.cs b/Src/LexText/ParserCore/ParserReport.cs index ea04de3231..a933a39e97 100644 --- a/Src/LexText/ParserCore/ParserReport.cs +++ b/Src/LexText/ParserCore/ParserReport.cs @@ -237,6 +237,7 @@ public ParserReport DiffParserReports(ParserReport other) diff.ProjectName = DiffNames(ProjectName, other.ProjectName); diff.SourceText = DiffNames(SourceText, other.SourceText); diff.MachineName = DiffNames(MachineName, other.MachineName); + diff.Comment = DiffNames(Comment, other.Comment); diff.Timestamp = Timestamp; diff.DiffTimestamp = other.Timestamp; diff.NumWords = NumWords - other.NumWords; diff --git a/Src/LexText/ParserUI/ParserReportDialog.xaml b/Src/LexText/ParserUI/ParserReportDialog.xaml index 23fa40cb3f..793ccd6450 100644 --- a/Src/LexText/ParserUI/ParserReportDialog.xaml +++ b/Src/LexText/ParserUI/ParserReportDialog.xaml @@ -188,6 +188,7 @@