简体   繁体   English

使用 Apache POI 为特定单词添加注释或在 docx 文档中运行

[英]Adding comment to a specific word or run in docx document using Apache POI

My goal is to search for a word or a phrase in a Word .docx document, and add a comment to it.我的目标是在 Word .docx 文档中搜索单词或短语,并为其添加注释。 I have been referring to the sample code found here , here , and here with regards to adding comments using Apache POI.关于使用 Apache POI 添加注释,我一直在参考此处此处此处的示例代码。 However, all three examples add comments to a whole paragraph (or even a whole table) rather than to a specific word, or run.但是,所有三个示例都将注释添加到整个段落(甚至整个表格),而不是添加到特定单词或运行。

I have tried creating an XML cursor at the run level, but cannot cast it to the necessary CTMarkupRange to apply the start and end of the comment.我曾尝试在运行级别创建一个 XML 游标,但无法将其转换为必要的 CTMarkupRange 以应用注释的开始和结束。

        // Create comment
                        BigInteger cId = getCommentId(comments);
                        ctComment = comments.addNewComment();
                        ctComment.setAuthor("John Smith");
                        ctComment.setInitials("JS");
                        ctComment.setDate(new GregorianCalendar(Locale.getDefault()));
                        ctComment.addNewP().addNewR().addNewT().setStringValue("Test Comment");
                        ctComment.setId(cId);
                        
        // Set CommentRangeStart
                        String uri = CTMarkupRange.type.getName().getNamespaceURI();
                        String localPart = "commentRangeStart";

                        // XmlCursor cursor = p.getCTP().newCursor();
                        XmlCursor cursor = r.getCTR().newCursor();  
                        cursor.toFirstChild();
                        cursor.beginElement(localPart, uri);
                        cursor.toParent();
                        CTMarkupRange commentRangeStart =  (CTMarkupRange) cursor.getObject(); // This line throws a ClassCastException error
                        cursor.dispose();

                        commentRangeStart.setId(cId);

        // Set CommentRangeEnd and CommentReference

                        p.getCTP().addNewCommentRangeEnd().setId(cId);
                        // p.getCTP().addNewR().addNewCommentReference().setId(cId);
                        r.getCTR().addNewCommentReference().setId(cId);

EDIT1: Snippet showing the logic for looping through the runs EDIT1:片段显示循环运行的逻辑

for(XWPFParagraph p:paragraphs){
    List<XWPFRun> runs = p.getRuns();
    if (runs.size() > 0) {
        for (XWPFRun r : runs) {
            String text = r.getText(0);
            for (Map.Entry<String, List<String>> entry : rules.entrySet()) {
                String key = entry.getKey();
                List<String> value = entry.getValue();

                for (int i = 0; i < value.size(); i++) {
                    if (text != null && regexContains(text, value.get(i))) {
                        // Create comment
                        BigInteger cId = getCommentId(comments);
                        ctComment = comments.addNewComment();
                        ctComment.setAuthor("John Smith");
                        ctComment.setInitials("JS");
                        ctComment.setDate(new GregorianCalendar(Locale.getDefault()));
                        ctComment.addNewP().addNewR().addNewT().setStringValue(key);
                        ctComment.setId(cId);

                        // New snippet from Axel Richter
                        p.getCTP().addNewCommentRangeStart().setId(cId);
                        

                        p.getCTP().addNewCommentRangeEnd().setId(cId);
                        p.getCTP().addNewR().addNewCommentReference().setId(cId);
                    }
                }
            }
        }
    }

}

This is not as difficult as you might think.这并不像您想象的那么困难。

To comment a run inside a paragraph, the comment range start needs to be set before text run starts in paragraph.要评论段落内的运行,需要在段落中的文本运行开始之前设置注释范围开始。 The comment range end needs to be set after text run ends in paragraph.文本运行在段落结束后需要设置注释范围结束。 This is exactly what my code examples had done already.这正是我的代码示例已经完成的。 Of course all paragraphs in my code examples have had only one text run.当然,我的代码示例中的所有段落都只运行了一个文本。

In following complete example the second comment comments the word "second" only.在以下完整示例中,第二条注释仅注释“第二”一词。 To do so the paragraph has three text runs.为此,该段落具有三个文本运行。 First having text "Paragraph with the ", second having text "second" and has comment and third having text " comment.".第一个有文本“带有”的段落,第二个有文本“第二个”和评论,第三个有文本“评论。”。

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 CreateWordWithComments {

//a method for creating the CommentsDocument /word/comments.xml in the *.docx ZIP archive  
 private static MyXWPFCommentsDocument createCommentsDocument(XWPFDocument document) throws Exception {
  OPCPackage oPCPackage = document.getPackage();
  PackagePartName partName = PackagingURIHelper.createPartName("/word/comments.xml");
  PackagePart part = oPCPackage.createPart(partName, "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml");
  MyXWPFCommentsDocument myXWPFCommentsDocument = new MyXWPFCommentsDocument(part);

  String rId = document.addRelation(null, XWPFRelation.COMMENT, myXWPFCommentsDocument).getRelationship().getId();

  return myXWPFCommentsDocument;
 }

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

  XWPFDocument document = new XWPFDocument();

  MyXWPFCommentsDocument myXWPFCommentsDocument = createCommentsDocument(document);

  CTComments comments = myXWPFCommentsDocument.getComments();
  CTComment ctComment;
  XWPFParagraph paragraph;
  XWPFRun run;

//first comment
  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); //comment range start is set before text run
  run = paragraph.createRun();
  run.setText("Paragraph with the first comment.");
  paragraph.getCTP().addNewCommentRangeEnd().setId(cId); //comment range end is set after text run

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

//paragraph without comment
  paragraph = document.createParagraph();
  run = paragraph.createRun();
  run.setText("Paragraph without comment.");

//second comment
  cId = cId.add(BigInteger.ONE);

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

  paragraph = document.createParagraph();
  run = paragraph.createRun();
  run.setText("Paragraph with the ");

  paragraph.getCTP().addNewCommentRangeStart().setId(cId); //comment range start is set before text run
  run = paragraph.createRun();
  run.setText("second");
  paragraph.getCTP().addNewCommentRangeEnd().setId(cId); //comment range end is set after text run

  run = paragraph.createRun();
  run.setText(" comment.");

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

//write document
  FileOutputStream out = new FileOutputStream("CreateWordWithComments.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);
   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();
  }

 }

}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM