-1

we update custom xml part using C# code. we are successfully update document in widows. but can we open this document in Linux environment, it could not be changed value.

how can we achieve change of custom xml part in windows as well as Document.xml in word Folder?

Cindy Meister
  • 25,071
  • 21
  • 34
  • 43
  • You'll need to explain your issue more clearly. Suffice to say that docx4j can be used in a linux environment to update the main document part from a bound custom xml part. You can google "OpenDoPE". – JasonPlutext Nov 27 '19 at 20:29
  • we are updating custom XML part value using open XML API. we would update that value in document also. but we open this document on LINUX environment. we could not show update value. – Parth SHah Nov 28 '19 at 04:45
  • @ParthSHah, did my answer help or are you still looking for a solution to your problem? – Thomas Barnekow Dec 06 '19 at 11:59
  • Thank you so much for your reply, Sorry for late response sir. today i am working for you answer. i will sure update you. – Parth SHah Dec 09 '19 at 05:00
  • now its working fine. thank you so much for your help – Parth SHah Dec 13 '19 at 09:03

1 Answers1

0

Using a slightly enhanced example from another question, let's assume you have a MainDocumentPart (/word/document.xml) with a data-bound w:sdt (noting that this is a block-level structured document tag (SDT) containing a w:p in this example; it could also be an inline-level SDT contained in a w:p).

<?xml version="1.0" encoding="utf-8"?>
<w:document xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
  <w:body>
    <w:sdt>
      <w:sdtPr>
        <w:tag w:val="Node" />
        <w:dataBinding w:prefixMappings="xmlns:ex='http://example.com'" 
                       w:xpath="ex:Root[1]/ex:Node[1]"
                       w:storeItemID="{C152D0E4-7C03-4CFA-97E6-721B2DCB5C7B}" />
      </w:sdtPr>
      <w:sdtContent>
        <w:p>
          <w:r>
            <w:t>VALUE1</w:t>
          </w:r>
        </w:p>
      </w:sdtContent>
    </w:sdt>
  </w:body>
</w:document>

Our MainDocumentPart references the following CustomXmlPart (/customXML/item.xml):

<?xml version="1.0" encoding="utf-8"?>
<ex:Root xmlns:ex="http://example.com">
  <ex:Node>VALUE1</ex:Node>
</ex:Root>

The above CustomXmlPart then references the following CustomXmlPropertiesPart (/customXML/itemProps1.xml):

<?xml version="1.0" encoding="utf-8"?>
<ds:datastoreItem xmlns:ds="http://schemas.openxmlformats.org/officeDocument/2006/customXml" 
                  ds:itemID="{C152D0E4-7C03-4CFA-97E6-721B2DCB5C7B}">
  <ds:schemaRefs>
    <ds:schemaRef ds:uri="http://example.com" />
  </ds:schemaRefs>
</ds:datastoreItem>

The "enhancement" in this case is the additional w:tag element in the MainDocumentPart at the top. This w:tag element is one way to create an easy-to-use link between a w:sdt element and the custom XML element to which it is bound (e.g., ex:Node). In this example, the value Node happens to be the local name of the ex:Node element.

Finally, here is a working code example that shows how you can update both the CustomXmlPart and the MainDocumentPart. This uses the Open-XML-PowerTools.

