diff --git a/Src/xWorks/LcmWordGenerator.cs b/Src/xWorks/LcmWordGenerator.cs index e4e2ec3fc6..117e67fe98 100644 --- a/Src/xWorks/LcmWordGenerator.cs +++ b/Src/xWorks/LcmWordGenerator.cs @@ -45,7 +45,7 @@ public class LcmWordGenerator : ILcmContentGenerator, ILcmStylesGenerator private ReadOnlyPropertyTable _propertyTable; public static bool IsBidi { get; private set; } - private static DocFragment MasterFragment { get; set; } + private static MasterDocFragment MasterFragment { get; set; } public LcmWordGenerator(LcmCache cache) { @@ -63,7 +63,7 @@ public static void SavePublishedDocx(int[] entryHvos, DictionaryPublicationDecor { using (MemoryStream mem = new MemoryStream()) { - MasterFragment = new DocFragment(mem); + MasterFragment = new MasterDocFragment(mem); var entryCount = entryHvos.Length; var cssPath = System.IO.Path.ChangeExtension(filePath, "css"); @@ -346,50 +346,55 @@ internal static IFragment GenerateLetterHeaderIfNeeded(ICmObject entry, ref stri return DocFragment.GenerateLetterHeaderDocFragment(headerTextBuilder.ToString(), WordStylesGenerator.LetterHeadingDisplayName, firstHeader, wsString); } - /* - * DocFragment Region - */ - #region DocFragment class - public class DocFragment : IFragment + #region MasterDocFragment class + /// + /// The MasterDocFragment contains the data to write to a docx file. Regular + /// DocFragments that are used to build the MasterDocFragment do not need + /// this data. + /// + public class MasterDocFragment : DocFragment { internal MemoryStream MemStr { get; } internal WordprocessingDocument DocFrag { get; } internal MainDocumentPart mainDocPart { get; } - internal WP.Body DocBody { get; } /// - /// Constructs a new memory stream and creates an empty doc fragment - /// that writes to that stream. + /// Initializes the memory stream from the argument and creates + /// an empty master doc fragment that writes to that stream. /// - public DocFragment() + public MasterDocFragment(MemoryStream str) { - MemStr = new MemoryStream(); - DocFrag = WordprocessingDocument.Open(MemStr, true); + MemStr = str; + DocFrag = WordprocessingDocument.Open(str, true); // Initialize the document and body. mainDocPart = DocFrag.AddMainDocumentPart(); mainDocPart.Document = new WP.Document(); - DocBody = mainDocPart.Document.AppendChild(new WP.Body()); + mainDocPart.Document.AppendChild(DocBody); } + } + #endregion MasterDocFragment class + + /* + * DocFragment Region + */ + #region DocFragment class + public class DocFragment : IFragment + { + internal WP.Body DocBody { get; } + /// - /// Initializes the memory stream from the argument and creates - /// an empty doc fragment that writes to that stream. + /// Constructs an empty doc fragment that has a Body. /// - public DocFragment(MemoryStream str) + public DocFragment() { - MemStr = str; - DocFrag = WordprocessingDocument.Open(str, true); - - // Initialize the document and body. - mainDocPart = DocFrag.AddMainDocumentPart(); - mainDocPart.Document = new WP.Document(); - DocBody = mainDocPart.Document.AppendChild(new WP.Body()); + DocBody = new WP.Body(); } /// - /// Constructs a new memory stream and creates a non-empty doc fragment, - /// containing the given string, that writes to that stream. + /// Constructs a non-empty doc fragment that has a Body which + /// contains the given string. /// public DocFragment(string str) : this() { @@ -688,7 +693,7 @@ public void AppendSpace() public bool IsNullOrEmpty() { // A docbody with no children is an empty document. - if (MemStr == null || DocFrag == null || DocBody == null || !DocBody.HasChildren) + if (DocBody == null || !DocBody.HasChildren) { return true; } @@ -941,7 +946,6 @@ public void Dispose() public void Flush() { - WordFragment.MemStr.Flush(); } public void Insert(IFragment frag) @@ -1775,12 +1779,6 @@ public IFragment AddImage(ConfigurableDictionaryNode config, ConfiguredLcmGenera // calculate the maximum image height from the configuration var maxHeight = config.Model.Pictures?.Height ?? (picOpts?.MaximumHeight ?? 1.0f); Drawing image = CreateImage(srcAttribute, partId, maxWidth, maxHeight); - - if (imageFrag.DocFrag.MainDocumentPart is null || imageFrag.DocFrag.MainDocumentPart.Document.Body is null) - { - throw new ArgumentNullException("MainDocumentPart and/or Body is null."); - } - Run imgRun = new Run(); imgRun.AppendChild(image); diff --git a/Src/xWorks/xWorksTests/LcmWordGeneratorTests.cs b/Src/xWorks/xWorksTests/LcmWordGeneratorTests.cs index e5a02c007d..ac6dd31e81 100644 --- a/Src/xWorks/xWorksTests/LcmWordGeneratorTests.cs +++ b/Src/xWorks/xWorksTests/LcmWordGeneratorTests.cs @@ -240,8 +240,8 @@ public void GenerateWordDocForEntry_OneSenseWithGlossGeneratesCorrectResult() //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']", + AssertThatXmlIn.String(result.DocBody.OuterXml).HasSpecifiedNumberOfMatchesForXpath( + "/w:body/w:p/w:r/w:t[text()='gloss']", 1, WordNamespaceManager); } @@ -275,8 +275,8 @@ public void GenerateWordDocForEntry_LineBreaksInBeforeContentWork() //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']", + AssertThatXmlIn.String(result?.DocBody?.OuterXml).HasSpecifiedNumberOfMatchesForXpath( + "/w:body/w:p/w:r/w:br[@w:type='textWrapping']", 2, WordNamespaceManager); } @@ -327,8 +327,8 @@ public void GenerateUniqueStyleName() //SUT var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, DefaultSettings, 0) as DocFragment; - Assert.True(result.mainDocPart.RootElement.OuterXml.Contains("Gloss[lang=en]")); - Assert.True(result.mainDocPart.RootElement.OuterXml.Contains("Gloss2[lang=en]")); + Assert.True(result.DocBody.OuterXml.Contains("Gloss[lang=en]")); + Assert.True(result.DocBody.OuterXml.Contains("Gloss2[lang=en]")); } [Test] @@ -385,7 +385,7 @@ public void GenerateSenseNumberData() // 3. Sense number: 2 // 4. Sense number after text: AFT const string senseNumberTwoRun = "BEF2AFT"; - Assert.True(result.mainDocPart.RootElement.OuterXml.Contains(senseNumberTwoRun)); + Assert.True(result.DocBody.OuterXml.Contains(senseNumberTwoRun)); } [Test] @@ -438,7 +438,7 @@ public void GenerateBeforeBetweenAfterContent() //SUT var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, DefaultSettings, 0) as DocFragment; - var outXml = result.mainDocPart.RootElement.OuterXml; + var outXml = result.DocBody.OuterXml; // Before text 'BE1' is before sense number '1' for 'gloss'. const string beforeFirstSense = @@ -520,7 +520,7 @@ public void GenerateBeforeBetweenAfterContentWithWSAbbreviation() //SUT var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, DefaultSettings, 0) as DocFragment; - var outXml = result.mainDocPart.RootElement.OuterXml; + var outXml = result.DocBody.OuterXml; // Before text 'BE3' is after the sense number '1' and before the english abbreviation, which is before 'gloss'. const string beforeAbbreviation = @@ -576,7 +576,7 @@ public void GeneratePropertyData() //SUT var result = ConfiguredLcmGenerator.GenerateContentForEntry(entry, mainEntryNode, null, DefaultSettings, 0) as DocFragment; - var outXml = result.mainDocPart.RootElement.OuterXml; + var outXml = result.DocBody.OuterXml; // The property before text 'BE4' is first, followed by the style that is applied to the property, 'DisplayNameBase'. const string beforeAndStyle = "BE4"; @@ -641,7 +641,7 @@ public void EmbeddedStylesHaveNoExtraSpace() //SUT var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, DefaultSettings, 0) as DocFragment; - var outXml = result.mainDocPart.RootElement.OuterXml; + var outXml = result.DocBody.OuterXml; // Verify that AREYOUCRAZY appears only once in the output. var betweenCount = Regex.Matches(outXml, "AREYOUCRAZY").Count; @@ -688,8 +688,8 @@ public void ReferenceParagraphDisplayNames() var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, DefaultSettings, 0) as DocFragment; // Assert that the references to the paragraph styles use the display names, not the style names. - Assert.True(result.mainDocPart.RootElement.OuterXml.Contains(MainEntryParagraphDisplayName)); - Assert.True(result.mainDocPart.RootElement.OuterXml.Contains(SensesParagraphDisplayName)); + Assert.True(result.DocBody.OuterXml.Contains(MainEntryParagraphDisplayName)); + Assert.True(result.DocBody.OuterXml.Contains(SensesParagraphDisplayName)); } [Test] @@ -751,8 +751,8 @@ public void GenerateParagraphForSensesAndSubSenses() var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, DefaultSettings, 0) as DocFragment; // There should be 5 paragraphs, one for the main entry, one for each sense, and one for each subsense. - AssertThatXmlIn.String(result.mainDocPart.RootElement.OuterXml).HasSpecifiedNumberOfMatchesForXpath( - "/w:document/w:body/w:p", + AssertThatXmlIn.String(result.DocBody.OuterXml).HasSpecifiedNumberOfMatchesForXpath( + "/w:body/w:p", 5, WordNamespaceManager); } @@ -815,7 +815,7 @@ public void GenerateBulletsAndNumbering() var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, DefaultSettings, 0) as DocFragment; // There should be two instances of the bulletId and one instance for each of the numberId's. - string resultStr = result.mainDocPart.RootElement.OuterXml; + string resultStr = result.DocBody.OuterXml; int count1 = Regex.Matches(resultStr, "").Count; int count2 = Regex.Matches(resultStr, "").Count; int count3 = Regex.Matches(resultStr, "").Count; @@ -888,13 +888,13 @@ public void GenerateContinueParagraph() var result = ConfiguredLcmGenerator.GenerateContentForEntry(testEntry, mainEntryNode, null, DefaultSettings, 0) as DocFragment; // There should be 3 paragraph styles, one for the main entry, one for the sense, and one for the continuation of the main entry. - AssertThatXmlIn.String(result.mainDocPart.RootElement.OuterXml).HasSpecifiedNumberOfMatchesForXpath( - "/w:document/w:body/w:p/w:pPr/w:pStyle", + AssertThatXmlIn.String(result.DocBody.OuterXml).HasSpecifiedNumberOfMatchesForXpath( + "/w:body/w:p/w:pPr/w:pStyle", 3, WordNamespaceManager); // Assert that the continuation paragraph uses the continuation style. - Assert.True(result.mainDocPart.RootElement.OuterXml.Contains(MainEntryParagraphDisplayName + WordStylesGenerator.EntryStyleContinue)); + Assert.True(result.DocBody.OuterXml.Contains(MainEntryParagraphDisplayName + WordStylesGenerator.EntryStyleContinue)); } [Test]