private void WriteReplacementHtml(W.SdtElement sdtElement, string html) { if (String.IsNullOrWhiteSpace(html)) { WriteReplacementText(sdtElement, html); return; } // Replace spaces between images and texts with non breaking spaces // http://stackoverflow.com/questions/43659775/regex-to-match-images-followed-with-a-space-in-an-html-string html = WordMLTemplateProcessor.ImageFollowedWithSpaceRegex.Replace(html, "$1 "); MemoryStream wordMLStream = new MemoryStream(); try { ConvertHtmlToWordML(html, wordMLStream); WordprocessingDocument convertedDocument = WordprocessingDocument.Open(wordMLStream, false); var convertedElements = convertedDocument.MainDocumentPart.RootElement.Descendants().Single().Elements(); // Skim the fat (SectionProperties) or risk screwing up your document convertedElements = convertedElements.Except(convertedElements.OfType()).ToArray(); try { List numberingIdsProcessed = new List(); OpenXmlElement paragraphProperties = null; OpenXmlElement runProperties = null; List insertedElements = new List(); // Find the first parent that can contain a block such as a paragraph or a table, // and keep track of the element before which we'll have to insert content for (OpenXmlElement sdtParent = sdtElement.Parent, sdtInsertBefore = sdtElement; sdtParent != null; sdtInsertBefore = sdtParent, sdtParent = sdtParent.Parent) { // Save the paragraph and run properties of the element (if not yet done) if (paragraphProperties == null) paragraphProperties = sdtInsertBefore.Descendants().FirstOrDefault(); if (runProperties == null) { W.SdtElement element = sdtInsertBefore as W.SdtElement; if (element != null) runProperties = element.SdtProperties.Descendants().FirstOrDefault(); } // Can the parent contain a paragraph or table? if (sdtParent is W.Body || sdtParent is W.Comment || sdtParent is W.CustomXmlBlock || sdtParent is W.DocPartBody || sdtParent is W.Endnote || sdtParent is W.Footnote || sdtParent is W.Footer || sdtParent is W.Header || sdtParent is W.SdtContentBlock || sdtParent is W.TableCell) { // Insert the converted elements as they come foreach (OpenXmlElement convertedElement in convertedElements) { // Insert the element OpenXmlElement insertedElement = convertedElement.CloneNode(true); sdtInsertBefore.InsertBeforeSelf(insertedElement); // Import its numbering from the converted document while avoid conflicts with // numbering IDs in the current document. ResetNumbering(insertedElement, convertedDocument.MainDocumentPart.NumberingDefinitionsPart, numberingIdsProcessed); // Copy the run properties to each run element if (runProperties != null) { foreach (W.Run run in insertedElement.Descendants()) { run.InsertAt(runProperties.CloneNode(true), 0); } } // Copy the paragraph properties to the paragraph if (paragraphProperties != null) { if (insertedElement is W.Paragraph) { // Copy the paragraph properties insertedElement.InsertAt(paragraphProperties.CloneNode(true), 0); // If the paragraph properties represent a list item, reset it // in order to avoid inserting next converted paragraphs as extra list items W.ParagraphStyleId paragraphStyle = ((W.ParagraphProperties)paragraphProperties).ParagraphStyleId; if (paragraphStyle != null && paragraphStyle.Val == "ListParagraph") { paragraphProperties = null; } } // COMMENTED: // not nice on tables: eg: you're not expecting the table contents to have the // same margins as the tag's paragraph //else //{ // foreach (W.Paragraph paragraph in insertedElement.Descendants()) // { // paragraph.InsertAt(paragraphProperties.CloneNode(true), 0); // } //} } // Add to the list of inserted elements insertedElements.Add(insertedElement); } // Remove the replaced element W.SdtBlock sdtBlock = sdtElement as W.SdtBlock; if (sdtBlock != null) { sdtBlock.SdtContentBlock.RemoveAllChildren(); } else { // Get the tag associated with the replaced element W.Tag tag = sdtElement.SdtProperties.Elements().FirstOrDefault(); // Find the highest parent that contains the replaced tag (and only this one) OpenXmlElement sdtDelete = null; for (OpenXmlElement sdtCandidate = sdtElement.Parent; sdtCandidate != sdtParent; sdtCandidate = sdtCandidate.Parent) { // Does the candidate contains the replaced tag and only it? IEnumerable tags = sdtCandidate.Descendants().ToArray(); if (tags.Count() == 1 && tags.First() == tag) { // Save it for deletion sdtDelete = sdtCandidate; continue; } // Stop break; } // Delete it if (sdtDelete != null) sdtDelete.Remove(); } // Stop break; } } // move images into the target package foreach (OpenXmlElement insertedElement in insertedElements) { ExtractImages(convertedDocument, insertedElement); } } finally { // convertedDocument.Dispose() also disposes the stream it was instantiated with (i.e. wordMLStream) convertedDocument.Dispose(); wordMLStream = null; } } finally { if (wordMLStream != null) wordMLStream.Dispose(); } }