To explain the principle, I will try give a complete example which is as minimal as possible.
At first: Apache POI 3.14 is too old. My answer relies on current apache poi 5.2.2
.
Generating the com.microsoft.schemas.office.word.x2012.wordml.*
classes from the *.xsd
schema files using XMLBeans
The word12.xsd
can be got from the Microsoft source: 5.2 http://schemas.microsoft.com/office/word/2012/wordml Schema. But it has to be changed a little bit to be compilable.
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:**w12**="http://schemas.openxmlformats.org/wordprocessingml/2006/main" ...>
Why is the wordprocessingml/2006/
name space prefixed w12
? That's irritating and should better be w06
.
<xsd:import id="w12" namespace=**"http://schemas.openxmlformats.org/wordprocessingml/2006/main"** ...>
Why get the well known wordprocessingml/2006/
name space additional imported as w12
? That violates the given targetNamespace
. It should be
<xsd:import id="w12" namespace="http://schemas.microsoft.com/office/word/2012/wordml" ...>
And since ECMA-376-Fifth-Edition the types ST_OnOff
and ST_String
are not more in http://schemas.openxmlformats.org/wordprocessingml/2006/main
namespace but in http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes
. So we need an additional namespace:
... xmlns:w06st="http://schemas.openxmlformats.org/officeDocument/2006/sharedTypes" ...
The complete 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>
The word12.xsd
refers to other types. So additional schemes needs to be accessible while compiling. Those can be got from ECMA-376, Download part 4 and unpack OfficeOpenXML-XMLSchema-Transitional.zip
of
ECMA-376-Fifth-Edition-Part-4-Transitional-Migration-Features.zip
to a directory of your filesystem. I have done this to ./xsd/ooxml
(replace the .
with your special location). Copy word12.xsd
to this directory too.
Now we can do the compiling using 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
This compiles the three schema files ./xsd/ooxml/word12.xsd ./xsd/ooxml/shared-commonSimpleTypes.xsd ./xsd/ooxml/shared-math.xsd
and creates the JAR ./xmlbeans/ooxml/ooxml-schemas-word12-5.2.2.jar
and puts the *.java
source files in ./xmlbeans/ooxml/src
.
After compiling you have the ooxml-schemas-word12-5.2.2.jar
containing the new com.microsoft.schemas.office.word.x2012.wordml.*
classes. If there are problems with compiling using XmlBeans, here is the resulting ooxml-schemas-word12-5.2.2.jar. This must be in class path while compiling and running following code.
Using the com.microsoft.schemas.office.word.x2012.wordml.*
classes in ooxml-schemas-word12-5.2.2.jar
to create a XWPFDocument
having comments and using extended comment properties
We know we need /word/comments.xml
and /word/commentsExtended.xml
in the *.docx
ZIP archive. In apache poi
ooxml
this XML files are called document parts. There must be XWPF
classes which extend POIXMLDocumentPart
to handle those document parts. This classes need override protected void commit()
to be able to save the content while XWPFDocument.write
. Additional we need a method to create (add) this document parts to the OPCPackage
(ZipPackage
) of the XWPFDocument
. The relation between the document parts and the OPCPackage
is saved using a POIXMLRelation
. So we need the XWPFRelation.COMMENT
, which is already provided in apache poi
, and additional XWPFCommentsExRelation
which extends POIXMLRelation
.
To be referable in CTCommentsEx
the paragraphs in CTComment
need attribute paraId
from namespace http://schemas.microsoft.com/office/word/2010/wordml
. We should avoid importing and compiling the 5.1 http://schemas.microsoft.com/office/word/2010/wordml Schema only to be able to set this attribute. So we use XmlCursor.insertAttributeWithValue
to add that attribute to CTP
in CTComment
. (Note: The above linked XSD seems not even to be the full schema for namespace http://schemas.microsoft.com/office/word/2010/wordml
. At least there is no definition for atribute paraId
in CT_P
.)
Putting all this together we can have following code:
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");
}
}
}
This generates:
