aboutsummaryrefslogtreecommitdiffstats
path: root/poi-ooxml
diff options
context:
space:
mode:
authorPJ Fanning <fanningpj@apache.org>2022-10-18 11:49:34 +0000
committerPJ Fanning <fanningpj@apache.org>2022-10-18 11:49:34 +0000
commit4c3a0b4e93391047e723ba6ac3a5fd5c0a241d67 (patch)
tree706354f99714e7739c7076ee060aba9ffe36c9db /poi-ooxml
parentaa63b125d3e381fb3dd7ce0ebf9d859b00452271 (diff)
downloadpoi-4c3a0b4e93391047e723ba6ac3a5fd5c0a241d67.tar.gz
poi-4c3a0b4e93391047e723ba6ac3a5fd5c0a241d67.zip
[github-389] Insert paragraphs and tables into XWPFDocuments recursively. Thanks to Anton Oellerer. This closes #389
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1904680 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'poi-ooxml')
-rw-r--r--poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java215
-rw-r--r--poi-ooxml/src/test/java/org/apache/poi/xwpf/TestXWPFBugs.java37
2 files changed, 193 insertions, 59 deletions
diff --git a/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java b/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java
index fdfb3429a8..75b5cf06dd 100644
--- a/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java
+++ b/poi-ooxml/src/main/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java
@@ -27,15 +27,16 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
+import java.util.Deque;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Optional;
import java.util.Spliterator;
-
import javax.xml.namespace.QName;
-
import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -568,8 +569,8 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody {
private CTSectPr getSection() {
CTBody ctBody = getDocument().getBody();
return (ctBody.isSetSectPr() ?
- ctBody.getSectPr() :
- ctBody.addNewSectPr());
+ ctBody.getSectPr() :
+ ctBody.addNewSectPr());
}
/**
@@ -682,6 +683,7 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody {
*/
@Override
public XWPFParagraph insertNewParagraph(XmlCursor cursor) {
+ Deque<XmlObject> path = getPathToObject(cursor);
String uri = CTP.type.getName().getNamespaceURI();
/*
* TODO DO not use a coded constant, find the constant in the OOXML
@@ -696,34 +698,129 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody {
cursor.toParent();
CTP p = (CTP) cursor.getObject();
XWPFParagraph newP = new XWPFParagraph(p, this);
- XmlObject o = null;
- /*
- * move the cursor to the previous element until a) the next
- * paragraph is found or b) all elements have been passed
- */
- while (!(o instanceof CTP) && (cursor.toPrevSibling())) {
- o = cursor.getObject();
+ insertIntoParentElement(newP, path);
+ cursor.toCursor(newP.getCTP().newCursor());
+ cursor.toEndToken();
+ return newP;
+ }
+
+ @Override
+ public XWPFTable insertNewTbl(XmlCursor cursor) {
+ Deque<XmlObject> path = getPathToObject(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);
+ insertIntoParentElement(newT, path);
+ cursor.toCursor(newT.getCTTbl().newCursor());
+ cursor.toEndToken();
+ return newT;
+ }
+
+ private Deque<XmlObject> getPathToObject(XmlCursor cursor) {
+ Deque<XmlObject> searchPath = new LinkedList<>();
+ try (XmlCursor verify = cursor.newCursor()) {
+ while (verify.toParent() && searchPath.peekFirst() != this.ctDocument.getBody()) {
+ searchPath.addFirst(verify.getObject());
+ }
}
- /*
- * if the object that has been found is a) not a paragraph or b) is
- * the paragraph that has just been inserted, as the cursor in the
- * while loop above was not moved as there were no other siblings,
- * then the paragraph that was just inserted is the first paragraph
- * in the body. Otherwise, take the previous paragraph and calculate
- * the new index for the new paragraph.
- */
- if ((!(o instanceof CTP)) || o == p) {
- paragraphs.add(0, newP);
+ return searchPath;
+ }
+
+ private void insertIntoParentElement(IBodyElement iBodyElement, Deque<XmlObject> path) {
+ XmlObject firstObject = path.pop();
+ if (path.isEmpty()) {
+ if (iBodyElement instanceof XWPFParagraph) {
+ insertIntoParagraphsAndElements((XWPFParagraph) iBodyElement, paragraphs, bodyElements);
+ } else if (iBodyElement instanceof XWPFTable) {
+ insertIntoTablesAndElements((XWPFTable) iBodyElement, tables, bodyElements);
+ }
} else {
- int pos = paragraphs.indexOf(getParagraph((CTP) o)) + 1;
- paragraphs.add(pos, newP);
+ CTTbl ctTbl = (CTTbl) path.pop(); //first object is always the body, we want the second one
+ for (XWPFTable xwpfTable : tables) {
+ if (ctTbl == xwpfTable.getCTTbl()) {
+ insertElementIntoTable(xwpfTable, iBodyElement, path);
+ }
+ }
+ }
+ }
+
+ private void insertIntoParagraphsAndElements(XWPFParagraph newP, List<XWPFParagraph> paragraphs, List<IBodyElement> bodyElements) {
+ insertIntoParagraphs(newP, paragraphs);
+ insertIntoBodyElements(newP, bodyElements);
+ }
+
+ private void insertIntoParagraphs(XWPFParagraph newP, List<XWPFParagraph> paragraphs) {
+ try (XmlCursor cursor = newP.getCTP().newCursor()) {
+ XmlObject p = cursor.getObject();
+ XmlObject o = null;
+ /*
+ * move the cursor to the previous element until a) the next
+ * paragraph is found or b) all elements have been passed
+ */
+ while (!(o instanceof CTP) && (cursor.toPrevSibling())) {
+ o = cursor.getObject();
+ }
+ /*
+ * if the object that has been found is a) not a paragraph or b) is
+ * the paragraph that has just been inserted, as the cursor in the
+ * while loop above was not moved as there were no other siblings,
+ * then the paragraph that was just inserted is the first paragraph
+ * in the body. Otherwise, take the previous paragraph and calculate
+ * the new index for the new paragraph.
+ */
+ if ((!(o instanceof CTP)) || o == p) {
+ paragraphs.add(0, newP);
+ } else {
+ int pos = paragraphs.indexOf(getParagraph((CTP) o)) + 1;
+ paragraphs.add(pos, newP);
+ }
}
+ }
+
+ private void insertIntoTablesAndElements(XWPFTable newT, List<XWPFTable> tables, List<IBodyElement> bodyElements) {
+ insertIntoTables(newT, tables);
+ insertIntoBodyElements(newT, bodyElements);
+ }
+ private void insertIntoTables(XWPFTable newT, List<XWPFTable> tables) {
+ try (XmlCursor cursor = newT.getCTTbl().newCursor()) {
+ XmlObject p = cursor.getObject();
+ XmlObject o = null;
+ /*
+ * move the cursor to the previous element until a) the next
+ * paragraph is found or b) all elements have been passed
+ */
+ while (!(o instanceof CTTbl) && (cursor.toPrevSibling())) {
+ o = cursor.getObject();
+ }
+ /*
+ * if the object that has been found is a) not a paragraph or b) is
+ * the paragraph that has just been inserted, as the cursor in the
+ * while loop above was not moved as there were no other siblings,
+ * then the paragraph that was just inserted is the first paragraph
+ * in the body. Otherwise, take the previous paragraph and calculate
+ * the new index for the new paragraph.
+ */
+ if (!(o instanceof CTTbl)) {
+ tables.add(0, newT);
+ } else {
+ int pos = tables.indexOf(getTable((CTTbl) o)) + 1;
+ tables.add(pos, newT);
+ }
+ }
+ }
+
+ private void insertIntoBodyElements(IBodyElement iBodyElement, List<IBodyElement> bodyElements) {
/*
* create a new cursor, that points to the START token of the just
* inserted paragraph
*/
- try (XmlCursor newParaPos = p.newCursor()) {
+ try (XmlCursor cursor = getNewCursor(iBodyElement).orElseThrow(NoSuchElementException::new);
+ XmlCursor newParaPos = getNewCursor(iBodyElement).orElseThrow(NoSuchElementException::new)) {
+ XmlObject o;
/*
* Calculate the paragraphs index in the list of all body
* elements
@@ -736,44 +833,52 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody {
i++;
}
}
- bodyElements.add(i, newP);
+ bodyElements.add(i, iBodyElement);
cursor.toCursor(newParaPos);
cursor.toEndToken();
- return newP;
+ } catch (NoSuchElementException ignored) {
+ //We could not open a cursor to the ibody element
}
}
- @Override
- public XWPFTable insertNewTbl(XmlCursor 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);
- XmlObject o = null;
- while (!(o instanceof CTTbl) && (cursor.toPrevSibling())) {
- o = cursor.getObject();
+ private Optional<XmlCursor> getNewCursor(IBodyElement iBodyElement) {
+ if (iBodyElement instanceof XWPFParagraph) {
+ return Optional.ofNullable(((XWPFParagraph) iBodyElement).getCTP().newCursor());
+ } else if (iBodyElement instanceof XWPFTable) {
+ return Optional.ofNullable(((XWPFTable) iBodyElement).getCTTbl().newCursor());
}
- if (!(o instanceof CTTbl)) {
- tables.add(0, newT);
- } else {
- int pos = tables.indexOf(getTable((CTTbl) o)) + 1;
- tables.add(pos, newT);
+ return Optional.empty();
+ }
+
+
+ private void insertElementIntoTable(XWPFTable xwpfTable, IBodyElement iBodyElement, Deque<XmlObject> path) {
+ CTRow row = (CTRow) path.pop();
+ for (XWPFTableRow tableRow : xwpfTable.getRows()) {
+ if (tableRow.getCtRow() == row) {
+ insertElementIntoRow(tableRow, iBodyElement, path);
+ }
}
- int i = 0;
- try (XmlCursor tableCursor = t.newCursor()) {
- cursor.toCursor(tableCursor);
- while (cursor.toPrevSibling()) {
- o = cursor.getObject();
- if (o instanceof CTP || o instanceof CTTbl) {
- i++;
- }
+ }
+
+ private void insertElementIntoRow(XWPFTableRow tableRow, IBodyElement iBodyElement, Deque<XmlObject> path) {
+ CTTc cell = (CTTc) path.pop();
+ for (XWPFTableCell tableCell : tableRow.getTableCells()) {
+ if (tableCell.getCTTc() == cell) {
+ insertElementIntoCell(tableCell, iBodyElement, path);
}
- bodyElements.add(i, newT);
- cursor.toCursor(tableCursor);
- cursor.toEndToken();
- return newT;
+ }
+ }
+
+ private void insertElementIntoCell(XWPFTableCell tableCell, IBodyElement iBodyElement, Deque<XmlObject> path) {
+ if (path.isEmpty()) {
+ if (iBodyElement instanceof XWPFParagraph) {
+ insertIntoParagraphsAndElements((XWPFParagraph) iBodyElement, tableCell.paragraphs, tableCell.bodyElements);
+ } else if (iBodyElement instanceof XWPFTable) {
+ insertIntoTablesAndElements((XWPFTable) iBodyElement, tableCell.tables, tableCell.bodyElements);
+ }
+ } else {
+ // another table
+ insertElementIntoTable((XWPFTable) path.pop(), iBodyElement, path);
}
}
@@ -1779,7 +1884,7 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody {
//create relationship in document for new chart
RelationPart rp = createRelationship(
- XWPFRelation.CHART, XWPFFactory.getInstance(), chartNumber, false);
+ XWPFRelation.CHART, XWPFFactory.getInstance(), chartNumber, false);
// initialize xwpfchart object
XWPFChart xwpfChart = rp.getDocumentPart();
diff --git a/poi-ooxml/src/test/java/org/apache/poi/xwpf/TestXWPFBugs.java b/poi-ooxml/src/test/java/org/apache/poi/xwpf/TestXWPFBugs.java
index 2de05f25d8..ecf9d13601 100644
--- a/poi-ooxml/src/test/java/org/apache/poi/xwpf/TestXWPFBugs.java
+++ b/poi-ooxml/src/test/java/org/apache/poi/xwpf/TestXWPFBugs.java
@@ -44,6 +44,7 @@ import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.xwpf.extractor.XWPFWordExtractor;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
+import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlException;
@@ -58,7 +59,7 @@ class TestXWPFBugs {
//started failing after uptake of commons-compress 1.21
assertThrows(IOException.class, () -> {
try (InputStream fis = samples.openResourceAsStream("truncated62886.docx");
- OPCPackage opc = OPCPackage.open(fis)) {
+ OPCPackage opc = OPCPackage.open(fis)) {
assertNotNull(opc);
//XWPFWordExtractor ext = new XWPFWordExtractor(opc)) {
//assertNotNull(ext.getText());
@@ -175,8 +176,8 @@ class TestXWPFBugs {
PackagePart part = document.getPackage().getPart(partName);
assertNotNull(part);
try (
- InputStream partStream = part.getInputStream();
- POIFSFileSystem poifs = new POIFSFileSystem(partStream)
+ InputStream partStream = part.getInputStream();
+ POIFSFileSystem poifs = new POIFSFileSystem(partStream)
) {
Ole10Native ole = Ole10Native.createFromEmbeddedOleObject(poifs);
String fn = "C:\\Users\\ross\\AppData\\Local\\Microsoft\\Windows\\INetCache\\Content.Word\\約翰的測試文件\uD83D\uDD96.msg";
@@ -197,19 +198,47 @@ class TestXWPFBugs {
}
@Test
+ void insertTableDirectlyIntoBody() throws IOException {
+ try (XWPFDocument document = new XWPFDocument(samples.openResourceAsStream("bug66312.docx"))) {
+ XWPFParagraph paragraph = document.getParagraphArray(0);
+ insertTable(paragraph, document);
+ assertEquals("Hello", document.getTableArray(0).getRow(0).getCell(0).getText());
+ assertEquals("World", document.getParagraphArray(0).getText());
+ }
+ }
+
+ @Test
void insertParagraphIntoTable() throws IOException {
try (XWPFDocument document = new XWPFDocument(samples.openResourceAsStream("bug66312.docx"))) {
XWPFTableCell cell = document.getTableArray(0).getRow(0).getCell(0);
XWPFParagraph paragraph = cell.getParagraphArray(0);
insertParagraph(paragraph, document);
- //TODO the issue reporter thinks that there should be 2 paragraphs (with 'Hello' and 'World' repectively).
+ assertEquals("Hello", cell.getParagraphArray(0).getText());
+ assertEquals("World", cell.getParagraphArray(1).getText());
+ }
+ }
+
+ @Test
+ void insertTableIntoTable() throws IOException {
+ try (XWPFDocument document = new XWPFDocument(samples.openResourceAsStream("bug66312.docx"))) {
+ XWPFTableCell cell = document.getTableArray(0).getRow(0).getCell(0);
+ XWPFParagraph paragraph = cell.getParagraphArray(0);
+ insertTable(paragraph, document);
+ assertEquals("Hello", cell.getTableArray(0).getRow(0).getCell(0).getText());
assertEquals("World", cell.getParagraphArray(0).getText());
}
}
+
public static void insertParagraph(XWPFParagraph xwpfParagraph, XWPFDocument document) {
XmlCursor xmlCursor = xwpfParagraph.getCTP().newCursor();
XWPFParagraph xwpfParagraph2 = document.insertNewParagraph(xmlCursor);
xwpfParagraph2.createRun().setText("Hello");
}
+
+ public static void insertTable(XWPFParagraph xwpfParagraph, XWPFDocument document) {
+ XmlCursor xmlCursor = xwpfParagraph.getCTP().newCursor();
+ XWPFTable xwpfTable = document.insertNewTbl(xmlCursor);
+ xwpfTable.getRow(0).getCell(0).setText("Hello");
+ }
}