Search code examples
javaapache-poidocx

How to manipulate content of a comment with Apache POI


I would like to find a comment in Docx document (somehow, by author or ID...), then create new content. I was able to create a comment, with the help of this answer, but had no luck with manipulation.


Solution

  • As said in my answer linked in your question, until now the XWPFdocument will only read that package part while creating. There is neither write access nor a possibility to create that package part. This is mentioned in XWPFDocument.java - protected void onDocumentRead(): code line 210: "// TODO Create according XWPFComment class, extending POIXMLDocumentPart".

    So we need doing this ourself until now. We need providing class extending POIXMLDocumentPart for comments and registering this relation instead of only relation to the simple POIXMLDocumentPart. So that and changings can be made which were committed while writing the XWPFDocument.

    Example:

    import java.io.*;
    
    import org.apache.poi.*;
    import org.apache.poi.openxml4j.opc.*;
    import org.apache.xmlbeans.*;
    
    import org.apache.poi.xwpf.usermodel.*;
    
    import static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS;
    
    import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
    
    import javax.xml.namespace.QName;
    
    import java.math.BigInteger;
    import java.util.GregorianCalendar;
    import java.util.Locale;
    
    public class WordChangeComments {
    
     public static void main(String[] args) throws Exception {
    
      XWPFDocument document = new XWPFDocument(new FileInputStream("WordDocumentHavingComments.docx"));
    
      for (POIXMLDocumentPart.RelationPart rpart : document.getRelationParts()) {
       String relation = rpart.getRelationship().getRelationshipType();
       if (relation.equals(XWPFRelation.COMMENT.getRelation())) {
        POIXMLDocumentPart part = rpart.getDocumentPart(); //this is only POIXMLDocumentPart, not a high level class extending POIXMLDocumentPart
        //provide class extending POIXMLDocumentPart for comments 
        MyXWPFCommentsDocument myXWPFCommentsDocument = new MyXWPFCommentsDocument(part.getPackagePart());
        //and registering this relation instead of only relation to POIXMLDocumentPart
        String rId = document.getRelationId(part);
        document.addRelation(rId, XWPFRelation.COMMENT, myXWPFCommentsDocument);
    
        //now the comments are available from the new MyXWPFCommentsDocument 
        for (CTComment ctComment : myXWPFCommentsDocument.getComments().getCommentArray()) {
    
         System.out.print("Comment: Id: " + ctComment.getId());
         System.out.print(", Author: " + ctComment.getAuthor());
         System.out.print(", Date: " + ctComment.getDate());
         System.out.print(", Text: ");
         for (CTP ctp : ctComment.getPArray()) {
          System.out.print(ctp.newCursor().getTextValue());
         }
         System.out.println();
    
         //and changings can be made which were committed while writing the XWPFDocument
         if (BigInteger.ONE.equals(ctComment.getId())) { //the second comment (Id 0 = first)
          ctComment.setAuthor("New Author");
          ctComment.setInitials("NA");
          ctComment.setDate(new GregorianCalendar(Locale.US));
          CTP newCTP = CTP.Factory.newInstance();
          newCTP.addNewR().addNewT().setStringValue("The new Text for Comment with Id 1.");
          ctComment.setPArray(new CTP[]{newCTP });
         }
        }
       }
      }
    
      document.write(new FileOutputStream("WordDocumentHavingComments.docx"));
    
      document.close();
     }
    
    //a wrapper class for the CommentsDocument /word/comments.xml in the *.docx ZIP archive
     private static class MyXWPFCommentsDocument extends POIXMLDocumentPart {
    
      private CTComments comments;
    
      private MyXWPFCommentsDocument(PackagePart part) throws Exception {
       super(part);
       comments = CommentsDocument.Factory.parse(part.getInputStream(), DEFAULT_XML_OPTIONS).getComments();
      }
    
      private CTComments getComments() {
       return comments;
      }
    
      @Override
      protected void commit() throws IOException {
    
    System.out.println("============MyXWPFCommentsDocument is committed=================");
    
       XmlOptions xmlOptions = new XmlOptions(DEFAULT_XML_OPTIONS);
       xmlOptions.setSaveSyntheticDocumentElement(new QName(CTComments.type.getName().getNamespaceURI(), "comments"));
       PackagePart part = getPackagePart();
       OutputStream out = part.getOutputStream();
       comments.save(out, xmlOptions);
       out.close();
      }
    
     }
    
    }
    

    This works for apache poi 3.17. Since apache poi 4.0.0 the ooxml part is separated. So there must be:

    ...
    import org.apache.poi.ooxml.*;
    ...
    import static org.apache.poi.ooxml.POIXMLTypeLoader.DEFAULT_XML_OPTIONS;
    ...