aboutsummaryrefslogtreecommitdiffstats
path: root/src/java/org/apache
diff options
context:
space:
mode:
authorAndreas Beeker <kiwiwings@apache.org>2018-05-27 21:59:18 +0000
committerAndreas Beeker <kiwiwings@apache.org>2018-05-27 21:59:18 +0000
commit6ce329eca43ad902a132cf86e7b9853f652fc3c1 (patch)
tree858df04c39e1f14f6ed6e863c4bd366f07529eb4 /src/java/org/apache
parent11457e8f684474e1583fc59477abe52452e66f65 (diff)
downloadpoi-6ce329eca43ad902a132cf86e7b9853f652fc3c1.tar.gz
poi-6ce329eca43ad902a132cf86e7b9853f652fc3c1.zip
#62355 - unsplit packages - 1 - moved classes
git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1832358 13f79535-47bb-0310-9956-ffa450edef68
Diffstat (limited to 'src/java/org/apache')
-rw-r--r--src/java/org/apache/poi/POIReadOnlyDocument.java75
-rw-r--r--src/java/org/apache/poi/dev/RecordGenerator.java160
-rw-r--r--src/java/org/apache/poi/extractor/POIOLE2TextExtractor.java (renamed from src/java/org/apache/poi/POIOLE2TextExtractor.java)3
-rw-r--r--src/java/org/apache/poi/extractor/POITextExtractor.java (renamed from src/java/org/apache/poi/POITextExtractor.java)2
-rw-r--r--src/java/org/apache/poi/hssf/usermodel/HSSFChart.java1371
-rw-r--r--src/java/org/apache/poi/ss/extractor/EmbeddedData.java104
-rw-r--r--src/java/org/apache/poi/ss/extractor/EmbeddedExtractor.java405
-rw-r--r--src/java/org/apache/poi/ss/usermodel/WorkbookFactory.java329
8 files changed, 2287 insertions, 162 deletions
diff --git a/src/java/org/apache/poi/POIReadOnlyDocument.java b/src/java/org/apache/poi/POIReadOnlyDocument.java
new file mode 100644
index 0000000000..3b3eca588d
--- /dev/null
+++ b/src/java/org/apache/poi/POIReadOnlyDocument.java
@@ -0,0 +1,75 @@
+/* ====================================================================
+ 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;
+
+import java.io.File;
+import java.io.OutputStream;
+
+import org.apache.poi.poifs.filesystem.DirectoryNode;
+import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
+import org.apache.poi.poifs.filesystem.OPOIFSFileSystem;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+
+
+/**
+ * This holds the common functionality for all read-only
+ * POI Document classes, i.e. ones which don't support writing.
+ *
+ * @since POI 3.15 beta 3
+ */
+public abstract class POIReadOnlyDocument extends POIDocument {
+ public POIReadOnlyDocument(DirectoryNode dir) {
+ super(dir);
+ }
+ public POIReadOnlyDocument(NPOIFSFileSystem fs) {
+ super(fs);
+ }
+ public POIReadOnlyDocument(OPOIFSFileSystem fs) {
+ super(fs);
+ }
+ public POIReadOnlyDocument(POIFSFileSystem fs) {
+ super(fs);
+ }
+
+ /**
+ * Note - writing is not yet supported for this file format, sorry.
+ *
+ * @throws IllegalStateException If you call the method, as writing is not supported
+ */
+ @Override
+ public void write() {
+ throw new IllegalStateException("Writing is not yet implemented for this Document Format");
+ }
+ /**
+ * Note - writing is not yet supported for this file format, sorry.
+ *
+ * @throws IllegalStateException If you call the method, as writing is not supported
+ */
+ @Override
+ public void write(File file) {
+ throw new IllegalStateException("Writing is not yet implemented for this Document Format");
+ }
+ /**
+ * Note - writing is not yet supported for this file format, sorry.
+ *
+ * @throws IllegalStateException If you call the method, as writing is not supported
+ */
+ @Override
+ public void write(OutputStream out) {
+ throw new IllegalStateException("Writing is not yet implemented for this Document Format");
+ }
+}
diff --git a/src/java/org/apache/poi/dev/RecordGenerator.java b/src/java/org/apache/poi/dev/RecordGenerator.java
deleted file mode 100644
index 585003c526..0000000000
--- a/src/java/org/apache/poi/dev/RecordGenerator.java
+++ /dev/null
@@ -1,160 +0,0 @@
-
-/* ====================================================================
- 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.dev;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.util.Locale;
-import java.util.Properties;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-import javax.xml.transform.OutputKeys;
-import javax.xml.transform.Result;
-import javax.xml.transform.Transformer;
-import javax.xml.transform.TransformerException;
-import javax.xml.transform.TransformerFactory;
-import javax.xml.transform.stream.StreamResult;
-import javax.xml.transform.stream.StreamSource;
-
-import org.apache.poi.util.XMLHelper;
-import org.w3c.dom.Document;
-import org.w3c.dom.Element;
-
-/**
- * Description of the Class
- *
- *@author andy
- *@since May 10, 2002
- */
-public class RecordGenerator {
- /**
- * The main program for the RecordGenerator class
- *
- *@param args The command line arguments
- *@exception Exception Description of the Exception
- */
- public static void main(String[] args)
- throws Exception {
- // Force load so that we don't start generating records and realise this hasn't compiled yet.
- Class.forName("org.apache.poi.generator.FieldIterator");
-
- if (args.length != 4) {
- System.out.println("Usage:");
- System.out.println(" java org.apache.poi.hssf.util.RecordGenerator RECORD_DEFINTIONS RECORD_STYLES DEST_SRC_PATH TEST_SRC_PATH");
- } else {
- generateRecords(args[0], args[1], args[2], args[3]);
- }
- }
-
-
- private static void generateRecords(String defintionsDir, String recordStyleDir, String destSrcPathDir, String testSrcPathDir)
- throws Exception {
- File definitionsFiles[] = new File(defintionsDir).listFiles();
- if (definitionsFiles == null) {
- System.err.println(defintionsDir+" is not a directory.");
- return;
- }
-
- for (File file : definitionsFiles) {
- if (file.isFile() &&
- (file.getName().endsWith("_record.xml") ||
- file.getName().endsWith("_type.xml")
- )
- ) {
- // Get record name and package
- DocumentBuilderFactory factory = XMLHelper.getDocumentBuilderFactory();
- DocumentBuilder builder = factory.newDocumentBuilder();
- Document document = builder.parse(file);
- Element record = document.getDocumentElement();
- String extendstg = record.getElementsByTagName("extends").item(0).getFirstChild().getNodeValue();
- String suffix = record.getElementsByTagName("suffix").item(0).getFirstChild().getNodeValue();
- String recordName = record.getAttributes().getNamedItem("name").getNodeValue();
- String packageName = record.getAttributes().getNamedItem("package").getNodeValue();
- packageName = packageName.replace('.', '/');
-
- // Generate record
- String destinationPath = destSrcPathDir + "/" + packageName;
- File destinationPathFile = new File(destinationPath);
- if(!destinationPathFile.mkdirs()) {
- throw new IOException("Could not create directory " + destinationPathFile);
- } else {
- System.out.println("Created destination directory: " + destinationPath);
- }
- String destinationFilepath = destinationPath + "/" + recordName + suffix + ".java";
- transform(file, new File(destinationFilepath),
- new File(recordStyleDir + "/" + extendstg.toLowerCase(Locale.ROOT) + ".xsl"));
- System.out.println("Generated " + suffix + ": " + destinationFilepath);
-
- // Generate test (if not already generated)
- destinationPath = testSrcPathDir + "/" + packageName;
- destinationPathFile = new File(destinationPath);
- if(!destinationPathFile.mkdirs()) {
- throw new IOException("Could not create directory " + destinationPathFile);
- } else {
- System.out.println("Created destination directory: " + destinationPath);
- }
- destinationFilepath = destinationPath + "/Test" + recordName + suffix + ".java";
- if (!new File(destinationFilepath).exists()) {
- String temp = (recordStyleDir + "/" + extendstg.toLowerCase(Locale.ROOT) + "_test.xsl");
- transform(file, new File(destinationFilepath), new File(temp));
- System.out.println("Generated test: " + destinationFilepath);
- } else {
- System.out.println("Skipped test generation: " + destinationFilepath);
- }
- }
- }
- }
-
-
-
- /**
- * <p>Executes an XSL transformation. This process transforms an XML input
- * file into a text output file controlled by an XSLT specification.</p>
- *
- * @param in the XML input file
- * @param out the text output file
- * @param xslt the XSLT specification, i.e. an XSL style sheet
- * @throws FileNotFoundException
- * @throws TransformerException
- */
- private static void transform(final File in, final File out, final File xslt)
- throws FileNotFoundException, TransformerException
- {
- final StreamSource ss = new StreamSource(xslt);
- final TransformerFactory tf = TransformerFactory.newInstance();
- final Transformer t;
- try
- {
- t = tf.newTransformer(ss);
- }
- catch (TransformerException ex)
- {
- System.err.println("Error compiling XSL style sheet " + xslt);
- throw ex;
- }
- final Properties p = new Properties();
- p.setProperty(OutputKeys.METHOD, "text");
- t.setOutputProperties(p);
- final Result result = new StreamResult(out);
- t.transform(new StreamSource(in), result);
- }
-
-}
diff --git a/src/java/org/apache/poi/POIOLE2TextExtractor.java b/src/java/org/apache/poi/extractor/POIOLE2TextExtractor.java
index 0fccf71c4a..465de40375 100644
--- a/src/java/org/apache/poi/POIOLE2TextExtractor.java
+++ b/src/java/org/apache/poi/extractor/POIOLE2TextExtractor.java
@@ -14,8 +14,9 @@
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
-package org.apache.poi;
+package org.apache.poi.extractor;
+import org.apache.poi.POIDocument;
import org.apache.poi.hpsf.DocumentSummaryInformation;
import org.apache.poi.hpsf.SummaryInformation;
import org.apache.poi.hpsf.extractor.HPSFPropertiesExtractor;
diff --git a/src/java/org/apache/poi/POITextExtractor.java b/src/java/org/apache/poi/extractor/POITextExtractor.java
index 55d0832f16..e32adcb12c 100644
--- a/src/java/org/apache/poi/POITextExtractor.java
+++ b/src/java/org/apache/poi/extractor/POITextExtractor.java
@@ -14,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
-package org.apache.poi;
+package org.apache.poi.extractor;
import java.io.Closeable;
import java.io.IOException;
diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFChart.java b/src/java/org/apache/poi/hssf/usermodel/HSSFChart.java
new file mode 100644
index 0000000000..f91781dc64
--- /dev/null
+++ b/src/java/org/apache/poi/hssf/usermodel/HSSFChart.java
@@ -0,0 +1,1371 @@
+/* ====================================================================
+ 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.hssf.usermodel;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.poi.hssf.record.BOFRecord;
+import org.apache.poi.hssf.record.DimensionsRecord;
+import org.apache.poi.hssf.record.EOFRecord;
+import org.apache.poi.hssf.record.FooterRecord;
+import org.apache.poi.hssf.record.HCenterRecord;
+import org.apache.poi.hssf.record.HeaderRecord;
+import org.apache.poi.hssf.record.PrintSetupRecord;
+import org.apache.poi.hssf.record.ProtectRecord;
+import org.apache.poi.hssf.record.Record;
+import org.apache.poi.hssf.record.RecordBase;
+import org.apache.poi.hssf.record.SCLRecord;
+import org.apache.poi.hssf.record.UnknownRecord;
+import org.apache.poi.hssf.record.VCenterRecord;
+import org.apache.poi.hssf.record.chart.AreaFormatRecord;
+import org.apache.poi.hssf.record.chart.AxisLineFormatRecord;
+import org.apache.poi.hssf.record.chart.AxisOptionsRecord;
+import org.apache.poi.hssf.record.chart.AxisParentRecord;
+import org.apache.poi.hssf.record.chart.AxisRecord;
+import org.apache.poi.hssf.record.chart.AxisUsedRecord;
+import org.apache.poi.hssf.record.chart.BarRecord;
+import org.apache.poi.hssf.record.chart.BeginRecord;
+import org.apache.poi.hssf.record.chart.CategorySeriesAxisRecord;
+import org.apache.poi.hssf.record.chart.ChartFormatRecord;
+import org.apache.poi.hssf.record.chart.ChartRecord;
+import org.apache.poi.hssf.record.chart.ChartTitleFormatRecord;
+import org.apache.poi.hssf.record.chart.DataFormatRecord;
+import org.apache.poi.hssf.record.chart.DefaultDataLabelTextPropertiesRecord;
+import org.apache.poi.hssf.record.chart.EndRecord;
+import org.apache.poi.hssf.record.chart.FontBasisRecord;
+import org.apache.poi.hssf.record.chart.FontIndexRecord;
+import org.apache.poi.hssf.record.chart.FrameRecord;
+import org.apache.poi.hssf.record.chart.LegendRecord;
+import org.apache.poi.hssf.record.chart.LineFormatRecord;
+import org.apache.poi.hssf.record.chart.LinkedDataRecord;
+import org.apache.poi.hssf.record.chart.PlotAreaRecord;
+import org.apache.poi.hssf.record.chart.PlotGrowthRecord;
+import org.apache.poi.hssf.record.chart.SeriesIndexRecord;
+import org.apache.poi.hssf.record.chart.SeriesRecord;
+import org.apache.poi.hssf.record.chart.SeriesTextRecord;
+import org.apache.poi.hssf.record.chart.SeriesToChartGroupRecord;
+import org.apache.poi.hssf.record.chart.SheetPropertiesRecord;
+import org.apache.poi.hssf.record.chart.TextRecord;
+import org.apache.poi.hssf.record.chart.TickRecord;
+import org.apache.poi.hssf.record.chart.UnitsRecord;
+import org.apache.poi.hssf.record.chart.ValueRangeRecord;
+import org.apache.poi.ss.formula.ptg.Area3DPtg;
+import org.apache.poi.ss.formula.ptg.AreaPtgBase;
+import org.apache.poi.ss.formula.ptg.Ptg;
+import org.apache.poi.ss.util.CellRangeAddress;
+import org.apache.poi.ss.util.CellRangeAddressBase;
+
+/**
+ * Has methods for construction of a chart object.
+ *
+ * @author Glen Stampoultzis (glens at apache.org)
+ */
+public final class HSSFChart {
+ private HSSFSheet sheet;
+ private ChartRecord chartRecord;
+
+ private LegendRecord legendRecord;
+ @SuppressWarnings("unused")
+ private ChartTitleFormatRecord chartTitleFormat;
+ private SeriesTextRecord chartTitleText;
+ private List<ValueRangeRecord> valueRanges = new ArrayList<>();
+
+ private HSSFChartType type = HSSFChartType.Unknown;
+
+ private List<HSSFSeries> series = new ArrayList<>();
+
+ public enum HSSFChartType {
+ Area {
+ @Override
+ public short getSid() {
+ return 0x101A;
+ }
+ },
+ Bar {
+ @Override
+ public short getSid() {
+ return 0x1017;
+ }
+ },
+ Line {
+ @Override
+ public short getSid() {
+ return 0x1018;
+ }
+ },
+ Pie {
+ @Override
+ public short getSid() {
+ return 0x1019;
+ }
+ },
+ Scatter {
+ @Override
+ public short getSid() {
+ return 0x101B;
+ }
+ },
+ Unknown {
+ @Override
+ public short getSid() {
+ return 0;
+ }
+ };
+
+ public abstract short getSid();
+ }
+
+ private HSSFChart(HSSFSheet sheet, ChartRecord chartRecord) {
+ this.chartRecord = chartRecord;
+ this.sheet = sheet;
+ }
+
+ /**
+ * Creates a bar chart. API needs some work. :)
+ * <p>
+ * NOTE: Does not yet work... checking it in just so others
+ * can take a look.
+ */
+ public void createBarChart( HSSFWorkbook workbook, HSSFSheet parentSheet )
+ {
+
+ List<Record> records = new ArrayList<>();
+ records.add( createMSDrawingObjectRecord() );
+ records.add( createOBJRecord() );
+ records.add( createBOFRecord() );
+ records.add(new HeaderRecord(""));
+ records.add(new FooterRecord(""));
+ records.add( createHCenterRecord() );
+ records.add( createVCenterRecord() );
+ records.add( createPrintSetupRecord() );
+ // unknown 33
+ records.add( createFontBasisRecord1() );
+ records.add( createFontBasisRecord2() );
+ records.add(new ProtectRecord(false));
+ records.add( createUnitsRecord() );
+ records.add( createChartRecord( 0, 0, 30434904, 19031616 ) );
+ records.add( createBeginRecord() );
+ records.add( createSCLRecord( (short) 1, (short) 1 ) );
+ records.add( createPlotGrowthRecord( 65536, 65536 ) );
+ records.add( createFrameRecord1() );
+ records.add( createBeginRecord() );
+ records.add( createLineFormatRecord(true) );
+ records.add( createAreaFormatRecord1() );
+ records.add( createEndRecord() );
+ records.add( createSeriesRecord() );
+ records.add( createBeginRecord() );
+ records.add( createTitleLinkedDataRecord() );
+ records.add( createValuesLinkedDataRecord() );
+ records.add( createCategoriesLinkedDataRecord() );
+ records.add( createDataFormatRecord() );
+ // records.add(createBeginRecord());
+ // unknown
+ // records.add(createEndRecord());
+ records.add( createSeriesToChartGroupRecord() );
+ records.add( createEndRecord() );
+ records.add( createSheetPropsRecord() );
+ records.add( createDefaultTextRecord( DefaultDataLabelTextPropertiesRecord.CATEGORY_DATA_TYPE_ALL_TEXT_CHARACTERISTIC ) );
+ records.add( createAllTextRecord() );
+ records.add( createBeginRecord() );
+ // unknown
+ records.add( createFontIndexRecord( 5 ) );
+ records.add( createDirectLinkRecord() );
+ records.add( createEndRecord() );
+ records.add( createDefaultTextRecord( (short) 3 ) ); // eek, undocumented text type
+ records.add( createUnknownTextRecord() );
+ records.add( createBeginRecord() );
+ records.add( createFontIndexRecord( (short) 6 ) );
+ records.add( createDirectLinkRecord() );
+ records.add( createEndRecord() );
+
+ records.add( createAxisUsedRecord( (short) 1 ) );
+ createAxisRecords( records );
+
+ records.add( createEndRecord() );
+ records.add( createDimensionsRecord() );
+ records.add( createSeriesIndexRecord(2) );
+ records.add( createSeriesIndexRecord(1) );
+ records.add( createSeriesIndexRecord(3) );
+ records.add(EOFRecord.instance);
+
+
+
+ parentSheet.insertChartRecords( records );
+ workbook.insertChartRecord();
+ }
+
+ /**
+ * Returns all the charts for the given sheet.
+ *
+ * NOTE: You won't be able to do very much with
+ * these charts yet, as this is very limited support
+ */
+ public static HSSFChart[] getSheetCharts(HSSFSheet sheet) {
+ List<HSSFChart> charts = new ArrayList<>();
+ HSSFChart lastChart = null;
+ HSSFSeries lastSeries = null;
+ // Find records of interest
+ List<RecordBase> records = sheet.getSheet().getRecords();
+ for(RecordBase r : records) {
+
+ if(r instanceof ChartRecord) {
+ lastSeries = null;
+ lastChart = new HSSFChart(sheet,(ChartRecord)r);
+ charts.add(lastChart);
+ } else if (r instanceof LinkedDataRecord) {
+ LinkedDataRecord linkedDataRecord = (LinkedDataRecord) r;
+ if (lastSeries != null) {
+ lastSeries.insertData(linkedDataRecord);
+ }
+ }
+
+ if (lastChart == null) {
+ continue;
+ }
+
+ if (r instanceof LegendRecord) {
+ lastChart.legendRecord = (LegendRecord)r;
+ } else if(r instanceof SeriesRecord) {
+ HSSFSeries series = new HSSFSeries( (SeriesRecord)r );
+ lastChart.series.add(series);
+ lastSeries = series;
+ } else if(r instanceof ChartTitleFormatRecord) {
+ lastChart.chartTitleFormat = (ChartTitleFormatRecord)r;
+ } else if(r instanceof SeriesTextRecord) {
+ // Applies to a series, unless we've seen a legend already
+ SeriesTextRecord str = (SeriesTextRecord)r;
+ if(lastChart.legendRecord == null && lastChart.series.size() > 0) {
+ HSSFSeries series = lastChart.series.get(lastChart.series.size()-1);
+ series.seriesTitleText = str;
+ } else {
+ lastChart.chartTitleText = str;
+ }
+ } else if(r instanceof ValueRangeRecord){
+ lastChart.valueRanges.add((ValueRangeRecord)r);
+ } else if (r instanceof Record) {
+ Record record = (Record) r;
+ for (HSSFChartType type : HSSFChartType.values()) {
+ if (type == HSSFChartType.Unknown) {
+ continue;
+ }
+ if (record.getSid() == type.getSid()) {
+ lastChart.type = type;
+ break;
+ }
+ }
+ }
+ }
+
+ return charts.toArray( new HSSFChart[charts.size()] );
+ }
+
+ /** Get the X offset of the chart */
+ public int getChartX() { return chartRecord.getX(); }
+ /** Get the Y offset of the chart */
+ public int getChartY() { return chartRecord.getY(); }
+ /** Get the width of the chart. {@link ChartRecord} */
+ public int getChartWidth() { return chartRecord.getWidth(); }
+ /** Get the height of the chart. {@link ChartRecord} */
+ public int getChartHeight() { return chartRecord.getHeight(); }
+
+ /** Sets the X offset of the chart */
+ public void setChartX(int x) { chartRecord.setX(x); }
+ /** Sets the Y offset of the chart */
+ public void setChartY(int y) { chartRecord.setY(y); }
+ /** Sets the width of the chart. {@link ChartRecord} */
+ public void setChartWidth(int width) { chartRecord.setWidth(width); }
+ /** Sets the height of the chart. {@link ChartRecord} */
+ public void setChartHeight(int height) { chartRecord.setHeight(height); }
+
+ /**
+ * Returns the series of the chart
+ */
+ public HSSFSeries[] getSeries() {
+ return series.toArray(new HSSFSeries[series.size()]);
+ }
+
+ /**
+ * Returns the chart's title, if there is one,
+ * or null if not
+ */
+ public String getChartTitle() {
+ if(chartTitleText != null) {
+ return chartTitleText.getText();
+ }
+ return null;
+ }
+
+ /**
+ * Changes the chart's title, but only if there
+ * was one already.
+ * TODO - add in the records if not
+ */
+ public void setChartTitle(String title) {
+ if(chartTitleText != null) {
+ chartTitleText.setText(title);
+ } else {
+ throw new IllegalStateException("No chart title found to change");
+ }
+ }
+
+ /**
+ * Set value range (basic Axis Options)
+ * @param axisIndex 0 - primary axis, 1 - secondary axis
+ * @param minimum minimum value; Double.NaN - automatic; null - no change
+ * @param maximum maximum value; Double.NaN - automatic; null - no change
+ * @param majorUnit major unit value; Double.NaN - automatic; null - no change
+ * @param minorUnit minor unit value; Double.NaN - automatic; null - no change
+ */
+ public void setValueRange( int axisIndex, Double minimum, Double maximum, Double majorUnit, Double minorUnit){
+ ValueRangeRecord valueRange = valueRanges.get( axisIndex );
+ if( valueRange == null ) return;
+ if( minimum != null ){
+ valueRange.setAutomaticMinimum(minimum.isNaN());
+ valueRange.setMinimumAxisValue(minimum);
+ }
+ if( maximum != null ){
+ valueRange.setAutomaticMaximum(maximum.isNaN());
+ valueRange.setMaximumAxisValue(maximum);
+ }
+ if( majorUnit != null ){
+ valueRange.setAutomaticMajor(majorUnit.isNaN());
+ valueRange.setMajorIncrement(majorUnit);
+ }
+ if( minorUnit != null ){
+ valueRange.setAutomaticMinor(minorUnit.isNaN());
+ valueRange.setMinorIncrement(minorUnit);
+ }
+ }
+
+ private SeriesIndexRecord createSeriesIndexRecord( int index )
+ {
+ SeriesIndexRecord r = new SeriesIndexRecord();
+ r.setIndex((short)index);
+ return r;
+ }
+
+ private DimensionsRecord createDimensionsRecord()
+ {
+ DimensionsRecord r = new DimensionsRecord();
+ r.setFirstRow(0);
+ r.setLastRow(31);
+ r.setFirstCol((short)0);
+ r.setLastCol((short)1);
+ return r;
+ }
+
+ private HCenterRecord createHCenterRecord()
+ {
+ HCenterRecord r = new HCenterRecord();
+ r.setHCenter(false);
+ return r;
+ }
+
+ private VCenterRecord createVCenterRecord()
+ {
+ VCenterRecord r = new VCenterRecord();
+ r.setVCenter(false);
+ return r;
+ }
+
+ private PrintSetupRecord createPrintSetupRecord()
+ {
+ PrintSetupRecord r = new PrintSetupRecord();
+ r.setPaperSize((short)0);
+ r.setScale((short)18);
+ r.setPageStart((short)1);
+ r.setFitWidth((short)1);
+ r.setFitHeight((short)1);
+ r.setLeftToRight(false);
+ r.setLandscape(false);
+ r.setValidSettings(true);
+ r.setNoColor(false);
+ r.setDraft(false);
+ r.setNotes(false);
+ r.setNoOrientation(false);
+ r.setUsePage(false);
+ r.setHResolution((short)0);
+ r.setVResolution((short)0);
+ r.setHeaderMargin(0.5);
+ r.setFooterMargin(0.5);
+ r.setCopies((short)15); // what the ??
+ return r;
+ }
+
+ private FontBasisRecord createFontBasisRecord1()
+ {
+ FontBasisRecord r = new FontBasisRecord();
+ r.setXBasis((short)9120);
+ r.setYBasis((short)5640);
+ r.setHeightBasis((short)200);
+ r.setScale((short)0);
+ r.setIndexToFontTable((short)5);
+ return r;
+ }
+
+ private FontBasisRecord createFontBasisRecord2()
+ {
+ FontBasisRecord r = createFontBasisRecord1();
+ r.setIndexToFontTable((short)6);
+ return r;
+ }
+
+ private BOFRecord createBOFRecord()
+ {
+ BOFRecord r = new BOFRecord();
+ r.setVersion((short)600);
+ r.setType((short)20);
+ r.setBuild((short)0x1CFE);
+ r.setBuildYear((short)1997);
+ r.setHistoryBitMask(0x40C9);
+ r.setRequiredVersion(106);
+ return r;
+ }
+
+ private UnknownRecord createOBJRecord()
+ {
+ byte[] data = {
+ (byte) 0x15, (byte) 0x00, (byte) 0x12, (byte) 0x00, (byte) 0x05, (byte) 0x00, (byte) 0x02, (byte) 0x00, (byte) 0x11, (byte) 0x60, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xB8, (byte) 0x03,
+ (byte) 0x87, (byte) 0x03, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+ };
+
+ return new UnknownRecord( (short) 0x005D, data );
+ }
+
+ private UnknownRecord createMSDrawingObjectRecord()
+ {
+ // Since we haven't created this object yet we'll just put in the raw
+ // form for the moment.
+
+ byte[] data = {
+ (byte)0x0F, (byte)0x00, (byte)0x02, (byte)0xF0, (byte)0xC0, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x10, (byte)0x00, (byte)0x08, (byte)0xF0, (byte)0x08, (byte)0x00, (byte)0x00, (byte)0x00,
+ (byte)0x02, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x02, (byte)0x04, (byte)0x00, (byte)0x00, (byte)0x0F, (byte)0x00, (byte)0x03, (byte)0xF0, (byte)0xA8, (byte)0x00, (byte)0x00, (byte)0x00,
+ (byte)0x0F, (byte)0x00, (byte)0x04, (byte)0xF0, (byte)0x28, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x01, (byte)0x00, (byte)0x09, (byte)0xF0, (byte)0x10, (byte)0x00, (byte)0x00, (byte)0x00,
+ (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
+ (byte)0x02, (byte)0x00, (byte)0x0A, (byte)0xF0, (byte)0x08, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x04, (byte)0x00, (byte)0x00, (byte)0x05, (byte)0x00, (byte)0x00, (byte)0x00,
+ (byte)0x0F, (byte)0x00, (byte)0x04, (byte)0xF0, (byte)0x70, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x92, (byte)0x0C, (byte)0x0A, (byte)0xF0, (byte)0x08, (byte)0x00, (byte)0x00, (byte)0x00,
+ (byte)0x02, (byte)0x04, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x0A, (byte)0x00, (byte)0x00, (byte)0x93, (byte)0x00, (byte)0x0B, (byte)0xF0, (byte)0x36, (byte)0x00, (byte)0x00, (byte)0x00,
+ (byte)0x7F, (byte)0x00, (byte)0x04, (byte)0x01, (byte)0x04, (byte)0x01, (byte)0xBF, (byte)0x00, (byte)0x08, (byte)0x00, (byte)0x08, (byte)0x00, (byte)0x81, (byte)0x01, (byte)0x4E, (byte)0x00,
+ (byte)0x00, (byte)0x08, (byte)0x83, (byte)0x01, (byte)0x4D, (byte)0x00, (byte)0x00, (byte)0x08, (byte)0xBF, (byte)0x01, (byte)0x10, (byte)0x00, (byte)0x11, (byte)0x00, (byte)0xC0, (byte)0x01,
+ (byte)0x4D, (byte)0x00, (byte)0x00, (byte)0x08, (byte)0xFF, (byte)0x01, (byte)0x08, (byte)0x00, (byte)0x08, (byte)0x00, (byte)0x3F, (byte)0x02, (byte)0x00, (byte)0x00, (byte)0x02, (byte)0x00,
+ (byte)0xBF, (byte)0x03, (byte)0x00, (byte)0x00, (byte)0x08, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x10, (byte)0xF0, (byte)0x12, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00,
+ (byte)0x04, (byte)0x00, (byte)0xC0, (byte)0x02, (byte)0x0A, (byte)0x00, (byte)0xF4, (byte)0x00, (byte)0x0E, (byte)0x00, (byte)0x66, (byte)0x01, (byte)0x20, (byte)0x00, (byte)0xE9, (byte)0x00,
+ (byte)0x00, (byte)0x00, (byte)0x11, (byte)0xF0, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00
+ };
+
+ return new UnknownRecord((short)0x00EC, data);
+ }
+
+ private void createAxisRecords( List<Record> records )
+ {
+ records.add( createAxisParentRecord() );
+ records.add( createBeginRecord() );
+ records.add( createAxisRecord( AxisRecord.AXIS_TYPE_CATEGORY_OR_X_AXIS ) );
+ records.add( createBeginRecord() );
+ records.add( createCategorySeriesAxisRecord() );
+ records.add( createAxisOptionsRecord() );
+ records.add( createTickRecord1() );
+ records.add( createEndRecord() );
+ records.add( createAxisRecord( AxisRecord.AXIS_TYPE_VALUE_AXIS ) );
+ records.add( createBeginRecord() );
+ records.add( createValueRangeRecord() );
+ records.add( createTickRecord2() );
+ records.add( createAxisLineFormatRecord( AxisLineFormatRecord.AXIS_TYPE_MAJOR_GRID_LINE ) );
+ records.add( createLineFormatRecord(false) );
+ records.add( createEndRecord() );
+ records.add( createPlotAreaRecord() );
+ records.add( createFrameRecord2() );
+ records.add( createBeginRecord() );
+ records.add( createLineFormatRecord2() );
+ records.add( createAreaFormatRecord2() );
+ records.add( createEndRecord() );
+ records.add( createChartFormatRecord() );
+ records.add( createBeginRecord() );
+ records.add( createBarRecord() );
+ // unknown 1022
+ records.add( createLegendRecord() );
+ records.add( createBeginRecord() );
+ // unknown 104f
+ records.add( createTextRecord() );
+ records.add( createBeginRecord() );
+ // unknown 104f
+ records.add( createLinkedDataRecord() );
+ records.add( createEndRecord() );
+ records.add( createEndRecord() );
+ records.add( createEndRecord() );
+ records.add( createEndRecord() );
+ }
+
+ private LinkedDataRecord createLinkedDataRecord()
+ {
+ LinkedDataRecord r = new LinkedDataRecord();
+ r.setLinkType(LinkedDataRecord.LINK_TYPE_TITLE_OR_TEXT);
+ r.setReferenceType(LinkedDataRecord.REFERENCE_TYPE_DIRECT);
+ r.setCustomNumberFormat(false);
+ r.setIndexNumberFmtRecord((short)0);
+ r.setFormulaOfLink(null);
+ return r;
+ }
+
+ private TextRecord createTextRecord()
+ {
+ TextRecord r = new TextRecord();
+ r.setHorizontalAlignment(TextRecord.HORIZONTAL_ALIGNMENT_CENTER);
+ r.setVerticalAlignment(TextRecord.VERTICAL_ALIGNMENT_CENTER);
+ r.setDisplayMode((short)1);
+ r.setRgbColor(0x00000000);
+ r.setX(-37);
+ r.setY(-60);
+ r.setWidth(0);
+ r.setHeight(0);
+ r.setAutoColor(true);
+ r.setShowKey(false);
+ r.setShowValue(false);
+ r.setVertical(false);
+ r.setAutoGeneratedText(true);
+ r.setGenerated(true);
+ r.setAutoLabelDeleted(false);
+ r.setAutoBackground(true);
+ r.setRotation((short)0);
+ r.setShowCategoryLabelAsPercentage(false);
+ r.setShowValueAsPercentage(false);
+ r.setShowBubbleSizes(false);
+ r.setShowLabel(false);
+ r.setIndexOfColorValue((short)77);
+ r.setDataLabelPlacement((short)0);
+ r.setTextRotation((short)0);
+ return r;
+ }
+
+ private LegendRecord createLegendRecord()
+ {
+ LegendRecord r = new LegendRecord();
+ r.setXAxisUpperLeft(3542);
+ r.setYAxisUpperLeft(1566);
+ r.setXSize(437);
+ r.setYSize(213);
+ r.setType(LegendRecord.TYPE_RIGHT);
+ r.setSpacing(LegendRecord.SPACING_MEDIUM);
+ r.setAutoPosition(true);
+ r.setAutoSeries(true);
+ r.setAutoXPositioning(true);
+ r.setAutoYPositioning(true);
+ r.setVertical(true);
+ r.setDataTable(false);
+ return r;
+ }
+
+ private BarRecord createBarRecord()
+ {
+ BarRecord r = new BarRecord();
+ r.setBarSpace((short)0);
+ r.setCategorySpace((short)150);
+ r.setHorizontal(false);
+ r.setStacked(false);
+ r.setDisplayAsPercentage(false);
+ r.setShadow(false);
+ return r;
+ }
+
+ private ChartFormatRecord createChartFormatRecord()
+ {
+ ChartFormatRecord r = new ChartFormatRecord();
+ r.setXPosition(0);
+ r.setYPosition(0);
+ r.setWidth(0);
+ r.setHeight(0);
+ r.setVaryDisplayPattern(false);
+ return r;
+ }
+
+ private PlotAreaRecord createPlotAreaRecord()
+ {
+ return new PlotAreaRecord( );
+ }
+
+ private AxisLineFormatRecord createAxisLineFormatRecord( short format )
+ {
+ AxisLineFormatRecord r = new AxisLineFormatRecord();
+ r.setAxisType( format );
+ return r;
+ }
+
+ private ValueRangeRecord createValueRangeRecord()
+ {
+ ValueRangeRecord r = new ValueRangeRecord();
+ r.setMinimumAxisValue( 0.0 );
+ r.setMaximumAxisValue( 0.0 );
+ r.setMajorIncrement( 0 );
+ r.setMinorIncrement( 0 );
+ r.setCategoryAxisCross( 0 );
+ r.setAutomaticMinimum( true );
+ r.setAutomaticMaximum( true );
+ r.setAutomaticMajor( true );
+ r.setAutomaticMinor( true );
+ r.setAutomaticCategoryCrossing( true );
+ r.setLogarithmicScale( false );
+ r.setValuesInReverse( false );
+ r.setCrossCategoryAxisAtMaximum( false );
+ r.setReserved( true ); // what's this do??
+ return r;
+ }
+
+ private TickRecord createTickRecord1()
+ {
+ TickRecord r = new TickRecord();
+ r.setMajorTickType( (byte) 2 );
+ r.setMinorTickType( (byte) 0 );
+ r.setLabelPosition( (byte) 3 );
+ r.setBackground( (byte) 1 );
+ r.setLabelColorRgb( 0 );
+ r.setZero1( (short) 0 );
+ r.setZero2( (short) 0 );
+ r.setZero3( (short) 45 );
+ r.setAutorotate( true );
+ r.setAutoTextBackground( true );
+ r.setRotation( (short) 0 );
+ r.setAutorotate( true );
+ r.setTickColor( (short) 77 );
+ return r;
+ }
+
+ private TickRecord createTickRecord2()
+ {
+ TickRecord r = createTickRecord1();
+ r.setZero3((short)0);
+ return r;
+ }
+
+ private AxisOptionsRecord createAxisOptionsRecord()
+ {
+ AxisOptionsRecord r = new AxisOptionsRecord();
+ r.setMinimumCategory( (short) -28644 );
+ r.setMaximumCategory( (short) -28715 );
+ r.setMajorUnitValue( (short) 2 );
+ r.setMajorUnit( (short) 0 );
+ r.setMinorUnitValue( (short) 1 );
+ r.setMinorUnit( (short) 0 );
+ r.setBaseUnit( (short) 0 );
+ r.setCrossingPoint( (short) -28644 );
+ r.setDefaultMinimum( true );
+ r.setDefaultMaximum( true );
+ r.setDefaultMajor( true );
+ r.setDefaultMinorUnit( true );
+ r.setIsDate( true );
+ r.setDefaultBase( true );
+ r.setDefaultCross( true );
+ r.setDefaultDateSettings( true );
+ return r;
+ }
+
+ private CategorySeriesAxisRecord createCategorySeriesAxisRecord()
+ {
+ CategorySeriesAxisRecord r = new CategorySeriesAxisRecord();
+ r.setCrossingPoint( (short) 1 );
+ r.setLabelFrequency( (short) 1 );
+ r.setTickMarkFrequency( (short) 1 );
+ r.setValueAxisCrossing( true );
+ r.setCrossesFarRight( false );
+ r.setReversed( false );
+ return r;
+ }
+
+ private AxisRecord createAxisRecord( short axisType )
+ {
+ AxisRecord r = new AxisRecord();
+ r.setAxisType( axisType );
+ return r;
+ }
+
+ private AxisParentRecord createAxisParentRecord()
+ {
+ AxisParentRecord r = new AxisParentRecord();
+ r.setAxisType( AxisParentRecord.AXIS_TYPE_MAIN );
+ r.setX( 479 );
+ r.setY( 221 );
+ r.setWidth( 2995 );
+ r.setHeight( 2902 );
+ return r;
+ }
+
+ private AxisUsedRecord createAxisUsedRecord( short numAxis )
+ {
+ AxisUsedRecord r = new AxisUsedRecord();
+ r.setNumAxis( numAxis );
+ return r;
+ }
+
+ private LinkedDataRecord createDirectLinkRecord()
+ {
+ LinkedDataRecord r = new LinkedDataRecord();
+ r.setLinkType( LinkedDataRecord.LINK_TYPE_TITLE_OR_TEXT );
+ r.setReferenceType( LinkedDataRecord.REFERENCE_TYPE_DIRECT );
+ r.setCustomNumberFormat( false );
+ r.setIndexNumberFmtRecord( (short) 0 );
+ r.setFormulaOfLink(null);
+ return r;
+ }
+
+ private FontIndexRecord createFontIndexRecord( int index )
+ {
+ FontIndexRecord r = new FontIndexRecord();
+ r.setFontIndex( (short) index );
+ return r;
+ }
+
+ private TextRecord createAllTextRecord()
+ {
+ TextRecord r = new TextRecord();
+ r.setHorizontalAlignment( TextRecord.HORIZONTAL_ALIGNMENT_CENTER );
+ r.setVerticalAlignment( TextRecord.VERTICAL_ALIGNMENT_CENTER );
+ r.setDisplayMode( TextRecord.DISPLAY_MODE_TRANSPARENT );
+ r.setRgbColor( 0 );
+ r.setX( -37 );
+ r.setY( -60 );
+ r.setWidth( 0 );
+ r.setHeight( 0 );
+ r.setAutoColor( true );
+ r.setShowKey( false );
+ r.setShowValue( true );
+ r.setVertical( false );
+ r.setAutoGeneratedText( true );
+ r.setGenerated( true );
+ r.setAutoLabelDeleted( false );
+ r.setAutoBackground( true );
+ r.setRotation( (short) 0 );
+ r.setShowCategoryLabelAsPercentage( false );
+ r.setShowValueAsPercentage( false );
+ r.setShowBubbleSizes( false );
+ r.setShowLabel( false );
+ r.setIndexOfColorValue( (short) 77 );
+ r.setDataLabelPlacement( (short) 0 );
+ r.setTextRotation( (short) 0 );
+ return r;
+ }
+
+ private TextRecord createUnknownTextRecord()
+ {
+ TextRecord r = new TextRecord();
+ r.setHorizontalAlignment( TextRecord.HORIZONTAL_ALIGNMENT_CENTER );
+ r.setVerticalAlignment( TextRecord.VERTICAL_ALIGNMENT_CENTER );
+ r.setDisplayMode( TextRecord.DISPLAY_MODE_TRANSPARENT );
+ r.setRgbColor( 0 );
+ r.setX( -37 );
+ r.setY( -60 );
+ r.setWidth( 0 );
+ r.setHeight( 0 );
+ r.setAutoColor( true );
+ r.setShowKey( false );
+ r.setShowValue( false );
+ r.setVertical( false );
+ r.setAutoGeneratedText( true );
+ r.setGenerated( true );
+ r.setAutoLabelDeleted( false );
+ r.setAutoBackground( true );
+ r.setRotation( (short) 0 );
+ r.setShowCategoryLabelAsPercentage( false );
+ r.setShowValueAsPercentage( false );
+ r.setShowBubbleSizes( false );
+ r.setShowLabel( false );
+ r.setIndexOfColorValue( (short) 77 );
+ r.setDataLabelPlacement( (short) 11088 );
+ r.setTextRotation( (short) 0 );
+ return r;
+ }
+
+ private DefaultDataLabelTextPropertiesRecord createDefaultTextRecord( short categoryDataType )
+ {
+ DefaultDataLabelTextPropertiesRecord r = new DefaultDataLabelTextPropertiesRecord();
+ r.setCategoryDataType( categoryDataType );
+ return r;
+ }
+
+ private SheetPropertiesRecord createSheetPropsRecord()
+ {
+ SheetPropertiesRecord r = new SheetPropertiesRecord();
+ r.setChartTypeManuallyFormatted( false );
+ r.setPlotVisibleOnly( true );
+ r.setDoNotSizeWithWindow( false );
+ r.setDefaultPlotDimensions( true );
+ r.setAutoPlotArea( false );
+ return r;
+ }
+
+ private SeriesToChartGroupRecord createSeriesToChartGroupRecord()
+ {
+ return new SeriesToChartGroupRecord();
+ }
+
+ private DataFormatRecord createDataFormatRecord()
+ {
+ DataFormatRecord r = new DataFormatRecord();
+ r.setPointNumber( (short) -1 );
+ r.setSeriesIndex( (short) 0 );
+ r.setSeriesNumber( (short) 0 );
+ r.setUseExcel4Colors( false );
+ return r;
+ }
+
+ private LinkedDataRecord createCategoriesLinkedDataRecord()
+ {
+ LinkedDataRecord r = new LinkedDataRecord();
+ r.setLinkType( LinkedDataRecord.LINK_TYPE_CATEGORIES );
+ r.setReferenceType( LinkedDataRecord.REFERENCE_TYPE_WORKSHEET );
+ r.setCustomNumberFormat( false );
+ r.setIndexNumberFmtRecord( (short) 0 );
+ Area3DPtg p = new Area3DPtg(0, 31, 1, 1,
+ false, false, false, false, 0);
+ r.setFormulaOfLink(new Ptg[] { p, });
+ return r;
+ }
+
+ private LinkedDataRecord createValuesLinkedDataRecord()
+ {
+ LinkedDataRecord r = new LinkedDataRecord();
+ r.setLinkType( LinkedDataRecord.LINK_TYPE_VALUES );
+ r.setReferenceType( LinkedDataRecord.REFERENCE_TYPE_WORKSHEET );
+ r.setCustomNumberFormat( false );
+ r.setIndexNumberFmtRecord( (short) 0 );
+ Area3DPtg p = new Area3DPtg(0, 31, 0, 0,
+ false, false, false, false, 0);
+ r.setFormulaOfLink(new Ptg[] { p, });
+ return r;
+ }
+
+ private LinkedDataRecord createTitleLinkedDataRecord()
+ {
+ LinkedDataRecord r = new LinkedDataRecord();
+ r.setLinkType( LinkedDataRecord.LINK_TYPE_TITLE_OR_TEXT );
+ r.setReferenceType( LinkedDataRecord.REFERENCE_TYPE_DIRECT );
+ r.setCustomNumberFormat( false );
+ r.setIndexNumberFmtRecord( (short) 0 );
+ r.setFormulaOfLink(null);
+ return r;
+ }
+
+ private SeriesRecord createSeriesRecord()
+ {
+ SeriesRecord r = new SeriesRecord();
+ r.setCategoryDataType( SeriesRecord.CATEGORY_DATA_TYPE_NUMERIC );
+ r.setValuesDataType( SeriesRecord.VALUES_DATA_TYPE_NUMERIC );
+ r.setNumCategories( (short) 32 );
+ r.setNumValues( (short) 31 );
+ r.setBubbleSeriesType( SeriesRecord.BUBBLE_SERIES_TYPE_NUMERIC );
+ r.setNumBubbleValues( (short) 0 );
+ return r;
+ }
+
+ private EndRecord createEndRecord()
+ {
+ return new EndRecord();
+ }
+
+ private AreaFormatRecord createAreaFormatRecord1()
+ {
+ AreaFormatRecord r = new AreaFormatRecord();
+ r.setForegroundColor( 16777215 ); // RGB Color
+ r.setBackgroundColor( 0 ); // RGB Color
+ r.setPattern( (short) 1 ); // TODO: Add Pattern constants to record
+ r.setAutomatic( true );
+ r.setInvert( false );
+ r.setForecolorIndex( (short) 78 );
+ r.setBackcolorIndex( (short) 77 );
+ return r;
+ }
+
+ private AreaFormatRecord createAreaFormatRecord2()
+ {
+ AreaFormatRecord r = new AreaFormatRecord();
+ r.setForegroundColor(0x00c0c0c0);
+ r.setBackgroundColor(0x00000000);
+ r.setPattern((short)1);
+ r.setAutomatic(false);
+ r.setInvert(false);
+ r.setForecolorIndex((short)22);
+ r.setBackcolorIndex((short)79);
+ return r;
+ }
+
+ private LineFormatRecord createLineFormatRecord( boolean drawTicks )
+ {
+ LineFormatRecord r = new LineFormatRecord();
+ r.setLineColor( 0 );
+ r.setLinePattern( LineFormatRecord.LINE_PATTERN_SOLID );
+ r.setWeight( (short) -1 );
+ r.setAuto( true );
+ r.setDrawTicks( drawTicks );
+ r.setColourPaletteIndex( (short) 77 ); // what colour is this?
+ return r;
+ }
+
+ private LineFormatRecord createLineFormatRecord2()
+ {
+ LineFormatRecord r = new LineFormatRecord();
+ r.setLineColor( 0x00808080 );
+ r.setLinePattern( (short) 0 );
+ r.setWeight( (short) 0 );
+ r.setAuto( false );
+ r.setDrawTicks( false );
+ r.setUnknown( false );
+ r.setColourPaletteIndex( (short) 23 );
+ return r;
+ }
+
+ private FrameRecord createFrameRecord1()
+ {
+ FrameRecord r = new FrameRecord();
+ r.setBorderType( FrameRecord.BORDER_TYPE_REGULAR );
+ r.setAutoSize( false );
+ r.setAutoPosition( true );
+ return r;
+ }
+
+ private FrameRecord createFrameRecord2()
+ {
+ FrameRecord r = new FrameRecord();
+ r.setBorderType( FrameRecord.BORDER_TYPE_REGULAR );
+ r.setAutoSize( true );
+ r.setAutoPosition( true );
+ return r;
+ }
+
+ private PlotGrowthRecord createPlotGrowthRecord( int horizScale, int vertScale )
+ {
+ PlotGrowthRecord r = new PlotGrowthRecord();
+ r.setHorizontalScale( horizScale );
+ r.setVerticalScale( vertScale );
+ return r;
+ }
+
+ private SCLRecord createSCLRecord( short numerator, short denominator )
+ {
+ SCLRecord r = new SCLRecord();
+ r.setDenominator( denominator );
+ r.setNumerator( numerator );
+ return r;
+ }
+
+ private BeginRecord createBeginRecord()
+ {
+ return new BeginRecord();
+ }
+
+ private ChartRecord createChartRecord( int x, int y, int width, int height )
+ {
+ ChartRecord r = new ChartRecord();
+ r.setX( x );
+ r.setY( y );
+ r.setWidth( width );
+ r.setHeight( height );
+ return r;
+ }
+
+ private UnitsRecord createUnitsRecord()
+ {
+ UnitsRecord r = new UnitsRecord();
+ r.setUnits( (short) 0 );
+ return r;
+ }
+
+
+ /**
+ * A series in a chart
+ */
+ public static class HSSFSeries {
+ private SeriesRecord series;
+ private SeriesTextRecord seriesTitleText;
+ private LinkedDataRecord dataName;
+ private LinkedDataRecord dataValues;
+ private LinkedDataRecord dataCategoryLabels;
+ private LinkedDataRecord dataSecondaryCategoryLabels;
+
+ /* package */ HSSFSeries(SeriesRecord series) {
+ this.series = series;
+ }
+
+ /* package */ void insertData(LinkedDataRecord data){
+ switch(data.getLinkType()){
+
+ case LinkedDataRecord.LINK_TYPE_TITLE_OR_TEXT:
+ dataName = data;
+ break;
+ case LinkedDataRecord.LINK_TYPE_VALUES:
+ dataValues = data;
+ break;
+ case LinkedDataRecord.LINK_TYPE_CATEGORIES:
+ dataCategoryLabels = data;
+ break;
+ case LinkedDataRecord.LINK_TYPE_SECONDARY_CATEGORIES:
+ dataSecondaryCategoryLabels = data;
+ break;
+ default:
+ throw new IllegalStateException("Invalid link type: " + data.getLinkType());
+ }
+ }
+
+ /* package */ void setSeriesTitleText(SeriesTextRecord seriesTitleText)
+ {
+ this.seriesTitleText = seriesTitleText;
+ }
+
+ public short getNumValues() {
+ return series.getNumValues();
+ }
+ /**
+ * See {@link SeriesRecord}
+ */
+ public short getValueType() {
+ return series.getValuesDataType();
+ }
+
+ /**
+ * Returns the series' title, if there is one,
+ * or null if not
+ */
+ public String getSeriesTitle() {
+ if(seriesTitleText != null) {
+ return seriesTitleText.getText();
+ }
+ return null;
+ }
+
+ /**
+ * Changes the series' title, but only if there
+ * was one already.
+ * TODO - add in the records if not
+ */
+ public void setSeriesTitle(String title) {
+ if(seriesTitleText != null) {
+ seriesTitleText.setText(title);
+ } else {
+ throw new IllegalStateException("No series title found to change");
+ }
+ }
+
+ /**
+ * @return record with data names
+ */
+ public LinkedDataRecord getDataName(){
+ return dataName;
+ }
+
+ /**
+ * @return record with data values
+ */
+ public LinkedDataRecord getDataValues(){
+ return dataValues;
+ }
+
+ /**
+ * @return record with data category labels
+ */
+ public LinkedDataRecord getDataCategoryLabels(){
+ return dataCategoryLabels;
+ }
+
+ /**
+ * @return record with data secondary category labels
+ */
+ public LinkedDataRecord getDataSecondaryCategoryLabels() {
+ return dataSecondaryCategoryLabels;
+ }
+
+ /**
+ * @return record with series
+ */
+ public SeriesRecord getSeries() {
+ return series;
+ }
+
+ private CellRangeAddressBase getCellRange(LinkedDataRecord linkedDataRecord) {
+ if (linkedDataRecord == null)
+ {
+ return null ;
+ }
+
+ int firstRow = 0;
+ int lastRow = 0;
+ int firstCol = 0;
+ int lastCol = 0;
+
+ for (Ptg ptg : linkedDataRecord.getFormulaOfLink()) {
+ if (ptg instanceof AreaPtgBase) {
+ AreaPtgBase areaPtg = (AreaPtgBase) ptg;
+
+ firstRow = areaPtg.getFirstRow();
+ lastRow = areaPtg.getLastRow();
+
+ firstCol = areaPtg.getFirstColumn();
+ lastCol = areaPtg.getLastColumn();
+ }
+ }
+
+ return new CellRangeAddress(firstRow, lastRow, firstCol, lastCol);
+ }
+
+ public CellRangeAddressBase getValuesCellRange() {
+ return getCellRange(dataValues);
+ }
+
+ public CellRangeAddressBase getCategoryLabelsCellRange() {
+ return getCellRange(dataCategoryLabels);
+ }
+
+ private Integer setVerticalCellRange(LinkedDataRecord linkedDataRecord,
+ CellRangeAddressBase range) {
+ if (linkedDataRecord == null)
+ {
+ return null;
+ }
+
+ List<Ptg> ptgList = new ArrayList<>();
+
+ int rowCount = (range.getLastRow() - range.getFirstRow()) + 1;
+ int colCount = (range.getLastColumn() - range.getFirstColumn()) + 1;
+
+ for (Ptg ptg : linkedDataRecord.getFormulaOfLink()) {
+ if (ptg instanceof AreaPtgBase) {
+ AreaPtgBase areaPtg = (AreaPtgBase) ptg;
+
+ areaPtg.setFirstRow(range.getFirstRow());
+ areaPtg.setLastRow(range.getLastRow());
+
+ areaPtg.setFirstColumn(range.getFirstColumn());
+ areaPtg.setLastColumn(range.getLastColumn());
+ ptgList.add(areaPtg);
+ }
+ }
+
+ linkedDataRecord.setFormulaOfLink(ptgList.toArray(new Ptg[ptgList.size()]));
+
+ return rowCount * colCount;
+ }
+
+ public void setValuesCellRange(CellRangeAddressBase range) {
+ Integer count = setVerticalCellRange(dataValues, range);
+ if (count == null)
+ {
+ return;
+ }
+
+ series.setNumValues((short)(int)count);
+ }
+
+ public void setCategoryLabelsCellRange(CellRangeAddressBase range) {
+ Integer count = setVerticalCellRange(dataCategoryLabels, range);
+ if (count == null)
+ {
+ return;
+ }
+
+ series.setNumCategories((short)(int)count);
+ }
+ }
+
+ public HSSFSeries createSeries() throws Exception {
+ ArrayList<RecordBase> seriesTemplate = new ArrayList<>();
+ boolean seriesTemplateFilled = false;
+
+ int idx = 0;
+ int deep = 0;
+ int chartRecordIdx = -1;
+ int chartDeep = -1;
+ int lastSeriesDeep = -1;
+ int endSeriesRecordIdx = -1;
+ int seriesIdx = 0;
+ final List<RecordBase> records = sheet.getSheet().getRecords();
+
+ /* store first series as template and find last series index */
+ for(final RecordBase record : records) {
+
+ idx++;
+
+ if (record instanceof BeginRecord) {
+ deep++;
+ } else if (record instanceof EndRecord) {
+ deep--;
+
+ if (lastSeriesDeep == deep) {
+ lastSeriesDeep = -1;
+ endSeriesRecordIdx = idx;
+ if (!seriesTemplateFilled) {
+ seriesTemplate.add(record);
+ seriesTemplateFilled = true;
+ }
+ }
+
+ if (chartDeep == deep) {
+ break;
+ }
+ }
+
+ if (record instanceof ChartRecord) {
+ if (record == chartRecord) {
+ chartRecordIdx = idx;
+ chartDeep = deep;
+ }
+ } else if (record instanceof SeriesRecord) {
+ if (chartRecordIdx != -1) {
+ seriesIdx++;
+ lastSeriesDeep = deep;
+ }
+ }
+
+ if (lastSeriesDeep != -1 && !seriesTemplateFilled) {
+ seriesTemplate.add(record) ;
+ }
+ }
+
+ /* check if a series was found */
+ if (endSeriesRecordIdx == -1) {
+ return null;
+ }
+
+ /* next index in the records list where the new series can be inserted */
+ idx = endSeriesRecordIdx + 1;
+
+ HSSFSeries newSeries = null;
+
+ /* duplicate record of the template series */
+ ArrayList<RecordBase> clonedRecords = new ArrayList<>();
+ for(final RecordBase record : seriesTemplate) {
+
+ Record newRecord = null;
+
+ if (record instanceof BeginRecord) {
+ newRecord = new BeginRecord();
+ } else if (record instanceof EndRecord) {
+ newRecord = new EndRecord();
+ } else if (record instanceof SeriesRecord) {
+ SeriesRecord seriesRecord = (SeriesRecord) ((SeriesRecord)record).clone();
+ newSeries = new HSSFSeries(seriesRecord);
+ newRecord = seriesRecord;
+ } else if (record instanceof LinkedDataRecord) {
+ LinkedDataRecord linkedDataRecord = ((LinkedDataRecord)record).clone();
+ if (newSeries != null) {
+ newSeries.insertData(linkedDataRecord);
+ }
+ newRecord = linkedDataRecord;
+ } else if (record instanceof DataFormatRecord) {
+ DataFormatRecord dataFormatRecord = ((DataFormatRecord)record).clone();
+
+ dataFormatRecord.setSeriesIndex((short)seriesIdx) ;
+ dataFormatRecord.setSeriesNumber((short)seriesIdx) ;
+
+ newRecord = dataFormatRecord;
+ } else if (record instanceof SeriesTextRecord) {
+ SeriesTextRecord seriesTextRecord = (SeriesTextRecord) ((SeriesTextRecord)record).clone();
+ if (newSeries != null) {
+ newSeries.setSeriesTitleText(seriesTextRecord);
+ }
+ newRecord = seriesTextRecord;
+ } else if (record instanceof Record) {
+ newRecord = (Record) ((Record)record).clone();
+ }
+
+ if (newRecord != null)
+ {
+ clonedRecords.add(newRecord);
+ }
+ }
+
+ /* check if a user model series object was created */
+ if (newSeries == null)
+ {
+ return null;
+ }
+
+ /* transfer series to record list */
+ for(final RecordBase record : clonedRecords) {
+ records.add(idx++, record);
+ }
+
+ return newSeries;
+ }
+
+ public boolean removeSeries(HSSFSeries remSeries) {
+ int deep = 0;
+ int chartDeep = -1;
+ int lastSeriesDeep = -1;
+ int seriesIdx = -1;
+ boolean removeSeries = false;
+ boolean chartEntered = false;
+ boolean result = false;
+ final List<RecordBase> records = sheet.getSheet().getRecords();
+
+ /* store first series as template and find last series index */
+ Iterator<RecordBase> iter = records.iterator();
+ while (iter.hasNext()) {
+ RecordBase record = iter.next();
+
+ if (record instanceof BeginRecord) {
+ deep++;
+ } else if (record instanceof EndRecord) {
+ deep--;
+
+ if (lastSeriesDeep == deep) {
+ lastSeriesDeep = -1;
+
+ if (removeSeries) {
+ removeSeries = false;
+ result = true;
+ iter.remove();
+ }
+ }
+
+ if (chartDeep == deep) {
+ break;
+ }
+ }
+
+ if (record instanceof ChartRecord) {
+ if (record == chartRecord) {
+ chartDeep = deep;
+ chartEntered = true;
+ }
+ } else if (record instanceof SeriesRecord) {
+ if (chartEntered) {
+ if (remSeries.series == record) {
+ lastSeriesDeep = deep;
+ removeSeries = true;
+ } else {
+ seriesIdx++;
+ }
+ }
+ } else if (record instanceof DataFormatRecord) {
+ if (chartEntered && !removeSeries) {
+ DataFormatRecord dataFormatRecord = (DataFormatRecord) record;
+ dataFormatRecord.setSeriesIndex((short) seriesIdx);
+ dataFormatRecord.setSeriesNumber((short) seriesIdx);
+ }
+ }
+
+ if (removeSeries) {
+ iter.remove();
+ }
+ }
+
+ return result;
+ }
+
+ public HSSFChartType getType() {
+ return type;
+ }
+}
diff --git a/src/java/org/apache/poi/ss/extractor/EmbeddedData.java b/src/java/org/apache/poi/ss/extractor/EmbeddedData.java
new file mode 100644
index 0000000000..0e598b3175
--- /dev/null
+++ b/src/java/org/apache/poi/ss/extractor/EmbeddedData.java
@@ -0,0 +1,104 @@
+/* ====================================================================
+ 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.ss.extractor;
+
+import org.apache.poi.ss.usermodel.Shape;
+
+/**
+ * A collection of embedded object informations and content
+ */
+public class EmbeddedData {
+ private String filename;
+ private byte[] embeddedData;
+ private Shape shape;
+ private String contentType = "binary/octet-stream";
+
+ public EmbeddedData(String filename, byte[] embeddedData, String contentType) {
+ setFilename(filename);
+ setEmbeddedData(embeddedData);
+ setContentType(contentType);
+ }
+
+ /**
+ * @return the filename
+ */
+ public String getFilename() {
+ return filename;
+ }
+
+ /**
+ * Sets the filename
+ *
+ * @param filename the filename
+ */
+ public void setFilename(String filename) {
+ if (filename == null) {
+ this.filename = "unknown.bin";
+ } else {
+ this.filename = filename.replaceAll("[^/\\\\]*[/\\\\]", "").trim();
+ }
+ }
+
+ /**
+ * @return the embedded object byte array
+ */
+ public byte[] getEmbeddedData() {
+ return embeddedData;
+ }
+
+ /**
+ * Sets the embedded object as byte array
+ *
+ * @param embeddedData the embedded object byte array
+ */
+ public void setEmbeddedData(byte[] embeddedData) {
+ this.embeddedData = (embeddedData == null) ? null : embeddedData.clone();
+ }
+
+ /**
+ * @return the shape which links to the embedded object
+ */
+ public Shape getShape() {
+ return shape;
+ }
+
+ /**
+ * Sets the shape which links to the embedded object
+ *
+ * @param shape the shape
+ */
+ public void setShape(Shape shape) {
+ this.shape = shape;
+ }
+
+ /**
+ * @return the content-/mime-type of the embedded object, the default (if unknown) is {@code binary/octet-stream}
+ */
+ public String getContentType() {
+ return contentType;
+ }
+
+ /**
+ * Sets the content-/mime-type
+ *
+ * @param contentType the content-type
+ */
+ public void setContentType(String contentType) {
+ this.contentType = contentType;
+ }
+} \ No newline at end of file
diff --git a/src/java/org/apache/poi/ss/extractor/EmbeddedExtractor.java b/src/java/org/apache/poi/ss/extractor/EmbeddedExtractor.java
new file mode 100644
index 0000000000..965a4d1a9e
--- /dev/null
+++ b/src/java/org/apache/poi/ss/extractor/EmbeddedExtractor.java
@@ -0,0 +1,405 @@
+/* ====================================================================
+ 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.ss.extractor;
+
+import static org.apache.poi.util.StringUtil.endsWithIgnoreCase;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.poi.hpsf.ClassID;
+import org.apache.poi.hpsf.ClassIDPredefined;
+import org.apache.poi.poifs.filesystem.DirectoryNode;
+import org.apache.poi.poifs.filesystem.DocumentInputStream;
+import org.apache.poi.poifs.filesystem.Entry;
+import org.apache.poi.poifs.filesystem.Ole10Native;
+import org.apache.poi.poifs.filesystem.Ole10NativeException;
+import org.apache.poi.poifs.filesystem.POIFSFileSystem;
+import org.apache.poi.ss.usermodel.Drawing;
+import org.apache.poi.ss.usermodel.ObjectData;
+import org.apache.poi.ss.usermodel.Picture;
+import org.apache.poi.ss.usermodel.PictureData;
+import org.apache.poi.ss.usermodel.Shape;
+import org.apache.poi.ss.usermodel.ShapeContainer;
+import org.apache.poi.ss.usermodel.Sheet;
+import org.apache.poi.ss.usermodel.Workbook;
+import org.apache.poi.util.Beta;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.LocaleUtil;
+import org.apache.poi.util.POILogFactory;
+import org.apache.poi.util.POILogger;
+
+/**
+ * This extractor class tries to identify various embedded documents within Excel files
+ * and provide them via a common interface, i.e. the EmbeddedData instances
+ */
+@Beta
+public class EmbeddedExtractor implements Iterable<EmbeddedExtractor> {
+ private static final POILogger LOG = POILogFactory.getLogger(EmbeddedExtractor.class);
+ //arbitrarily selected; may need to increase
+ private static final int MAX_RECORD_LENGTH = 1_000_000;
+
+ // contentType
+ private static final String CONTENT_TYPE_BYTES = "binary/octet-stream";
+ private static final String CONTENT_TYPE_PDF = "application/pdf";
+ private static final String CONTENT_TYPE_DOC = "application/msword";
+ private static final String CONTENT_TYPE_XLS = "application/vnd.ms-excel";
+
+ /**
+ * @return the list of known extractors, if you provide custom extractors, override this method
+ */
+ @Override
+ public Iterator<EmbeddedExtractor> iterator() {
+ EmbeddedExtractor[] ee = {
+ new Ole10Extractor(), new PdfExtractor(), new BiffExtractor(), new OOXMLExtractor(), new FsExtractor()
+ };
+ return Arrays.asList(ee).iterator();
+ }
+
+ public EmbeddedData extractOne(DirectoryNode src) throws IOException {
+ for (EmbeddedExtractor ee : this) {
+ if (ee.canExtract(src)) {
+ return ee.extract(src);
+ }
+ }
+ return null;
+ }
+
+ public EmbeddedData extractOne(Picture src) throws IOException {
+ for (EmbeddedExtractor ee : this) {
+ if (ee.canExtract(src)) {
+ return ee.extract(src);
+ }
+ }
+ return null;
+ }
+
+ public List<EmbeddedData> extractAll(Sheet sheet) throws IOException {
+ Drawing<?> patriarch = sheet.getDrawingPatriarch();
+ if (null == patriarch){
+ return Collections.emptyList();
+ }
+ List<EmbeddedData> embeddings = new ArrayList<>();
+ extractAll(patriarch, embeddings);
+ return embeddings;
+ }
+
+ protected void extractAll(ShapeContainer<?> parent, List<EmbeddedData> embeddings) throws IOException {
+ for (Shape shape : parent) {
+ EmbeddedData data = null;
+ if (shape instanceof ObjectData) {
+ ObjectData od = (ObjectData)shape;
+ try {
+ if (od.hasDirectoryEntry()) {
+ data = extractOne((DirectoryNode)od.getDirectory());
+ } else {
+ data = new EmbeddedData(od.getFileName(), od.getObjectData(), od.getContentType());
+ }
+ } catch (Exception e) {
+ LOG.log(POILogger.WARN, "Entry not found / readable - ignoring OLE embedding", e);
+ }
+ } else if (shape instanceof Picture) {
+ data = extractOne((Picture)shape);
+ } else if (shape instanceof ShapeContainer) {
+ extractAll((ShapeContainer<?>)shape, embeddings);
+ }
+
+ if (data == null) {
+ continue;
+ }
+
+ data.setShape(shape);
+ String filename = data.getFilename();
+ String extension = (filename == null || filename.lastIndexOf('.') == -1) ? ".bin" : filename.substring(filename.lastIndexOf('.'));
+
+ // try to find an alternative name
+ if (filename == null || filename.isEmpty() || filename.startsWith("MBD") || filename.startsWith("Root Entry")) {
+ filename = shape.getShapeName();
+ if (filename != null) {
+ filename += extension;
+ }
+ }
+ // default to dummy name
+ if (filename == null || filename.isEmpty()) {
+ filename = "picture_" + embeddings.size() + extension;
+ }
+ filename = filename.trim();
+ data.setFilename(filename);
+
+ embeddings.add(data);
+ }
+ }
+
+
+ public boolean canExtract(DirectoryNode source) {
+ return false;
+ }
+
+ public boolean canExtract(Picture source) {
+ return false;
+ }
+
+ protected EmbeddedData extract(DirectoryNode dn) throws IOException {
+ assert(canExtract(dn));
+ ByteArrayOutputStream bos = new ByteArrayOutputStream(20000);
+ try (POIFSFileSystem dest = new POIFSFileSystem()) {
+ copyNodes(dn, dest.getRoot());
+ // start with a reasonable big size
+ dest.writeFilesystem(bos);
+ }
+
+ return new EmbeddedData(dn.getName(), bos.toByteArray(), CONTENT_TYPE_BYTES);
+ }
+
+ protected EmbeddedData extract(Picture source) throws IOException {
+ return null;
+ }
+
+ public static class Ole10Extractor extends EmbeddedExtractor {
+ @Override
+ public boolean canExtract(DirectoryNode dn) {
+ ClassID clsId = dn.getStorageClsid();
+ return ClassIDPredefined.lookup(clsId) == ClassIDPredefined.OLE_V1_PACKAGE;
+ }
+
+ @Override
+ public EmbeddedData extract(DirectoryNode dn) throws IOException {
+ try {
+ // TODO: inspect the CompObj record for more details, i.e. the content type
+ Ole10Native ole10 = Ole10Native.createFromEmbeddedOleObject(dn);
+ return new EmbeddedData(ole10.getFileName(), ole10.getDataBuffer(), CONTENT_TYPE_BYTES);
+ } catch (Ole10NativeException e) {
+ throw new IOException(e);
+ }
+ }
+ }
+
+ static class PdfExtractor extends EmbeddedExtractor {
+ static ClassID PdfClassID = new ClassID("{B801CA65-A1FC-11D0-85AD-444553540000}");
+ @Override
+ public boolean canExtract(DirectoryNode dn) {
+ ClassID clsId = dn.getStorageClsid();
+ return (PdfClassID.equals(clsId) || dn.hasEntry("CONTENTS"));
+ }
+
+ @Override
+ public EmbeddedData extract(DirectoryNode dn) throws IOException {
+ try(ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ InputStream is = dn.createDocumentInputStream("CONTENTS")) {
+ IOUtils.copy(is, bos);
+ return new EmbeddedData(dn.getName() + ".pdf", bos.toByteArray(), CONTENT_TYPE_PDF);
+ }
+ }
+
+ @Override
+ public boolean canExtract(Picture source) {
+ PictureData pd = source.getPictureData();
+ return (pd != null && pd.getPictureType() == Workbook.PICTURE_TYPE_EMF);
+ }
+
+ /**
+ * Mac Office encodes embedded objects inside the picture, e.g. PDF is part of an EMF.
+ * If an embedded stream is inside an EMF picture, this method extracts the payload.
+ *
+ * @return the embedded data in an EMF picture or null if none is found
+ */
+ @Override
+ protected EmbeddedData extract(Picture source) throws IOException {
+ // check for emf+ embedded pdf (poor mans style :( )
+ // Mac Excel 2011 embeds pdf files with this method.
+ PictureData pd = source.getPictureData();
+ if (pd == null || pd.getPictureType() != Workbook.PICTURE_TYPE_EMF) {
+ return null;
+ }
+
+ // TODO: investigate if this is just an EMF-hack or if other formats are also embedded in EMF
+ byte pictureBytes[] = pd.getData();
+ int idxStart = indexOf(pictureBytes, 0, "%PDF-".getBytes(LocaleUtil.CHARSET_1252));
+ if (idxStart == -1) {
+ return null;
+ }
+
+ int idxEnd = indexOf(pictureBytes, idxStart, "%%EOF".getBytes(LocaleUtil.CHARSET_1252));
+ if (idxEnd == -1) {
+ return null;
+ }
+
+ int pictureBytesLen = idxEnd-idxStart+6;
+ byte[] pdfBytes = IOUtils.safelyAllocate(pictureBytesLen, MAX_RECORD_LENGTH);
+ System.arraycopy(pictureBytes, idxStart, pdfBytes, 0, pictureBytesLen);
+ String filename = source.getShapeName().trim();
+ if (!endsWithIgnoreCase(filename, ".pdf")) {
+ filename += ".pdf";
+ }
+ return new EmbeddedData(filename, pdfBytes, CONTENT_TYPE_PDF);
+ }
+
+
+ }
+
+ static class OOXMLExtractor extends EmbeddedExtractor {
+ @Override
+ public boolean canExtract(DirectoryNode dn) {
+ return dn.hasEntry("package");
+ }
+
+ @Override
+ public EmbeddedData extract(DirectoryNode dn) throws IOException {
+
+ ClassIDPredefined clsId = ClassIDPredefined.lookup(dn.getStorageClsid());
+
+ String contentType = null;
+ String ext = null;
+
+ if (clsId != null) {
+ contentType = clsId.getContentType();
+ ext = clsId.getFileExtension();
+ }
+
+ if (contentType == null || ext == null) {
+ contentType = "application/zip";
+ ext = ".zip";
+ }
+
+ DocumentInputStream dis = dn.createDocumentInputStream("package");
+ byte data[] = IOUtils.toByteArray(dis);
+ dis.close();
+
+ return new EmbeddedData(dn.getName()+ext, data, contentType);
+ }
+ }
+
+ static class BiffExtractor extends EmbeddedExtractor {
+ @Override
+ public boolean canExtract(DirectoryNode dn) {
+ return canExtractExcel(dn) || canExtractWord(dn);
+ }
+
+ protected boolean canExtractExcel(DirectoryNode dn) {
+ ClassIDPredefined clsId = ClassIDPredefined.lookup(dn.getStorageClsid());
+ return (ClassIDPredefined.EXCEL_V7 == clsId
+ || ClassIDPredefined.EXCEL_V8 == clsId
+ || dn.hasEntry("Workbook") /*...*/);
+ }
+
+ protected boolean canExtractWord(DirectoryNode dn) {
+ ClassIDPredefined clsId = ClassIDPredefined.lookup(dn.getStorageClsid());
+ return (ClassIDPredefined.WORD_V7 == clsId
+ || ClassIDPredefined.WORD_V8 == clsId
+ || dn.hasEntry("WordDocument"));
+ }
+
+ @Override
+ public EmbeddedData extract(DirectoryNode dn) throws IOException {
+ EmbeddedData ed = super.extract(dn);
+ if (canExtractExcel(dn)) {
+ ed.setFilename(dn.getName() + ".xls");
+ ed.setContentType(CONTENT_TYPE_XLS);
+ } else if (canExtractWord(dn)) {
+ ed.setFilename(dn.getName() + ".doc");
+ ed.setContentType(CONTENT_TYPE_DOC);
+ }
+
+ return ed;
+ }
+ }
+
+ static class FsExtractor extends EmbeddedExtractor {
+ @Override
+ public boolean canExtract(DirectoryNode dn) {
+ return true;
+ }
+ @Override
+ public EmbeddedData extract(DirectoryNode dn) throws IOException {
+ EmbeddedData ed = super.extract(dn);
+ ed.setFilename(dn.getName() + ".ole");
+ // TODO: read the content type from CombObj stream
+ return ed;
+ }
+ }
+
+ protected static void copyNodes(DirectoryNode src, DirectoryNode dest) throws IOException {
+ for (Entry e : src) {
+ if (e instanceof DirectoryNode) {
+ DirectoryNode srcDir = (DirectoryNode)e;
+ DirectoryNode destDir = (DirectoryNode)dest.createDirectory(srcDir.getName());
+ destDir.setStorageClsid(srcDir.getStorageClsid());
+ copyNodes(srcDir, destDir);
+ } else {
+ try (InputStream is = src.createDocumentInputStream(e)) {
+ dest.createDocument(e.getName(), is);
+ }
+ }
+ }
+ }
+
+
+
+ /**
+ * Knuth-Morris-Pratt Algorithm for Pattern Matching
+ * Finds the first occurrence of the pattern in the text.
+ */
+ private static int indexOf(byte[] data, int offset, byte[] pattern) {
+ int[] failure = computeFailure(pattern);
+
+ int j = 0;
+ if (data.length == 0) {
+ return -1;
+ }
+
+ for (int i = offset; i < data.length; i++) {
+ while (j > 0 && pattern[j] != data[i]) {
+ j = failure[j - 1];
+ }
+ if (pattern[j] == data[i]) { j++; }
+ if (j == pattern.length) {
+ return i - pattern.length + 1;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Computes the failure function using a boot-strapping process,
+ * where the pattern is matched against itself.
+ */
+ private static int[] computeFailure(byte[] pattern) {
+ int[] failure = new int[pattern.length];
+
+ int j = 0;
+ for (int i = 1; i < pattern.length; i++) {
+ while (j > 0 && pattern[j] != pattern[i]) {
+ j = failure[j - 1];
+ }
+ if (pattern[j] == pattern[i]) {
+ j++;
+ }
+ failure[i] = j;
+ }
+
+ return failure;
+ }
+
+
+}
diff --git a/src/java/org/apache/poi/ss/usermodel/WorkbookFactory.java b/src/java/org/apache/poi/ss/usermodel/WorkbookFactory.java
new file mode 100644
index 0000000000..d62fdfc85f
--- /dev/null
+++ b/src/java/org/apache/poi/ss/usermodel/WorkbookFactory.java
@@ -0,0 +1,329 @@
+/* ====================================================================
+ 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.ss.usermodel;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import org.apache.poi.EncryptedDocumentException;
+import org.apache.poi.OldFileFormatException;
+import org.apache.poi.hssf.record.crypto.Biff8EncryptionKey;
+import org.apache.poi.hssf.usermodel.HSSFWorkbook;
+import org.apache.poi.poifs.crypt.Decryptor;
+import org.apache.poi.poifs.filesystem.DirectoryNode;
+import org.apache.poi.poifs.filesystem.DocumentFactoryHelper;
+import org.apache.poi.poifs.filesystem.FileMagic;
+import org.apache.poi.poifs.filesystem.NPOIFSFileSystem;
+import org.apache.poi.poifs.filesystem.OfficeXmlFileException;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.Removal;
+
+/**
+ * Factory for creating the appropriate kind of Workbook
+ * (be it {@link HSSFWorkbook} or XSSFWorkbook),
+ * by auto-detecting from the supplied input.
+ */
+public class WorkbookFactory {
+ /**
+ * Creates a HSSFWorkbook from the given NPOIFSFileSystem<p>
+ *
+ * Note that in order to properly release resources the
+ * Workbook should be closed after use.
+ *
+ * @param fs The {@link NPOIFSFileSystem} to read the document from
+ *
+ * @return The created workbook
+ *
+ * @throws IOException if an error occurs while reading the data
+ */
+ public static Workbook create(NPOIFSFileSystem fs) throws IOException {
+ return create(fs, null);
+ }
+
+ /**
+ * Creates a Workbook from the given NPOIFSFileSystem, which may
+ * be password protected
+ *
+ * @param fs The {@link NPOIFSFileSystem} to read the document from
+ * @param password The password that should be used or null if no password is necessary.
+ *
+ * @return The created Workbook
+ *
+ * @throws IOException if an error occurs while reading the data
+ */
+ private static Workbook create(final NPOIFSFileSystem fs, String password) throws IOException {
+ return create(fs.getRoot(), password);
+ }
+
+
+ /**
+ * Creates a Workbook from the given NPOIFSFileSystem.
+ *
+ * @param root The {@link DirectoryNode} to start reading the document from
+ *
+ * @return The created Workbook
+ *
+ * @throws IOException if an error occurs while reading the data
+ */
+ public static Workbook create(final DirectoryNode root) throws IOException {
+ return create(root, null);
+ }
+
+
+ /**
+ * Creates a Workbook from the given NPOIFSFileSystem, which may
+ * be password protected
+ *
+ * @param root The {@link DirectoryNode} to start reading the document from
+ * @param password The password that should be used or null if no password is necessary.
+ *
+ * @return The created Workbook
+ *
+ * @throws IOException if an error occurs while reading the data
+ */
+ public static Workbook create(final DirectoryNode root, String password) throws IOException {
+ // Encrypted OOXML files go inside OLE2 containers, is this one?
+ if (root.hasEntry(Decryptor.DEFAULT_POIFS_ENTRY)) {
+ InputStream stream = null;
+ try {
+ stream = DocumentFactoryHelper.getDecryptedStream(root, password);
+
+ return createXSSFWorkbook(stream);
+ } finally {
+ IOUtils.closeQuietly(stream);
+ }
+ }
+
+ // If we get here, it isn't an encrypted PPTX file
+ // So, treat it as a regular HSLF PPT one
+ boolean passwordSet = false;
+ if (password != null) {
+ Biff8EncryptionKey.setCurrentUserPassword(password);
+ passwordSet = true;
+ }
+ try {
+ return createHSSFWorkbook(root);
+ } finally {
+ if (passwordSet) {
+ Biff8EncryptionKey.setCurrentUserPassword(null);
+ }
+ }
+ }
+
+ /**
+ * Creates a XSSFWorkbook from the given OOXML Package.
+ * As the WorkbookFactory is located in the POI module, which doesn't know about the OOXML formats,
+ * this can be only achieved by using an Object reference to the OPCPackage.
+ *
+ * <p>Note that in order to properly release resources the
+ * Workbook should be closed after use.</p>
+ *
+ * @param pkg The {@link OPCPackage} opened for reading data.
+ *
+ * @return The created Workbook
+ *
+ * @throws IOException if an error occurs while reading the data
+ *
+ * @deprecated use XSSFWorkbookFactory.create
+ */
+ @Deprecated
+ @Removal(version = "4.2.0")
+ public static Workbook create(Object pkg) throws IOException {
+ return createXSSFWorkbook(pkg);
+ }
+
+ /**
+ * Creates the appropriate HSSFWorkbook / XSSFWorkbook from
+ * the given InputStream.
+ *
+ * <p>Your input stream MUST either support mark/reset, or
+ * be wrapped as a {@link BufferedInputStream}!
+ * Note that using an {@link InputStream} has a higher memory footprint
+ * than using a {@link File}.</p>
+ *
+ * <p>Note that in order to properly release resources the
+ * Workbook should be closed after use. Note also that loading
+ * from an InputStream requires more memory than loading
+ * from a File, so prefer {@link #create(File)} where possible.
+ *
+ * @param inp The {@link InputStream} to read data from.
+ *
+ * @return The created Workbook
+ *
+ * @throws IOException if an error occurs while reading the data
+ * @throws EncryptedDocumentException If the Workbook given is password protected
+ */
+ public static Workbook create(InputStream inp) throws IOException, EncryptedDocumentException {
+ return create(inp, null);
+ }
+
+ /**
+ * Creates the appropriate HSSFWorkbook / XSSFWorkbook from
+ * the given InputStream, which may be password protected.
+ *
+ * <p>Your input stream MUST either support mark/reset, or
+ * be wrapped as a {@link BufferedInputStream}!
+ * Note that using an {@link InputStream} has a higher memory footprint
+ * than using a {@link File}.</p>
+ *
+ * <p>Note that in order to properly release resources the
+ * Workbook should be closed after use. Note also that loading
+ * from an InputStream requires more memory than loading
+ * from a File, so prefer {@link #create(File)} where possible.</p>
+ *
+ * @param inp The {@link InputStream} to read data from.
+ * @param password The password that should be used or null if no password is necessary.
+ *
+ * @return The created Workbook
+ *
+ * @throws IOException if an error occurs while reading the data
+ * @throws EncryptedDocumentException If the wrong password is given for a protected file
+ */
+ public static Workbook create(InputStream inp, String password) throws IOException, EncryptedDocumentException {
+ InputStream is = FileMagic.prepareToCheckMagic(inp);
+ FileMagic fm = FileMagic.valueOf(is);
+
+ switch (fm) {
+ case OLE2:
+ NPOIFSFileSystem fs = new NPOIFSFileSystem(is);
+ return create(fs, password);
+ case OOXML:
+ return createXSSFWorkbook(is);
+ default:
+ throw new IOException("Your InputStream was neither an OLE2 stream, nor an OOXML stream");
+ }
+ }
+
+ /**
+ * Creates the appropriate HSSFWorkbook / XSSFWorkbook from
+ * the given File, which must exist and be readable.
+ * <p>Note that in order to properly release resources the
+ * Workbook should be closed after use.
+ *
+ * @param file The file to read data from.
+ *
+ * @return The created Workbook
+ *
+ * @throws IOException if an error occurs while reading the data
+ * @throws EncryptedDocumentException If the Workbook given is password protected
+ */
+ public static Workbook create(File file) throws IOException, EncryptedDocumentException {
+ return create(file, null);
+ }
+
+ /**
+ * Creates the appropriate HSSFWorkbook / XSSFWorkbook from
+ * the given File, which must exist and be readable, and
+ * may be password protected
+ * <p>Note that in order to properly release resources the
+ * Workbook should be closed after use.
+ *
+ * @param file The file to read data from.
+ * @param password The password that should be used or null if no password is necessary.
+ *
+ * @return The created Workbook
+ *
+ * @throws IOException if an error occurs while reading the data
+ * @throws EncryptedDocumentException If the wrong password is given for a protected file
+ */
+ public static Workbook create(File file, String password) throws IOException, EncryptedDocumentException {
+ return create(file, password, false);
+ }
+
+ /**
+ * Creates the appropriate HSSFWorkbook / XSSFWorkbook from
+ * the given File, which must exist and be readable, and
+ * may be password protected
+ * <p>Note that in order to properly release resources the
+ * Workbook should be closed after use.
+ *
+ * @param file The file to read data from.
+ * @param password The password that should be used or null if no password is necessary.
+ * @param readOnly If the Workbook should be opened in read-only mode to avoid writing back
+ * changes when the document is closed.
+ *
+ * @return The created Workbook
+ *
+ * @throws IOException if an error occurs while reading the data
+ * @throws EncryptedDocumentException If the wrong password is given for a protected file
+ */
+ public static Workbook create(File file, String password, boolean readOnly) throws IOException, EncryptedDocumentException {
+ if (!file.exists()) {
+ throw new FileNotFoundException(file.toString());
+ }
+
+ NPOIFSFileSystem fs = null;
+ try {
+ fs = new NPOIFSFileSystem(file, readOnly);
+ return create(fs, password);
+ } catch(OfficeXmlFileException e) {
+ IOUtils.closeQuietly(fs);
+ return createXSSFWorkbook(file, readOnly);
+ } catch(RuntimeException e) {
+ IOUtils.closeQuietly(fs);
+ throw e;
+ }
+ }
+
+ private static Workbook createHSSFWorkbook(Object... args) throws IOException, EncryptedDocumentException {
+ return createWorkbook("org.apache.poi.hssf.usermodel.HSSFWorkbookFactory", args);
+ }
+
+ private static Workbook createXSSFWorkbook(Object... args) throws IOException, EncryptedDocumentException {
+ return createWorkbook("org.apache.poi.xssf.usermodel.XSSFWorkbookFactory", args);
+ }
+
+ private static Workbook createWorkbook(String factoryClass, Object args[]) throws IOException, EncryptedDocumentException {
+ try {
+ Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(factoryClass);
+ Class<?> argsClz[] = new Class<?>[args.length];
+ int i=0;
+ for (Object o : args) {
+ Class<?> c = o.getClass();
+ if (Boolean.class.isAssignableFrom(c)) {
+ c = boolean.class;
+ } else if (InputStream.class.isAssignableFrom(c)) {
+ c = InputStream.class;
+ }
+ argsClz[i++] = c;
+ }
+ Method m = clazz.getMethod("createWorkbook", argsClz);
+ return (Workbook)m.invoke(null, args);
+ } catch (InvocationTargetException e) {
+ Throwable t = e.getCause();
+ if (t instanceof IOException) {
+ throw (IOException)t;
+ } else if (t instanceof EncryptedDocumentException) {
+ throw (EncryptedDocumentException)t;
+ } else if (t instanceof OldFileFormatException) {
+ throw (OldFileFormatException)t;
+ } else if (t instanceof RuntimeException) {
+ throw (RuntimeException)t;
+ } else {
+ throw new IOException(t.getMessage(), t);
+ }
+ } catch (Exception e) {
+ throw new IOException(e);
+ }
+ }
+
+}