如何为 CommentsExtended.xml & CommentsExtensible.xml 添加 类 到 POI 3.14

How to add Classes for CommentsExtended.xml & CommentsExtensible.xml to POI 3.14

我知道 POI 仅支持 2007 年的 XSD,但 2012 年引入了 CommentsExtended.xml 和 CommentsExtensible.xml。但我需要在 POI 中添加此功能并最终使用它.我已经下载了这两个文件的 XSD。但不知道从哪里开始。我通常知道 POI 使用 Xmlbeans 为 XSD 模式生成低级别 类。但不知道从哪里开始以及如何进行。任何帮助都将非常宝贵。

第一次尝试

乍一看,我尝试添加创建 CommentsExtended.xml 包并使用 OPCPackage API 添加关系并成功。但问题似乎是创建了一个像 commentsExtended.xml.rels 这样的新关系文件,而不是我想在已经存在的 document.xml.rels 中添加 commentsExtended.xml 的条目。 我做错了什么?如何在 document.xml.rels 本身中为 commentsExtended 文件添加 realtion ? Alex ritcher

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

private static void createCommentsExtendedPackage() throws Exception {

        XWPFDocument document = new XWPFDocument();
        PackagePartName partName = PackagingURIHelper.createPartName("/word/commentsExtended.xml");
        OPCPackage opcPackage = document.getPackage();
        PackagePart part = opcPackage.createPart(partName, ContentTypes.PLAIN_OLD_XML);
        OutputStream outputStream = part.getOutputStream();
        outputStream.write("<test>A</test>".getBytes());
        outputStream.close();
        
        //Creates commentsExtended.xml.rels file which is not needed.
        document.getPackagePart().addRelationship(partName, TargetMode.INTERNAL, PackageRelationshipTypes.CUSTOM_XML);
        
        //When trying to add relationShip in document getting NULL Pointer Exception
        POIXMLDocumentPart documentPart = new POIXMLDocumentPart(part);
        String rIdExtLink = "rId" + (document.getRelationParts().size()+1);
        document.addRelation(rIdExtLink, documentPart);
        
        // Save document
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        document.write(outStream);
        byte[] fileBytes = outStream.toByteArray();
        FileUtil.write("CommentsExtended.docx", fileBytes); //no i18n
    }

第二次尝试: 我现在可以使用下面的代码在 document.xml.rels 中添加 commentsExtended.xml 关系,但现在我在 commentsExtended.xml 中的值在保存时被清除。设置值后如何在 commentsExtended.xml 中保留值。 非常感谢任何帮助。

private static void createCommentsExtendedPackage() throws Exception {

        XWPFDocument document = new XWPFDocument();
        OPCPackage opcPackage = document.getPackage();
        
        PackagePartName partName = PackagingURIHelper.createPartName("/word/commentsExtended.xml");
        PackagePart part = opcPackage.createPart(partName, ContentTypes.PLAIN_OLD_XML);
        
        class CommentsExtendedRelation extends POIXMLRelation {
            private CommentsExtendedRelation() {
                super(  "application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml",
                        "http://schemas.microsoft.com/office/2011/relationships/commentsExtended",
                        "/word/commentsExtended.xml");
            }
        }
        
        //Adding relation in document.xml.rels
        POIXMLDocumentPart documentPart = new POIXMLDocumentPart(part);
        String rIdExtLink = "rId" + (document.getRelationParts().size()+1);
        document.addRelation(rIdExtLink, new CommentsExtendedRelation() , documentPart);            
                   
        OutputStream outputStream = part.getOutputStream();
        outputStream.write("<test>A</test>".getBytes());
        outputStream.close();
        
        // Create main document part
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        document.write(outStream);
        byte[] fileBytes = outStream.toByteArray();
        FileUtil.write("CommentsExtendedOld.docx", fileBytes); //no i18n
    }

为了说明原理,我会尽量给出一个完整的例子,尽量少。

起初:Apache POI 3.14 太旧了。我的回答依赖于当前 apache poi 5.2.2.

使用 XMLBeans

*.xsd 模式文件生成 com.microsoft.schemas.office.word.x2012.wordml.* classes

word12.xsd可以从微软的源码中获取:5.2 http://schemas.microsoft.com/office/word/2012/wordml Schema。但它必须稍微改变一下才能编译。

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:**w12**="http://schemas.openxmlformats.org/wordprocessingml/2006/main" ...>

为什么 wordprocessingml/2006/ 名称 space 有前缀 w12?这很烦人,最好是 w06.

