How to: Accept All Revisions in a Word 2007 Document by Using the Open XML API

This content is outdated and is no longer being maintained. It is provided as a courtesy for individuals who are still using these technologies. This page may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

The Office Open XML Package specification defines a set of XML files that contain the content and define the relationships for all of the document parts stored in a single package. These packages combine the document parts that comprise the document files for Microsoft® Office Excel® 2007, Microsoft Office PowerPoint® 2007, and Microsoft Office Word® 2007. The Open XML object model allows you to create packages and manipulate the files that make up the packages. This topic walks through the code and steps to modify the XML in an Office Open XML package to signal to Office Word 2007 that you accept all revisions, although the steps are the same for each of the three 2007 Microsoft Office system programs that support the Office Open XML Format.

NoteNote

The code samples in this topic are in Microsoft Visual Basic® .NET and Microsoft Visual C#®. You can use them in an add-in created in Microsoft Visual Studio® 2008. For more information about how to create an add-in in Visual Studio 2008, see Getting Started with the Open XML Format SDK 1.0.

Accepting All Revisions in a Document

In the following code, you add a new document part containing custom XML from an external file and then populate the document part:

Public Sub WDAcceptRevisions(ByVal docName As String, ByVal authorName As String)
    ' Given a document name and an author name, accept revisions. 
    ' Note: leave author name blank to accept revisions for all authors.
    Const wordmlNamespace As String = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"
    Dim wdDoc As WordprocessingDocument = WordprocessingDocument.Open(docName, True)
    '  Manage namespaces to perform Xml XPath queries.
    Dim nt As NameTable = New NameTable
    Dim nsManager As XmlNamespaceManager = New XmlNamespaceManager(nt)
    nsManager.AddNamespace("w", wordmlNamespace)
    '  Get the document part from the package.
    Dim xdoc As XmlDocument = New XmlDocument(nt)
    '  Load the XML in the document part into an XmlDocument instance.
    xdoc.Load(wdDoc.MainDocumentPart.GetStream)
    '  Handle the formatting changes.
    Dim nodes As XmlNodeList = Nothing
    If String.IsNullOrEmpty(authorName) Then
        nodes = xdoc.SelectNodes("//w:pPrChange", nsManager)
    Else
        nodes = xdoc.SelectNodes(String.Format("//w:pPrChange[@w:author='{0}']", authorName), nsManager)
    End If
    For Each node As System.Xml.XmlNode In nodes
        node.ParentNode.RemoveChild(node)
    Next
    '  Handle the deletions.
    If String.IsNullOrEmpty(authorName) Then
        nodes = xdoc.SelectNodes("//w:del", nsManager)
    Else
        nodes = xdoc.SelectNodes(String.Format("//w:del[@w:author='{0}']", authorName), nsManager)
    End If
    For Each node As System.Xml.XmlNode In nodes
        node.ParentNode.RemoveChild(node)
    Next
    '  Handle the insertions.
    If String.IsNullOrEmpty(authorName) Then
        nodes = xdoc.SelectNodes("//w:ins", nsManager)
    Else
        nodes = xdoc.SelectNodes(String.Format("//w:ins[@w:author='{0}']", authorName), nsManager)
    End If
    For Each node As System.Xml.XmlNode In nodes
        '  You found one or more new content.
        '  Promote them to the same level as node, and then
        '  delete the node.
        Dim childNodes As XmlNodeList
        childNodes = node.SelectNodes(".//w:r", nsManager)
        For Each childNode As System.Xml.XmlNode In childNodes
            If (childNode Is node.FirstChild) Then
                node.ParentNode.InsertAfter(childNode, node)
            Else
                node.ParentNode.InsertAfter(childNode, node.NextSibling)
            End If
        Next
        node.ParentNode.RemoveChild(node)
        '  Remove the modification id from the node 
        '  so Word can merge it on the next save.
        node.Attributes.RemoveNamedItem("w:rsidR")
        node.Attributes.RemoveNamedItem("w:rsidRPr")
    Next
    '  Save the document XML back to its part.
    xdoc.Save(wdDoc.MainDocumentPart.GetStream(FileMode.Create))