[Fact]
public void CanUpdateCustomXmlAndMainDocumentPart()
{
    // Define the initial and updated values of our custom XML element and
    // the data-bound w:sdt element.
    const string initialValue = "VALUE1";
    const string updatedValue = "value2";

    // Create the root element of the custom XML part with the initial value.
    var customXmlRoot =
        new XElement(Ns + "Root",
            new XAttribute(XNamespace.Xmlns + NsPrefix, NsName),
            new XElement(Ns + "Node", initialValue));

    // Create the w:sdtContent child element of our w:sdt with the initial value.
    var sdtContent =
        new XElement(W.sdtContent,
            new XElement(W.p,
                new XElement(W.r,
                    new XElement(W.t, initialValue))));

    // Create a WordprocessingDocument with the initial values.
    using var stream = new MemoryStream();
    using (WordprocessingDocument wordDocument = WordprocessingDocument.Create(stream, Type))
    {
        InitializeWordprocessingDocument(wordDocument, customXmlRoot, sdtContent);
    }

    // Assert the WordprocessingDocument has the expected, initial values.
    using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(stream, true))
    {
        AssertValuesAreAsExpected(wordDocument, initialValue);
    }

    // Update the WordprocessingDocument, using the updated value.
    using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(stream, true))
    {
        MainDocumentPart mainDocumentPart = wordDocument.MainDocumentPart;

        // Change custom XML element, again using the simplifying assumption
        // that we only have a single custom XML part and a single ex:Node
        // element.
        CustomXmlPart customXmlPart = mainDocumentPart.CustomXmlParts.Single();
        XElement root = customXmlPart.GetXElement();
        XElement node = root.Elements(Ns + "Node").Single();
        node.Value = updatedValue;
        customXmlPart.PutXDocument();

        // Change the w:sdt contained in the MainDocumentPart.
        XElement document = mainDocumentPart.GetXElement();
        XElement sdt = FindSdtWithTag("Node", document);
        sdtContent = sdt.Elements(W.sdtContent).Single();
        sdtContent.ReplaceAll(
            new XElement(W.p,
                new XElement(W.r,
                    new XElement(W.t, updatedValue))));

        mainDocumentPart.PutXDocument();
    }

    // Assert the WordprocessingDocument has the expected, updated values.
    using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(stream, true))
    {
        AssertValuesAreAsExpected(wordDocument, updatedValue);
    }
}

private static void InitializeWordprocessingDocument(
    WordprocessingDocument wordDocument,
    XElement customXmlRoot,
    XElement sdtContent)
{
    MainDocumentPart mainDocumentPart = wordDocument.AddMainDocumentPart();
    string storeItemId = CreateCustomXmlPart(mainDocumentPart, customXmlRoot);

    mainDocumentPart.PutXDocument(new XDocument(
        new XElement(W.document,
            new XAttribute(XNamespace.Xmlns + "w", W.w.NamespaceName),
            new XElement(W.body,
                new XElement(W.sdt,
                    new XElement(W.sdtPr,
                        new XElement(W.tag, new XAttribute(W.val, "Node")),
                        new XElement(W.dataBinding,
                            new XAttribute(W.prefixMappings, $"xmlns:{NsPrefix}='{NsName}'"),
                            new XAttribute(W.xpath, $"{NsPrefix}:Root[1]/{NsPrefix}:Node[1]"),
                            new XAttribute(W.storeItemID, storeItemId))),
                    sdtContent)))));
}

private static void AssertValuesAreAsExpected(
    WordprocessingDocument wordDocument,
    string expectedValue)
{
    // Retrieve inner text of w:sdt element.
    MainDocumentPart mainDocumentPart = wordDocument.MainDocumentPart;
    XElement sdt = FindSdtWithTag("Node", mainDocumentPart.GetXElement());
    string sdtInnerText = GetInnerText(sdt);

    // Retrieve inner text of custom XML element, making the simplifying
    // assumption that we only have a single custom XML part. In reality,
    // we would have to find the custom XML part to which our w:sdt elements
    // are bound among any number of custom XML parts. Further, in our
    // simplified example, we also assume there is a single ex:Node element.
    CustomXmlPart customXmlPart = mainDocumentPart.CustomXmlParts.Single();
    XElement root = customXmlPart.GetXElement();
    XElement node = root.Elements(Ns + "Node").Single();
    string nodeInnerText = node.Value;

    // Assert those inner text are indeed equal.
    Assert.Equal(expectedValue, sdtInnerText);
    Assert.Equal(expectedValue, nodeInnerText);
}

private static XElement FindSdtWithTag(string tagValue, XElement openXmlCompositeElement)
{
    return openXmlCompositeElement
        .Descendants(W.sdt)
        .FirstOrDefault(e => e
            .Elements(W.sdtPr)
            .Elements(W.tag)
            .Any(tag => (string) tag.Attribute(W.val) == tagValue));
}

private static string GetInnerText(XElement openXmlElement)
{
    return openXmlElement
        .DescendantsAndSelf(W.r)
        .Select(UnicodeMapper.RunToString)
        .StringConcatenate();
}

The complete source code is contained in my CodeSnippets GitHub repository. Look for the DataBoundContentControlTests class.

Thomas Barnekow
  • 2,059
  • 1
  • 12
  • 21