diff --git a/Src/xWorks/LcmWordGenerator.cs b/Src/xWorks/LcmWordGenerator.cs
index e2bd0109c2..dc32532404 100644
--- a/Src/xWorks/LcmWordGenerator.cs
+++ b/Src/xWorks/LcmWordGenerator.cs
@@ -26,6 +26,7 @@
using XmlDrawing = DocumentFormat.OpenXml.Drawing;
using DrawingWP = DocumentFormat.OpenXml.Drawing.Wordprocessing;
using Pictures = DocumentFormat.OpenXml.Drawing.Pictures;
+using System.Text.RegularExpressions;
namespace SIL.FieldWorks.XWorks
{
@@ -1754,6 +1755,25 @@ private WP.Run CreateRun(string runText, string styleDisplayName)
/// The BeforeAfterBetween run.
private WP.Run CreateBeforeAfterBetweenRun(string text)
{
+ if(text.Contains("\\A"))
+ {
+ var run = new WP.Run()
+ {
+ RunProperties = new WP.RunProperties(new RunStyle() { Val = WordStylesGenerator.BeforeAfterBetweenDisplayName })
+ };
+ // If the before after between text has line break characters return a composite run including theline breaks
+ // Use Regex.Matches to capture both the content and the delimiters
+ var matches = Regex.Matches(text, @"(\\A|\\0A)|[^\\]*(?:(?=\\A|\\0A)|$)");
+ foreach (Match match in matches)
+ {
+ if (match.Groups[1].Success)
+ run.Append(new WP.Break() { Type = BreakValues.TextWrapping });
+ else
+ run.Append(new WP.Text(match.Value));
+ }
+ return run;
+ }
+
return CreateRun(text, WordStylesGenerator.BeforeAfterBetweenDisplayName);
}
diff --git a/Src/xWorks/xWorksTests/LcmWordGeneratorTests.cs b/Src/xWorks/xWorksTests/LcmWordGeneratorTests.cs
new file mode 100644
index 0000000000..165e6e538a
--- /dev/null
+++ b/Src/xWorks/xWorksTests/LcmWordGeneratorTests.cs
@@ -0,0 +1,217 @@
+// Copyright (c) 2020-2023 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.IO;
+using System.Linq;
+using System.Windows.Forms.VisualStyles;
+using System.Xml;
+using DocumentFormat.OpenXml.Packaging;
+using DocumentFormat.OpenXml.Wordprocessing;
+using NUnit.Framework;
+using SIL.FieldWorks.Common.Framework;
+using SIL.FieldWorks.Common.FwUtils;
+using SIL.FieldWorks.Common.Widgets;
+using SIL.LCModel;
+using SIL.LCModel.Application;
+using SIL.LCModel.DomainImpl;
+using SIL.LCModel.DomainServices;
+using SIL.TestUtilities;
+using XCore;
+using static SIL.FieldWorks.XWorks.LcmWordGenerator;
+// ReSharper disable StringLiteralTypo
+
+namespace SIL.FieldWorks.XWorks
+{
+ [TestFixture]
+ class LcmWordGeneratorTests : MemoryOnlyBackendProviderRestoredForEachTestTestBase, IDisposable
+ {
+ private FwXApp m_application;
+ private FwXWindow m_window;
+ private PropertyTable m_propertyTable;
+ private Mediator m_mediator;
+ private RecordClerk m_Clerk;
+
+ private const string DictionaryNormal = "Dictionary-Normal";
+
+ private ConfiguredLcmGenerator.GeneratorSettings DefaultSettings;
+
+ private static XmlNamespaceManager WordNamespaceManager;
+
+ static LcmWordGeneratorTests()
+ {
+ var openXmlSchema = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
+ WordNamespaceManager = new XmlNamespaceManager(new NameTable());
+ WordNamespaceManager.AddNamespace("w", openXmlSchema);
+ WordNamespaceManager.AddNamespace("r", openXmlSchema);
+ WordNamespaceManager.AddNamespace("wp", openXmlSchema);
+ }
+
+ [OneTimeSetUp]
+ public override void FixtureSetup()
+ {
+ base.FixtureSetup();
+ FwRegistrySettings.Init();
+ m_application = new MockFwXApp(new MockFwManager { Cache = Cache }, null, null);
+ var m_configFilePath = Path.Combine(FwDirectoryFinder.CodeDirectory, m_application.DefaultConfigurationPathname);
+ m_window = new MockFwXWindow(m_application, m_configFilePath);
+ ((MockFwXWindow)m_window).Init(Cache); // initializes Mediator values
+ m_propertyTable = m_window.PropTable;
+ m_mediator = m_window.Mediator;
+ m_window.LoadUI(m_configFilePath); // actually loads UI here; needed for non-null stylesheet
+ var wordGenerator = new LcmWordGenerator(Cache);
+ wordGenerator.Init(new ReadOnlyPropertyTable(m_propertyTable));
+ DefaultSettings = new ConfiguredLcmGenerator.GeneratorSettings(Cache,
+ new ReadOnlyPropertyTable(m_propertyTable), true, false, null)
+ { ContentGenerator = wordGenerator, StylesGenerator = wordGenerator };
+
+ var styles = FontHeightAdjuster.StyleSheetFromPropertyTable(m_propertyTable).Styles;
+ if (!styles.Contains(DictionaryNormal))
+ styles.Add(new BaseStyleInfo { Name = DictionaryNormal });
+ if (!styles.Contains("Dictionary-Headword"))
+ styles.Add(new BaseStyleInfo { Name = "Dictionary-Headword", IsParagraphStyle = false});
+
+ m_Clerk = CreateClerk();
+ m_propertyTable.SetProperty("ActiveClerk", m_Clerk, false);
+
+ m_propertyTable.SetProperty("currentContentControl", "lexiconDictionary", false);
+ Cache.ProjectId.Path = Path.Combine(FwDirectoryFinder.SourceDirectory, "xWorks/xWorksTests/TestData/");
+ }
+
+ private RecordClerk CreateClerk()
+ {
+ const string entryClerk = @"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ";
+ var doc = new XmlDocument();
+ doc.LoadXml(entryClerk);
+ XmlNode clerkNode = doc.SelectSingleNode("//tools/tool[@label='Dictionary']//parameters[@area='lexicon']");
+ RecordClerk clerk = RecordClerkFactory.CreateClerk(m_mediator, m_propertyTable, clerkNode, false);
+ clerk.SortName = "Headword";
+ return clerk;
+ }
+ #region disposal
+ protected virtual void Dispose(bool disposing)
+ {
+ System.Diagnostics.Debug.WriteLineIf(!disposing, "****** Missing Dispose() call for " + GetType().Name + ". ****** ");
+ if (disposing)
+ {
+ m_Clerk?.Dispose();
+ m_application?.Dispose();
+ m_window?.Dispose();
+ m_mediator?.Dispose();
+ m_propertyTable?.Dispose();
+ }
+ }
+
+ ~LcmWordGeneratorTests()
+ {
+ Dispose(false);
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ // This object will be cleaned up by the Dispose method.
+ // Therefore, you should call GC.SupressFinalize to
+ // take this object off the finalization queue
+ // and prevent finalization code for this object
+ // from executing a second time.
+ GC.SuppressFinalize(this);
+ }
+ #endregion disposal
+
+ [OneTimeTearDown]
+ public override void FixtureTeardown()
+ {
+ base.FixtureTeardown();
+ Dispose();
+ }
+
+
+ [Test]
+ public void GenerateWordDocForEntry_OneSenseWithGlossGeneratesCorrectResult()
+ {
+ var wsOpts = ConfiguredXHTMLGeneratorTests.GetWsOptionsForLanguages(new[] { "en" });
+ var glossNode = new ConfigurableDictionaryNode
+ {
+ FieldDescription = "Gloss",
+ DictionaryNodeOptions = wsOpts,
+ Style = "Dictionary-Headword"
+ };
+ var sensesNode = new ConfigurableDictionaryNode
+ {
+ FieldDescription = "Senses",
+ DictionaryNodeOptions = ConfiguredXHTMLGeneratorTests.GetSenseNodeOptions(),
+ Children = new List { glossNode },
+ Style = DictionaryNormal
+ };
+ var mainEntryNode = new ConfigurableDictionaryNode
+ {
+ Children = new List { sensesNode },
+ FieldDescription = "LexEntry",
+ Style = DictionaryNormal
+ };
+ CssGeneratorTests.PopulateFieldsForTesting(mainEntryNode);
+ var entry = ConfiguredXHTMLGeneratorTests.CreateInterestingLexEntry(Cache);
+
+ //SUT
+ var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, DefaultSettings, 0) as DocFragment;
+ Console.WriteLine(result);
+ AssertThatXmlIn.String(result.mainDocPart.RootElement.OuterXml).HasSpecifiedNumberOfMatchesForXpath(
+ "/w:document/w:body/w:p/w:r/w:t[text()='gloss']",
+ 1,
+ WordNamespaceManager);
+ }
+
+ [Test]
+ public void GenerateWordDocForEntry_LineBreaksInBeforeContentWork()
+ {
+ var wsOpts = ConfiguredXHTMLGeneratorTests.GetWsOptionsForLanguages(new[] { "en" });
+ var glossNode = new ConfigurableDictionaryNode
+ {
+ FieldDescription = "Gloss",
+ DictionaryNodeOptions = wsOpts,
+ Style = "Dictionary-Headword",
+ Before = "\\Abefore\\0A"
+ };
+ var sensesNode = new ConfigurableDictionaryNode
+ {
+ FieldDescription = "Senses",
+ DictionaryNodeOptions = ConfiguredXHTMLGeneratorTests.GetSenseNodeOptions(),
+ Children = new List { glossNode },
+ Style = DictionaryNormal
+ };
+ var mainEntryNode = new ConfigurableDictionaryNode
+ {
+ Children = new List { sensesNode },
+ FieldDescription = "LexEntry",
+ Style = DictionaryNormal
+ };
+ CssGeneratorTests.PopulateFieldsForTesting(mainEntryNode);
+ var entry = ConfiguredXHTMLGeneratorTests.CreateInterestingLexEntry(Cache);
+ //SUT
+ var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, DefaultSettings, 0) as DocFragment;
+ Console.WriteLine(result);
+ AssertThatXmlIn.String(result?.mainDocPart.RootElement?.OuterXml).HasSpecifiedNumberOfMatchesForXpath(
+ "/w:document/w:body/w:p/w:r/w:br[@w:type='textWrapping']",
+ 2,
+ WordNamespaceManager);
+ }
+ }
+}
diff --git a/Src/xWorks/xWorksTests/xWorksTests.csproj b/Src/xWorks/xWorksTests/xWorksTests.csproj
index 6ad730e7c1..04c4b0183a 100644
--- a/Src/xWorks/xWorksTests/xWorksTests.csproj
+++ b/Src/xWorks/xWorksTests/xWorksTests.csproj
@@ -130,6 +130,10 @@
AnyCPU
+
+ False
+ ..\..\..\Output\Debug\DocumentFormat.OpenXml.dll
+
False
@@ -308,6 +312,7 @@
+