End Sub
public static void WDAcceptRevisions(string docName, string authorName)
{
    // Given a document name and an author name, accept revisions. 
    // Note: leave author name blank to accept revisions for all    

    const string wordmlNamespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";

    using (WordprocessingDocument wdDoc = WordprocessingDocument.Open(docName, true))
    {
        //  Manage namespaces to perform Xml XPath queries.
        NameTable nt = new NameTable();
        XmlNamespaceManager nsManager = new XmlNamespaceManager(nt);
        nsManager.AddNamespace("w", wordmlNamespace);

        //  Get the document part from the package.
        XmlDocument xdoc = new XmlDocument(nt);
        //  Load the XML in the document part into an XmlDocument instance.
        xdoc.Load(wdDoc.MainDocumentPart.GetStream());

        //  Handle the formatting changes.
        XmlNodeList nodes = null;
        if (string.IsNullOrEmpty(authorName))
        {
            nodes = xdoc.SelectNodes("//w:pPrChange", nsManager);
        }
        else
        {
            nodes = xdoc.SelectNodes(string.Format("//w:pPrChange[@w:author='{0}']", authorName), nsManager);
        }
        foreach (System.Xml.XmlNode node in nodes)
        {
            node.ParentNode.RemoveChild(node);
        }

        //  Handle the deletions.
        if (string.IsNullOrEmpty(authorName))
        {
            nodes = xdoc.SelectNodes("//w:del", nsManager);
        }
        else
        {
            nodes = xdoc.SelectNodes(string.Format("//w:del[@w:author='{0}']", authorName), nsManager);
        }

        foreach (System.Xml.XmlNode node in nodes)
        {
            node.ParentNode.RemoveChild(node);
        }


        //  Handle the insertions.
        if (string.IsNullOrEmpty(authorName))
        {
            nodes = xdoc.SelectNodes("//w:ins", nsManager);
        }
        else
        {
            nodes = xdoc.SelectNodes(string.Format("//w:ins[@w:author='{0}']", authorName), nsManager);
        }

        foreach (System.Xml.XmlNode node in nodes)
        {
            //  You found one or more new content.
            //  Promote them to the same level as node, and then
            //  delete the node.
            XmlNodeList childNodes;
           childNodes = node.SelectNodes(".//w:r", nsManager);
            foreach (System.Xml.XmlNode childNode in childNodes)
            {
                if (childNode == node.FirstChild)
                {
                    node.ParentNode.InsertAfter(childNode, node);
                }
                else
                {
                    node.ParentNode.InsertAfter(childNode, node.NextSibling);
                }
            }
            node.ParentNode.RemoveChild(node);

            //  Remove the modification id from the node 
            //  so Word can merge it on the next save.
            node.Attributes.RemoveNamedItem("w:rsidR");
            node.Attributes.RemoveNamedItem("w:rsidRPr");
        }

        //  Save the document XML back to its document part.
        xdoc.Save(wdDoc.MainDocumentPart.GetStream(FileMode.Create));
    }
}

To add a new part containing custom XML from an external file and then populate the part

  1. First, pass in parameters representing the path to and the name of the source Word 2007 document and, optionally, the name of the document author.

  2. Then, open the document as a WordprocessingDocument object.

  3. Next, you set a reference to the default word namespace (w) used throughout the document. The contents of the main document part (/word/document.xml) is loaded into an XML document.

  4. Then, an if...else statement allows you to test for revisions assigned by a specific author.

    If no author name was passed into the procedure, all revisions are affected regardless of author.

  5. Either way, select all [w:pPrChange] nodes (if any exist) with the following statement:

    nodes = xdoc.SelectNodes("//w:pPrChange", nsManager)
    
    nodes = xdoc.SelectNodes("//w:pPrChange", nsManager);
    

These nodes denote pending formatting changes to the document. If any of these nodes are found, the child nodes are removed. To understand how this works, consider the following example.

