0

I want to write a comment to a existing word document programmatically using apache poi. I have successfully added comments to the paragraph by the method mentioned here, but when adding comments to the table, I don't know how to associate it with real comments

BigInteger cId = BigInteger.ZERO;

 ctComment = comments.addNewComment();
 ctComment.setAuthor("Axel Ríchter");
 ctComment.setInitials("AR");
 ctComment.setDate(new GregorianCalendar(Locale.US));
 ctComment.addNewP().addNewR().addNewT().setStringValue("The first comment.");
 ctComment.setId(cId);

 paragraph = document.createParagraph();
 paragraph.getCTP().addNewCommentRangeStart().setId(cId);

 run = paragraph.createRun();
 run.setText("Paragraph with the first comment.");

 paragraph.getCTP().addNewCommentRangeEnd().setId(cId);

 paragraph.getCTP().addNewR().addNewCommentReference().setId(cId);

in paragraph, we can use paragraph.getCTP().addNewR().addNewCommentReference().setId() to link a comment, but there seems no similar method in table. what should i do? I am a beginner to apache poi, any suggestions are greatly appreciated.

gt y
  • 3
  • 1
  • The content of a [XWPFTableCell](https://poi.apache.org/apidocs/dev/org/apache/poi/xwpf/usermodel/XWPFTableCell.html) also is in paragraphs. So get the needed `XWPFParagraph` from the `XWPFTableCell` or add a new one. Then do as with other paragraphs. – Axel Richter Dec 30 '19 at 13:13
  • Thank you very much for your answer, this helped me a lot. but if i want to add a comment to the whole table. what should i do to link it to the real comment. looking forward to your answer again.@AxelRichter – gt y Dec 31 '19 at 01:42

1 Answers1

1

The short answer to your question how to comment a whole table is: You get the first paragraph in first cell of that table to set the CommentRangeStart as the first element in that paragraph. Then you get the last paragraph in last cell of that table to set the CommentRangeEnd as the last element in that paragraph.

But if you wants changing a already present Word document, then much more needs to be taken into account. What if there are comment already? My example you are linking is for creating a new Word document from scratch. So there cannot be comments already before. I have provided also How to manipulate content of a comment with Apache POI. But this rely on already present comments. Now we have to use a combination of both. This needs to take into account already present comments as well as a not already present comments document which then needs to be new created.

Additional there is difficulty to to set the CommentRangeStart as the first element in a already present paragraph. There are no methods for this. So we need using very low level XML manipulating to achieve this.

The following example needs a WordDocument.docx having at least one table. This whole table gets commented then.

import java.io.*;

import org.apache.poi.*;
import org.apache.poi.ooxml.*;
import org.apache.poi.openxml4j.opc.*;
import org.apache.xmlbeans.*;

import org.apache.poi.xwpf.usermodel.*;

import static org.apache.poi.ooxml.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 WordCommentWholeTable {

 //a method to get or create the CommentsDocument /word/comments.xml in the *.docx ZIP archive  
 private static MyXWPFCommentsDocument createCommentsDocument(XWPFDocument document) throws Exception {
  MyXWPFCommentsDocument myXWPFCommentsDocument = null;

  //trying to get the CommentsDocument
  for (POIXMLDocumentPart.RelationPart rpart : document.getRelationParts()) {
   String relation = rpart.getRelationship().getRelationshipType();
   if (relation.equals(XWPFRelation.COMMENT.getRelation())) {
    POIXMLDocumentPart part = rpart.getDocumentPart();
    myXWPFCommentsDocument = new MyXWPFCommentsDocument(part.getPackagePart());
    String rId = document.getRelationId(part);
    document.addRelation(rId, XWPFRelation.COMMENT, myXWPFCommentsDocument);
   }
  }

  //create a new CommentsDocument if there is not one already
  if (myXWPFCommentsDocument == null) {
   OPCPackage oPCPackage = document.getPackage();
   PackagePartName partName = PackagingURIHelper.createPartName("/word/comments.xml");
   PackagePart part = oPCPackage.createPart(
    partName, 
    "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml");
   myXWPFCommentsDocument = new MyXWPFCommentsDocument(part);
   document.addRelation(null, XWPFRelation.COMMENT, myXWPFCommentsDocument);
  }

  return myXWPFCommentsDocument;
 }

 //a method to get the next comment Id from CTComments
 private static BigInteger getCommentId(CTComments comments) {
  BigInteger cId = BigInteger.ZERO;
  for (CTComment ctComment : comments.getCommentList()) {
   if (ctComment.getId().compareTo(cId) == 1) {
    cId = ctComment.getId();
   }
  }
  cId = cId.add(BigInteger.ONE);
  return cId;
 }

 //method to set CommentRangeStart as first element in paragraph
 private static CTMarkupRange insertCommentRangeStartAsFirstElement(XWPFParagraph paragraph) {
  String uri = CTMarkupRange.type.getName().getNamespaceURI();
  String localPart = "commentRangeStart";
  XmlCursor cursor = paragraph.getCTP().newCursor();
  cursor.toFirstChild();  
  cursor.beginElement(localPart, uri);
  cursor.toParent();
  CTMarkupRange commentRangeStart = (CTMarkupRange)cursor.getObject();
  cursor.dispose();
  return commentRangeStart;  
 }


 public static void main(String[] args) throws Exception {

  XWPFDocument document = new XWPFDocument(new FileInputStream("WordDocument.docx"));

  MyXWPFCommentsDocument myXWPFCommentsDocument = createCommentsDocument(document);
  CTComments comments = myXWPFCommentsDocument.getComments();

  CTComment ctComment;
  XWPFParagraph paragraph;
  XWPFRun run;

  //comment for the table
  BigInteger cId = getCommentId(comments);
  ctComment = comments.addNewComment();
  ctComment.setAuthor("Axel Ríchter");
  ctComment.setInitials("AR");
  ctComment.setDate(new GregorianCalendar(Locale.US));
  ctComment.addNewP().addNewR().addNewT().setStringValue("This is a comment for whole table.");
  ctComment.setId(cId);

  //get first paragraph in first table cell to set CommentRangeStart
  XWPFTable table = document.getTables().get(0);
  XWPFTableRow row = table.getRow(0);
  XWPFTableCell cell = row.getCell(0);
  paragraph = cell.getParagraphArray(0);
  CTMarkupRange commentRangeStart = insertCommentRangeStartAsFirstElement(paragraph);
  commentRangeStart.setId(cId);

  //get last paragraph in last table cell to set CommentRangeEnd and CommentReference
  row = table.getRows().get(table.getRows().size()-1);
  cell = row.getTableCells().get(row.getTableCells().size()-1);
  paragraph = cell.getParagraphs().get(cell.getParagraphs().size()-1);
  paragraph.getCTP().addNewCommentRangeEnd().setId(cId);
  paragraph.getCTP().addNewR().addNewCommentReference().setId(cId);


  FileOutputStream out = new FileOutputStream("WordDocumentWithComments.docx");
  document.write(out);
  out.close();
  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);
   try {
    comments = CommentsDocument.Factory.parse(part.getInputStream(), DEFAULT_XML_OPTIONS).getComments();
   } catch (Exception ex) {
    // there was no comments yet
   }
   if (comments == null) comments = CommentsDocument.Factory.newInstance().addNewComments();
  }

  private CTComments getComments() {
   return comments;
  }

  @Override
  protected void commit() throws IOException {
   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();
  }

 }

}
Axel Richter
  • 56,077
  • 6
  • 60
  • 87