diff --git a/Src/xWorks/ConfiguredLcmGenerator.cs b/Src/xWorks/ConfiguredLcmGenerator.cs index c9f86c22c0..e0892846b2 100644 --- a/Src/xWorks/ConfiguredLcmGenerator.cs +++ b/Src/xWorks/ConfiguredLcmGenerator.cs @@ -1741,24 +1741,25 @@ private static void GenerateContentForLexEntryRefsByType(List t.Guid == typeGuid) : lexEntRef.VariantEntryTypesRS.Any(t => t.Guid == typeGuid)) { - var content = GenerateCollectionItemContent(nodeList, pubDecorator, lexEntRef, collectionOwner, settings, first, typeNode); + var content = GenerateCollectionItemContent(nodeList, pubDecorator, lexEntRef, collectionOwner, settings, innerFirst, typeNode); if (!content.IsNullOrEmpty()) { combinedContent.Append(content); - first = false; + innerFirst = false; } } } - if (!first) + if (combinedContent.Length() > 0) { var lexEntryType = lexEntryTypes.First(t => t.Guid.Equals(typeGuid)); // Display the Type if there were refs of this Type (and we are factoring) @@ -1769,8 +1770,9 @@ private static void GenerateContentForLexEntryRefsByType(List nodeList, object IFragment AddImage(ConfigurableDictionaryNode config, ConfiguredLcmGenerator.GeneratorSettings settings, string classAttribute, string srcAttribute, string pictureGuid, string license); IFragment AddImageCaption(ConfigurableDictionaryNode config, IFragment captionContent); IFragment GenerateSenseNumber(List nodeList, ConfiguredLcmGenerator.GeneratorSettings settings, string formattedSenseNumber, string senseNumberWs); - IFragment AddLexReferences(List nodeList, bool generateLexType, IFragment lexTypeContent, string className, IFragment referencesContent, bool typeBefore); + IFragment AddLexReferences(List nodeList, bool generateLexType, IFragment lexTypeContent, string className, IFragment referencesContent, bool typeBefore, bool firstItem); void BeginCrossReference(IFragmentWriter writer, ConfigurableDictionaryNode config, ConfiguredLcmGenerator.GeneratorSettings settings, bool isBlockProperty, string className); void EndCrossReference(IFragmentWriter writer); void BetweenCrossReferenceType(IFragment content, List nodeList, bool firstItem); diff --git a/Src/xWorks/LcmJsonGenerator.cs b/Src/xWorks/LcmJsonGenerator.cs index 8b9d84e509..a8dbda31a9 100644 --- a/Src/xWorks/LcmJsonGenerator.cs +++ b/Src/xWorks/LcmJsonGenerator.cs @@ -375,7 +375,7 @@ public IFragment GenerateSenseNumber(List nodeList, } public IFragment AddLexReferences(List nodeList, bool generateLexType, - IFragment lexTypeContent, string className, IFragment referencesContent, bool typeBefore) + IFragment lexTypeContent, string className, IFragment referencesContent, bool typeBefore, bool firstItem) { var bldr = new StringBuilder(); var fragment = new StringFragment(bldr); diff --git a/Src/xWorks/LcmWordGenerator.cs b/Src/xWorks/LcmWordGenerator.cs index d6e0923786..d4ec0508e6 100644 --- a/Src/xWorks/LcmWordGenerator.cs +++ b/Src/xWorks/LcmWordGenerator.cs @@ -1044,7 +1044,8 @@ public IFragment WriteProcessedCollection(List nodeL return WriteProcessedElementContent(nodeList, elementContent); } - private IFragment WriteProcessedElementContent(List nodeList, IFragment elementContent) + private IFragment WriteProcessedElementContent(List nodeList, IFragment elementContent, + bool includeBefore = true) { var config = nodeList.Last(); bool eachInAParagraph = config.DictionaryNodeOptions is IParaOption && @@ -1098,7 +1099,7 @@ private IFragment WriteProcessedElementContent(List } // Add Before text, if it is not going to be displayed in a paragraph. - if (!eachInAParagraph && !string.IsNullOrEmpty(config.Before)) + if (includeBefore &&!eachInAParagraph && !string.IsNullOrEmpty(config.Before)) { var beforeRun = wsId.HasValue ? CreateBeforeAfterBetweenRun(nodeList, config.Before, wsId.Value) : CreateDefaultBeforeAfterBetweenRun(nodeList, config.Before); @@ -1875,22 +1876,37 @@ public IFragment GenerateSenseNumber(List nodeList, } public IFragment AddLexReferences(List nodeList, bool generateLexType, - IFragment lexTypeContent, string className, IFragment referencesContent, bool typeBefore) + IFragment lexTypeContent, string className, IFragment referencesContent, bool typeBefore, bool firstItem) { var fragment = new DocFragment(); + var node = nodeList.Last(); + bool includeBefore = true; + + // Add Between text if it is not the first item in the collection. + if (!firstItem && !string.IsNullOrEmpty(node.Between)) + { + var betweenRun = CreateDefaultBeforeAfterBetweenRun(nodeList, node.Between); + fragment.DocBody.Append(betweenRun); + + // To keep the Word display the same as the display in Flex: + // If there is Between text, then we should not also add the Before text. LT-22517 + includeBefore = false; + } + // Generate the factored ref types element (if before). if (generateLexType && typeBefore) { - fragment.Append(WriteProcessedObject(nodeList, false, lexTypeContent, className)); + fragment.Append(WriteProcessedElementContent(nodeList, lexTypeContent, includeBefore)); } + // Then add all the contents for the LexReferences (e.g. headwords) fragment.Append(referencesContent); + // Generate the factored ref types element (if after). if (generateLexType && !typeBefore) { - fragment.Append(WriteProcessedObject(nodeList, false, lexTypeContent, className)); + fragment.Append(WriteProcessedElementContent(nodeList, lexTypeContent, includeBefore)); } - return fragment; } diff --git a/Src/xWorks/LcmXhtmlGenerator.cs b/Src/xWorks/LcmXhtmlGenerator.cs index f89e6d3ed4..4f48a594c0 100644 --- a/Src/xWorks/LcmXhtmlGenerator.cs +++ b/Src/xWorks/LcmXhtmlGenerator.cs @@ -999,7 +999,7 @@ public IFragment GenerateSenseNumber(List nodeList, } public IFragment AddLexReferences(List nodeList, bool generateLexType, - IFragment lexTypeContent, string className, IFragment referencesContent, bool typeBefore) + IFragment lexTypeContent, string className, IFragment referencesContent, bool typeBefore, bool firstItem) { var bldr = new StringBuilder(100); var fragment = new StringFragment(bldr); diff --git a/Src/xWorks/xWorksTests/LcmWordGeneratorTests.cs b/Src/xWorks/xWorksTests/LcmWordGeneratorTests.cs index 6af984d64e..84f6f8b845 100644 --- a/Src/xWorks/xWorksTests/LcmWordGeneratorTests.cs +++ b/Src/xWorks/xWorksTests/LcmWordGeneratorTests.cs @@ -389,7 +389,7 @@ public void GenerateSenseNumberData() } [Test] - public void GenerateBeforeBetweenAfterContent() + public void BeforeBetweenAfterContent() { var wsOpts = ConfiguredXHTMLGeneratorTests.GetWsOptionsForLanguages(new[] { "en" }); var senseOptions = new DictionaryNodeSenseOptions @@ -472,7 +472,7 @@ public void GenerateBeforeBetweenAfterContent() } [Test] - public void GenerateBeforeBetweenAfterContentWithWSAbbreviation() + public void BeforeBetweenAfterContentWithWSAbbreviation() { var wsOpts = new DictionaryNodeWritingSystemOptions { @@ -538,6 +538,202 @@ public void GenerateBeforeBetweenAfterContentWithWSAbbreviation() Assert.That(outXml.Contains(afterAbbreviation), Is.True); } + [Test] + public void BetweenContentOnceForMultipleVariantTypesGroups() + { + // LT-22517: When an entry has variants of two different types, the Between text on the + // VariantEntryTypesRS node should appear exactly once — between the two type groups — + // and not before the first group. + const string betweenText = "BETWEEN_VARIANT_TYPES"; + const string secondVariantType = "Spelling Variant"; + + // Create entries BEFORE building list options so both types are captured in the snapshot. + var mainEntry = ConfiguredXHTMLGeneratorTests.CreateInterestingLexEntry(Cache); + var variantForm1 = ConfiguredXHTMLGeneratorTests.CreateInterestingLexEntry(Cache); + var variantForm2 = ConfiguredXHTMLGeneratorTests.CreateInterestingLexEntry(Cache); + // Two variants of different types → two type groups in the output. + ConfiguredXHTMLGeneratorTests.CreateVariantForm(Cache, mainEntry, variantForm1); // "Crazy Variant" (TestVariantName) + ConfiguredXHTMLGeneratorTests.CreateVariantForm(Cache, mainEntry, variantForm2, secondVariantType); + + var variantFormTypeNameNode = new ConfigurableDictionaryNode + { + FieldDescription = "Name", + IsEnabled = true, + DictionaryNodeOptions = ConfiguredXHTMLGeneratorTests.GetWsOptionsForLanguages(new[] { "en" }) + }; + // Between is on the VariantEntryTypesRS node because that is what nodeList.Last() + // resolves to inside AddLexReferences when factoring by type. + var variantFormTypeNode = new ConfigurableDictionaryNode + { + FieldDescription = "VariantEntryTypesRS", + CSSClassNameOverride = "variantentrytypes", + IsEnabled = true, + Between = betweenText, + Children = new List { variantFormTypeNameNode } + }; + var formNode = new ConfigurableDictionaryNode + { + FieldDescription = "OwningEntry", + SubField = "MLHeadWord", + IsEnabled = true, + DictionaryNodeOptions = ConfiguredXHTMLGeneratorTests.GetWsOptionsForLanguages(new[] { "fr" }) + }; + var variantsNode = new ConfigurableDictionaryNode + { + FieldDescription = "VariantFormEntryBackRefs", + IsEnabled = true, + DictionaryNodeOptions = ConfiguredXHTMLGeneratorTests.GetFullyEnabledListOptions( + DictionaryNodeListOptions.ListIds.Variant, Cache), + Children = new List { variantFormTypeNode, formNode } + }; + var mainEntryNode = new ConfigurableDictionaryNode + { + FieldDescription = "LexEntry", + IsEnabled = true, + Children = new List { variantsNode }, + Style = DictionaryNormal + }; + CssGeneratorTests.PopulateFieldsForTesting(mainEntryNode); + + // SUT + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, DefaultSettings, 0) as DocFragment; + var outXml = result.DocBody.OuterXml; + + // Between text should appear exactly once — between the two groups, not before the first. + Assert.That(Regex.Matches(outXml, betweenText).Count, Is.EqualTo(1), + "Between text should appear exactly once, between the two variant type groups"); + } + + [Test] + public void BetweenContentAbsentForSingleVariantTypeGroup() + { + // LT-22517: When an entry has variants of only one type, no Between text should appear. + const string betweenText = "BETWEEN_VARIANT_TYPES"; + + var mainEntry = ConfiguredXHTMLGeneratorTests.CreateInterestingLexEntry(Cache); + var variantForm1 = ConfiguredXHTMLGeneratorTests.CreateInterestingLexEntry(Cache); + ConfiguredXHTMLGeneratorTests.CreateVariantForm(Cache, mainEntry, variantForm1); // single type group + + var variantFormTypeNameNode = new ConfigurableDictionaryNode + { + FieldDescription = "Name", + IsEnabled = true, + DictionaryNodeOptions = ConfiguredXHTMLGeneratorTests.GetWsOptionsForLanguages(new[] { "en" }) + }; + var variantFormTypeNode = new ConfigurableDictionaryNode + { + FieldDescription = "VariantEntryTypesRS", + CSSClassNameOverride = "variantentrytypes", + IsEnabled = true, + Between = betweenText, + Children = new List { variantFormTypeNameNode } + }; + var formNode = new ConfigurableDictionaryNode + { + FieldDescription = "OwningEntry", + SubField = "MLHeadWord", + IsEnabled = true, + DictionaryNodeOptions = ConfiguredXHTMLGeneratorTests.GetWsOptionsForLanguages(new[] { "fr" }) + }; + var variantsNode = new ConfigurableDictionaryNode + { + FieldDescription = "VariantFormEntryBackRefs", + IsEnabled = true, + DictionaryNodeOptions = ConfiguredXHTMLGeneratorTests.GetFullyEnabledListOptions( + DictionaryNodeListOptions.ListIds.Variant, Cache), + Children = new List { variantFormTypeNode, formNode } + }; + var mainEntryNode = new ConfigurableDictionaryNode + { + FieldDescription = "LexEntry", + IsEnabled = true, + Children = new List { variantsNode }, + Style = DictionaryNormal + }; + CssGeneratorTests.PopulateFieldsForTesting(mainEntryNode); + + // SUT + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, DefaultSettings, 0) as DocFragment; + var outXml = result.DocBody.OuterXml; + + // No Between text should appear when there is only one type group. + Assert.That(outXml, Does.Not.Contain(betweenText), + "Between text should not appear when there is only one variant type group"); + } + + [Test] + public void BeforeContentSuppressedOnSubsequentVariantTypeGroupsWhenBetweenPresent() + { + // LT-22517: When Between text separates variant type groups, the Before text should + // appear only before the first group. It should NOT be re-emitted after the Between + // text on subsequent groups (which would produce e.g. "; (TypeB)" instead of "; TypeB"). + // This keeps the Word Export consistent with the display in Flex. + const string beforeText = "BEFORE_VARIANT_TYPES"; + const string betweenText = "BETWEEN_VARIANT_TYPES"; + const string secondVariantType = "Spelling Variant"; + + var mainEntry = ConfiguredXHTMLGeneratorTests.CreateInterestingLexEntry(Cache); + var variantForm1 = ConfiguredXHTMLGeneratorTests.CreateInterestingLexEntry(Cache); + var variantForm2 = ConfiguredXHTMLGeneratorTests.CreateInterestingLexEntry(Cache); + ConfiguredXHTMLGeneratorTests.CreateVariantForm(Cache, mainEntry, variantForm1); // "Crazy Variant" (TestVariantName) + ConfiguredXHTMLGeneratorTests.CreateVariantForm(Cache, mainEntry, variantForm2, secondVariantType); + + var variantFormTypeNameNode = new ConfigurableDictionaryNode + { + FieldDescription = "Name", + IsEnabled = true, + DictionaryNodeOptions = ConfiguredXHTMLGeneratorTests.GetWsOptionsForLanguages(new[] { "en" }) + }; + var variantFormTypeNode = new ConfigurableDictionaryNode + { + FieldDescription = "VariantEntryTypesRS", + CSSClassNameOverride = "variantentrytypes", + IsEnabled = true, + Before = beforeText, + Between = betweenText, + Children = new List { variantFormTypeNameNode } + }; + var formNode = new ConfigurableDictionaryNode + { + FieldDescription = "OwningEntry", + SubField = "MLHeadWord", + IsEnabled = true, + DictionaryNodeOptions = ConfiguredXHTMLGeneratorTests.GetWsOptionsForLanguages(new[] { "fr" }) + }; + var variantsNode = new ConfigurableDictionaryNode + { + FieldDescription = "VariantFormEntryBackRefs", + IsEnabled = true, + DictionaryNodeOptions = ConfiguredXHTMLGeneratorTests.GetFullyEnabledListOptions( + DictionaryNodeListOptions.ListIds.Variant, Cache), + Children = new List { variantFormTypeNode, formNode } + }; + var mainEntryNode = new ConfigurableDictionaryNode + { + FieldDescription = "LexEntry", + IsEnabled = true, + Children = new List { variantsNode }, + Style = DictionaryNormal + }; + CssGeneratorTests.PopulateFieldsForTesting(mainEntryNode); + + // SUT + var result = ConfiguredLcmGenerator.GenerateContentForEntry(mainEntry, mainEntryNode, null, DefaultSettings, 0) as DocFragment; + var outXml = result.DocBody.OuterXml; + + // Before text should appear exactly once — only before the first group. + Assert.That(Regex.Matches(outXml, beforeText).Count, Is.EqualTo(1), + "Before text should appear exactly once, only before the first variant type group"); + // Between text should appear exactly once — between the two groups. + Assert.That(Regex.Matches(outXml, betweenText).Count, Is.EqualTo(1), + "Between text should appear exactly once, between the two variant type groups"); + // Before text must appear earlier in the output than Between text: + // Before belongs to the first group; Between separates the first from the second. + // If Before appeared after Between it would mean it was (incorrectly) re-emitted on the second group. + Assert.That(outXml.IndexOf(beforeText), Is.LessThan(outXml.IndexOf(betweenText)), + "Before text should precede Between text in the output — it belongs to the first group only"); + } + [Test] public void GeneratePropertyData() {