From f0ecab6988c3ddce1110253d52102090785bb7d5 Mon Sep 17 00:00:00 2001 From: Ariel Rorabaugh Date: Tue, 26 May 2026 13:04:31 -0400 Subject: [PATCH 1/2] LT-22476: Add Classified Dictionary word export * Add classified dictionary word export option. * Extend guideword handling to allow for multiple run styles included in a single guideword. Change-Id: I4b1a4305f66e5bea49ff52183dfbd41f25ac02b6 --- Src/xWorks/DictionaryExportService.cs | 12 + Src/xWorks/ExportDialog.cs | 95 +++--- Src/xWorks/ExportDialog.resx | 421 +++++++++++++------------- Src/xWorks/LcmWordGenerator.cs | 95 ++++-- Src/xWorks/WordStylesGenerator.cs | 2 + 5 files changed, 353 insertions(+), 272 deletions(-) diff --git a/Src/xWorks/DictionaryExportService.cs b/Src/xWorks/DictionaryExportService.cs index 348960a5ca..eaaed17239 100644 --- a/Src/xWorks/DictionaryExportService.cs +++ b/Src/xWorks/DictionaryExportService.cs @@ -113,6 +113,18 @@ public void ExportWordDictionary(string filePath, int[] entriesToSave, RecordCle filePath, progress); } + public void ExportWordClassifiedDictionary(string filePath, int[] entriesToSave, RecordClerk clerk, + DictionaryPublicationDecorator pubDecorator, IThreadedProgress progress) + { + if (progress != null) + progress.Maximum = entriesToSave.Length; + + var dictConfig = new DictionaryConfigurationModel( + DictionaryConfigurationListener.GetCurrentConfiguration(m_propertyTable, "Classified Dictionary"), m_cache); + LcmWordGenerator.SavePublishedDocx(entriesToSave, clerk, pubDecorator, int.MaxValue, dictConfig, m_propertyTable, + filePath, progress); + } + public void ExportWordReversal(string filePath, string reversalWs, int[] entriesToSave, RecordClerk revClerk, DictionaryPublicationDecorator pubDecorator, DictionaryConfigurationModel revConfig, IThreadedProgress progress) { diff --git a/Src/xWorks/ExportDialog.cs b/Src/xWorks/ExportDialog.cs index 47e5b84684..122b5220d4 100644 --- a/Src/xWorks/ExportDialog.cs +++ b/Src/xWorks/ExportDialog.cs @@ -88,6 +88,7 @@ protected internal enum FxtTypes kftSemanticDomains, kftWebonary, kftWordOpenXml, + kftWordClassifiedDict, kftPhonology } // ReSharper restore InconsistentNaming @@ -100,7 +101,7 @@ protected internal struct FxtType public string m_sXsltFiles; public string m_path; // Used to keep track of items after they are sorted. } - protected List m_rgFxtTypes = new List(8); + protected List m_rgFxtTypes = new List(9); protected ConfiguredExport m_ce = null; protected XmlSeqView m_seqView = null; @@ -368,10 +369,8 @@ private void InitializeComponent() this.columnHeader2}); this.m_exportList.FullRowSelect = true; this.m_exportList.HideSelection = false; - this.m_exportList.MinimumSize = new Size(256, 183); this.m_exportList.MultiSelect = false; this.m_exportList.Name = "m_exportList"; - this.m_exportList.ListViewItemSorter = new ExportListComparer(); this.m_exportList.Sorting = SortOrder.Ascending; this.m_exportList.UseCompatibleStateImageBehavior = false; this.m_exportList.View = View.Details; @@ -644,6 +643,7 @@ private void btnExport_Click(object sender, EventArgs e) ProcessWebonaryExport(); return; case FxtTypes.kftWordOpenXml: + case FxtTypes.kftWordClassifiedDict: default: using (var dlg = new SaveFileDialogAdapter()) { @@ -844,6 +844,7 @@ protected void DoExport(string outPath, bool fLiftOutput) progressDlg.RunTask(true, ExportPhonology, outPath, ft.m_sDataType, ft.m_sXsltFiles); break; case FxtTypes.kftWordOpenXml: + case FxtTypes.kftWordClassifiedDict: progressDlg.Minimum = 0; progressDlg.Maximum = 1000; progressDlg.AllowCancel = true; @@ -965,55 +966,68 @@ private void GetClassifiedDictionaryDomains(DictionaryExportService exportServic private object ExportWordOpenXml(IThreadedProgress progress, object[] args) { + + var exportType = m_rgFxtTypes[FxtIndex((string)m_exportItems[0].Tag)].m_ft; + if (args.Length < 1) return null; var filePath = (string)args[0]; var exportService = new DictionaryExportService(m_propertyTable, m_mediator); - // Export the main dictionary. - GetDictionaryEntries(exportService, out var clerk, out var pubDecorator, out var entriesToSave); - exportService.ExportWordDictionary(filePath, entriesToSave, clerk, pubDecorator, progress); - - // Export all the reversals. - var revClerk = exportService.GetReversalClerk(); - try + switch (exportType) { - exportService.StoreReversalData(revClerk, false); - foreach (var reversal in m_cache.ServiceLocator.GetInstance().AllInstances()) - { - var revConfig = DictionaryConfigurationModel.GetReversalConfigurationModel(reversal.WritingSystem, m_cache, m_propertyTable); - if (revConfig != null) + case FxtTypes.kftWordClassifiedDict: + // Export the classified dictionary + GetClassifiedDictionaryDomains(exportService, out var classifiedClerk, out var classifiedDecorator, out var domainsToSave); + exportService.ExportWordClassifiedDictionary(filePath, domainsToSave, classifiedClerk, classifiedDecorator, progress); + break; + + case FxtTypes.kftWordOpenXml: + // Export the main dictionary. + GetDictionaryEntries(exportService, out var clerk, out var pubDecorator, out var entriesToSave); + exportService.ExportWordDictionary(filePath, entriesToSave, clerk, pubDecorator, progress); + + // Export all the reversals. + var revClerk = exportService.GetReversalClerk(); + try { - GetReversalEntries(reversal.Guid, exportService, revConfig, revClerk, out pubDecorator, out entriesToSave); + exportService.StoreReversalData(revClerk, false); + foreach (var reversal in m_cache.ServiceLocator.GetInstance().AllInstances()) + { + var revConfig = DictionaryConfigurationModel.GetReversalConfigurationModel(reversal.WritingSystem, m_cache, m_propertyTable); + if (revConfig != null) + { + GetReversalEntries(reversal.Guid, exportService, revConfig, revClerk, out pubDecorator, out entriesToSave); + + if (entriesToSave.Length > 0) + { + exportService.ExportWordReversal(filePath, reversal.WritingSystem, entriesToSave, + revClerk, pubDecorator, revConfig, progress); + } - if (entriesToSave.Length > 0) + // If we just sorted for the original reversal, then keep it's sorted objects to restore later. + exportService.UpdateSortedObjects(revClerk, reversal.Guid, false); + } + } + } + finally + { + // Restore data. + if (btnExport.InvokeRequired) + { + btnExport.Invoke(() => { exportService.RestoreReversalData(revClerk, false); }); + } + else { - exportService.ExportWordReversal(filePath, reversal.WritingSystem, entriesToSave, - revClerk, pubDecorator, revConfig, progress); + exportService.RestoreReversalData(revClerk, false); } - // If we just sorted for the original reversal, then keep it's sorted objects to restore later. - exportService.UpdateSortedObjects(revClerk, reversal.Guid, false); + // If the reversal clerk was created as part of the export, then we need to stop suppressing + // list loading, or the 'Bulk Edit Reversal Entries' view may be blank the first time viewed. + revClerk.ListLoadingSuppressed = false; } - } - } - finally - { - // Restore data. - if (btnExport.InvokeRequired) - { - btnExport.Invoke(() => { exportService.RestoreReversalData(revClerk, false); }); - } - else - { - exportService.RestoreReversalData(revClerk, false); - } - - // If the reversal clerk was created as part of the export, then we need to stop suppressing - // list loading, or the 'Bulk Edit Reversal Entries' view may be blank the first time viewed. - revClerk.ListLoadingSuppressed = false; + break; } - return null; } @@ -1442,6 +1456,9 @@ protected virtual void ConfigureItem(XmlDocument document, ListViewItem item, Xm case "wordOpenXml": ft.m_ft = FxtTypes.kftWordOpenXml; break; + case "wordClassified": + ft.m_ft = FxtTypes.kftWordClassifiedDict; + break; case "LIFT": ft.m_ft = FxtTypes.kftLift; break; diff --git a/Src/xWorks/ExportDialog.resx b/Src/xWorks/ExportDialog.resx index 7869ad29c1..12adf6bcb9 100644 --- a/Src/xWorks/ExportDialog.resx +++ b/Src/xWorks/ExportDialog.resx @@ -1,396 +1,399 @@ - + mimetype: application/x-microsoft.net.object.bytearray.base64 + value : The object must be serialized into a byte array + : using a System.ComponentModel.TypeConverter + : and then encoded with base64 encoding. + --> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - text/microsoft-resx + text/microsoft-resx - 2.0 + 2.0 - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - + - Bottom, Right + Bottom, Right - + - 390, 235 + 356, 263 - 75, 23 + 90, 27 - + - 0 + 0 - &Export... + &Export... - btnExport + btnExport - System.Windows.Forms.Button, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - $this + $this - 7 + 7 - Bottom, Right + Bottom, Right - 478, 235 + 462, 263 - 75, 23 + 90, 27 - 1 + 1 - &Cancel + &Cancel - btnCancel + btnCancel - System.Windows.Forms.Button, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - $this + $this - 6 + 6 - Top, Bottom, Left, Right + Top, Bottom, Left, Right + + + Data + + + 150 + + + Format + + + 138 - 16, 16 + 19, 18 + + + 256, 183 - 342, 187 + 299, 208 - 2 + 2 - m_exportList + m_exportList - System.Windows.Forms.ListView, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Windows.Forms.ListView, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - $this + $this - 5 - - - Data - - - 150 - - - Format - - - 138 + 5 - Top, Bottom, Right + Top, Bottom, Right - 382, 40 + 346, 46 - 256, 160 + 308, 177 - 3 + 3 - This feature is not implemented yet. + This feature is not implemented yet. - m_description + m_description - System.Windows.Forms.RichTextBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Windows.Forms.RichTextBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - $this + $this - 4 + 4 - Top, Right + Top, Right - 382, 16 + 346, 18 - 208, 23 + 250, 27 - 4 + 4 - About the selected export method: + About the selected export method: - label1 + label1 - System.Windows.Forms.Label, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Windows.Forms.Label, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - $this + $this - 3 + 3 - Bottom, Right + Bottom, Right - 566, 235 + 567, 263 - 75, 23 + 90, 27 - 5 + 5 - Help + Help - buttonHelp + buttonHelp - System.Windows.Forms.Button, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Windows.Forms.Button, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - $this + $this - 2 + 2 - Bottom, Left + Bottom, Left - True + True - False + False - 16, 232 + 19, 259 - 257, 17 + 320, 20 - 6 + 6 - Copy pictures and media files to the export folder. + Copy pictures and media files to the export folder. - m_chkExportPictures + m_chkExportPictures - System.Windows.Forms.CheckBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Windows.Forms.CheckBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - $this + $this - 1 + 1 - Bottom, Left + Bottom, Left - True + True - NoControl + NoControl - 16, 209 + 19, 233 - 96, 17 + 115, 20 - 7 + 7 - Show in folder. + Show in folder. - m_chkShowInFolder + m_chkShowInFolder - System.Windows.Forms.CheckBox, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Windows.Forms.CheckBox, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - $this + $this - 0 + 0 - - True + + True - 5, 13 + 6, 15 - 654, 273 + 673, 307 - 576, 307 + 691, 354 - CenterParent + CenterParent - Export + Export - columnHeader1 + columnHeader1 - System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - columnHeader2 + columnHeader2 - System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Windows.Forms.ColumnHeader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - ExportDialog + ExportDialog - System.Windows.Forms.Form, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + System.Windows.Forms.Form, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 \ No newline at end of file diff --git a/Src/xWorks/LcmWordGenerator.cs b/Src/xWorks/LcmWordGenerator.cs index d6e0923786..34642d7e0a 100644 --- a/Src/xWorks/LcmWordGenerator.cs +++ b/Src/xWorks/LcmWordGenerator.cs @@ -80,7 +80,7 @@ public static void SavePublishedDocx(int[] entryHvos, RecordClerk clerk, Diction settings.StylesGenerator.AddGlobalStyles(configuration, readOnlyPropertyTable); string lastHeader = null; bool firstHeader = true; - string firstGuidewordStyle = null; + List firstGuidewordStyles = null; var entryContents = new Tuple[entryCount]; var entryActions = new List(); @@ -183,9 +183,10 @@ public static void SavePublishedDocx(int[] entryHvos, RecordClerk clerk, Diction // Append the entry to the word doc MasterFragment.Append(entry.Item2); - if (string.IsNullOrEmpty(firstGuidewordStyle)) + // Get styles if firstGuidewordStyles is null or empty + if (firstGuidewordStyles == null || firstGuidewordStyles.Count == 0) { - firstGuidewordStyle = GetFirstGuidewordStyle((DocFragment)entry.Item2, configuration.Type); + firstGuidewordStyles = GetFirstGuidewordStylesList((DocFragment)entry.Item2, configuration.Type); } } } @@ -279,7 +280,7 @@ public static void SavePublishedDocx(int[] entryHvos, RecordClerk clerk, Diction var headerParts = MasterFragment.mainDocPart.HeaderParts; if (!headerParts.Any()) { - AddPageHeaderPartsToPackage(MasterFragment.DocFrag, firstGuidewordStyle); + AddPageHeaderPartsToPackage(MasterFragment.DocFrag, firstGuidewordStyles); } // Add document settings @@ -2014,15 +2015,15 @@ public static NumberingDefinitionsPart AddNumberingPartToPackage(WordprocessingD } // Add the page HeaderParts to the document. - public static void AddPageHeaderPartsToPackage(WordprocessingDocument doc, string guidewordStyle) + public static void AddPageHeaderPartsToPackage(WordprocessingDocument doc, List guidewordStyles) { // Generate header for even pages. HeaderPart even = doc.MainDocumentPart.AddNewPart(WordStylesGenerator.PageHeaderIdEven); - GenerateHeaderPartContent(even, true, guidewordStyle); + GenerateHeaderPartContent(even, true, guidewordStyles); // Generate header for odd pages. HeaderPart odd = doc.MainDocumentPart.AddNewPart(WordStylesGenerator.PageHeaderIdOdd); - GenerateHeaderPartContent(odd, false, guidewordStyle); + GenerateHeaderPartContent(odd, false, guidewordStyles); } /// @@ -2032,18 +2033,28 @@ public static void AddPageHeaderPartsToPackage(WordprocessingDocument doc, strin /// True = generate content for even pages. /// False = generate content for odd pages. /// The style that will be used to find the first or last guideword on the page. - private static void GenerateHeaderPartContent(HeaderPart part, bool even, string guidewordStyle) + private static void GenerateHeaderPartContent(HeaderPart part, bool even, List guidewordStyles) { ParagraphStyleId paraStyleId = new ParagraphStyleId() { Val = WordStylesGenerator.PageHeaderStyleName }; Paragraph para = new Paragraph(new ParagraphProperties(paraStyleId)); if (even) { - if (!string.IsNullOrEmpty(guidewordStyle)) + if (guidewordStyles != null) { // Add the first guideword on the page to the header. - para.Append(new Run(new SimpleField() { Instruction = "STYLEREF \"" + guidewordStyle + "\" \\* MERGEFORMAT" })); + foreach (string style in guidewordStyles) + { + if (!string.IsNullOrEmpty(style)) + { + para.Append(new Run(new SimpleField() + { + Instruction = "STYLEREF \"" + style + "\" \\* MERGEFORMAT" + })); + } + } } + para.Append(new WP.Run(new WP.TabChar())); // Add the page number to the header. para.Append(new WP.Run(new SimpleField() { Instruction = "PAGE" })); @@ -2053,10 +2064,19 @@ private static void GenerateHeaderPartContent(HeaderPart part, bool even, string // Add the page number to the header. para.Append(new WP.Run(new SimpleField() { Instruction = "PAGE" })); para.Append(new WP.Run(new WP.TabChar())); - if (!string.IsNullOrEmpty(guidewordStyle)) + if (guidewordStyles != null) { // Add the last guideword on the page to the header. - para.Append(new WP.Run(new SimpleField() { Instruction = "STYLEREF \"" + guidewordStyle + "\" \\l \\* MERGEFORMAT" })); + foreach (string style in guidewordStyles) + { + if (!string.IsNullOrEmpty(style)) + { + para.Append(new WP.Run(new SimpleField() + { + Instruction = "STYLEREF \"" + style + "\" \\l \\* MERGEFORMAT" + })); + } + } } } @@ -2850,25 +2870,52 @@ internal static bool IsWritingSystemRightToLeft(LcmCache cache, int wsId) } /// - /// Get the full style name for the first RunStyle that begins with the guideword style. + /// Get the full style names for the runs that should be used for guidewords. + /// For each of the guideword styles, the full style name is retrieved from the first RunStyle that begins with that guideword style name. /// - /// Indicates if we are are exporting a Reversal or regular dictionary. - /// The full style name that begins with the guideword style. + /// Indicates if we are are exporting a Reversal, Classified Dictionary, or regular Configured Dictionary. + /// A list of the full style names beginning with each guideword style. /// Null if none are found. - public static string GetFirstGuidewordStyle(DocFragment frag, DictionaryConfigurationModel.ConfigType type) - { - string guidewordStyle = type == DictionaryConfigurationModel.ConfigType.Reversal ? - WordStylesGenerator.ReversalFormDisplayName : WordStylesGenerator.HeadwordDisplayName; + public static List GetFirstGuidewordStylesList(DocFragment frag, DictionaryConfigurationModel.ConfigType type) + { + List guidewordBaseStyles = new List(); + switch (type) + { + case DictionaryConfigurationModel.ConfigType.Reversal: + guidewordBaseStyles.Add(WordStylesGenerator.ReversalFormDisplayName); + break; + // Lexeme type means this is a Classified Dictionary; + // the Abbreviation and Name are semantic domain numbers and names respectively, + // which we want to combine into the guidewords in this case. + case DictionaryConfigurationModel.ConfigType.Lexeme: + guidewordBaseStyles.Add(WordStylesGenerator.Abbreviation); + guidewordBaseStyles.Add(WordStylesGenerator.Name); + break; + // Root type is the default Configured Dictionary; + // use headwords as the guidewords. + case DictionaryConfigurationModel.ConfigType.Root: + default: + guidewordBaseStyles.Add(WordStylesGenerator.HeadwordDisplayName); + break; + } - // Find the first run style with a value that begins with the guideword style. - foreach (RunStyle runStyle in frag.DocBody.Descendants()) + List guidewordFinalStyleNames = new List(); + foreach (string style in guidewordBaseStyles) { - if (runStyle.Val.Value.StartsWith(guidewordStyle) && - !runStyle.Val.Value.Contains(WordStylesGenerator.BeforeAfterBetween)) + // Find the first run style with a value that begins with the guideword style. + foreach (RunStyle runStyle in frag.DocBody.Descendants()) { - return runStyle.Val.Value; + if (runStyle.Val.Value.StartsWith(style)) + // we only include BeforeAfterBetween content if the guideword includes multiple runs + if (guidewordBaseStyles.Count > 1 || !runStyle.Val.Value.Contains(WordStylesGenerator.BeforeAfterBetween)) + { + guidewordFinalStyleNames.Add(runStyle.Val.Value); + } } } + if (guidewordFinalStyleNames.Count > 0) + return guidewordFinalStyleNames; + return null; } diff --git a/Src/xWorks/WordStylesGenerator.cs b/Src/xWorks/WordStylesGenerator.cs index 616ad7ee84..0e4e752f83 100644 --- a/Src/xWorks/WordStylesGenerator.cs +++ b/Src/xWorks/WordStylesGenerator.cs @@ -40,6 +40,8 @@ public class WordStylesGenerator internal const string BeforeAfterBetween = "-Context"; internal const string LinkedCharacterStyle = "-char"; internal const string SubentriesHeadword = "Subheadword"; + internal const string Abbreviation = "Abbreviation"; + internal const string Name = "Name"; // Globals and default paragraph styles. // Nodepaths declared here are common names to use for the global styles From 4cb2bf9086fdf02a74ef555d5e36b7066318d8d5 Mon Sep 17 00:00:00 2001 From: Ariel Rorabaugh Date: Wed, 27 May 2026 10:35:57 -0400 Subject: [PATCH 2/2] Update LcmWordGeneratorTests * Add test for guideword styles for classified dictionary * Update test for guideword styles for configured dictionary * Fix GetFirstGuidewordStylesList in WordGenerator to get at most one primary style and one beforeafterbetween style Change-Id: Ia270e5e2a5199ba3f7c64dc8771b6bac9d70f778 --- Src/xWorks/LcmWordGenerator.cs | 49 +++++++-- .../xWorksTests/LcmWordGeneratorTests.cs | 104 +++++++++++++++++- 2 files changed, 140 insertions(+), 13 deletions(-) diff --git a/Src/xWorks/LcmWordGenerator.cs b/Src/xWorks/LcmWordGenerator.cs index 34642d7e0a..b532abbdf5 100644 --- a/Src/xWorks/LcmWordGenerator.cs +++ b/Src/xWorks/LcmWordGenerator.cs @@ -2879,6 +2879,9 @@ internal static bool IsWritingSystemRightToLeft(LcmCache cache, int wsId) public static List GetFirstGuidewordStylesList(DocFragment frag, DictionaryConfigurationModel.ConfigType type) { List guidewordBaseStyles = new List(); + var runStyles = frag.DocBody.Descendants().ToList(); + List guidewordFinalStyleNames = new List(); + switch (type) { case DictionaryConfigurationModel.ConfigType.Reversal: @@ -2899,18 +2902,44 @@ public static List GetFirstGuidewordStylesList(DocFragment frag, Diction break; } - List guidewordFinalStyleNames = new List(); - foreach (string style in guidewordBaseStyles) + // Include beforeafterbetween only if there is more than one guideword base style + bool includeBeforeAfterBetween = guidewordBaseStyles.Count > 1; + + for (int i = 0; i < guidewordBaseStyles.Count; i++) { - // Find the first run style with a value that begins with the guideword style. - foreach (RunStyle runStyle in frag.DocBody.Descendants()) + string baseStyle = guidewordBaseStyles[i]; + + bool foundPrimaryStyle = false; + bool foundBeforeAfterBetweenStyle = false; + + // Find the first run style with a value that begins with the guideword style, and if applicable the first beforeafterbetween style. + for (int j = 0; j < runStyles.Count; j++) { - if (runStyle.Val.Value.StartsWith(style)) - // we only include BeforeAfterBetween content if the guideword includes multiple runs - if (guidewordBaseStyles.Count > 1 || !runStyle.Val.Value.Contains(WordStylesGenerator.BeforeAfterBetween)) - { - guidewordFinalStyleNames.Add(runStyle.Val.Value); - } + string styleValue = runStyles[j]?.Val?.Value; + + if (string.IsNullOrEmpty(styleValue) || !styleValue.StartsWith(baseStyle)) + continue; + + bool isBeforeAfterBetween = styleValue.Contains(WordStylesGenerator.BeforeAfterBetween); + + if (!isBeforeAfterBetween && !foundPrimaryStyle) + { + guidewordFinalStyleNames.Add(styleValue); + foundPrimaryStyle = true; + } + + else if (includeBeforeAfterBetween && isBeforeAfterBetween && !foundBeforeAfterBetweenStyle) + { + guidewordFinalStyleNames.Add(styleValue); + foundBeforeAfterBetweenStyle = true; + } + + // if there is only one guideword base style, we don't include beforeafterbetween + if (foundPrimaryStyle && (!includeBeforeAfterBetween || foundBeforeAfterBetweenStyle)) + { + // Stop looking for style names for this guideword base style & move to the next. + break; + } } } if (guidewordFinalStyleNames.Count > 0) diff --git a/Src/xWorks/xWorksTests/LcmWordGeneratorTests.cs b/Src/xWorks/xWorksTests/LcmWordGeneratorTests.cs index 6af984d64e..7fb4c88e70 100644 --- a/Src/xWorks/xWorksTests/LcmWordGeneratorTests.cs +++ b/Src/xWorks/xWorksTests/LcmWordGeneratorTests.cs @@ -101,6 +101,14 @@ public override void FixtureSetup() if (!styles.Contains(DictionaryGlossStyleName)) styles.Add(new BaseStyleInfo { Name = DictionaryGlossStyleName, IsParagraphStyle = false }); + // Add character styles that are used in Classified Dictionary + if (!styles.Contains(WordStylesGenerator.Abbreviation)) + styles.Add(new BaseStyleInfo { Name = WordStylesGenerator.Abbreviation, IsParagraphStyle = false }); + if (!styles.Contains(WordStylesGenerator.Abbreviation + WordStylesGenerator.BeforeAfterBetween)) + styles.Add(new BaseStyleInfo { Name = WordStylesGenerator.Abbreviation + WordStylesGenerator.BeforeAfterBetween, IsParagraphStyle = false }); + if (!styles.Contains(WordStylesGenerator.Name)) + styles.Add(new BaseStyleInfo { Name = WordStylesGenerator.Name, IsParagraphStyle = false }); + // Add paragraph styles if (!styles.Contains(WordStylesGenerator.NormalParagraphStyleName)) styles.Add(new BaseStyleInfo { Name = WordStylesGenerator.NormalParagraphStyleName, IsParagraphStyle = true }); @@ -165,6 +173,44 @@ private RecordClerk CreateClerk() clerk.SortName = "Headword"; return clerk; } + + private RecordClerk CreateClassifiedClerk() + { + const string classifiedClerk = @" + + + + + + + + + + + + + + + "; + var doc = new XmlDocument(); + doc.LoadXml(classifiedClerk); + XmlNode clerkNode = doc.SelectSingleNode("//tools/tool[@label='Classified Dictionary']//parameters[@area='lexicon']"); + RecordClerk clerk = RecordClerkFactory.CreateClerk(m_mediator, m_propertyTable, clerkNode, false, false); + clerk.SortName = "Name"; + return clerk; + } + + private ICmSemanticDomain CreateSemanticDomain(LcmCache cache) + { + var domain = cache.ServiceLocator.GetInstance().Create(); + // Add domain to the semantic domain list before setting its name & abbreviation + cache.LangProject.SemanticDomainListOA.PossibilitiesOS.Add(domain); + int ws = cache.WritingSystemFactory.GetWsFromStr("en"); + domain.Name.set_String(ws, "Test Domain"); + domain.Abbreviation.set_String(ws, "1.0"); + return domain; + } + #region disposal protected virtual void Dispose(bool disposing) { @@ -898,7 +944,7 @@ public void GenerateContinueParagraph() } [Test] - public void GetFirstHeadwordStyle() + public void GetGuidewordStyleForConfiguredDictionary() { var wsOpts = ConfiguredXHTMLGeneratorTests.GetWsOptionsForLanguages(new[] { "en" }); var glossNode = new ConfigurableDictionaryNode @@ -927,10 +973,62 @@ public void GetFirstHeadwordStyle() var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, DefaultSettings, 0) as DocFragment; //SUT - string firstHeadwordStyle = LcmWordGenerator.GetFirstGuidewordStyle(result, DictionaryConfigurationModel.ConfigType.Root); + List firstHeadwordStyles = LcmWordGenerator.GetFirstGuidewordStylesList(result, DictionaryConfigurationModel.ConfigType.Root); + + Assert.That(firstHeadwordStyles.Count == 1, Is.True); + Assert.That(firstHeadwordStyles[0] == "Headword[lang=en]", Is.True); + } + + [Test] + public void GetGuidewordStyleForClassifiedDictionary() + { + var classifiedClerk = CreateClassifiedClerk(); + m_propertyTable.SetProperty("ActiveClerk", classifiedClerk, false); + m_propertyTable.SetProperty("currentContentControl", "lexiconClassifiedDictionary", false); + + var wsOpts = ConfiguredXHTMLGeneratorTests.GetWsOptionsForLanguages(new[] { "en" }); + var domainNumberNode = new ConfigurableDictionaryNode + { + FieldDescription = "Abbreviation", + After = " - ", + DictionaryNodeOptions = wsOpts, + Style = "Classified-Abbreviation", + Label = WordStylesGenerator.Abbreviation + }; + var domainNameNode = new ConfigurableDictionaryNode + { + FieldDescription = "Name", + DictionaryNodeOptions = wsOpts, + Style = "Classified-Name", + Label = WordStylesGenerator.Name + }; + var mainEntryNode = new ConfigurableDictionaryNode + { + FieldDescription = "CmSemanticDomain", + DictionaryNodeOptions = ConfiguredXHTMLGeneratorTests.GetSenseNodeOptions(), + Children = new List { domainNumberNode, domainNameNode }, + Style = MainEntryParagraphStyleName, + Label = MainEntryParagraphDisplayName + }; + CssGeneratorTests.PopulateFieldsForTesting(mainEntryNode); + var domain = CreateSemanticDomain(Cache); + var result = ConfiguredLcmGenerator.GenerateContentForEntry(domain, mainEntryNode, null, DefaultSettings, 0) as DocFragment; + + //SUT + List firstGuidewordStyles = LcmWordGenerator.GetFirstGuidewordStylesList(result, DictionaryConfigurationModel.ConfigType.Lexeme); + + // For Classified Dictionary, the guidewords should consist of the following three pieces: + // semantic domain number (abbreviation), after content associated with the semantic domain number, semantic domain name. + Assert.That(firstGuidewordStyles.Count, Is.EqualTo(3)); + Assert.That(firstGuidewordStyles[0], Is.EqualTo(WordStylesGenerator.Abbreviation+"[lang=en]")); + Assert.That(firstGuidewordStyles[1], Is.EqualTo(WordStylesGenerator.Abbreviation+WordStylesGenerator.BeforeAfterBetween+"[lang=en]")); + Assert.That(firstGuidewordStyles[2], Is.EqualTo(WordStylesGenerator.Name + "[lang=en]")); - Assert.That(firstHeadwordStyle == "Headword[lang=en]", Is.True); + // Reset activeclerk and currentContentControl to avoid affecting other tests. + m_propertyTable.SetProperty("ActiveClerk", m_Clerk, false); + m_propertyTable.SetProperty("currentContentControl", "lexiconDictionary", false); } + private ITsString MakeMuliStyleTss(IEnumerable content) { var wsEn = Cache.WritingSystemFactory.GetWsFromStr("en");