<xsd:import id="w12" namespace=**"http://schemas.openxmlformats.org/wordprocessingml/2006/main"** ...>

为什么要将众所周知的wordprocessingml/2006/名称space额外导入为w12?这违反了给定的 targetNamespace。应该是

<xsd:import id="w12" namespace="http://schemas.microsoft.com/office/word/2012/wordml" ...>

并且由于 ECMA-376-Fifth-Edition 类型 ST_OnOffST_String 不再出现在 http://schemas.openxmlformats.org/wordprocessingml/2006/main namespace 中,而是出现在 http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes.所以我们需要一个额外的名字space:

... xmlns:w06st="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" ...

完整word12.xsd:

 <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
  xmlns:w06="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
  xmlns:w06st="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes"
  elementFormDefault="qualified" attributeFormDefault="qualified" blockDefault="#all" 
  xmlns="http://schemas.microsoft.com/office/word/2012/wordml" 
  targetNamespace="http://schemas.microsoft.com/office/word/2012/wordml">
   <xsd:import id="w12" namespace="http://schemas.microsoft.com/office/word/2012/wordml" schemaLocation="word12.xsd"/>
   <xsd:element name="color" type="w06:CT_Color"/>
   <xsd:simpleType name="ST_SdtAppearance">
     <xsd:restriction base="xsd:string">
       <xsd:enumeration value="boundingBox"/>
       <xsd:enumeration value="tags"/>
       <xsd:enumeration value="hidden"/>
     </xsd:restriction>
   </xsd:simpleType>
   <xsd:element name="dataBinding" type="w06:CT_DataBinding"/>
   <xsd:complexType name="CT_SdtAppearance">
     <xsd:attribute name="val" type="ST_SdtAppearance"/>
   </xsd:complexType>
   <xsd:element name="appearance" type="CT_SdtAppearance"/>
   <xsd:complexType name="CT_CommentsEx">
     <xsd:sequence>
       <xsd:element name="commentEx" type="CT_CommentEx" minOccurs="0" maxOccurs="unbounded"/>
     </xsd:sequence>
   </xsd:complexType>
   <xsd:complexType name="CT_CommentEx">
     <xsd:attribute name="paraId" type="w06:ST_LongHexNumber" use="required"/>
     <xsd:attribute name="paraIdParent" type="w06:ST_LongHexNumber" use="optional"/>
     <xsd:attribute name="done" type="w06st:ST_OnOff" use="optional"/>
   </xsd:complexType>
   <xsd:element name="commentsEx" type="CT_CommentsEx"/>
   <xsd:complexType name="CT_People">
     <xsd:sequence>
       <xsd:element name="person" type="CT_Person" minOccurs="0" maxOccurs="unbounded"/>
     </xsd:sequence>
   </xsd:complexType>
   <xsd:complexType name="CT_PresenceInfo">
     <xsd:attribute name="providerId" type="xsd:string" use="required"/>
     <xsd:attribute name="userId" type="xsd:string" use="required"/>
   </xsd:complexType>
   <xsd:complexType name="CT_Person">
     <xsd:sequence>
       <xsd:element name="presenceInfo" type="CT_PresenceInfo" minOccurs="0" maxOccurs="1"/>
     </xsd:sequence>
     <xsd:attribute name="author" type="w06st:ST_String" use="required"/>
   </xsd:complexType>
   <xsd:element name="people" type="CT_People"/>
   <xsd:complexType name="CT_SdtRepeatedSection">
     <xsd:sequence>
       <xsd:element name="sectionTitle" type="w06:CT_String" minOccurs="0"/>
       <xsd:element name="doNotAllowInsertDeleteSection" type="w06:CT_OnOff" minOccurs="0"/>
     </xsd:sequence>
   </xsd:complexType>
   <xsd:simpleType name="ST_Guid">
     <xsd:restriction base="xsd:token">
       <xsd:pattern value="\{[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}\}"/>
     </xsd:restriction>
   </xsd:simpleType>
   <xsd:complexType name="CT_Guid">
     <xsd:attribute name="val" type="ST_Guid"/>
   </xsd:complexType>
   <xsd:element name="repeatingSection" type="CT_SdtRepeatedSection"/>
   <xsd:element name="repeatingSectionItem" type="w06:CT_Empty"/>
   <xsd:element name="chartTrackingRefBased" type="w06:CT_OnOff"/>
   <xsd:element name="collapsed" type="w06:CT_OnOff"/>
   <xsd:element name="docId" type="CT_Guid"/>
   <xsd:element name="footnoteColumns" type="w06:CT_DecimalNumber"/>
   <xsd:element name="webExtensionLinked" type="w06:CT_OnOff"/>
   <xsd:element name="webExtensionCreated" type="w06:CT_OnOff"/>
   <xsd:attribute name="restartNumberingAfterBreak" type="w06st:ST_OnOff"/>
 </xsd:schema>

