Search code examples
xmlms-wordopenxml

Add first comment and comments.xml to word doc


I’m writing RoR app to add comments to a docx file. Nokogiri works great for modifying XML to add/edit comments in a doc where at least one comment already exits. Those docs have a comments.xml file inserted by word in the docx package.

Where I’m stuck is programmatically adding the first comment. A doc with no preexisting comments has no comments.xml file. Simply adding the file doesn’t work, it needs to have the appropriate relations defined. I’m struggling to find exactly how to add one and identify it within the docx package relationship structure so that document.xml (Where the main content exists) knows to look at comments.xml for comments.

I’d post post code, but I’ve tried so many different things I’m pretty much at zero. I’m mainly looking for anyone with Office Open XML experience who may have done this before. I can figure out the Rails/Nokogiri stuff, I’m just stuck on understanding what actually needs to change in the Office Open XML package.


Solution

  • The following C# example shows how you would do this with the Open XML SDK:

    public void CreateComment()
    {
        // Let's say we have a document called Comments.docx with a single
        // paragraph saying "Hello World!".
        using WordprocessingDocument wordDocument = WordprocessingDocument.Create(
            "Comments.docx", WordprocessingDocumentType.Document);
    
        MainDocumentPart mainDocumentPart = wordDocument.AddMainDocumentPart();
        mainDocumentPart.Document =
            new Document(
                new Body(
                    new Paragraph(
                        new Run(
                            new Text("Hello World!")))));
    
        // Our MainDocumentPart needs to have a related WordprocessingCommentsPart,
        // which you see as the comments.xml file. That file needs to have a
        // w:comments root element.
    
        // The next line of code is the key step that adds the comments.xml file
        // and the required relationships and content types. To add individual
        // w:comment elements, we need the root w:comments element.
        var commentsPart = mainDocumentPart.AddNewPart<WordprocessingCommentsPart>();
        commentsPart.Comments = new Comments();
    
        // The rest of the code just shows how you would use the Open XML SDK
        // to add a comment.
        // So say we want to comment on the whole paragraph. As a first step,
        // we create a comment and add it to the w:comments root element. 
        var comment = new Comment(new Paragraph(new Run(new Text("This is my comment."))))
        {
            Id = "1",
            Author = "Thomas Barnekow"
        };
    
        commentsPart.Comments.AppendChild(comment);
    
        // Then, we need to add w:commentRangeStart and w:commentRangeEnd
        // elements to the text on which we want to comment. In this example,
        // we are getting "some" w:p element and some first and last w:r
        // elements and insert the w:commentRangeStart before the first and
        // the w:commentRangeEnd after the last w:r.
        Paragraph p = mainDocumentPart.Document.Descendants<Paragraph>().First();
        Run firstRun = p.Elements<Run>().First();
        Run lastRun = p.Elements<Run>().Last();
    
        firstRun.InsertBeforeSelf(new CommentRangeStart { Id = "1" });
        CommentRangeEnd commentRangeEnd = lastRun.InsertAfterSelf(new CommentRangeEnd { Id = "1" });
        commentRangeEnd.InsertAfterSelf(new Run(new CommentReference { Id = "1" }));
    }
    
    

    Thus, if you can use the Open XML SDK, it's very easy to add the comments.xml part. If you can't use the SDK, though, you'd have to write your own code to create the same effect. To do that, you'll have to understand where to make which changes.

    The first step is to create the comments.xml file within the /word folder. An empty comments.xml should look like this:

    <?xml version="1.0" encoding="utf-8"?>
    <w:comments xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
    </w:comments>
    

    Next, you need to establish the relationship between the MainDocumentPart (documents.xml) and the WordprocessingCommentsPart (comments.xml). To do that, you need to create or amend the document.xml.rels file contained in the /word/_rels folder. For the document created by the above example, document.xml.rels has the following contents (i.e., a single related child part):

    <?xml version="1.0" encoding="utf-8"?>
    <Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
      <Relationship Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments" Target="/word/comments.xml" Id="Rf416c937546c47ef" />
    </Relationships>
    

    The last file to consider is [Content_Types].xml at the root of the package. Having added the comments.xml part with the Open XML SDK in the above example, it has the following contents:

    <?xml version="1.0" encoding="utf-8"?>
    <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
      <Default Extension="xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" />
      <Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml" />
      <Override PartName="/word/comments.xml" ContentType="application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml" />
    </Types>
    

    Take note of the last child element, which overrides the the content type of the /word/comments.xml part.