diff options
author | Sayi <sayi@apache.org> | 2021-03-20 16:21:41 +0000 |
---|---|---|
committer | Sayi <sayi@apache.org> | 2021-03-20 16:21:41 +0000 |
commit | 0efa53456a11291cc4bdd1d93f135bb065db61cd (patch) | |
tree | 4fba57857c43070fd68bb4960c11592b8ad866d1 /src | |
parent | 6167f3416fefc30997e1c8742d04ae83dbb09338 (diff) | |
download | poi-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')
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()); + } + } + +} |