aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSayi <sayi@apache.org>2021-03-20 16:21:41 +0000
committerSayi <sayi@apache.org>2021-03-20 16:21:41 +0000
commit0efa53456a11291cc4bdd1d93f135bb065db61cd (patch)
tree4fba57857c43070fd68bb4960c11592b8ad866d1 /src
parent6167f3416fefc30997e1c8742d04ae83dbb09338 (diff)
downloadpoi-0efa53456a11291cc4bdd1d93f135bb065db61cd.tar.gz
poi-0efa53456a11291cc4bdd1d93f135bb065db61cd.zip
Create, get, modify and remove comments, support operating paragraphs, pictures and tables in comments
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1887867 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src')
-rw-r--r--src/ooxml/java/org/apache/poi/xwpf/usermodel/BodyType.java3
-rw-r--r--src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFComment.java412
-rw-r--r--src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFComments.java280
-rw-r--r--src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java53
-rw-r--r--src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRelation.java5
-rw-r--r--src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRun.java4
-rw-r--r--src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFComment.java86
-rw-r--r--src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFComments.java62
8 files changed, 866 insertions, 39 deletions
diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/BodyType.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/BodyType.java
index 39e4758b63..ebf6a4d188 100644
--- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/BodyType.java
+++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/BodyType.java
@@ -26,5 +26,6 @@ public enum BodyType {
HEADER,
FOOTER,
FOOTNOTE,
- TABLECELL
+ TABLECELL,
+ COMMENT
}
diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFComment.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFComment.java
index 0df8111117..f9ee63fc44 100644
--- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFComment.java
+++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFComment.java
@@ -16,43 +16,411 @@
==================================================================== */
package org.apache.poi.xwpf.usermodel;
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTComment;
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
+import org.apache.poi.ooxml.POIXMLDocumentPart;
+import org.apache.xmlbeans.XmlCursor;
+import org.apache.xmlbeans.XmlObject;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.List;
/**
* Sketch of XWPF comment class
- *
- * @author Yury Batrakov (batrakov at gmail.com)
*/
-public class XWPFComment {
- protected String id;
- protected String author;
- protected StringBuilder text;
-
- public XWPFComment(CTComment comment, XWPFDocument document) {
- text = new StringBuilder(64);
- id = comment.getId().toString();
- author = comment.getAuthor();
-
- for (CTP ctp : comment.getPArray()) {
- if(text.length() > 0) {
- text.append("\n");
+public class XWPFComment implements IBody {
+
+ protected CTComment ctComment;
+ protected XWPFComments comments;
+ protected XWPFDocument document;
+ private List<XWPFParagraph> paragraphs = new ArrayList<>();
+ private List<XWPFTable> tables = new ArrayList<>();
+ private List<IBodyElement> bodyElements = new ArrayList<>();
+
+ public XWPFComment(CTComment ctComment, XWPFComments comments) {
+ this.comments = comments;
+ this.ctComment = ctComment;
+ this.document = comments.getXWPFDocument();
+ init();
+ }
+
+ protected void init() {
+ XmlCursor cursor = ctComment.newCursor();
+ cursor.selectPath("./*");
+ while (cursor.toNextSelection()) {
+ XmlObject o = cursor.getObject();
+ if (o instanceof CTP) {
+ XWPFParagraph p = new XWPFParagraph((CTP) o, this);
+ bodyElements.add(p);
+ paragraphs.add(p);
+ } else if (o instanceof CTTbl) {
+ XWPFTable t = new XWPFTable((CTTbl) o, this);
+ bodyElements.add(t);
+ tables.add(t);
+ } else if (o instanceof CTSdtBlock) {
+ XWPFSDT c = new XWPFSDT((CTSdtBlock) o, this);
+ bodyElements.add(c);
+ }
+
+ }
+ cursor.dispose();
+ }
+
+ /**
+ * Get the Part to which the comment belongs, which you need for adding
+ * relationships to other parts
+ *
+ * @return {@link POIXMLDocumentPart} that contains the comment.
+ * @see org.apache.poi.xwpf.usermodel.IBody#getPart()
+ */
+ @Override
+ public POIXMLDocumentPart getPart() {
+ return comments;
+ }
+
+ /**
+ * Get the part type {@link BodyType} of the comment.
+ *
+ * @return The {@link BodyType} value.
+ * @see org.apache.poi.xwpf.usermodel.IBody#getPartType()
+ */
+ @Override
+ public BodyType getPartType() {
+ return BodyType.COMMENT;
+ }
+
+ /**
+ * Gets the body elements ({@link IBodyElement}) of the comment.
+ *
+ * @return List of body elements.
+ */
+ @Override
+ public List<IBodyElement> getBodyElements() {
+ return Collections.unmodifiableList(bodyElements);
+ }
+
+ /**
+ * Returns the paragraph(s) that holds the text of the comment.
+ */
+ @Override
+ public List<XWPFParagraph> getParagraphs() {
+ return Collections.unmodifiableList(paragraphs);
+ }
+
+ /**
+ * Get the list of {@link XWPFTable}s in the comment.
+ *
+ * @return List of tables
+ */
+ @Override
+ public List<XWPFTable> getTables() {
+ return Collections.unmodifiableList(tables);
+ }
+
+ @Override
+ public XWPFParagraph getParagraph(CTP p) {
+ for (XWPFParagraph paragraph : paragraphs) {
+ if (paragraph.getCTP().equals(p))
+ return paragraph;
+ }
+ return null;
+ }
+
+ @Override
+ public XWPFTable getTable(CTTbl ctTable) {
+ for (XWPFTable table : tables) {
+ if (table == null)
+ return null;
+ if (table.getCTTbl().equals(ctTable))
+ return table;
+ }
+ return null;
+ }
+
+ @Override
+ public XWPFParagraph getParagraphArray(int pos) {
+ if (pos >= 0 && pos < paragraphs.size()) {
+ return paragraphs.get(pos);
+ }
+ return null;
+ }
+
+ @Override
+ public XWPFTable getTableArray(int pos) {
+ if (pos >= 0 && pos < tables.size()) {
+ return tables.get(pos);
+ }
+ return null;
+ }
+
+ @Override
+ public XWPFParagraph insertNewParagraph(XmlCursor cursor) {
+ if (isCursorInCmt(cursor)) {
+ String uri = CTP.type.getName().getNamespaceURI();
+ String localPart = "p";
+ cursor.beginElement(localPart, uri);
+ cursor.toParent();
+ CTP p = (CTP) cursor.getObject();
+ XWPFParagraph newP = new XWPFParagraph(p, this);
+ XmlObject o = null;
+ while (!(o instanceof CTP) && (cursor.toPrevSibling())) {
+ o = cursor.getObject();
+ }
+ if ((!(o instanceof CTP)) || o == p) {
+ paragraphs.add(0, newP);
+ } else {
+ int pos = paragraphs.indexOf(getParagraph((CTP) o)) + 1;
+ paragraphs.add(pos, newP);
+ }
+ int i = 0;
+ XmlCursor p2 = p.newCursor();
+ cursor.toCursor(p2);
+ p2.dispose();
+ while (cursor.toPrevSibling()) {
+ o = cursor.getObject();
+ if (o instanceof CTP || o instanceof CTTbl)
+ i++;
+ }
+ bodyElements.add(i, newP);
+ p2 = p.newCursor();
+ cursor.toCursor(p2);
+ cursor.toEndToken();
+ p2.dispose();
+ return newP;
+ }
+ return null;
+ }
+
+ private boolean isCursorInCmt(XmlCursor cursor) {
+ XmlCursor verify = cursor.newCursor();
+ verify.toParent();
+ boolean result = (verify.getObject() == this.ctComment);
+ verify.dispose();
+ return result;
+ }
+
+ @Override
+ public XWPFTable insertNewTbl(XmlCursor cursor) {
+ if (isCursorInCmt(cursor)) {
+ String uri = CTTbl.type.getName().getNamespaceURI();
+ String localPart = "tbl";
+ cursor.beginElement(localPart, uri);
+ cursor.toParent();
+ CTTbl t = (CTTbl) cursor.getObject();
+ XWPFTable newT = new XWPFTable(t, this);
+ cursor.removeXmlContents();
+ XmlObject o = null;
+ while (!(o instanceof CTTbl) && (cursor.toPrevSibling())) {
+ o = cursor.getObject();
+ }
+ if (!(o instanceof CTTbl)) {
+ tables.add(0, newT);
+ } else {
+ int pos = tables.indexOf(getTable((CTTbl) o)) + 1;
+ tables.add(pos, newT);
+ }
+ int i = 0;
+ XmlCursor cursor2 = t.newCursor();
+ while (cursor2.toPrevSibling()) {
+ o = cursor2.getObject();
+ if (o instanceof CTP || o instanceof CTTbl) {
+ i++;
+ }
}
+ cursor2.dispose();
+ bodyElements.add(i, newT);
+ cursor2 = t.newCursor();
+ cursor.toCursor(cursor2);
+ cursor.toEndToken();
+ cursor2.dispose();
+ return newT;
+ }
+ return null;
+ }
+
+ @Override
+ public void insertTable(int pos, XWPFTable table) {
+ bodyElements.add(pos, table);
+ int i = 0;
+ for (CTTbl tbl : ctComment.getTblList()) {
+ if (tbl == table.getCTTbl()) {
+ break;
+ }
+ i++;
+ }
+ tables.add(i, table);
+
+ }
+
+ @Override
+ public XWPFTableCell getTableCell(CTTc cell) {
+ XmlCursor cursor = cell.newCursor();
+ cursor.toParent();
+ XmlObject o = cursor.getObject();
+ if (!(o instanceof CTRow)) {
+ cursor.dispose();
+ return null;
+ }
+ CTRow row = (CTRow) o;
+ cursor.toParent();
+ o = cursor.getObject();
+ cursor.dispose();
+ if (!(o instanceof CTTbl)) {
+ return null;
+ }
+ CTTbl tbl = (CTTbl) o;
+ XWPFTable table = getTable(tbl);
+ if (table == null) {
+ return null;
+ }
+ XWPFTableRow tableRow = table.getRow(row);
+ return tableRow.getTableCell(cell);
+ }
+
+ /**
+ * Get the {@link XWPFDocument} the comment is part of.
+ *
+ * @see org.apache.poi.xwpf.usermodel.IBody#getXWPFDocument()
+ */
+ @Override
+ public XWPFDocument getXWPFDocument() {
+ return document;
+ }
- XWPFParagraph p = new XWPFParagraph(ctp, document);
+ public String getText() {
+ StringBuilder text = new StringBuilder();
+ for (XWPFParagraph p : paragraphs) {
+ if (text.length() > 0) {
+ text.append("\n");
+ }
text.append(p.getText());
}
+ return text.toString();
+ }
+
+ public XWPFParagraph createParagraph() {
+ XWPFParagraph paragraph = new XWPFParagraph(ctComment.addNewP(), this);
+ paragraphs.add(paragraph);
+ bodyElements.add(paragraph);
+ return paragraph;
}
+ public void removeParagraph(XWPFParagraph paragraph) {
+ if (paragraphs.contains(paragraph)) {
+ CTP ctP = paragraph.getCTP();
+ XmlCursor c = ctP.newCursor();
+ c.removeXml();
+ c.dispose();
+ paragraphs.remove(paragraph);
+ bodyElements.remove(paragraph);
+ }
+ }
+
+ public void removeTable(XWPFTable table) {
+ if (tables.contains(table)) {
+ CTTbl ctTbl = table.getCTTbl();
+ XmlCursor c = ctTbl.newCursor();
+ c.removeXml();
+ c.dispose();
+ tables.remove(table);
+ bodyElements.remove(table);
+ }
+ }
+
+ public XWPFTable createTable(int rows, int cols) {
+ XWPFTable table = new XWPFTable(ctComment.addNewTbl(), this, rows,
+ cols);
+ tables.add(table);
+ bodyElements.add(table);
+ return table;
+ }
+
+ /**
+ * Gets the underlying CTComment object for the comment.
+ *
+ * @return CTComment object
+ */
+ public CTComment getCtComment() {
+ return ctComment;
+ }
+
+ /**
+ * The owning object for this comment
+ *
+ * @return The {@link XWPFComments} object that contains this comment.
+ */
+ public XWPFComments getComments() {
+ return comments;
+ }
+
+ /**
+ * Get a unique identifier for the current comment. The restrictions on the
+ * id attribute, if any, are defined by the parent XML element. If this
+ * attribute is omitted, then the document is non-conformant.
+ *
+ * @return string id
+ */
public String getId() {
- return id;
+ return ctComment.getId().toString();
}
+ /**
+ * Get the author of the current comment
+ *
+ * @return author of the current comment
+ */
public String getAuthor() {
- return author;
+ return ctComment.getAuthor();
}
- public String getText() {
- return text.toString();
+ /**
+ * Specifies the author for the current comment If this attribute is
+ * omitted, then no author shall be associated with the parent annotation
+ * type.
+ *
+ * @param author author of the current comment
+ */
+ public void setAuthor(String author) {
+ ctComment.setAuthor(author);
+ }
+
+ /**
+ * Get the initials of the author of the current comment
+ *
+ * @return initials the initials of the author of the current comment
+ */
+ public String getInitials() {
+ return ctComment.getInitials();
+ }
+
+ /**
+ * Specifies the initials of the author of the current comment
+ *
+ * @param initials the initials of the author of the current comment
+ */
+ public void setInitials(String initials) {
+ ctComment.setInitials(initials);
}
+
+ /**
+ * Get the date information of the current comment
+ *
+ * @return the date information for the current comment.
+ */
+ public Calendar getDate() {
+ return ctComment.getDate();
+ }
+
+ /**
+ * Specifies the date information for the current comment. If this attribute
+ * is omitted, then no date information shall be associated with the parent
+ * annotation type.
+ *
+ * @param date the date information for the current comment.
+ */
+ public void setDate(Calendar date) {
+ ctComment.setDate(date);
+ }
+
}
diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFComments.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFComments.java
new file mode 100644
index 0000000000..3cf82b680c
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFComments.java
@@ -0,0 +1,280 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+
+package org.apache.poi.xwpf.usermodel;
+
+import org.apache.poi.ooxml.POIXMLDocumentPart;
+import org.apache.poi.ooxml.POIXMLException;
+import org.apache.poi.ooxml.POIXMLRelation;
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.Internal;
+import org.apache.xmlbeans.XmlException;
+import org.apache.xmlbeans.XmlOptions;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTComment;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTComments;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CommentsDocument;
+
+import javax.xml.namespace.QName;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.apache.poi.ooxml.POIXMLTypeLoader.DEFAULT_XML_OPTIONS;
+
+/**
+ * specifies all of the comments defined in the current document
+ */
+public class XWPFComments extends POIXMLDocumentPart {
+
+ XWPFDocument document;
+ private List<XWPFComment> comments = new ArrayList<>();
+ private List<XWPFPictureData> pictures = new ArrayList<>();
+ private CTComments ctComments;
+
+ /**
+ * Construct XWPFComments from a package part
+ *
+ * @param part the package part holding the data of the footnotes,
+ */
+ public XWPFComments(PackagePart part) {
+ super(part);
+ }
+
+ /**
+ * Construct XWPFComments from scratch for a new document.
+ */
+ public XWPFComments() {
+ ctComments = CTComments.Factory.newInstance();
+ }
+
+ /**
+ * read comments form an existing package
+ */
+ @Override
+ public void onDocumentRead() throws IOException {
+ try (InputStream is = getPackagePart().getInputStream()) {
+ CommentsDocument doc = CommentsDocument.Factory.parse(is, DEFAULT_XML_OPTIONS);
+ ctComments = doc.getComments();
+ for (CTComment ctComment : ctComments.getCommentList()) {
+ comments.add(new XWPFComment(ctComment, this));
+ }
+ } catch (XmlException e) {
+ throw new POIXMLException("Unable to read comments", e);
+ }
+
+ for (POIXMLDocumentPart poixmlDocumentPart : getRelations()) {
+ if (poixmlDocumentPart instanceof XWPFPictureData) {
+ XWPFPictureData xwpfPicData = (XWPFPictureData) poixmlDocumentPart;
+ pictures.add(xwpfPicData);
+ document.registerPackagePictureData(xwpfPicData);
+ }
+ }
+ }
+
+ /**
+ * Adds a picture to the comments.
+ *
+ * @param is The stream to read image from
+ * @param format The format of the picture.
+ * @return the index to this picture (0 based), the added picture can be
+ * obtained from {@link #getAllPictures()} .
+ * @throws InvalidFormatException If the format of the picture is not known.
+ * @throws IOException If reading the picture-data from the stream fails.
+ */
+ public String addPictureData(InputStream is, int format) throws InvalidFormatException, IOException {
+ byte[] data = IOUtils.toByteArray(is);
+ return addPictureData(data, format);
+ }
+
+ /**
+ * Adds a picture to the comments.
+ *
+ * @param pictureData The picture data
+ * @param format The format of the picture.
+ * @return the index to this picture (0 based), the added picture can be
+ * obtained from {@link #getAllPictures()} .
+ * @throws InvalidFormatException If the format of the picture is not known.
+ */
+ public String addPictureData(byte[] pictureData, int format) throws InvalidFormatException {
+ XWPFPictureData xwpfPicData = document.findPackagePictureData(pictureData, format);
+ POIXMLRelation relDesc = XWPFPictureData.RELATIONS[format];
+
+ if (xwpfPicData == null) {
+ /* Part doesn't exist, create a new one */
+ int idx = getXWPFDocument().getNextPicNameNumber(format);
+ xwpfPicData = (XWPFPictureData) createRelationship(relDesc, XWPFFactory.getInstance(), idx);
+ /* write bytes to new part */
+ PackagePart picDataPart = xwpfPicData.getPackagePart();
+ try (OutputStream out = picDataPart.getOutputStream()) {
+ out.write(pictureData);
+ } catch (IOException e) {
+ throw new POIXMLException(e);
+ }
+
+ document.registerPackagePictureData(xwpfPicData);
+ pictures.add(xwpfPicData);
+ return getRelationId(xwpfPicData);
+ } else if (!getRelations().contains(xwpfPicData)) {
+ /*
+ * Part already existed, but was not related so far. Create
+ * relationship to the already existing part and update
+ * POIXMLDocumentPart data.
+ */
+ // TODO add support for TargetMode.EXTERNAL relations.
+ RelationPart rp = addRelation(null, XWPFRelation.IMAGES, xwpfPicData);
+ pictures.add(xwpfPicData);
+ return rp.getRelationship().getId();
+ } else {
+ /* Part already existed, get relation id and return it */
+ return getRelationId(xwpfPicData);
+ }
+ }
+
+ /**
+ * save and commit 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();
+ ctComments.save(out, xmlOptions);
+ out.close();
+ }
+
+ public List<XWPFPictureData> getAllPictures() {
+ return Collections.unmodifiableList(pictures);
+ }
+
+ /**
+ * Gets the underlying CTComments object for the comments.
+ *
+ * @return CTComments object
+ */
+ public CTComments getCtComments() {
+ return ctComments;
+ }
+
+ /**
+ * set a new comments
+ */
+ @Internal
+ public void setCtComments(CTComments ctComments) {
+ this.ctComments = ctComments;
+ }
+
+ /**
+ * Get the list of {@link XWPFComment} in the Comments part.
+ *
+ * @return
+ */
+ public List<XWPFComment> getComments() {
+ return comments;
+ }
+
+ /**
+ * Get the specified comment by position
+ *
+ * @param pos Array position of the comment
+ * @return
+ */
+ public XWPFComment getComment(int pos) {
+ if (pos >= 0 && pos < ctComments.sizeOfCommentArray()) {
+ return getComments().get(pos);
+ }
+ return null;
+ }
+
+ /**
+ * Get the specified comment by comment id
+ *
+ * @param id comment id
+ * @return the specified comment
+ */
+ public XWPFComment getCommentByID(String id) {
+ for (XWPFComment comment : comments) {
+ if (comment.getId().equals(id)) {
+ return comment;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Get the specified comment by ctComment
+ *
+ * @param ctComment
+ * @return
+ */
+ public XWPFComment getComment(CTComment ctComment) {
+ for (int i = 0; i < comments.size(); i++) {
+ if (comments.get(i).getCtComment() == ctComment) {
+ return comments.get(i);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Create a new comment and add it to the document.
+ *
+ * @param cid comment Id
+ * @return
+ */
+ public XWPFComment createComment(BigInteger cid) {
+ CTComment ctComment = ctComments.addNewComment();
+ ctComment.setId(cid);
+ XWPFComment comment = new XWPFComment(ctComment, this);
+ comments.add(comment);
+ return comment;
+ }
+
+ /**
+ * Remove the specified comment if present.
+ *
+ * @param pos Array position of the comment to be removed
+ * @return True if the comment was removed.
+ */
+ public boolean removeComment(int pos) {
+ if (pos >= 0 && pos < ctComments.sizeOfCommentArray()) {
+ comments.remove(pos);
+ ctComments.removeComment(pos);
+ return true;
+ }
+ return false;
+ }
+
+ public XWPFDocument getXWPFDocument() {
+ if (null != document) {
+ return document;
+ }
+ return (XWPFDocument) getParent();
+ }
+
+ public void setXWPFDocument(XWPFDocument document) {
+ this.document = document;
+ }
+
+}
diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java
index e454365b4d..fcf58ed692 100644
--- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java
+++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java
@@ -85,7 +85,6 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody {
protected List<XWPFFooter> footers = new ArrayList<>();
protected List<XWPFHeader> headers = new ArrayList<>();
- protected List<XWPFComment> comments = new ArrayList<>();
protected List<XWPFHyperlink> hyperlinks = new ArrayList<>();
protected List<XWPFParagraph> paragraphs = new ArrayList<>();
protected List<XWPFTable> tables = new ArrayList<>();
@@ -99,6 +98,7 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody {
protected XWPFFootnotes footnotes;
private CTDocument1 ctDocument;
private XWPFSettings settings;
+ private XWPFComments comments;
protected final List<XWPFChart> charts = new ArrayList<>();
/**
* Keeps track on all id-values used in this document and included parts, like headers, footers, etc.
@@ -217,11 +217,8 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody {
headers.add(header);
header.onDocumentRead();
} else if (relation.equals(XWPFRelation.COMMENT.getRelation())) {
- // TODO Create according XWPFComment class, extending POIXMLDocumentPart
- CommentsDocument cmntdoc = CommentsDocument.Factory.parse(p.getPackagePart().getInputStream(), DEFAULT_XML_OPTIONS);
- for (CTComment ctcomment : cmntdoc.getComments().getCommentArray()) {
- comments.add(new XWPFComment(ctcomment, this));
- }
+ this.comments = (XWPFComments) p;
+ this.comments.onDocumentRead();
} else if (relation.equals(XWPFRelation.SETTINGS.getRelation())) {
settings = (XWPFSettings) p;
settings.onDocumentRead();
@@ -431,18 +428,27 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody {
return hyperlinks.toArray(new XWPFHyperlink[0]);
}
+ /**
+ * Get Comments
+ *
+ * @return comments
+ */
+ public XWPFComments getDocComments() {
+ return comments;
+ }
+
public XWPFComment getCommentByID(String id) {
- for (XWPFComment comment : comments) {
- if (comment.getId().equals(id)) {
- return comment;
- }
+ if (null == comments) {
+ return null;
}
-
- return null;
+ return comments.getCommentByID(id);
}
public XWPFComment[] getComments() {
- return comments.toArray(new XWPFComment[0]);
+ if (null == comments) {
+ return null;
+ }
+ return comments.getComments().toArray(new XWPFComment[0]);
}
/**
@@ -837,6 +843,27 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody {
paragraphs.add(p);
return p;
}
+
+
+ /**
+ * Creates an empty comments for the document if one does not already exist
+ *
+ * @return comments
+ */
+ public XWPFComments createComments() {
+ if (comments == null) {
+ CommentsDocument commentsDoc = CommentsDocument.Factory.newInstance();
+
+ XWPFRelation relation = XWPFRelation.COMMENT;
+ int i = getRelationIndex(relation);
+
+ XWPFComments wrapper = (XWPFComments) createRelationship(relation, XWPFFactory.getInstance(), i);
+ wrapper.setCtComments(commentsDoc.addNewComments());
+ wrapper.setXWPFDocument(getXWPFDocument());
+ comments = wrapper;
+ }
+ return comments;
+ }
/**
* Creates an empty numbering if one does not already exist and sets the numbering member
diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRelation.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRelation.java
index 7ba9e065b2..d67a8303b9 100644
--- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRelation.java
+++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRelation.java
@@ -137,9 +137,10 @@ public final class XWPFRelation extends POIXMLRelation {
null
);
public static final XWPFRelation COMMENT = new XWPFRelation(
- null,
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml",
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments",
- null
+ "/word/comments.xml",
+ XWPFComments::new, XWPFComments::new
);
public static final XWPFRelation FOOTNOTE = new XWPFRelation(
"application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml",
diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRun.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRun.java
index 06df99be12..ee741bace4 100644
--- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRun.java
+++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRun.java
@@ -1082,6 +1082,10 @@ public class XWPFRun implements ISDTContents, IRunElement, CharacterRun {
XWPFHeaderFooter headerFooter = (XWPFHeaderFooter) parent.getPart();
relationId = headerFooter.addPictureData(pictureData, pictureType);
picData = (XWPFPictureData) headerFooter.getRelationById(relationId);
+ } else if (parent.getPart() instanceof XWPFComments) {
+ XWPFComments comments = (XWPFComments) parent.getPart();
+ relationId = comments.addPictureData(pictureData, pictureType);
+ picData = (XWPFPictureData) comments.getRelationById(relationId);
} else {
@SuppressWarnings("resource")
XWPFDocument doc = parent.getDocument();
diff --git a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFComment.java b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFComment.java
index 5f0d8995da..f1ba77714a 100644
--- a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFComment.java
+++ b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFComment.java
@@ -16,14 +16,19 @@
==================================================================== */
package org.apache.poi.xwpf.usermodel;
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.xwpf.XWPFTestDataSamples;
import org.junit.jupiter.api.Test;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.math.BigInteger;
+import java.util.Calendar;
-import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.*;
class TestXWPFComment {
+
@Test
void testText() throws IOException {
try (XWPFDocument doc = XWPFTestDataSamples.openSampleDocument("comment.docx")) {
@@ -34,4 +39,83 @@ class TestXWPFComment {
assertEquals("This is the first line\n\nThis is the second line", comment.getText());
}
}
+
+ @Test
+ public void testAddComment() throws IOException {
+ BigInteger cId = BigInteger.valueOf(0);
+ Calendar date = Calendar.getInstance();
+ try (XWPFDocument docOut = new XWPFDocument()) {
+ assertNull(docOut.getDocComments());
+
+ XWPFComments comments = docOut.createComments();
+ XWPFComment comment = comments.createComment(cId);
+ comment.setAuthor("Author");
+ comment.setInitials("s");
+ comment.setDate(date);
+
+ XWPFDocument docIn = XWPFTestDataSamples.writeOutAndReadBack(docOut);
+ assertEquals(1, docIn.getComments().length);
+ comment = docIn.getCommentByID(cId.toString());
+ assertNotNull(comment);
+ assertEquals("Author", comment.getAuthor());
+ assertEquals("s", comment.getInitials());
+ assertEquals(date.getTimeInMillis(), comment.getDate().getTimeInMillis());
+ }
+ }
+
+ @Test
+ void testRemoveComment() throws IOException {
+ try (XWPFDocument doc = XWPFTestDataSamples.openSampleDocument("comment.docx")) {
+ assertEquals(1, doc.getComments().length);
+
+ doc.getDocComments().removeComment(0);
+
+ XWPFDocument docIn = XWPFTestDataSamples.writeOutAndReadBack(doc);
+ assertEquals(0, docIn.getComments().length);
+ }
+ }
+
+ @Test
+ void testCreateParagraph() throws IOException {
+ try (XWPFDocument doc = new XWPFDocument()) {
+ XWPFComments comments = doc.createComments();
+ XWPFComment comment = comments.createComment(BigInteger.ONE);
+ XWPFParagraph paragraph = comment.createParagraph();
+ paragraph.createRun().setText("comment paragraph text");
+
+ XWPFDocument docIn = XWPFTestDataSamples.writeOutAndReadBack(doc);
+ XWPFComment xwpfComment = docIn.getCommentByID("1");
+ assertEquals(1, xwpfComment.getParagraphs().size());
+ String text = xwpfComment.getParagraphArray(0).getText();
+ assertEquals("comment paragraph text", text);
+ }
+ }
+
+ @Test
+ void testAddPicture() throws IOException, InvalidFormatException {
+ try (XWPFDocument doc = new XWPFDocument()) {
+ XWPFComments comments = doc.createComments();
+ XWPFComment comment = comments.createComment(BigInteger.ONE);
+ XWPFParagraph paragraph = comment.createParagraph();
+ XWPFRun r = paragraph.createRun();
+ r.addPicture(new ByteArrayInputStream(new byte[0]),
+ Document.PICTURE_TYPE_JPEG, "test.jpg", 21, 32);
+
+ assertEquals(1, comments.getAllPictures().size());
+ assertEquals(1, doc.getAllPackagePictures().size());
+ }
+ }
+
+ @Test
+ void testCreateTable() throws IOException {
+ try (XWPFDocument doc = new XWPFDocument()) {
+ XWPFComments comments = doc.createComments();
+ XWPFComment comment = comments.createComment(BigInteger.ONE);
+ comment.createTable(1, 1);
+
+ XWPFDocument docIn = XWPFTestDataSamples.writeOutAndReadBack(doc);
+ XWPFComment xwpfComment = docIn.getCommentByID("1");
+ assertEquals(1, xwpfComment.getTables().size());
+ }
+ }
}
diff --git a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFComments.java b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFComments.java
new file mode 100644
index 0000000000..dad5ab877b
--- /dev/null
+++ b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFComments.java
@@ -0,0 +1,62 @@
+/* ====================================================================
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+==================================================================== */
+package org.apache.poi.xwpf.usermodel;
+
+import org.apache.poi.xwpf.XWPFTestDataSamples;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.math.BigInteger;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class TestXWPFComments {
+
+ @Test
+ void testAddCommentsToDoc() throws IOException {
+ BigInteger cId = BigInteger.ZERO;
+ try (XWPFDocument docOut = new XWPFDocument()) {
+ assertNull(docOut.getDocComments());
+
+ // create comments
+ XWPFComments comments = docOut.createComments();
+ assertNotNull(comments);
+ assertSame(comments, docOut.createComments());
+
+ // create comment
+ XWPFComment comment = comments.createComment(cId);
+ comment.setAuthor("Author");
+ comment.createParagraph().createRun().setText("comment paragraph");
+
+ // apply comment to run text
+ XWPFParagraph paragraph = docOut.createParagraph();
+ paragraph.getCTP().addNewCommentRangeStart().setId(cId);
+ paragraph.getCTP().addNewR().addNewT().setStringValue("HelloWorld");
+ paragraph.getCTP().addNewCommentRangeEnd().setId(cId);
+ paragraph.getCTP().addNewR().addNewCommentReference().setId(cId);
+
+ // check
+ XWPFDocument docIn = XWPFTestDataSamples.writeOutAndReadBack(docOut);
+ assertNotNull(docIn.getDocComments());
+ assertEquals(1, docIn.getComments().length);
+ comment = docIn.getCommentByID("0");
+ assertTrue(null != comment);
+ assertEquals("Author", comment.getAuthor());
+ }
+ }
+
+}