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