diff options
author | Andreas Beeker <kiwiwings@apache.org> | 2018-05-27 21:59:18 +0000 |
---|---|---|
committer | Andreas Beeker <kiwiwings@apache.org> | 2018-05-27 21:59:18 +0000 |
commit | 6ce329eca43ad902a132cf86e7b9853f652fc3c1 (patch) | |
tree | 858df04c39e1f14f6ed6e863c4bd366f07529eb4 /src/java/org/apache | |
parent | 11457e8f684474e1583fc59477abe52452e66f65 (diff) | |
download | poi-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.java | 75 | ||||
-rw-r--r-- | src/java/org/apache/poi/dev/RecordGenerator.java | 160 | ||||
-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.java | 1371 | ||||
-rw-r--r-- | src/java/org/apache/poi/ss/extractor/EmbeddedData.java | 104 | ||||
-rw-r--r-- | src/java/org/apache/poi/ss/extractor/EmbeddedExtractor.java | 405 | ||||
-rw-r--r-- | src/java/org/apache/poi/ss/usermodel/WorkbookFactory.java | 329 |
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); + } + } + +} |