中的word12.xsd指的是其他类型。因此,在编译时需要访问其他方案。这些可以从 ECMA-376 获得,下载第 4 部分并解压 OfficeOpenXML-XMLSchema-Transitional.zip ECMA-376-Fifth-Edition-Part-4-Transitional-Migration-Features.zip 到文件系统的目录。我这样做是为了 ./xsd/ooxml(将 . 替换为您的特殊位置)。也将 word12.xsd 复制到此目录。

现在我们可以使用scomp进行编译了。

scomp -src ./xmlbeans/ooxml/src -out ./xmlbeans/ooxml/ooxml-schemas-word12-5.2.2.jar ./xsd/ooxml/word12.xsd ./xsd/ooxml/shared-commonSimpleTypes.xsd ./xsd/ooxml/shared-math.xsd

这会编译三个模式文件 ./xsd/ooxml/word12.xsd ./xsd/ooxml/shared-commonSimpleTypes.xsd ./xsd/ooxml/shared-math.xsd 并创建 JAR ./xmlbeans/ooxml/ooxml-schemas-word12-5.2.2.jar 并将 *.java 源文件放入 ./xmlbeans/ooxml/src.

编译后,您的 ooxml-schemas-word12-5.2.2.jar 包含新的 com.microsoft.schemas.office.word.x2012.wordml.* classes。如果使用 XmlBeans 编译有问题,这里是结果 ooxml-schemas-word12-5.2.2.jar。这必须在编译时位于 class 路径中,并且在代码后 运行 中。

使用 ooxml-schemas-word12-5.2.2.jar 中的 com.microsoft.schemas.office.word.x2012.wordml.* classes 创建一个 XWPFDocument 具有注释并使用扩展注释属性

我们知道 *.docx ZIP 压缩包中需要 /word/comments.xml/word/commentsExtended.xml。在 apache poi ooxml 中,此 XML 文件称为文档部件。必须有扩展 POIXMLDocumentPartXWPF classes 来处理这些文档部分。此 classes 需要覆盖 protected void commit() 才能在 XWPFDocument.write 时保存内容。此外,我们需要一种方法来创建(添加)此文档部分到 XWPFDocumentOPCPackageZipPackage)。文档部分和 OPCPackage 之间的关系使用 POIXMLRelation 保存。所以我们需要 XWPFRelation.COMMENT,它已经在 apache poi 中提供,另外 XWPFCommentsExRelation 扩展了 POIXMLRelation.

要在 CTCommentsEx 中引用,CTComment 中的段落需要名称 space http://schemas.microsoft.com/office/word/2010/wordml 中的属性 paraId。我们应该避免导入和编译 5.1 http://schemas.microsoft.com/office/word/2010/wordml Schema 只是为了能够设置这个属性。所以我们使用 XmlCursor.insertAttributeWithValue 将该属性添加到 CTComment 中的 CTP。 (注意:上面链接的 XSD 似乎甚至不是名称 space http://schemas.microsoft.com/office/word/2010/wordml 的完整模式。至少 [=75= 中没有属性 paraId 的定义].)

将所有这些放在一起我们可以得到以下代码:

import java.io.*;

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

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

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

import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
import com.microsoft.schemas.office.word.x2012.wordml.*;

import javax.xml.namespace.QName;

import java.math.BigInteger;
import java.util.GregorianCalendar;
import java.util.Locale;


public class CreateWordWithCommentsAndCommentsEx {

//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;
 }
 