Assume that in reviewing a document, you highlight a line of text and set its style as a Title. Doing this generates the following XML markup in the main document part:

<w:pPr>
   <w:pStyle w:val="Title" /> 
      <w:pPrChange w:id="0" w:author="Nancy Davolio" w:date="2007-07-03T08:22:00Z">
         <w:pPr /> 
      </w:pPrChange>
</w:pPr>
<w:r w:rsidRPr="00655EFA">
  <w:t>Gettysburg Address</w:t> 
</w:r>

The [w:pStyle] element specifies that this is a style change; in this case, the change sets the highlighted text to the Title style. The [w:pPrChange] element identifies the author and date of the revision. This element also signals to Word 2007 that the change is pending. The [w:r] and [w:t] elements designate the run and the text that contain the highlighted text; in this case, the phrase Gettysburg Address. During a document review in Office Word 2007, you highlight the text, and then on the Review tab, click Accept, and then click Accept and Move Next. Office Word 2007 implements the change and removes the [w:pPrChange] element. We can emulate this behavior by removing the [w:pPrChange] element in code, which accepts the revision. This is exactly what we do in the code with the following statement.

node.ParentNode.RemoveChild(node)
node.ParentNode.RemoveChild(node);

Here, the current node is the [w:pPrChange] element. Specifying the parent of the current node (the [w:pStyle] element) and calling the RemoveChild method removes the current node (the [w:pPrChange] element). This is the same as accepting the change. A similar process is performed for deletions by using the [w:del] element.

Insertions are more complex than formatting changes. Insertions use the [w:ins] element. Either of those elements may be a container for one or more insertions. Specifically, you may be inserting text and spaces in the same operation such as in the following markup.

<w:ins w:id="12" w:author="Nancy Davolio" w:date="2007-07-03T08:23:00Z">
   <w:r w:rsidR="00655EFA">
      <w:t>word</w:t> 
   </w:r>
   <w:proofErr w:type="spellEnd" /> 
   <w:r w:rsidR="00655EFA">
      <w:t xml:space="preserve"></w:t> 
   </w:r>
</w:ins>

In this segment of markup, the word word is inserted into the document followed by a blank space. Both of these text elements ([w:t]) are contained within run elements ([w:r]) which are contained in the same insertion element ([w:ins]). In the programming code procedure, these nodes (child nodes to the [w:ins] node) are promoted to the same level as the [w:ins] node. Then the [w:ins] node is deleted, which has the effect of accepting the revisions.

nodes = xdoc.SelectNodes("//w:ins", nsManager)
...
For Each childNode As System.Xml.XmlNode In childNodes
   If (childNode Is node.FirstChild) Then
      node.ParentNode.InsertAfter(childNode, node)
   Else
      node.ParentNode.InsertAfter(childNode, node.NextSibling)
   End If
Next
node.ParentNode.RemoveChild(node)
nodes = xdoc.SelectNodes("//w:ins", nsManager);
...
childNodes = node.SelectNodes(".//w:r", nsManager);
foreach (System.Xml.XmlNode childNode in childNodes)
{
   if (childNode == node.FirstChild)
   {
       node.ParentNode.InsertAfter(childNode, node);
   }
   else
   {
       node.ParentNode.InsertAfter(childNode, node.NextSibling);
   }
}
node.ParentNode.RemoveChild(node);

The order that the text elements ([w:t]) are processed is essential. For example, to insert the word word followed by a blank space into the document at a specific point, the [w:t] element for the word is processed and inserted after the parent node ([w:r]). Next, the [w:t] for the space is processed and inserted after the parent node. This results in the space and then the text word appearing after the parent ([w:r]) element Unfortunately, this is not the goal. To overcome this, you must detect the order of the text elements. The previous code example does this by inserting the first [w:t] element encountered after the parent node and then inserting subsequent elements after that first child node.

After ordering the nodes correctly, the code deletes the appropriate nodes and saves the updated XML back to the document. Now, when the updated document is opened, the missing XML statements indicate to Word 2007 that the revisions are accepted.