diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFChart.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFChart.java new file mode 100644 index 000000000..e7df6ec69 --- /dev/null +++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFChart.java @@ -0,0 +1,165 @@ +/* ==================================================================== + 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 static org.apache.poi.POIXMLTypeLoader.DEFAULT_XML_OPTIONS; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Arrays; + +import javax.xml.namespace.QName; + +import org.apache.poi.POIXMLDocumentPart; +import org.apache.poi.POIXMLException; +import org.apache.poi.openxml4j.opc.OPCPackage; +import org.apache.poi.openxml4j.opc.PackagePart; +import org.apache.poi.openxml4j.opc.PackageRelationship; +import org.apache.poi.util.Beta; +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.drawingml.x2006.chart.CTChart; +import org.openxmlformats.schemas.drawingml.x2006.chart.CTChartSpace; +import org.openxmlformats.schemas.drawingml.x2006.chart.ChartSpaceDocument; + +/** + * Represents a Chart in a .docx file + */ +@Beta +public class XWPFChart extends POIXMLDocumentPart { + + /** + * Root element of the Chart part + */ + private final CTChartSpace chartSpace; + + /** + * The Chart within that + */ + private final CTChart chart; + + // lazy initialization + private Long checksum; + + /** + * Construct a chart from a package part. + * + * @param part the package part holding the chart data, + * the content type must be application/vnd.openxmlformats-officedocument.drawingml.chart+xml + * + * @since POI 4.0.0 + */ + protected XWPFChart(PackagePart part) throws IOException, XmlException { + super(part); + + chartSpace = ChartSpaceDocument.Factory.parse(part.getInputStream(), DEFAULT_XML_OPTIONS).getChartSpace(); + chart = chartSpace.getChart(); + } + + @Override + protected void onDocumentRead() throws IOException { + super.onDocumentRead(); + } + + /** + * Return the underlying CTChartSpace bean, the root element of the Chart part. + * + * @return the underlying CTChartSpace bean + */ + @Internal + public CTChartSpace getCTChartSpace() { + return chartSpace; + } + + /** + * Return the underlying CTChart bean, within the Chart Space + * + * @return the underlying CTChart bean + */ + @Internal + public CTChart getCTChart() { + return chart; + } + + @Override + protected void commit() throws IOException { + XmlOptions xmlOptions = new XmlOptions(DEFAULT_XML_OPTIONS); + xmlOptions.setSaveSyntheticDocumentElement(new QName(CTChartSpace.type.getName().getNamespaceURI(), "chartSpace", "c")); + + try (OutputStream out = getPackagePart().getOutputStream()) { + chartSpace.save(out, xmlOptions); + } + } + + public Long getChecksum() { + if (this.checksum == null) { + InputStream is = null; + byte[] data; + try { + is = getPackagePart().getInputStream(); + data = IOUtils.toByteArray(is); + } catch (IOException e) { + throw new POIXMLException(e); + } finally { + try { + if (is != null) is.close(); + } catch (IOException e) { + throw new POIXMLException(e); + } + } + this.checksum = IOUtils.calculateChecksum(data); + } + return this.checksum; + } + + @Override + public boolean equals(Object obj) { + /** + * In case two objects ARE equal, but its not the same instance, this + * implementation will always run through the whole + * byte-array-comparison before returning true. If this will turn into a + * performance issue, two possible approaches are available:
+ * a) Use the checksum only and take the risk that two images might have + * the same CRC32 sum, although they are not the same.
+ * b) Use a second (or third) checksum algorithm to minimise the chance + * that two images have the same checksums but are not equal (e.g. + * CRC32, MD5 and SHA-1 checksums, additionally compare the + * data-byte-array lengths). + */ + if (obj == this) { + return true; + } + + if (obj == null) { + return false; + } + + if (!(obj instanceof XWPFChart)) { + return false; + } + return false; + } + + @Override + public int hashCode() { + return getChecksum().hashCode(); + } +} 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 37f377d8f..e63b98995 100644 --- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java +++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java @@ -96,6 +96,7 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody { protected XWPFFootnotes footnotes; private CTDocument1 ctDocument; private XWPFSettings settings; + protected final List charts = new ArrayList<>(); /** * Keeps track on all id-values used in this document and included parts, like headers, footers, etc. */ @@ -220,6 +221,11 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody { picData.onDocumentRead(); registerPackagePictureData(picData); pictures.add(picData); + } else if (relation.equals(XWPFRelation.CHART.getRelation())) { + //now we can use all methods to modify charts in XWPFDocument + XWPFChart chartData = (XWPFChart) p; + chartData.onDocumentRead(); + charts.add(chartData); } else if (relation.equals(XWPFRelation.GLOSSARY_DOCUMENT.getRelation())) { // We don't currently process the glossary itself // Until we do, we do need to load the glossary child parts of it @@ -323,6 +329,12 @@ public class XWPFDocument extends POIXMLDocument implements Document, IBody { return Collections.unmodifiableList(tables); } + /** + * @return list of XWPFCharts in this document + */ + public List getCharts() { + return Collections.unmodifiableList(charts); + } /** * @see org.apache.poi.xwpf.usermodel.IBody#getTableArray(int) */ 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 9f628845e..f8da0c504 100644 --- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRelation.java +++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRelation.java @@ -112,6 +112,12 @@ public final class XWPFRelation extends POIXMLRelation { "/word/theme/theme#.xml", null ); + public static final XWPFRelation CHART = new XWPFRelation( + "application/vnd.openxmlformats-officedocument.drawingml.chart+xml", + "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart", + "/word/charts/chart#.xml", + XWPFChart.class + ); public static final XWPFRelation HYPERLINK = new XWPFRelation( null, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink", diff --git a/src/ooxml/testcases/org/apache/poi/xwpf/AllXWPFTests.java b/src/ooxml/testcases/org/apache/poi/xwpf/AllXWPFTests.java index e3311ff2f..00683b6ce 100644 --- a/src/ooxml/testcases/org/apache/poi/xwpf/AllXWPFTests.java +++ b/src/ooxml/testcases/org/apache/poi/xwpf/AllXWPFTests.java @@ -19,6 +19,7 @@ package org.apache.poi.xwpf; import org.apache.poi.xwpf.extractor.TestXWPFWordExtractor; import org.apache.poi.xwpf.model.TestXWPFHeaderFooterPolicy; +import org.apache.poi.xwpf.usermodel.TestXWPFChart; import org.apache.poi.xwpf.usermodel.TestXWPFDocument; import org.apache.poi.xwpf.usermodel.TestXWPFHeader; import org.apache.poi.xwpf.usermodel.TestXWPFHeadings; @@ -38,6 +39,7 @@ import org.junit.runners.Suite; @Suite.SuiteClasses({ TestXWPFBugs.class, org.apache.poi.xwpf.usermodel.TestXWPFBugs.class, + TestXWPFChart.class, TestXWPFDocument.class, TestXWPFWordExtractor.class, TestXWPFHeaderFooterPolicy.class, @@ -53,4 +55,4 @@ import org.junit.runners.Suite; TestPackageCorePropertiesGetKeywords.class }) public final class AllXWPFTests { -} \ No newline at end of file +} diff --git a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFChart.java b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFChart.java new file mode 100644 index 000000000..e2afbf4ca --- /dev/null +++ b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFChart.java @@ -0,0 +1,87 @@ +/* ==================================================================== + 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 java.io.IOException; +import java.util.List; + +import org.apache.poi.xwpf.XWPFTestDataSamples; +import org.openxmlformats.schemas.drawingml.x2006.chart.CTChart; +import org.openxmlformats.schemas.drawingml.x2006.chart.CTTitle; +import org.openxmlformats.schemas.drawingml.x2006.chart.CTTx; +import org.openxmlformats.schemas.drawingml.x2006.main.CTRegularTextRun; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextBody; +import org.openxmlformats.schemas.drawingml.x2006.main.CTTextParagraph; + +import junit.framework.TestCase; + +public class TestXWPFChart extends TestCase { + + /** + * test method to check charts are null + * + * @throws IOException + */ + public void testRead() throws IOException + { + XWPFDocument sampleDoc = XWPFTestDataSamples.openSampleDocument("61745.docx"); + List charts = sampleDoc.getCharts(); + assertNotNull(charts); + assertEquals(2, charts.size()); + assertNotNull(charts.get(0)); + assertNotNull(charts.get(1)); + } + + /** + * test method to add chart title and check whether it's set + * + * @throws IOException + */ + public void testChartTitle() throws IOException + { + XWPFDocument sampleDoc = XWPFTestDataSamples.openSampleDocument("61745.docx"); + List charts = sampleDoc.getCharts(); + XWPFChart chart=charts.get(0); + CTChart ctChart = chart.getCTChart(); + CTTitle title = ctChart.getTitle(); + CTTx tx = title.addNewTx(); + CTTextBody rich = tx.addNewRich(); + rich.addNewBodyPr(); + rich.addNewLstStyle(); + CTTextParagraph p = rich.addNewP(); + CTRegularTextRun r = p.addNewR(); + r.addNewRPr(); + r.setT("XWPF CHART"); + assertEquals("XWPF CHART", chart.getCTChart().getTitle().getTx().getRich().getPArray(0).getRArray(0).getT().toString()); + } + /** + * test method to check relationship + * + * @throws IOException + */ + public void testChartRelation() throws IOException + { + XWPFDocument sampleDoc = XWPFTestDataSamples.openSampleDocument("61745.docx"); + List charts = sampleDoc.getCharts(); + XWPFChart chart=charts.get(0); + assertEquals(XWPFRelation.CHART.getContentType(), chart.getPackagePart().getContentType().toString()); + assertEquals("/word/document.xml", chart.getParent().getPackagePart().getPartName().toString()); + assertEquals("/word/charts/chart1.xml", chart.getPackagePart().getPartName().toString()); + } +} + diff --git a/test-data/document/61745.docx b/test-data/document/61745.docx new file mode 100644 index 000000000..d4e5bc827 Binary files /dev/null and b/test-data/document/61745.docx differ