//a method for creating the CommentsExtendedDocument /word/commentsExtended.xml in the *.docx ZIP archive  
 private static MyXWPFCommentsExtendedDocument createCommentsExtendedDocument(XWPFDocument document) throws Exception {
  OPCPackage oPCPackage = document.getPackage();
  PackagePartName partName = PackagingURIHelper.createPartName("/word/commentsExtended.xml");
  PackagePart part = oPCPackage.createPart(partName, "application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml");
  MyXWPFCommentsExtendedDocument myXWPFCommentsExtendedDocument = new MyXWPFCommentsExtendedDocument(part);
 
  String rId = document.addRelation(null, new XWPFCommentsExRelation(), myXWPFCommentsExtendedDocument).getRelationship().getId();

  return myXWPFCommentsExtendedDocument;
 }

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

  XWPFDocument document = new XWPFDocument();
 
  MyXWPFCommentsDocument myXWPFCommentsDocument = createCommentsDocument(document);
  MyXWPFCommentsExtendedDocument myXWPFCommentsExtendedDocument = createCommentsExtendedDocument(document);
 
  CTComments comments = myXWPFCommentsDocument.getComments();
  CTCommentsEx commentsEx = myXWPFCommentsExtendedDocument.getCommentsEx();
  
  CTComment ctComment;
  CTCommentEx ctCommentEx;
  CTP ctP;
  XWPFParagraph paragraph;
  XWPFRun run;
  BigInteger cId;
  XmlCursor cursor;

//first comment
  cId = BigInteger.ZERO;
  ctComment = comments.addNewComment();
  ctComment.setAuthor("Axel Richter");
  ctComment.setInitials("AR");
  ctComment.setDate(new GregorianCalendar(Locale.US));
  ctP = ctComment.addNewP();
  cursor = ctP.newCursor();
  cursor.toNextToken();
  cursor.insertAttributeWithValue​(new QName("http://schemas.microsoft.com/office/word/2010/wordml", "paraId"), "01020304");
  cursor.dispose();
  ctP.addNewR().addNewT().setStringValue("The first comment.");
  ctComment.setId(cId);
  
  ctCommentEx = commentsEx.addNewCommentEx();
  ctCommentEx.setParaId(new byte[]{1,2,3,4});
  
  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);

//sub comment to first comment
  cId = cId.add(BigInteger.ONE);
  ctComment = comments.addNewComment();
  ctComment.setAuthor("Axel Richter");
  ctComment.setInitials("AR");
  ctComment.setDate(new GregorianCalendar(Locale.US));
  ctP = ctComment.addNewP();
  cursor = ctP.newCursor();
  cursor.toNextToken();
  cursor.insertAttributeWithValue​(new QName("http://schemas.microsoft.com/office/word/2010/wordml", "paraId"), "01020305");
  cursor.dispose();
  ctP.addNewR().addNewT().setStringValue("Sub comment to the first comment.");
  ctComment.setId(cId);
  
  ctCommentEx = commentsEx.addNewCommentEx();
  ctCommentEx.setParaId(new byte[]{1,2,3,5});
  ctCommentEx.setParaIdParent(new byte[]{1,2,3,4});
  
  paragraph.getCTP().addNewCommentRangeStart().setId(cId);
  paragraph.getCTP().addNewCommentRangeEnd().setId(cId);
  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 Richter");
  ctComment.setInitials("AR");
  ctComment.setDate(new GregorianCalendar(Locale.US));
  ctComment.addNewP().addNewR().addNewT().setStringValue("The second comment.");
  ctComment.setId(cId);
  
 // ctCommentEx = commentsEx.addNewCommentEx();

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

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

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

  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();
  }

 }
 
//a wrapper class for the CommentsExDocument /word/commentsExtended.xml in the *.docx ZIP archive
 private static class MyXWPFCommentsExtendedDocument extends POIXMLDocumentPart {

  private CTCommentsEx commentsEx;

  private MyXWPFCommentsExtendedDocument(PackagePart part) throws Exception {
   super(part);
   commentsEx = CommentsExDocument.Factory.newInstance().addNewCommentsEx();
  }

  private CTCommentsEx getCommentsEx() {
   return commentsEx;
  }

  @Override
  protected void commit() throws IOException {
   XmlOptions xmlOptions = new XmlOptions(DEFAULT_XML_OPTIONS);
   xmlOptions.setSaveSyntheticDocumentElement(new QName(CTCommentsEx.type.getName().getNamespaceURI(), "commentsExtended"));
   PackagePart part = getPackagePart();
   OutputStream out = part.getOutputStream();
   commentsEx.save(out, xmlOptions);
   out.close();
  }

 }
 
 //the XWPFRelation for /word/commentsExtended.xml
 private final static class XWPFCommentsExRelation extends POIXMLRelation {
  private XWPFCommentsExRelation() {
   super(
    "application/vnd.openxmlformats-officedocument.wordprocessingml.commentsExtended+xml", 
    "http://schemas.microsoft.com/office/2011/relationships/commentsExtended", 
    "/word/commentsExtended.xml");
  }
 }

}

这会生成: