diff options
author | Evgeniy Berlog <berlog@apache.org> | 2012-08-05 13:05:44 +0000 |
---|---|---|
committer | Evgeniy Berlog <berlog@apache.org> | 2012-08-05 13:05:44 +0000 |
commit | de70938945b079b631bc94c7c5bf6943be22898c (patch) | |
tree | e01695e6f0c8fb26b9213af977a03819419d6a0b | |
parent | 3786b5d599afa60e320b8ef3e20f03650dbc9a07 (diff) | |
parent | 02678b307325a5bfd8bc4329e48a21f7639a7414 (diff) | |
download | poi-de70938945b079b631bc94c7c5bf6943be22898c.tar.gz poi-de70938945b079b631bc94c7c5bf6943be22898c.zip |
merged with trunk
git-svn-id: https://svn.apache.org/repos/asf/poi/branches/gsoc2012@1369572 13f79535-47bb-0310-9956-ffa450edef68
92 files changed, 2968 insertions, 1293 deletions
diff --git a/src/documentation/content/xdocs/casestudies.xml b/src/documentation/content/xdocs/casestudies.xml index a9f796fb02..d1deb60bb5 100644 --- a/src/documentation/content/xdocs/casestudies.xml +++ b/src/documentation/content/xdocs/casestudies.xml @@ -217,8 +217,13 @@ format, <section> <title>iDATA Development Ltd (IDD)</title> <p> - <link href="http://www.iexlsoftware.com/">IDD</link> have developed the iEXL product to generate Excel spreadsheets - directly on the Iseries/AS400 IBM platform. Using RPG, SQL, QUERY, JAVA, COBOL etc. In other words your existing staffs knowledge. + <link href="http://www.iexlsoftware.com/">IDD</link> have developed the iEXL product to + generate Excel spreadsheets directly on the Iseries/AS400 IBM I on Power platform. + </p> + <p> + Professional spreadsheets created via a menu system. Some basic programming is required for more complex options. + When programming is required it can be carried out using RPG, SQL, QUERY, JAVA, COBOL etc. + In other words your existing staffs knowledge </p> <p> Design spreadsheets with: @@ -236,26 +241,34 @@ format, <li>Page breaks</li> <li>Sheet breaks</li> <li>Text insertion and much more</li> + <li>Functions/Formula</li> + <li>Merge cells</li> + <li>Row Height</li> + <li>Cell text alignment</li> + <li>Text Rotation </li> + <li>50 Database files per workbook.</li> <li>E-mail the spreadsheet</li> </ul> <p> - The product name is ‘iEXL’ and has been live on both European and North American systems for over three years. - It is being used in preference to more established commercial products which my clients have already purchased. + The product name is 'iEXL' and has been live on both European and North American systems for over four years. + It is being used in preference to more established commercial products which our clients have already purchased. This is due to cost and ease of use. </p> <p> - All spreadsheets can be archived if required so that historical spreadsheets can be retrieved and in the case - of one client a full external company audit can be approved. The system has benefits for all departments within an organisation. - Examples of this are accounts department for things such as aged trial balance, distribution department for ASN’s, - warehousing for stock figures, IS for security reporting etc. + All spreadsheets can be archived if required so that historical spreadsheets can be retrieved. + </p> + <p> + The system has benefits for all departments within an organisation. + Examples of this are accounts department for things such as aged trial balance, + distribution department for ASN’s, warehousing for stock figures, IS for security reporting etc. </p> <p> - Clients have at this point (Nov 2010) created over 200 spreadsheets - which in turn have generated over 120,000 E-mails. iEXL has a menu driven email system. + Clients have at this point (June 2012) created over 300 spreadsheets which in turn have generated over + 500,000 E-mails. iEXL has a menu driven email system. </p> <p> Due to the Apache-POI project IDD have been able to create the IEXL product. - This is a well priced product which allows companies of all sizes access to a product that opens up their reporting capabilities. + This is a well priced product which allows companies of all sizes access to a product that opens up their reporting capabilities </p> <p> Within the <link href="http://www.iexlsoftware.com/">iEXLSOFTWARE.COM</link> website you will find a full user manual, diff --git a/src/documentation/content/xdocs/spreadsheet/eval.xml b/src/documentation/content/xdocs/spreadsheet/eval.xml index 0eb54411a9..5ff76effb5 100644 --- a/src/documentation/content/xdocs/spreadsheet/eval.xml +++ b/src/documentation/content/xdocs/spreadsheet/eval.xml @@ -272,28 +272,41 @@ for(int sheetNum = 0; sheetNum < wb.getNumberOfSheets(); sheetNum++) { </source> </section> - <anchor id="Performance"/> - <section><title>Performance Notes</title> - <ul> - <li>Generally you should have to create only one FormulaEvaluator - instance per sheet, but there really is no overhead in creating - multiple FormulaEvaluators per sheet other than that of the - FormulaEvaluator object creation. - </li> - <li>Also note that FormulaEvaluator maintains a reference to - the sheet and workbook, so ensure that the evaluator instance - is available for garbage collection when you are done with it - (in other words don't maintain long lived reference to - FormulaEvaluator if you don't really need to - unless - all references to the sheet and workbook are removed, these - don't get garbage collected and continue to occupy potentially - large amounts of memory). - </li> - <li>CellValue instances however do not maintain reference to the - Cell or the sheet or workbook, so these can be long-lived - objects without any adverse effect on performance. - </li> - </ul> - </section> - </body> + <anchor id="Performance"/> + <section><title>Performance Notes</title> + <ul> + <li>Generally you should have to create only one FormulaEvaluator + instance per Workbook. The FormulaEvaluator will cache + evaluations of dependent cells, so if you have multiple + formulas all depending on a cell then subsequent evaluations + will be faster. + </li> + <li>You should normally perform all of your updates to cells, + before triggering the evaluation, rather than doing one + cell at a time. By waiting until all the updates/sets are + performed, you'll be able to take best advantage of the caching + for complex formulas. + </li> + <li>If you do end up making changes to cells part way through + evaluation, you should call <em>notifySetFormula</em> or + <em>notifyUpdateCell</em> to trigger suitable cache clearance. + Alternately, you could instantiate a new FormulaEvaluator, + which will start with empty caches. + </li> + <li>Also note that FormulaEvaluator maintains a reference to + the sheet and workbook, so ensure that the evaluator instance + is available for garbage collection when you are done with it + (in other words don't maintain long lived reference to + FormulaEvaluator if you don't really need to - unless + all references to the sheet and workbook are removed, these + don't get garbage collected and continue to occupy potentially + large amounts of memory). + </li> + <li>CellValue instances however do not maintain reference to the + Cell or the sheet or workbook, so these can be long-lived + objects without any adverse effect on performance. + </li> + </ul> + </section> + </body> </document> diff --git a/src/documentation/content/xdocs/spreadsheet/quick-guide.xml b/src/documentation/content/xdocs/spreadsheet/quick-guide.xml index 2140cda6ea..b4dc72eba1 100644 --- a/src/documentation/content/xdocs/spreadsheet/quick-guide.xml +++ b/src/documentation/content/xdocs/spreadsheet/quick-guide.xml @@ -865,26 +865,31 @@ Examples: <section><title>Repeating rows and columns</title> <p> It's possible to set up repeating rows and columns in - your printouts by using the setRepeatingRowsAndColumns() - function in the HSSFWorkbook class. + your printouts by using the setRepeatingRows() and + setRepeatingColumns() methods in the Sheet class. </p> <p> - This function Contains 5 parameters. - The first parameter is the index to the sheet (0 = first sheet). - The second and third parameters specify the range for the columns to repreat. - To stop the columns from repeating pass in -1 as the start and end column. - The fourth and fifth parameters specify the range for the rows to repeat. - To stop the columns from repeating pass in -1 as the start and end rows. + These methods expect a CellRangeAddress parameter + which specifies the range for the rows or columns to + repeat. + For setRepeatingRows(), it should specify a range of + rows to repeat, with the column part spanning all + columns. + For setRepeatingColums(), it should specify a range of + columns to repeat, with the row part spanning all + rows. + If the parameter is null, the repeating rows or columns + will be removed. </p> <source> - Workbook wb = new HSSFWorkbook(); - Sheet sheet1 = wb.createSheet("new sheet"); - Sheet sheet2 = wb.createSheet("second sheet"); - - // Set the columns to repeat from column 0 to 2 on the first sheet - wb.setRepeatingRowsAndColumns(0,0,2,-1,-1); - // Set the the repeating rows and columns on the second sheet. - wb.setRepeatingRowsAndColumns(1,4,5,1,2); + Workbook wb = new HSSFWorkbook(); // or new XSSFWorkbook(); + Sheet sheet1 = wb.createSheet("Sheet1"); + Sheet sheet2 = wb.createSheet("Sheet2"); + + // Set the rows to repeat from row 4 to 5 on the first sheet. + sheet1.setRepeatingRows(CellRangeAddress.valueOf("4:5")); + // Set the columns to repeat from column A to C on the second sheet + sheet2.setRepeatingColumns(CellRangeAddress.valueOf("A:C")); FileOutputStream fileOut = new FileOutputStream("workbook.xls"); wb.write(fileOut); diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index d27c150ac1..d3a744b48e 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -34,7 +34,19 @@ <changes> <release version="3.9-beta1" date="2012-??-??"> - <action dev="poi-developers" type="add">53302 - Fixed EscherAggregate to correctly handle Continue records in drawing blocks </action> + <action dev="poi-developers" type="add">53446 - Fixed some problems extracting PNGs </action> + <action dev="poi-developers" type="fix">53205 - Fixed some parsing errors and encoding issues in HDGF </action> + <action dev="poi-developers" type="add">53204 - Improved performanceof PageSettingsBlock in HSSF </action> + <action dev="poi-developers" type="add">53500 - Getter for repeating rows and columns</action> + <action dev="poi-developers" type="fix">53369 - Fixed tests failing on JDK 1.7</action> + <action dev="poi-developers" type="fix">53360 - Fixed SXSSF to correctly write text before escaped Unicode control character</action> + <action dev="poi-developers" type="add">Change HSMF Types to have full data on ID, Name and Length, rather than just being a simple ID</action> + <action dev="poi-developers" type="add">48469 - Updated case study</action> + <action dev="poi-developers" type="add">53476 - Support Complex Name in formulas </action> + <action dev="poi-developers" type="fix">53414 - properly update sheet dimensions when adding column </action> + <action dev="poi-developers" type="add">Add File based constructor to OPCPackage, alongside existing String one (which constructed a File from the string internally)</action> + <action dev="poi-developers" type="fix">53389 - Handle formatting General and @ formats even if a locale is prefixed to them</action> + <action dev="poi-developers" type="fix">53271 - Removed unconditional asserts in SXSSF</action> <action dev="poi-developers" type="add">53025 - Updatad documentation and example on using Data Validations </action> <action dev="poi-developers" type="add">53227 - Corrected AddDimensionedImage.java to support XSSF/SXSSF </action> <action dev="poi-developers" type="add">53058 - Utility for representing drawings contained in a binary Excel file as a XML tree</action> diff --git a/src/documentation/skinconf.xml b/src/documentation/skinconf.xml index bcec8209c8..9f34b88b61 100644 --- a/src/documentation/skinconf.xml +++ b/src/documentation/skinconf.xml @@ -82,7 +82,7 @@ be used to configure the chosen Forrest skin. <!-- Do we want to disable the print link? If enabled, invalid HTML 4.0.1 --> <disable-print-link>false</disable-print-link> <!-- Do we want to disable the PDF link? --> - <disable-pdf-link>false</disable-pdf-link> + <disable-pdf-link>true</disable-pdf-link> <!-- Do we want to disable the xml source link? yes, it avoids disclosing author emails --> <disable-xml-link>true</disable-xml-link> <!-- Do we want to disable w3c compliance links? --> @@ -114,7 +114,7 @@ be used to configure the chosen Forrest skin. <host-logo></host-logo> <!-- The following are used to construct a copyright statement --> - <year>2002-2011</year> + <year>2002-2012</year> <vendor>The Apache Software Foundation</vendor> <!-- Some skins use this to form a 'breadcrumb trail' of links. If you don't diff --git a/src/java/org/apache/poi/hssf/model/InternalSheet.java b/src/java/org/apache/poi/hssf/model/InternalSheet.java index 62da2bb1ad..bb4ce3fb3d 100644 --- a/src/java/org/apache/poi/hssf/model/InternalSheet.java +++ b/src/java/org/apache/poi/hssf/model/InternalSheet.java @@ -597,7 +597,7 @@ public final class InternalSheet { } DimensionsRecord d = _dimensions; - if (col.getColumn() > d.getLastCol()) { + if (col.getColumn() >= d.getLastCol()) { d.setLastCol(( short ) (col.getColumn() + 1)); } if (col.getColumn() < d.getFirstCol()) { diff --git a/src/java/org/apache/poi/hssf/record/aggregates/PageSettingsBlock.java b/src/java/org/apache/poi/hssf/record/aggregates/PageSettingsBlock.java index 5236e184b8..c183b5070a 100644 --- a/src/java/org/apache/poi/hssf/record/aggregates/PageSettingsBlock.java +++ b/src/java/org/apache/poi/hssf/record/aggregates/PageSettingsBlock.java @@ -18,13 +18,15 @@ package org.apache.poi.hssf.record.aggregates; import java.util.ArrayList; +import java.util.HashMap; import java.util.Iterator; import java.util.List; -import java.util.Arrays; +import java.util.Map; import org.apache.poi.hssf.model.RecordStream; import org.apache.poi.hssf.model.InternalSheet; import org.apache.poi.hssf.record.*; +import org.apache.poi.util.HexDump; /** * Groups the page settings records for a worksheet.<p/> @@ -35,59 +37,59 @@ import org.apache.poi.hssf.record.*; */ public final class PageSettingsBlock extends RecordAggregate { - /** - * PLS is potentially a <i>continued</i> record, but is currently uninterpreted by POI - */ - private static final class PLSAggregate extends RecordAggregate { - private static final ContinueRecord[] EMPTY_CONTINUE_RECORD_ARRAY = { }; - private final Record _pls; - /** - * holds any continue records found after the PLS record.<br/> - * This would not be required if PLS was properly interpreted. - * Currently, PLS is an {@link UnknownRecord} and does not automatically - * include any trailing {@link ContinueRecord}s. - */ - private ContinueRecord[] _plsContinues; - - public PLSAggregate(RecordStream rs) { - _pls = rs.getNext(); - if (rs.peekNextSid()==ContinueRecord.sid) { - List<ContinueRecord> temp = new ArrayList<ContinueRecord>(); - while (rs.peekNextSid()==ContinueRecord.sid) { - temp.add((ContinueRecord)rs.getNext()); - } - _plsContinues = new ContinueRecord[temp.size()]; - temp.toArray(_plsContinues); - } else { - _plsContinues = EMPTY_CONTINUE_RECORD_ARRAY; - } - } - - @Override - public void visitContainedRecords(RecordVisitor rv) { - rv.visitRecord(_pls); - for (int i = 0; i < _plsContinues.length; i++) { - rv.visitRecord(_plsContinues[i]); - } - } - } - - // Every one of these component records is optional - // (The whole PageSettingsBlock may not be present) - private PageBreakRecord _rowBreaksRecord; - private PageBreakRecord _columnBreaksRecord; - private HeaderRecord _header; - private FooterRecord _footer; - private HCenterRecord _hCenter; - private VCenterRecord _vCenter; - private LeftMarginRecord _leftMargin; - private RightMarginRecord _rightMargin; - private TopMarginRecord _topMargin; - private BottomMarginRecord _bottomMargin; - private final List<PLSAggregate> _plsRecords; - private PrintSetupRecord _printSetup; - private Record _bitmap; - private HeaderFooterRecord _headerFooter; + /** + * PLS is potentially a <i>continued</i> record, but is currently uninterpreted by POI + */ + private static final class PLSAggregate extends RecordAggregate { + private static final ContinueRecord[] EMPTY_CONTINUE_RECORD_ARRAY = { }; + private final Record _pls; + /** + * holds any continue records found after the PLS record.<br/> + * This would not be required if PLS was properly interpreted. + * Currently, PLS is an {@link UnknownRecord} and does not automatically + * include any trailing {@link ContinueRecord}s. + */ + private ContinueRecord[] _plsContinues; + + public PLSAggregate(RecordStream rs) { + _pls = rs.getNext(); + if (rs.peekNextSid()==ContinueRecord.sid) { + List<ContinueRecord> temp = new ArrayList<ContinueRecord>(); + while (rs.peekNextSid()==ContinueRecord.sid) { + temp.add((ContinueRecord)rs.getNext()); + } + _plsContinues = new ContinueRecord[temp.size()]; + temp.toArray(_plsContinues); + } else { + _plsContinues = EMPTY_CONTINUE_RECORD_ARRAY; + } + } + + @Override + public void visitContainedRecords(RecordVisitor rv) { + rv.visitRecord(_pls); + for (int i = 0; i < _plsContinues.length; i++) { + rv.visitRecord(_plsContinues[i]); + } + } + } + + // Every one of these component records is optional + // (The whole PageSettingsBlock may not be present) + private PageBreakRecord _rowBreaksRecord; + private PageBreakRecord _columnBreaksRecord; + private HeaderRecord _header; + private FooterRecord _footer; + private HCenterRecord _hCenter; + private VCenterRecord _vCenter; + private LeftMarginRecord _leftMargin; + private RightMarginRecord _rightMargin; + private TopMarginRecord _topMargin; + private BottomMarginRecord _bottomMargin; + private final List<PLSAggregate> _plsRecords; + private PrintSetupRecord _printSetup; + private Record _bitmap; + private HeaderFooterRecord _headerFooter; /** * HeaderFooterRecord records belonging to preceding CustomViewSettingsRecordAggregates. * The indicator of such records is a non-zero GUID, @@ -96,546 +98,546 @@ public final class PageSettingsBlock extends RecordAggregate { private List<HeaderFooterRecord> _sviewHeaderFooters = new ArrayList<HeaderFooterRecord>(); private Record _printSize; - public PageSettingsBlock(RecordStream rs) { - _plsRecords = new ArrayList<PLSAggregate>(); - while(true) { - if (!readARecord(rs)) { - break; - } - } - } - - /** - * Creates a PageSettingsBlock with default settings - */ - public PageSettingsBlock() { - _plsRecords = new ArrayList<PLSAggregate>(); - _rowBreaksRecord = new HorizontalPageBreakRecord(); - _columnBreaksRecord = new VerticalPageBreakRecord(); - _header = new HeaderRecord(""); - _footer = new FooterRecord(""); - _hCenter = createHCenter(); - _vCenter = createVCenter(); - _printSetup = createPrintSetup(); - } - - /** - * @return <code>true</code> if the specified Record sid is one belonging to the - * 'Page Settings Block'. - */ - public static boolean isComponentRecord(int sid) { - switch (sid) { - case HorizontalPageBreakRecord.sid: - case VerticalPageBreakRecord.sid: - case HeaderRecord.sid: - case FooterRecord.sid: - case HCenterRecord.sid: - case VCenterRecord.sid: - case LeftMarginRecord.sid: - case RightMarginRecord.sid: - case TopMarginRecord.sid: - case BottomMarginRecord.sid: - case UnknownRecord.PLS_004D: - case PrintSetupRecord.sid: - case UnknownRecord.BITMAP_00E9: - case UnknownRecord.PRINTSIZE_0033: - case HeaderFooterRecord.sid: // extra header/footer settings supported by Excel 2007 - return true; - } - return false; - } - - private boolean readARecord(RecordStream rs) { - switch (rs.peekNextSid()) { - case HorizontalPageBreakRecord.sid: - checkNotPresent(_rowBreaksRecord); - _rowBreaksRecord = (PageBreakRecord) rs.getNext(); - break; - case VerticalPageBreakRecord.sid: - checkNotPresent(_columnBreaksRecord); - _columnBreaksRecord = (PageBreakRecord) rs.getNext(); - break; - case HeaderRecord.sid: - checkNotPresent(_header); - _header = (HeaderRecord) rs.getNext(); - break; - case FooterRecord.sid: - checkNotPresent(_footer); - _footer = (FooterRecord) rs.getNext(); - break; - case HCenterRecord.sid: - checkNotPresent(_hCenter); - _hCenter = (HCenterRecord) rs.getNext(); - break; - case VCenterRecord.sid: - checkNotPresent(_vCenter); - _vCenter = (VCenterRecord) rs.getNext(); - break; - case LeftMarginRecord.sid: - checkNotPresent(_leftMargin); - _leftMargin = (LeftMarginRecord) rs.getNext(); - break; - case RightMarginRecord.sid: - checkNotPresent(_rightMargin); - _rightMargin = (RightMarginRecord) rs.getNext(); - break; - case TopMarginRecord.sid: - checkNotPresent(_topMargin); - _topMargin = (TopMarginRecord) rs.getNext(); - break; - case BottomMarginRecord.sid: - checkNotPresent(_bottomMargin); - _bottomMargin = (BottomMarginRecord) rs.getNext(); - break; - case UnknownRecord.PLS_004D: - _plsRecords.add(new PLSAggregate(rs)); - break; - case PrintSetupRecord.sid: - checkNotPresent(_printSetup); - _printSetup = (PrintSetupRecord)rs.getNext(); - break; - case UnknownRecord.BITMAP_00E9: - checkNotPresent(_bitmap); - _bitmap = rs.getNext(); - break; - case UnknownRecord.PRINTSIZE_0033: - checkNotPresent(_printSize); - _printSize = rs.getNext(); - break; - case HeaderFooterRecord.sid: - //there can be multiple HeaderFooterRecord records belonging to different sheet views - HeaderFooterRecord hf = (HeaderFooterRecord)rs.getNext(); + public PageSettingsBlock(RecordStream rs) { + _plsRecords = new ArrayList<PLSAggregate>(); + while(true) { + if (!readARecord(rs)) { + break; + } + } + } + + /** + * Creates a PageSettingsBlock with default settings + */ + public PageSettingsBlock() { + _plsRecords = new ArrayList<PLSAggregate>(); + _rowBreaksRecord = new HorizontalPageBreakRecord(); + _columnBreaksRecord = new VerticalPageBreakRecord(); + _header = new HeaderRecord(""); + _footer = new FooterRecord(""); + _hCenter = createHCenter(); + _vCenter = createVCenter(); + _printSetup = createPrintSetup(); + } + + /** + * @return <code>true</code> if the specified Record sid is one belonging to the + * 'Page Settings Block'. + */ + public static boolean isComponentRecord(int sid) { + switch (sid) { + case HorizontalPageBreakRecord.sid: + case VerticalPageBreakRecord.sid: + case HeaderRecord.sid: + case FooterRecord.sid: + case HCenterRecord.sid: + case VCenterRecord.sid: + case LeftMarginRecord.sid: + case RightMarginRecord.sid: + case TopMarginRecord.sid: + case BottomMarginRecord.sid: + case UnknownRecord.PLS_004D: + case PrintSetupRecord.sid: + case UnknownRecord.BITMAP_00E9: + case UnknownRecord.PRINTSIZE_0033: + case HeaderFooterRecord.sid: // extra header/footer settings supported by Excel 2007 + return true; + } + return false; + } + + private boolean readARecord(RecordStream rs) { + switch (rs.peekNextSid()) { + case HorizontalPageBreakRecord.sid: + checkNotPresent(_rowBreaksRecord); + _rowBreaksRecord = (PageBreakRecord) rs.getNext(); + break; + case VerticalPageBreakRecord.sid: + checkNotPresent(_columnBreaksRecord); + _columnBreaksRecord = (PageBreakRecord) rs.getNext(); + break; + case HeaderRecord.sid: + checkNotPresent(_header); + _header = (HeaderRecord) rs.getNext(); + break; + case FooterRecord.sid: + checkNotPresent(_footer); + _footer = (FooterRecord) rs.getNext(); + break; + case HCenterRecord.sid: + checkNotPresent(_hCenter); + _hCenter = (HCenterRecord) rs.getNext(); + break; + case VCenterRecord.sid: + checkNotPresent(_vCenter); + _vCenter = (VCenterRecord) rs.getNext(); + break; + case LeftMarginRecord.sid: + checkNotPresent(_leftMargin); + _leftMargin = (LeftMarginRecord) rs.getNext(); + break; + case RightMarginRecord.sid: + checkNotPresent(_rightMargin); + _rightMargin = (RightMarginRecord) rs.getNext(); + break; + case TopMarginRecord.sid: + checkNotPresent(_topMargin); + _topMargin = (TopMarginRecord) rs.getNext(); + break; + case BottomMarginRecord.sid: + checkNotPresent(_bottomMargin); + _bottomMargin = (BottomMarginRecord) rs.getNext(); + break; + case UnknownRecord.PLS_004D: + _plsRecords.add(new PLSAggregate(rs)); + break; + case PrintSetupRecord.sid: + checkNotPresent(_printSetup); + _printSetup = (PrintSetupRecord)rs.getNext(); + break; + case UnknownRecord.BITMAP_00E9: + checkNotPresent(_bitmap); + _bitmap = rs.getNext(); + break; + case UnknownRecord.PRINTSIZE_0033: + checkNotPresent(_printSize); + _printSize = rs.getNext(); + break; + case HeaderFooterRecord.sid: + //there can be multiple HeaderFooterRecord records belonging to different sheet views + HeaderFooterRecord hf = (HeaderFooterRecord)rs.getNext(); if(hf.isCurrentSheet()) _headerFooter = hf; else { _sviewHeaderFooters.add(hf); } break; - default: - // all other record types are not part of the PageSettingsBlock - return false; - } - return true; - } - - private void checkNotPresent(Record rec) { - if (rec != null) { - throw new RecordFormatException("Duplicate PageSettingsBlock record (sid=0x" - + Integer.toHexString(rec.getSid()) + ")"); - } - } - - private PageBreakRecord getRowBreaksRecord() { - if (_rowBreaksRecord == null) { - _rowBreaksRecord = new HorizontalPageBreakRecord(); - } - return _rowBreaksRecord; - } - - private PageBreakRecord getColumnBreaksRecord() { - if (_columnBreaksRecord == null) { - _columnBreaksRecord = new VerticalPageBreakRecord(); - } - return _columnBreaksRecord; - } - - - /** - * Sets a page break at the indicated column - * - */ - public void setColumnBreak(short column, short fromRow, short toRow) { - getColumnBreaksRecord().addBreak(column, fromRow, toRow); - } - - /** - * Removes a page break at the indicated column - * - */ - public void removeColumnBreak(int column) { - getColumnBreaksRecord().removeBreak(column); - } - - - - - public void visitContainedRecords(RecordVisitor rv) { - // Replicates record order from Excel 2007, though this is not critical - - visitIfPresent(_rowBreaksRecord, rv); - visitIfPresent(_columnBreaksRecord, rv); - // Write out empty header / footer records if these are missing - if (_header == null) { - rv.visitRecord(new HeaderRecord("")); - } else { - rv.visitRecord(_header); - } - if (_footer == null) { - rv.visitRecord(new FooterRecord("")); - } else { - rv.visitRecord(_footer); - } - visitIfPresent(_hCenter, rv); - visitIfPresent(_vCenter, rv); - visitIfPresent(_leftMargin, rv); - visitIfPresent(_rightMargin, rv); - visitIfPresent(_topMargin, rv); - visitIfPresent(_bottomMargin, rv); - for (RecordAggregate pls : _plsRecords) { - pls.visitContainedRecords(rv); - } - visitIfPresent(_printSetup, rv); - visitIfPresent(_printSize, rv); - visitIfPresent(_headerFooter, rv); + default: + // all other record types are not part of the PageSettingsBlock + return false; + } + return true; + } + + private void checkNotPresent(Record rec) { + if (rec != null) { + throw new RecordFormatException("Duplicate PageSettingsBlock record (sid=0x" + + Integer.toHexString(rec.getSid()) + ")"); + } + } + + private PageBreakRecord getRowBreaksRecord() { + if (_rowBreaksRecord == null) { + _rowBreaksRecord = new HorizontalPageBreakRecord(); + } + return _rowBreaksRecord; + } + + private PageBreakRecord getColumnBreaksRecord() { + if (_columnBreaksRecord == null) { + _columnBreaksRecord = new VerticalPageBreakRecord(); + } + return _columnBreaksRecord; + } + + + /** + * Sets a page break at the indicated column + * + */ + public void setColumnBreak(short column, short fromRow, short toRow) { + getColumnBreaksRecord().addBreak(column, fromRow, toRow); + } + + /** + * Removes a page break at the indicated column + * + */ + public void removeColumnBreak(int column) { + getColumnBreaksRecord().removeBreak(column); + } + + + + + public void visitContainedRecords(RecordVisitor rv) { + // Replicates record order from Excel 2007, though this is not critical + + visitIfPresent(_rowBreaksRecord, rv); + visitIfPresent(_columnBreaksRecord, rv); + // Write out empty header / footer records if these are missing + if (_header == null) { + rv.visitRecord(new HeaderRecord("")); + } else { + rv.visitRecord(_header); + } + if (_footer == null) { + rv.visitRecord(new FooterRecord("")); + } else { + rv.visitRecord(_footer); + } + visitIfPresent(_hCenter, rv); + visitIfPresent(_vCenter, rv); + visitIfPresent(_leftMargin, rv); + visitIfPresent(_rightMargin, rv); + visitIfPresent(_topMargin, rv); + visitIfPresent(_bottomMargin, rv); + for (RecordAggregate pls : _plsRecords) { + pls.visitContainedRecords(rv); + } + visitIfPresent(_printSetup, rv); + visitIfPresent(_printSize, rv); + visitIfPresent(_headerFooter, rv); visitIfPresent(_bitmap, rv); - } - private static void visitIfPresent(Record r, RecordVisitor rv) { - if (r != null) { - rv.visitRecord(r); - } - } - private static void visitIfPresent(PageBreakRecord r, RecordVisitor rv) { - if (r != null) { - if (r.isEmpty()) { - // its OK to not serialize empty page break records - return; - } - rv.visitRecord(r); - } - } - - /** - * creates the HCenter Record and sets it to false (don't horizontally center) - */ - private static HCenterRecord createHCenter() { - HCenterRecord retval = new HCenterRecord(); - - retval.setHCenter(false); - return retval; - } - - /** - * creates the VCenter Record and sets it to false (don't horizontally center) - */ - private static VCenterRecord createVCenter() { - VCenterRecord retval = new VCenterRecord(); - - retval.setVCenter(false); - return retval; - } - - /** - * creates the PrintSetup Record and sets it to defaults and marks it invalid - * @see org.apache.poi.hssf.record.PrintSetupRecord - * @see org.apache.poi.hssf.record.Record - * @return record containing a PrintSetupRecord - */ - private static PrintSetupRecord createPrintSetup() { - PrintSetupRecord retval = new PrintSetupRecord(); - - retval.setPaperSize(( short ) 1); - retval.setScale(( short ) 100); - retval.setPageStart(( short ) 1); - retval.setFitWidth(( short ) 1); - retval.setFitHeight(( short ) 1); - retval.setOptions(( short ) 2); - retval.setHResolution(( short ) 300); - retval.setVResolution(( short ) 300); - retval.setHeaderMargin( 0.5); - retval.setFooterMargin( 0.5); - retval.setCopies(( short ) 1); - return retval; - } - - - /** - * Returns the HeaderRecord. - * @return HeaderRecord for the sheet. - */ - public HeaderRecord getHeader () - { - return _header; - } - - /** - * Sets the HeaderRecord. - * @param newHeader The new HeaderRecord for the sheet. - */ - public void setHeader (HeaderRecord newHeader) - { - _header = newHeader; - } - - /** - * Returns the FooterRecord. - * @return FooterRecord for the sheet. - */ - public FooterRecord getFooter () - { - return _footer; - } - - /** - * Sets the FooterRecord. - * @param newFooter The new FooterRecord for the sheet. - */ - public void setFooter (FooterRecord newFooter) - { - _footer = newFooter; - } - - /** - * Returns the PrintSetupRecord. - * @return PrintSetupRecord for the sheet. - */ - public PrintSetupRecord getPrintSetup () - { - return _printSetup; - } - - /** - * Sets the PrintSetupRecord. - * @param newPrintSetup The new PrintSetupRecord for the sheet. - */ - public void setPrintSetup (PrintSetupRecord newPrintSetup) - { - _printSetup = newPrintSetup; - } - - - private Margin getMarginRec(int marginIndex) { - switch (marginIndex) { - case InternalSheet.LeftMargin: return _leftMargin; - case InternalSheet.RightMargin: return _rightMargin; - case InternalSheet.TopMargin: return _topMargin; - case InternalSheet.BottomMargin: return _bottomMargin; - } - throw new IllegalArgumentException( "Unknown margin constant: " + marginIndex ); - } - - - /** - * Gets the size of the margin in inches. - * @param margin which margin to get - * @return the size of the margin - */ - public double getMargin(short margin) { - Margin m = getMarginRec(margin); - if (m != null) { - return m.getMargin(); - } - switch (margin) { - case InternalSheet.LeftMargin: return .75; - case InternalSheet.RightMargin: return .75; - case InternalSheet.TopMargin: return 1.0; - case InternalSheet.BottomMargin: return 1.0; - } - throw new IllegalArgumentException( "Unknown margin constant: " + margin ); - } - - /** - * Sets the size of the margin in inches. - * @param margin which margin to get - * @param size the size of the margin - */ - public void setMargin(short margin, double size) { - Margin m = getMarginRec(margin); - if (m == null) { - switch (margin) { - case InternalSheet.LeftMargin: - _leftMargin = new LeftMarginRecord(); - m = _leftMargin; - break; - case InternalSheet.RightMargin: - _rightMargin = new RightMarginRecord(); - m = _rightMargin; - break; - case InternalSheet.TopMargin: - _topMargin = new TopMarginRecord(); - m = _topMargin; - break; - case InternalSheet.BottomMargin: - _bottomMargin = new BottomMarginRecord(); - m = _bottomMargin; - break; - default : - throw new IllegalArgumentException( "Unknown margin constant: " + margin ); - } - } - m.setMargin( size ); - } - - /** - * Shifts all the page breaks in the range "count" number of rows/columns - * @param breaks The page record to be shifted - * @param start Starting "main" value to shift breaks - * @param stop Ending "main" value to shift breaks - * @param count number of units (rows/columns) to shift by - */ - private static void shiftBreaks(PageBreakRecord breaks, int start, int stop, int count) { - - Iterator<PageBreakRecord.Break> iterator = breaks.getBreaksIterator(); - List<PageBreakRecord.Break> shiftedBreak = new ArrayList<PageBreakRecord.Break>(); - while(iterator.hasNext()) - { - PageBreakRecord.Break breakItem = iterator.next(); - int breakLocation = breakItem.main; - boolean inStart = (breakLocation >= start); - boolean inEnd = (breakLocation <= stop); - if(inStart && inEnd) - shiftedBreak.add(breakItem); - } - - iterator = shiftedBreak.iterator(); - while (iterator.hasNext()) { - PageBreakRecord.Break breakItem = iterator.next(); - breaks.removeBreak(breakItem.main); - breaks.addBreak((short)(breakItem.main+count), breakItem.subFrom, breakItem.subTo); - } - } - - - /** - * Sets a page break at the indicated row - * @param row - */ - public void setRowBreak(int row, short fromCol, short toCol) { - getRowBreaksRecord().addBreak((short)row, fromCol, toCol); - } - - /** - * Removes a page break at the indicated row - * @param row - */ - public void removeRowBreak(int row) { - if (getRowBreaksRecord().getBreaks().length < 1) - throw new IllegalArgumentException("Sheet does not define any row breaks"); - getRowBreaksRecord().removeBreak((short)row); - } - - /** - * Queries if the specified row has a page break - * @param row - * @return true if the specified row has a page break - */ - public boolean isRowBroken(int row) { - return getRowBreaksRecord().getBreak(row) != null; - } - - - /** - * Queries if the specified column has a page break - * - * @return <code>true</code> if the specified column has a page break - */ - public boolean isColumnBroken(int column) { - return getColumnBreaksRecord().getBreak(column) != null; - } - - /** - * Shifts the horizontal page breaks for the indicated count - * @param startingRow - * @param endingRow - * @param count - */ - public void shiftRowBreaks(int startingRow, int endingRow, int count) { - shiftBreaks(getRowBreaksRecord(), startingRow, endingRow, count); - } - - /** - * Shifts the vertical page breaks for the indicated count - * @param startingCol - * @param endingCol - * @param count - */ - public void shiftColumnBreaks(short startingCol, short endingCol, short count) { - shiftBreaks(getColumnBreaksRecord(), startingCol, endingCol, count); - } - - /** - * @return all the horizontal page breaks, never <code>null</code> - */ - public int[] getRowBreaks() { - return getRowBreaksRecord().getBreaks(); - } - - /** - * @return the number of row page breaks - */ - public int getNumRowBreaks(){ - return getRowBreaksRecord().getNumBreaks(); - } - - /** - * @return all the column page breaks, never <code>null</code> - */ - public int[] getColumnBreaks(){ - return getColumnBreaksRecord().getBreaks(); - } - - /** - * @return the number of column page breaks - */ - public int getNumColumnBreaks(){ - return getColumnBreaksRecord().getNumBreaks(); - } - - public VCenterRecord getVCenter() { - return _vCenter; - } - - public HCenterRecord getHCenter() { - return _hCenter; - } - - /** - * HEADERFOOTER is new in 2007. Some apps seem to have scattered this record long after - * the {@link PageSettingsBlock} where it belongs. - */ - public void addLateHeaderFooter(HeaderFooterRecord rec) { - if (_headerFooter != null) { - throw new IllegalStateException("This page settings block already has a header/footer record"); - } - if (rec.getSid() != HeaderFooterRecord.sid) { - throw new RecordFormatException("Unexpected header-footer record sid: 0x" + Integer.toHexString(rec.getSid())); - } - _headerFooter = rec; - } - - /** - * This method reads PageSettingsBlock records from the supplied RecordStream until the first - * non-PageSettingsBlock record is encountered. As each record is read, it is incorporated - * into this PageSettingsBlock. - * <p/> - * The latest Excel version seems to write the PageSettingsBlock uninterrupted. However there - * are several examples (that Excel reads OK) where these records are not written together: - * <ul> - * <li><b>HEADER_FOOTER(0x089C) after WINDOW2</b> - This record is new in 2007. Some apps - * seem to have scattered this record long after the PageSettingsBlock where it belongs - * test samples: SharedFormulaTest.xls, ex44921-21902.xls, ex42570-20305.xls</li> - * <li><b>PLS, WSBOOL, PageSettingsBlock</b> - WSBOOL is not a PSB record. - * This happens in the test sample file "NoGutsRecords.xls" and "WORKBOOK_in_capitals.xls"</li> - * <li><b>Margins after DIMENSION</b> - All of PSB should be before DIMENSION. (Bug-47199)</li> - * </ul> - * These were probably written by other applications (or earlier versions of Excel). It was - * decided to not write specific code for detecting each of these cases. POI now tolerates - * PageSettingsBlock records scattered all over the sheet record stream, and in any order, but - * does not allow duplicates of any of those records. - * - * <p/> - * <b>Note</b> - when POI writes out this PageSettingsBlock, the records will always be written - * in one consolidated block (in the standard ordering) regardless of how scattered the records - * were when they were originally read. - * - * @throws RecordFormatException if any PSB record encountered has the same type (sid) as - * a record that is already part of this PageSettingsBlock - */ - public void addLateRecords(RecordStream rs) { - while(true) { - if (!readARecord(rs)) { - break; - } - } - } + } + private static void visitIfPresent(Record r, RecordVisitor rv) { + if (r != null) { + rv.visitRecord(r); + } + } + private static void visitIfPresent(PageBreakRecord r, RecordVisitor rv) { + if (r != null) { + if (r.isEmpty()) { + // its OK to not serialize empty page break records + return; + } + rv.visitRecord(r); + } + } + + /** + * creates the HCenter Record and sets it to false (don't horizontally center) + */ + private static HCenterRecord createHCenter() { + HCenterRecord retval = new HCenterRecord(); + + retval.setHCenter(false); + return retval; + } + + /** + * creates the VCenter Record and sets it to false (don't horizontally center) + */ + private static VCenterRecord createVCenter() { + VCenterRecord retval = new VCenterRecord(); + + retval.setVCenter(false); + return retval; + } + + /** + * creates the PrintSetup Record and sets it to defaults and marks it invalid + * @see org.apache.poi.hssf.record.PrintSetupRecord + * @see org.apache.poi.hssf.record.Record + * @return record containing a PrintSetupRecord + */ + private static PrintSetupRecord createPrintSetup() { + PrintSetupRecord retval = new PrintSetupRecord(); + + retval.setPaperSize(( short ) 1); + retval.setScale(( short ) 100); + retval.setPageStart(( short ) 1); + retval.setFitWidth(( short ) 1); + retval.setFitHeight(( short ) 1); + retval.setOptions(( short ) 2); + retval.setHResolution(( short ) 300); + retval.setVResolution(( short ) 300); + retval.setHeaderMargin( 0.5); + retval.setFooterMargin( 0.5); + retval.setCopies(( short ) 1); + return retval; + } + + + /** + * Returns the HeaderRecord. + * @return HeaderRecord for the sheet. + */ + public HeaderRecord getHeader () + { + return _header; + } + + /** + * Sets the HeaderRecord. + * @param newHeader The new HeaderRecord for the sheet. + */ + public void setHeader (HeaderRecord newHeader) + { + _header = newHeader; + } + + /** + * Returns the FooterRecord. + * @return FooterRecord for the sheet. + */ + public FooterRecord getFooter () + { + return _footer; + } + + /** + * Sets the FooterRecord. + * @param newFooter The new FooterRecord for the sheet. + */ + public void setFooter (FooterRecord newFooter) + { + _footer = newFooter; + } + + /** + * Returns the PrintSetupRecord. + * @return PrintSetupRecord for the sheet. + */ + public PrintSetupRecord getPrintSetup () + { + return _printSetup; + } + + /** + * Sets the PrintSetupRecord. + * @param newPrintSetup The new PrintSetupRecord for the sheet. + */ + public void setPrintSetup (PrintSetupRecord newPrintSetup) + { + _printSetup = newPrintSetup; + } + + + private Margin getMarginRec(int marginIndex) { + switch (marginIndex) { + case InternalSheet.LeftMargin: return _leftMargin; + case InternalSheet.RightMargin: return _rightMargin; + case InternalSheet.TopMargin: return _topMargin; + case InternalSheet.BottomMargin: return _bottomMargin; + } + throw new IllegalArgumentException( "Unknown margin constant: " + marginIndex ); + } + + + /** + * Gets the size of the margin in inches. + * @param margin which margin to get + * @return the size of the margin + */ + public double getMargin(short margin) { + Margin m = getMarginRec(margin); + if (m != null) { + return m.getMargin(); + } + switch (margin) { + case InternalSheet.LeftMargin: return .75; + case InternalSheet.RightMargin: return .75; + case InternalSheet.TopMargin: return 1.0; + case InternalSheet.BottomMargin: return 1.0; + } + throw new IllegalArgumentException( "Unknown margin constant: " + margin ); + } + + /** + * Sets the size of the margin in inches. + * @param margin which margin to get + * @param size the size of the margin + */ + public void setMargin(short margin, double size) { + Margin m = getMarginRec(margin); + if (m == null) { + switch (margin) { + case InternalSheet.LeftMargin: + _leftMargin = new LeftMarginRecord(); + m = _leftMargin; + break; + case InternalSheet.RightMargin: + _rightMargin = new RightMarginRecord(); + m = _rightMargin; + break; + case InternalSheet.TopMargin: + _topMargin = new TopMarginRecord(); + m = _topMargin; + break; + case InternalSheet.BottomMargin: + _bottomMargin = new BottomMarginRecord(); + m = _bottomMargin; + break; + default : + throw new IllegalArgumentException( "Unknown margin constant: " + margin ); + } + } + m.setMargin( size ); + } + + /** + * Shifts all the page breaks in the range "count" number of rows/columns + * @param breaks The page record to be shifted + * @param start Starting "main" value to shift breaks + * @param stop Ending "main" value to shift breaks + * @param count number of units (rows/columns) to shift by + */ + private static void shiftBreaks(PageBreakRecord breaks, int start, int stop, int count) { + + Iterator<PageBreakRecord.Break> iterator = breaks.getBreaksIterator(); + List<PageBreakRecord.Break> shiftedBreak = new ArrayList<PageBreakRecord.Break>(); + while(iterator.hasNext()) + { + PageBreakRecord.Break breakItem = iterator.next(); + int breakLocation = breakItem.main; + boolean inStart = (breakLocation >= start); + boolean inEnd = (breakLocation <= stop); + if(inStart && inEnd) + shiftedBreak.add(breakItem); + } + + iterator = shiftedBreak.iterator(); + while (iterator.hasNext()) { + PageBreakRecord.Break breakItem = iterator.next(); + breaks.removeBreak(breakItem.main); + breaks.addBreak((short)(breakItem.main+count), breakItem.subFrom, breakItem.subTo); + } + } + + + /** + * Sets a page break at the indicated row + * @param row + */ + public void setRowBreak(int row, short fromCol, short toCol) { + getRowBreaksRecord().addBreak((short)row, fromCol, toCol); + } + + /** + * Removes a page break at the indicated row + * @param row + */ + public void removeRowBreak(int row) { + if (getRowBreaksRecord().getBreaks().length < 1) + throw new IllegalArgumentException("Sheet does not define any row breaks"); + getRowBreaksRecord().removeBreak((short)row); + } + + /** + * Queries if the specified row has a page break + * @param row + * @return true if the specified row has a page break + */ + public boolean isRowBroken(int row) { + return getRowBreaksRecord().getBreak(row) != null; + } + + + /** + * Queries if the specified column has a page break + * + * @return <code>true</code> if the specified column has a page break + */ + public boolean isColumnBroken(int column) { + return getColumnBreaksRecord().getBreak(column) != null; + } + + /** + * Shifts the horizontal page breaks for the indicated count + * @param startingRow + * @param endingRow + * @param count + */ + public void shiftRowBreaks(int startingRow, int endingRow, int count) { + shiftBreaks(getRowBreaksRecord(), startingRow, endingRow, count); + } + + /** + * Shifts the vertical page breaks for the indicated count + * @param startingCol + * @param endingCol + * @param count + */ + public void shiftColumnBreaks(short startingCol, short endingCol, short count) { + shiftBreaks(getColumnBreaksRecord(), startingCol, endingCol, count); + } + + /** + * @return all the horizontal page breaks, never <code>null</code> + */ + public int[] getRowBreaks() { + return getRowBreaksRecord().getBreaks(); + } + + /** + * @return the number of row page breaks + */ + public int getNumRowBreaks(){ + return getRowBreaksRecord().getNumBreaks(); + } + + /** + * @return all the column page breaks, never <code>null</code> + */ + public int[] getColumnBreaks(){ + return getColumnBreaksRecord().getBreaks(); + } + + /** + * @return the number of column page breaks + */ + public int getNumColumnBreaks(){ + return getColumnBreaksRecord().getNumBreaks(); + } + + public VCenterRecord getVCenter() { + return _vCenter; + } + + public HCenterRecord getHCenter() { + return _hCenter; + } + + /** + * HEADERFOOTER is new in 2007. Some apps seem to have scattered this record long after + * the {@link PageSettingsBlock} where it belongs. + */ + public void addLateHeaderFooter(HeaderFooterRecord rec) { + if (_headerFooter != null) { + throw new IllegalStateException("This page settings block already has a header/footer record"); + } + if (rec.getSid() != HeaderFooterRecord.sid) { + throw new RecordFormatException("Unexpected header-footer record sid: 0x" + Integer.toHexString(rec.getSid())); + } + _headerFooter = rec; + } + + /** + * This method reads PageSettingsBlock records from the supplied RecordStream until the first + * non-PageSettingsBlock record is encountered. As each record is read, it is incorporated + * into this PageSettingsBlock. + * <p/> + * The latest Excel version seems to write the PageSettingsBlock uninterrupted. However there + * are several examples (that Excel reads OK) where these records are not written together: + * <ul> + * <li><b>HEADER_FOOTER(0x089C) after WINDOW2</b> - This record is new in 2007. Some apps + * seem to have scattered this record long after the PageSettingsBlock where it belongs + * test samples: SharedFormulaTest.xls, ex44921-21902.xls, ex42570-20305.xls</li> + * <li><b>PLS, WSBOOL, PageSettingsBlock</b> - WSBOOL is not a PSB record. + * This happens in the test sample file "NoGutsRecords.xls" and "WORKBOOK_in_capitals.xls"</li> + * <li><b>Margins after DIMENSION</b> - All of PSB should be before DIMENSION. (Bug-47199)</li> + * </ul> + * These were probably written by other applications (or earlier versions of Excel). It was + * decided to not write specific code for detecting each of these cases. POI now tolerates + * PageSettingsBlock records scattered all over the sheet record stream, and in any order, but + * does not allow duplicates of any of those records. + * + * <p/> + * <b>Note</b> - when POI writes out this PageSettingsBlock, the records will always be written + * in one consolidated block (in the standard ordering) regardless of how scattered the records + * were when they were originally read. + * + * @throws RecordFormatException if any PSB record encountered has the same type (sid) as + * a record that is already part of this PageSettingsBlock + */ + public void addLateRecords(RecordStream rs) { + while(true) { + if (!readARecord(rs)) { + break; + } + } + } /** * Some apps can define multiple HeaderFooterRecord records for a sheet. @@ -652,26 +654,31 @@ public final class PageSettingsBlock extends RecordAggregate { // Take a copy to loop over, so we can update the real one // without concurrency issues List<HeaderFooterRecord> hfRecordsToIterate = new ArrayList<HeaderFooterRecord>(_sviewHeaderFooters); - + + final Map<String, HeaderFooterRecord> hfGuidMap = new HashMap<String, HeaderFooterRecord>(); + + for(final HeaderFooterRecord hf : hfRecordsToIterate) { + hfGuidMap.put(HexDump.toHex(hf.getGuid()), hf); + } + // loop through HeaderFooterRecord records having not-empty GUID and match them with // CustomViewSettingsRecordAggregate blocks having UserSViewBegin with the same GUID - for(final HeaderFooterRecord hf : hfRecordsToIterate) { - for (RecordBase rb : sheetRecords) { - if (rb instanceof CustomViewSettingsRecordAggregate) { - final CustomViewSettingsRecordAggregate cv = (CustomViewSettingsRecordAggregate) rb; - cv.visitContainedRecords(new RecordVisitor() { - public void visitRecord(Record r) { - if (r.getSid() == UserSViewBegin.sid) { - byte[] guid1 = ((UserSViewBegin) r).getGuid(); - byte[] guid2 = hf.getGuid(); - if (Arrays.equals(guid1, guid2)) { - cv.append(hf); - _sviewHeaderFooters.remove(hf); - } + for (RecordBase rb : sheetRecords) { + if (rb instanceof CustomViewSettingsRecordAggregate) { + final CustomViewSettingsRecordAggregate cv = (CustomViewSettingsRecordAggregate) rb; + cv.visitContainedRecords(new RecordVisitor() { + public void visitRecord(Record r) { + if (r.getSid() == UserSViewBegin.sid) { + String guid = HexDump.toHex(((UserSViewBegin) r).getGuid()); + HeaderFooterRecord hf = hfGuidMap.get(guid); + + if (hf != null) { + cv.append(hf); + _sviewHeaderFooters.remove(hf); } } - }); - } + } + }); } } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFName.java b/src/java/org/apache/poi/hssf/usermodel/HSSFName.java index 9309bd7165..d6d041430b 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFName.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFName.java @@ -200,6 +200,18 @@ public final class HSSFName implements Name { return HSSFFormulaParser.toFormulaString(_book, ptgs); } + + /** + * Sets the NameParsedFormula structure that specifies the formula for the + * defined name. + * + * @param ptgs the sequence of {@link Ptg}s for the formula. + */ + void setNameDefinition(Ptg[] ptgs) { + _definedNameRec.setNameDefinition(ptgs); + } + + public boolean isDeleted(){ Ptg[] ptgs = _definedNameRec.getNameDefinition(); return Ptg.doesFormulaReferToDeletedCell(ptgs); diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFPictureData.java b/src/java/org/apache/poi/hssf/usermodel/HSSFPictureData.java index 0516cccf6b..69bdae1521 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFPictureData.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFPictureData.java @@ -22,6 +22,7 @@ import org.apache.poi.ddf.EscherBitmapBlip; import org.apache.poi.ddf.EscherBlipRecord; import org.apache.poi.ddf.EscherMetafileBlip; import org.apache.poi.ss.usermodel.PictureData; +import org.apache.poi.util.PngUtils; /** * Represents binary data stored in the file. Eg. A GIF, JPEG etc... @@ -60,7 +61,18 @@ public class HSSFPictureData implements PictureData */ public byte[] getData() { - return blip.getPicturedata(); + byte[] pictureData = blip.getPicturedata(); + + //PNG created on MAC may have a 16-byte prefix which prevents successful reading. + //Just cut it off!. + if (PngUtils.matchesPngHeader(pictureData, 16)) + { + byte[] png = new byte[pictureData.length-16]; + System.arraycopy(pictureData, 16, png, 0, png.length); + pictureData = png; + } + + return pictureData; } /** diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java index f0ec2d6804..bb3c92ced0 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java @@ -33,8 +33,10 @@ import org.apache.poi.hssf.record.aggregates.DataValidityTable; import org.apache.poi.hssf.record.aggregates.FormulaRecordAggregate; import org.apache.poi.hssf.record.aggregates.WorksheetProtectionBlock; import org.apache.poi.ss.formula.FormulaShifter; +import org.apache.poi.ss.formula.ptg.MemFuncPtg; import org.apache.poi.ss.formula.ptg.Ptg; import org.apache.poi.ss.formula.ptg.Area3DPtg; +import org.apache.poi.ss.formula.ptg.UnionPtg; import org.apache.poi.hssf.util.PaneInformation; import org.apache.poi.ss.SpreadsheetVersion; import org.apache.poi.ss.formula.FormulaType; @@ -371,6 +373,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Creates a data validation object + * * @param dataValidation The Data validation object settings */ public void addValidationData(DataValidation dataValidation) { @@ -522,6 +525,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * get the default row height for the sheet (if the rows do not define their own height) in * twips (1/20 of a point) + * * @return default row height */ public short getDefaultRowHeight() { @@ -531,6 +535,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * get the default row height for the sheet (if the rows do not define their own height) in * points. + * * @return default row height in points */ @@ -813,6 +818,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * used internally in the API to get the low level Sheet record represented by this * Object. + * * @return Sheet - low level representation of this HSSFSheet. */ InternalSheet getSheet() { @@ -821,6 +827,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * whether alternate expression evaluation is on + * * @param b alternative expression evaluation or not */ public void setAlternativeExpression(boolean b) { @@ -832,6 +839,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * whether alternative formula entry is on + * * @param b alternative formulas or not */ public void setAlternativeFormula(boolean b) { @@ -843,6 +851,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * show automatic page breaks or not + * * @param b whether to show auto page breaks */ public void setAutobreaks(boolean b) { @@ -854,6 +863,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * set whether sheet is a dialog sheet or not + * * @param b isDialog or not */ public void setDialog(boolean b) { @@ -877,6 +887,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * fit to page option is on + * * @param b fit or not */ public void setFitToPage(boolean b) { @@ -888,6 +899,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * set if row summaries appear below detail in the outline + * * @param b below or not */ public void setRowSumsBelow(boolean b) { @@ -901,6 +913,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * set if col summaries appear right of the detail in the outline + * * @param b right or not */ public void setRowSumsRight(boolean b) { @@ -912,6 +925,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * whether alternate expression evaluation is on + * * @return alternative expression evaluation or not */ public boolean getAlternateExpression() { @@ -921,6 +935,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * whether alternative formula entry is on + * * @return alternative formulas or not */ public boolean getAlternateFormula() { @@ -930,6 +945,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * show automatic page breaks or not + * * @return whether to show auto page breaks */ public boolean getAutobreaks() { @@ -939,6 +955,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * get whether sheet is a dialog sheet or not + * * @return isDialog or not */ public boolean getDialog() { @@ -963,6 +980,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * <p> * In Excel 2003 this option can be changed in the Options dialog on the View tab. * </p> + * * @return whether all zero values on the worksheet are displayed */ public boolean isDisplayZeros() { @@ -975,6 +993,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { * <p> * In Excel 2003 this option can be set in the Options dialog on the View tab. * </p> + * * @param value whether to display or hide all zero values on the worksheet */ public void setDisplayZeros(boolean value) { @@ -983,6 +1002,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * fit to page option is on + * * @return fit or not */ public boolean getFitToPage() { @@ -992,6 +1012,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * get if row summaries appear below detail in the outline + * * @return below or not */ public boolean getRowSumsBelow() { @@ -1001,6 +1022,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * get if col summaries appear right of the detail in the outline + * * @return right or not */ public boolean getRowSumsRight() { @@ -1010,6 +1032,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Returns whether gridlines are printed. + * * @return Gridlines are printed */ public boolean isPrintGridlines() { @@ -1018,8 +1041,9 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Turns on or off the printing of gridlines. + * * @param newPrintGridlines boolean to turn on or off the printing of - * gridlines + * gridlines */ public void setPrintGridlines(boolean newPrintGridlines) { getSheet().getPrintGridlines().setPrintGridlines(newPrintGridlines); @@ -1027,6 +1051,7 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { /** * Gets the print setup object. + * * @return The user model for the print setup object. */ public HSSFPrintSetup getPrintSetup() { @@ -1799,10 +1824,9 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { return new HSSFPatriarch(this, agg); } - /** - * @deprecated (Sep 2008) use {@link #setColumnGroupCollapsed(int, boolean)} - */ - + /** + * @deprecated (Sep 2008) use {@link #setColumnGroupCollapsed(int, boolean)} + */ public void setColumnGroupCollapsed(short columnNumber, boolean collapsed) { setColumnGroupCollapsed(columnNumber & 0xFFFF, collapsed); } @@ -2070,4 +2094,160 @@ public final class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet { } return null; } + + + public CellRangeAddress getRepeatingRows() { + return getRepeatingRowsOrColums(true); + } + + + public CellRangeAddress getRepeatingColumns() { + return getRepeatingRowsOrColums(false); + } + + + public void setRepeatingRows(CellRangeAddress rowRangeRef) { + CellRangeAddress columnRangeRef = getRepeatingColumns(); + setRepeatingRowsAndColumns(rowRangeRef, columnRangeRef); + } + + + public void setRepeatingColumns(CellRangeAddress columnRangeRef) { + CellRangeAddress rowRangeRef = getRepeatingRows(); + setRepeatingRowsAndColumns(rowRangeRef, columnRangeRef); + } + + + private void setRepeatingRowsAndColumns( + CellRangeAddress rowDef, CellRangeAddress colDef) { + int sheetIndex = _workbook.getSheetIndex(this); + int maxRowIndex = SpreadsheetVersion.EXCEL97.getLastRowIndex(); + int maxColIndex = SpreadsheetVersion.EXCEL97.getLastColumnIndex(); + + int col1 = -1; + int col2 = -1; + int row1 = -1; + int row2 = -1; + + if (rowDef != null) { + row1 = rowDef.getFirstRow(); + row2 = rowDef.getLastRow(); + if ((row1 == -1 && row2 != -1) || (row1 > row2) + || (row1 < 0 || row1 > maxRowIndex) + || (row2 < 0 || row2 > maxRowIndex)) { + throw new IllegalArgumentException("Invalid row range specification"); + } + } + if (colDef != null) { + col1 = colDef.getFirstColumn(); + col2 = colDef.getLastColumn(); + if ((col1 == -1 && col2 != -1) || (col1 > col2) + || (col1 < 0 || col1 > maxColIndex) + || (col2 < 0 || col2 > maxColIndex)) { + throw new IllegalArgumentException("Invalid column range specification"); + } + } + + short externSheetIndex = + _workbook.getWorkbook().checkExternSheet(sheetIndex); + + boolean setBoth = rowDef != null && colDef != null; + boolean removeAll = rowDef == null && colDef == null; + + HSSFName name = _workbook.getBuiltInName( + NameRecord.BUILTIN_PRINT_TITLE, sheetIndex); + if (removeAll) { + if (name != null) { + _workbook.removeName(name); + } + return; + } + if (name == null) { + name = _workbook.createBuiltInName( + NameRecord.BUILTIN_PRINT_TITLE, sheetIndex); + } + + List<Ptg> ptgList = new ArrayList<Ptg>(); + if (setBoth) { + final int exprsSize = 2 * 11 + 1; // 2 * Area3DPtg.SIZE + UnionPtg.SIZE + ptgList.add(new MemFuncPtg(exprsSize)); + } + if (colDef != null) { + Area3DPtg colArea = new Area3DPtg(0, maxRowIndex, col1, col2, + false, false, false, false, externSheetIndex); + ptgList.add(colArea); + } + if (rowDef != null) { + Area3DPtg rowArea = new Area3DPtg(row1, row2, 0, maxColIndex, + false, false, false, false, externSheetIndex); + ptgList.add(rowArea); + } + if (setBoth) { + ptgList.add(UnionPtg.instance); + } + + Ptg[] ptgs = new Ptg[ptgList.size()]; + ptgList.toArray(ptgs); + name.setNameDefinition(ptgs); + + HSSFPrintSetup printSetup = getPrintSetup(); + printSetup.setValidSettings(false); + setActive(true); + } + + + private CellRangeAddress getRepeatingRowsOrColums(boolean rows) { + NameRecord rec = getBuiltinNameRecord(NameRecord.BUILTIN_PRINT_TITLE); + if (rec == null) { + return null; + } + + Ptg[] nameDefinition = rec.getNameDefinition(); + if (nameDefinition == null) { + return null; + } + + int maxRowIndex = SpreadsheetVersion.EXCEL97.getLastRowIndex(); + int maxColIndex = SpreadsheetVersion.EXCEL97.getLastColumnIndex(); + + for (Ptg ptg : nameDefinition) { + + if (ptg instanceof Area3DPtg) { + Area3DPtg areaPtg = (Area3DPtg) ptg; + + if (areaPtg.getFirstColumn() == 0 + && areaPtg.getLastColumn() == maxColIndex) { + if (rows) { + CellRangeAddress rowRange = new CellRangeAddress( + areaPtg.getFirstRow(), areaPtg.getLastRow(), -1, -1); + return rowRange; + } + } else if (areaPtg.getFirstRow() == 0 + && areaPtg.getLastRow() == maxRowIndex) { + if (!rows) { + CellRangeAddress columnRange = new CellRangeAddress(-1, -1, + areaPtg.getFirstColumn(), areaPtg.getLastColumn()); + return columnRange; + } + } + + } + + } + + return null; + } + + + private NameRecord getBuiltinNameRecord(byte builtinCode) { + int sheetIndex = _workbook.getSheetIndex(this); + int recIndex = + _workbook.findExistingBuiltinNameRecordIdx(sheetIndex, builtinCode); + if (recIndex == -1) { + return null; + } + return _workbook.getNameRecord(recIndex); + } + + } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java index cc6e40c271..f1828fa387 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java @@ -49,13 +49,10 @@ import org.apache.poi.poifs.filesystem.POIFSFileSystem; import org.apache.poi.ss.formula.FormulaShifter; import org.apache.poi.ss.formula.FormulaType; import org.apache.poi.ss.formula.SheetNameFormatter; -import org.apache.poi.ss.formula.ptg.Area3DPtg; -import org.apache.poi.ss.formula.ptg.MemFuncPtg; -import org.apache.poi.ss.formula.ptg.Ptg; -import org.apache.poi.ss.formula.ptg.UnionPtg; import org.apache.poi.ss.formula.udf.AggregatingUDFFinder; import org.apache.poi.ss.formula.udf.UDFFinder; import org.apache.poi.ss.usermodel.Row.MissingCellPolicy; +import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.WorkbookUtil; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; @@ -75,8 +72,6 @@ import org.apache.commons.codec.digest.DigestUtils; */ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.usermodel.Workbook { private static final Pattern COMMA_PATTERN = Pattern.compile(","); - private static final int MAX_ROW = 0xFFFF; - private static final short MAX_COLUMN = (short)0x00FF; /** * The maximum number of cell styles in a .xls workbook. @@ -957,84 +952,31 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss * @param endColumn 0 based end of repeating columns. * @param startRow 0 based start of repeating rows. * @param endRow 0 based end of repeating rows. + * + * @deprecated use {@link HSSFSheet#setRepeatingRows(CellRangeAddress)} + * or {@link HSSFSheet#setRepeatingColumns(CellRangeAddress)} */ public void setRepeatingRowsAndColumns(int sheetIndex, int startColumn, int endColumn, - int startRow, int endRow) - { - // Check arguments - if (startColumn == -1 && endColumn != -1) throw new IllegalArgumentException("Invalid column range specification"); - if (startRow == -1 && endRow != -1) throw new IllegalArgumentException("Invalid row range specification"); - if (startColumn < -1 || startColumn >= MAX_COLUMN) throw new IllegalArgumentException("Invalid column range specification"); - if (endColumn < -1 || endColumn >= MAX_COLUMN) throw new IllegalArgumentException("Invalid column range specification"); - if (startRow < -1 || startRow > MAX_ROW) throw new IllegalArgumentException("Invalid row range specification"); - if (endRow < -1 || endRow > MAX_ROW) throw new IllegalArgumentException("Invalid row range specification"); - if (startColumn > endColumn) throw new IllegalArgumentException("Invalid column range specification"); - if (startRow > endRow) throw new IllegalArgumentException("Invalid row range specification"); - - HSSFSheet sheet = getSheetAt(sheetIndex); - short externSheetIndex = getWorkbook().checkExternSheet(sheetIndex); - - boolean settingRowAndColumn = - startColumn != -1 && endColumn != -1 && startRow != -1 && endRow != -1; - boolean removingRange = - startColumn == -1 && endColumn == -1 && startRow == -1 && endRow == -1; - - int rowColHeaderNameIndex = findExistingBuiltinNameRecordIdx(sheetIndex, NameRecord.BUILTIN_PRINT_TITLE); - if (removingRange) { - if (rowColHeaderNameIndex >= 0) { - workbook.removeName(rowColHeaderNameIndex); - } - return; - } - boolean isNewRecord; - NameRecord nameRecord; - if (rowColHeaderNameIndex < 0) { - //does a lot of the house keeping for builtin records, like setting lengths to zero etc - nameRecord = workbook.createBuiltInName(NameRecord.BUILTIN_PRINT_TITLE, sheetIndex+1); - isNewRecord = true; - } else { - nameRecord = workbook.getNameRecord(rowColHeaderNameIndex); - isNewRecord = false; - } - - List temp = new ArrayList(); - - if (settingRowAndColumn) { - final int exprsSize = 2 * 11 + 1; // 2 * Area3DPtg.SIZE + UnionPtg.SIZE - temp.add(new MemFuncPtg(exprsSize)); - } - if (startColumn >= 0) { - Area3DPtg colArea = new Area3DPtg(0, MAX_ROW, startColumn, endColumn, - false, false, false, false, externSheetIndex); - temp.add(colArea); - } - if (startRow >= 0) { - Area3DPtg rowArea = new Area3DPtg(startRow, endRow, 0, MAX_COLUMN, - false, false, false, false, externSheetIndex); - temp.add(rowArea); - } - if (settingRowAndColumn) { - temp.add(UnionPtg.instance); - } - Ptg[] ptgs = new Ptg[temp.size()]; - temp.toArray(ptgs); - nameRecord.setNameDefinition(ptgs); + int startRow, int endRow) { + HSSFSheet sheet = getSheetAt(sheetIndex); - if (isNewRecord) - { - HSSFName newName = new HSSFName(this, nameRecord, nameRecord.isBuiltInName() ? null : workbook.getNameCommentRecord(nameRecord)); - names.add(newName); - } + CellRangeAddress rows = null; + CellRangeAddress cols = null; - HSSFPrintSetup printSetup = sheet.getPrintSetup(); - printSetup.setValidSettings(false); + if (startRow != -1) { + rows = new CellRangeAddress(startRow, endRow, -1, -1); + } + if (startColumn != -1) { + cols = new CellRangeAddress(-1, -1, startColumn, endColumn); + } - sheet.setActive(true); + sheet.setRepeatingRows(rows); + sheet.setRepeatingColumns(cols); } - private int findExistingBuiltinNameRecordIdx(int sheetIndex, byte builtinCode) { + int findExistingBuiltinNameRecordIdx(int sheetIndex, byte builtinCode) { for(int defNameIndex =0; defNameIndex<names.size(); defNameIndex++) { NameRecord r = workbook.getNameRecord(defNameIndex); if (r == null) { @@ -1050,6 +992,26 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss return -1; } + + HSSFName createBuiltInName(byte builtinCode, int sheetIndex) { + NameRecord nameRecord = + workbook.createBuiltInName(builtinCode, sheetIndex + 1); + HSSFName newName = new HSSFName(this, nameRecord, null); + names.add(newName); + return newName; + } + + + HSSFName getBuiltInName(byte builtinCode, int sheetIndex) { + int index = findExistingBuiltinNameRecordIdx(sheetIndex, builtinCode); + if (index < 0) { + return null; + } else { + return names.get(index); + } + } + + /** * create a new Font and add it to the workbook's font table * @return new font object @@ -1477,6 +1439,25 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss } + /** + * As {@link #getNameIndex(String)} is not necessarily unique + * (name + sheet index is unique), this method is more accurate. + * + * @param name the name whose index in the list of names of this workbook + * should be looked up. + * @return an index value >= 0 if the name was found; -1, if the name was + * not found + */ + int getNameIndex(HSSFName name) { + for (int k = 0; k < names.size(); k++) { + if (name == names.get(k)) { + return k; + } + } + return -1; + } + + public void removeName(int index){ names.remove(index); workbook.removeName(index); @@ -1497,10 +1478,21 @@ public final class HSSFWorkbook extends POIDocument implements org.apache.poi.ss public void removeName(String name) { int index = getNameIndex(name); - removeName(index); } + + /** + * As {@link #removeName(String)} is not necessarily unique + * (name + sheet index is unique), this method is more accurate. + * + * @param name the name to remove. + */ + void removeName(HSSFName name) { + int index = getNameIndex(name); + removeName(index); + } + public HSSFPalette getCustomPalette() { return new HSSFPalette(workbook.getCustomPalette()); diff --git a/src/java/org/apache/poi/ss/format/CellDateFormatter.java b/src/java/org/apache/poi/ss/format/CellDateFormatter.java index 33a9118b15..e45fa267df 100644 --- a/src/java/org/apache/poi/ss/format/CellDateFormatter.java +++ b/src/java/org/apache/poi/ss/format/CellDateFormatter.java @@ -150,7 +150,10 @@ public class CellDateFormatter extends CellFormatter { StringBuffer descBuf = CellFormatPart.parseFormat(format, CellFormatType.DATE, partHandler); partHandler.finish(descBuf); - dateFmt = new SimpleDateFormat(descBuf.toString()); + // tweak the format pattern to pass tests on JDK 1.7, + // See https://issues.apache.org/bugzilla/show_bug.cgi?id=53369 + String ptrn = descBuf.toString().replaceAll("((y)(?!y))(?<!yy)", "yy"); + dateFmt = new SimpleDateFormat(ptrn, LOCALE); } /** {@inheritDoc} */ @@ -214,4 +217,4 @@ public class CellDateFormatter extends CellFormatter { public void simpleValue(StringBuffer toAppendTo, Object value) { SIMPLE_DATE.formatValue(toAppendTo, value); } -}
\ No newline at end of file +} diff --git a/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java b/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java index 4e45a76a39..79676d6a07 100644 --- a/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java +++ b/src/java/org/apache/poi/ss/formula/WorkbookEvaluator.java @@ -637,10 +637,10 @@ public final class WorkbookEvaluator { * YK: Used by OperationEvaluationContext to resolve indirect names. */ /*package*/ ValueEval evaluateNameFormula(Ptg[] ptgs, OperationEvaluationContext ec) { - if (ptgs.length > 1) { - throw new RuntimeException("Complex name formulas not supported yet"); - } - return getEvalForPtg(ptgs[0], ec); + if (ptgs.length == 1) { + return getEvalForPtg(ptgs[0], ec); + } + return evaluateFormula(ec, ptgs); } /** diff --git a/src/java/org/apache/poi/ss/usermodel/DataFormatter.java b/src/java/org/apache/poi/ss/usermodel/DataFormatter.java index 3f09aafa2b..cfa20914f8 100644 --- a/src/java/org/apache/poi/ss/usermodel/DataFormatter.java +++ b/src/java/org/apache/poi/ss/usermodel/DataFormatter.java @@ -111,8 +111,11 @@ public class DataFormatter { /** Pattern to find "AM/PM" marker */ private static final Pattern amPmPattern = Pattern.compile("((A|P)[M/P]*)", Pattern.CASE_INSENSITIVE); - /** A regex to find patterns like [$$-1009] and [$?-452]. */ - private static final Pattern specialPatternGroup = Pattern.compile("(\\[\\$[^-\\]]*-[0-9A-Z]+\\])"); + /** + * A regex to find locale patterns like [$$-1009] and [$?-452]. + * Note that we don't currently process these into locales + */ + private static final Pattern localePatternGroup = Pattern.compile("(\\[\\$[^-\\]]*-[0-9A-Z]+\\])"); /** * A regex to match the colour formattings rules. @@ -278,12 +281,16 @@ public class DataFormatter { if (format != null) { return format; } + + // Is it one of the special built in types, General or @? if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) { if (isWholeNumber(cellValue)) { return generalWholeNumFormat; } return generalDecimalNumFormat; } + + // Build a formatter, and cache it format = createFormat(cellValue, formatIndex, formatStr); formats.put(formatStr, format); return format; @@ -323,8 +330,8 @@ public class DataFormatter { colourM = colorPattern.matcher(formatStr); } - // try to extract special characters like currency - Matcher m = specialPatternGroup.matcher(formatStr); + // Strip off the locale information, we use an instance-wide locale for everything + Matcher m = localePatternGroup.matcher(formatStr); while(m.find()) { String match = m.group(); String symbol = match.substring(match.indexOf('$') + 1, match.indexOf('-')); @@ -336,12 +343,20 @@ public class DataFormatter { symbol = sb.toString(); } formatStr = m.replaceAll(symbol); - m = specialPatternGroup.matcher(formatStr); + m = localePatternGroup.matcher(formatStr); } + // Check for special cases if(formatStr == null || formatStr.trim().length() == 0) { return getDefaultFormat(cellValue); } + + if ("General".equalsIgnoreCase(formatStr) || "@".equals(formatStr)) { + if (isWholeNumber(cellValue)) { + return generalWholeNumFormat; + } + return generalDecimalNumFormat; + } if(DateUtil.isADateFormat(formatIndex,formatStr) && DateUtil.isValidExcelDate(cellValue)) { diff --git a/src/java/org/apache/poi/ss/usermodel/Sheet.java b/src/java/org/apache/poi/ss/usermodel/Sheet.java index 5be4f1f5e6..8b6b41678f 100644 --- a/src/java/org/apache/poi/ss/usermodel/Sheet.java +++ b/src/java/org/apache/poi/ss/usermodel/Sheet.java @@ -927,4 +927,95 @@ public interface Sheet extends Iterable<Row> { */ SheetConditionalFormatting getSheetConditionalFormatting(); + + /** + * Gets the repeating rows used when printing the sheet, as found in + * File->PageSetup->Sheet. + * <p/> + * Repeating rows cover a range of contiguous rows, e.g.: + * <pre> + * Sheet1!$1:$1 + * Sheet2!$5:$8 + * </pre> + * The {@link CellRangeAddress} returned contains a column part which spans + * all columns, and a row part which specifies the contiguous range of + * repeating rows. + * <p/> + * If the Sheet does not have any repeating rows defined, null is returned. + * + * @return an {@link CellRangeAddress} containing the repeating rows for the + * Sheet, or null. + */ + CellRangeAddress getRepeatingRows(); + + + /** + * Gets the repeating columns used when printing the sheet, as found in + * File->PageSetup->Sheet. + * <p/> + * Repeating columns cover a range of contiguous columns, e.g.: + * <pre> + * Sheet1!$A:$A + * Sheet2!$C:$F + * </pre> + * The {@link CellRangeAddress} returned contains a row part which spans all + * rows, and a column part which specifies the contiguous range of + * repeating columns. + * <p/> + * If the Sheet does not have any repeating columns defined, null is + * returned. + * + * @return an {@link CellRangeAddress} containing the repeating columns for + * the Sheet, or null. + */ + CellRangeAddress getRepeatingColumns(); + + + /** + * Sets the repeating rows used when printing the sheet, as found in + * File->PageSetup->Sheet. + * <p/> + * Repeating rows cover a range of contiguous rows, e.g.: + * <pre> + * Sheet1!$1:$1 + * Sheet2!$5:$8</pre> + * The parameter {@link CellRangeAddress} should specify a column part + * which spans all columns, and a row part which specifies the contiguous + * range of repeating rows, e.g.: + * <pre> + * sheet.setRepeatingRows(CellRangeAddress.valueOf("2:3"));</pre> + * A null parameter value indicates that repeating rows should be removed + * from the Sheet: + * <pre> + * sheet.setRepeatingRows(null);</pre> + * + * @param rowRangeRef a {@link CellRangeAddress} containing the repeating + * rows for the Sheet, or null. + */ + void setRepeatingRows(CellRangeAddress rowRangeRef); + + + /** + * Sets the repeating columns used when printing the sheet, as found in + * File->PageSetup->Sheet. + * <p/> + * Repeating columns cover a range of contiguous columns, e.g.: + * <pre> + * Sheet1!$A:$A + * Sheet2!$C:$F</pre> + * The parameter {@link CellRangeAddress} should specify a row part + * which spans all rows, and a column part which specifies the contiguous + * range of repeating columns, e.g.: + * <pre> + * sheet.setRepeatingColumns(CellRangeAddress.valueOf("B:C"));</pre> + * A null parameter value indicates that repeating columns should be removed + * from the Sheet: + * <pre> + * sheet.setRepeatingColumns(null);</pre> + * + * @param columnRangeRef a {@link CellRangeAddress} containing the repeating + * columns for the Sheet, or null. + */ + void setRepeatingColumns(CellRangeAddress columnRangeRef); + } diff --git a/src/java/org/apache/poi/ss/usermodel/Workbook.java b/src/java/org/apache/poi/ss/usermodel/Workbook.java index ce117f4cde..a476a74f8c 100644 --- a/src/java/org/apache/poi/ss/usermodel/Workbook.java +++ b/src/java/org/apache/poi/ss/usermodel/Workbook.java @@ -23,6 +23,7 @@ import java.util.List; import org.apache.poi.ss.formula.udf.UDFFinder; import org.apache.poi.ss.usermodel.Row.MissingCellPolicy; +import org.apache.poi.ss.util.CellRangeAddress; /** * High level representation of a Excel workbook. This is the first object most users @@ -284,6 +285,9 @@ public interface Workbook { * @param endColumn 0 based end of repeating columns. * @param startRow 0 based start of repeating rows. * @param endRow 0 based end of repeating rows. + * + * @deprecated use {@link Sheet#setRepeatingRows(CellRangeAddress)} + * or {@link Sheet#setRepeatingColumns(CellRangeAddress)} */ void setRepeatingRowsAndColumns(int sheetIndex, int startColumn, int endColumn, int startRow, int endRow); diff --git a/src/java/org/apache/poi/ss/util/CellRangeAddress.java b/src/java/org/apache/poi/ss/util/CellRangeAddress.java index 4a44a6c8a5..b69476ea24 100644 --- a/src/java/org/apache/poi/ss/util/CellRangeAddress.java +++ b/src/java/org/apache/poi/ss/util/CellRangeAddress.java @@ -100,7 +100,10 @@ public class CellRangeAddress extends CellRangeAddressBase { sb.append(cellRefFrom.formatAsString()); //for a single-cell reference return A1 instead of A1:A1 - if(!cellRefFrom.equals(cellRefTo)){ + //for full-column ranges or full-row ranges return A:A instead of A, + //and 1:1 instead of 1 + if(!cellRefFrom.equals(cellRefTo) + || isFullColumnRange() || isFullRowRange()){ sb.append(':'); sb.append(cellRefTo.formatAsString()); } @@ -108,8 +111,12 @@ public class CellRangeAddress extends CellRangeAddressBase { } /** - * @param ref usually a standard area ref (e.g. "B1:D8"). May be a single cell - * ref (e.g. "B5") in which case the result is a 1 x 1 cell range. + * Creates a CellRangeAddress from a cell range reference string. + * + * @param ref usually a standard area ref (e.g. "B1:D8"). May be a single + * cell ref (e.g. "B5") in which case the result is a 1 x 1 cell + * range. May also be a whole row range (e.g. "3:5"), or a whole + * column range (e.g. "C:F") */ public static CellRangeAddress valueOf(String ref) { int sep = ref.indexOf(":"); diff --git a/src/java/org/apache/poi/ss/util/CellRangeAddressBase.java b/src/java/org/apache/poi/ss/util/CellRangeAddressBase.java index e8d9c9100a..12aaa6f629 100644 --- a/src/java/org/apache/poi/ss/util/CellRangeAddressBase.java +++ b/src/java/org/apache/poi/ss/util/CellRangeAddressBase.java @@ -76,11 +76,13 @@ public abstract class CellRangeAddressBase { //TODO use the correct SpreadsheetVersion public final boolean isFullColumnRange() { - return _firstRow == 0 && _lastRow == SpreadsheetVersion.EXCEL97.getLastRowIndex(); + return (_firstRow == 0 && _lastRow == SpreadsheetVersion.EXCEL97.getLastRowIndex()) + || (_firstRow == -1 && _lastRow == -1); } //TODO use the correct SpreadsheetVersion public final boolean isFullRowRange() { - return _firstCol == 0 && _lastCol == SpreadsheetVersion.EXCEL97.getLastColumnIndex(); + return (_firstCol == 0 && _lastCol == SpreadsheetVersion.EXCEL97.getLastColumnIndex()) + || (_firstCol == -1 && _lastCol == -1); } /** diff --git a/src/java/org/apache/poi/ss/util/CellReference.java b/src/java/org/apache/poi/ss/util/CellReference.java index 12120b7489..f7c37e3005 100644 --- a/src/java/org/apache/poi/ss/util/CellReference.java +++ b/src/java/org/apache/poi/ss/util/CellReference.java @@ -91,25 +91,28 @@ public class CellReference { String[] parts = separateRefParts(cellRef); _sheetName = parts[0]; + String colRef = parts[1]; - if (colRef.length() < 1) { - throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'"); - } - _isColAbs = colRef.charAt(0) == '$'; + _isColAbs = (colRef.length() > 0) && colRef.charAt(0) == '$'; if (_isColAbs) { - colRef=colRef.substring(1); + colRef = colRef.substring(1); + } + if (colRef.length() == 0) { + _colIndex = -1; + } else { + _colIndex = convertColStringToIndex(colRef); } - _colIndex = convertColStringToIndex(colRef); String rowRef=parts[2]; - if (rowRef.length() < 1) { - throw new IllegalArgumentException("Invalid Formula cell reference: '"+cellRef+"'"); - } - _isRowAbs = rowRef.charAt(0) == '$'; + _isRowAbs = (rowRef.length() > 0) && rowRef.charAt(0) == '$'; if (_isRowAbs) { - rowRef=rowRef.substring(1); + rowRef = rowRef.substring(1); + } + if (rowRef.length() == 0) { + _rowIndex = -1; + } else { + _rowIndex = Integer.parseInt(rowRef)-1; // -1 to convert 1-based to zero-based } - _rowIndex = Integer.parseInt(rowRef)-1; // -1 to convert 1-based to zero-based } public CellReference(int pRow, int pCol) { @@ -482,14 +485,18 @@ public class CellReference { * Sheet name is not included. */ /* package */ void appendCellReference(StringBuffer sb) { - if(_isColAbs) { - sb.append(ABSOLUTE_REFERENCE_MARKER); - } - sb.append( convertNumToColString(_colIndex)); - if(_isRowAbs) { - sb.append(ABSOLUTE_REFERENCE_MARKER); + if (_colIndex != -1) { + if(_isColAbs) { + sb.append(ABSOLUTE_REFERENCE_MARKER); + } + sb.append( convertNumToColString(_colIndex)); + } + if (_rowIndex != -1) { + if(_isRowAbs) { + sb.append(ABSOLUTE_REFERENCE_MARKER); + } + sb.append(_rowIndex+1); } - sb.append(_rowIndex+1); } /** diff --git a/src/java/org/apache/poi/util/LittleEndian.java b/src/java/org/apache/poi/util/LittleEndian.java index c07c4beb30..6ceb12a4a1 100644 --- a/src/java/org/apache/poi/util/LittleEndian.java +++ b/src/java/org/apache/poi/util/LittleEndian.java @@ -724,6 +724,24 @@ public class LittleEndian implements LittleEndianConsts } return ( ch4 << 24 ) + ( ch3 << 16 ) + ( ch2 << 8 ) + ( ch1 << 0 ); } + + /** + * get an unsigned int value from an InputStream + * + * @param stream + * the InputStream from which the int is to be read + * @return the unsigned int (32-bit) value + * @exception IOException + * will be propagated back to the caller + * @exception BufferUnderrunException + * if the stream cannot provide enough bytes + */ + public static long readUInt( InputStream stream ) throws IOException, + BufferUnderrunException + { + long retNum = readInt(stream); + return retNum & 0x00FFFFFFFFl; + } /** * get a long value from an InputStream diff --git a/src/java/org/apache/poi/util/PngUtils.java b/src/java/org/apache/poi/util/PngUtils.java new file mode 100644 index 0000000000..785675830b --- /dev/null +++ b/src/java/org/apache/poi/util/PngUtils.java @@ -0,0 +1,57 @@ +/* ====================================================================
+ 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.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public final class PngUtils {
+
+ /**
+ * File header for PNG format.
+ */
+ private static final byte[] PNG_FILE_HEADER =
+ new byte[] { (byte) 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
+
+ private PngUtils() {
+ // no instances of this class
+ }
+
+ /**
+ * Checks if the offset matches the PNG header.
+ *
+ * @param data the data to check.
+ * @param offset the offset to check at.
+ * @return {@code true} if the offset matches.
+ */
+ public static boolean matchesPngHeader(byte[] data, int offset) {
+ if (data == null || data.length - offset < PNG_FILE_HEADER.length) {
+ return false;
+ }
+
+ for (int i = 0; i < PNG_FILE_HEADER.length; i++) {
+ if (PNG_FILE_HEADER[i] != data[i + offset]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/OPCPackage.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/OPCPackage.java index 7a0da815b7..26c17c5404 100644 --- a/src/ooxml/java/org/apache/poi/openxml4j/opc/OPCPackage.java +++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/OPCPackage.java @@ -186,6 +186,20 @@ public abstract class OPCPackage implements RelationshipSource, Closeable { return open(path, defaultPackageAccess); } + /** + * Open a package with read/write permission. + * + * @param file + * The file to open. + * @return A Package object, else <b>null</b>. + * @throws InvalidFormatException + * If the specified file doesn't exist, and a parsing error + * occur. + */ + public static OPCPackage open(File file) throws InvalidFormatException { + return open(file, defaultPackageAccess); + } + /** * Open a package. * @@ -212,6 +226,31 @@ public abstract class OPCPackage implements RelationshipSource, Closeable { return pack; } + /** + * Open a package. + * + * @param file + * The file to open. + * @param access + * PackageBase access. + * @return A PackageBase object, else <b>null</b>. + * @throws InvalidFormatException + * If the specified file doesn't exist, and a parsing error + * occur. + */ + public static OPCPackage open(File file, PackageAccess access) + throws InvalidFormatException { + if (file == null|| (file.exists() && file.isDirectory())) + throw new IllegalArgumentException("file"); + + OPCPackage pack = new ZipPackage(file, access); + if (pack.partList == null && access != PackageAccess.WRITE) { + pack.getParts(); + } + pack.originalPackagePath = file.getAbsolutePath(); + return pack; + } + /** * Open a package. * diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/ZipPackage.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/ZipPackage.java index 5ac16d3a0b..bf98cdd29b 100644 --- a/src/ooxml/java/org/apache/poi/openxml4j/opc/ZipPackage.java +++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/ZipPackage.java @@ -85,30 +85,55 @@ public final class ZipPackage extends Package { ); } - /** - * Constructor. Opens a Zip based Open XML document. - * - * @param path - * The path of the file to open or create. - * @param access - * The package access mode. - * @throws InvalidFormatException - * If the content type part parsing encounters an error. - */ - ZipPackage(String path, PackageAccess access) { - super(access); - - ZipFile zipFile = null; - - try { - zipFile = ZipHelper.openZipFile(path); - } catch (IOException e) { - throw new InvalidOperationException( - "Can't open the specified file: '" + path + "'", e); - } - - this.zipArchive = new ZipFileZipEntrySource(zipFile); - } + /** + * Constructor. Opens a Zip based Open XML document. + * + * @param path + * The path of the file to open or create. + * @param access + * The package access mode. + * @throws InvalidFormatException + * If the content type part parsing encounters an error. + */ + ZipPackage(String path, PackageAccess access) { + super(access); + + ZipFile zipFile = null; + + try { + zipFile = ZipHelper.openZipFile(path); + } catch (IOException e) { + throw new InvalidOperationException( + "Can't open the specified file: '" + path + "'", e); + } + + this.zipArchive = new ZipFileZipEntrySource(zipFile); + } + + /** + * Constructor. Opens a Zip based Open XML document. + * + * @param file + * The file to open or create. + * @param access + * The package access mode. + * @throws InvalidFormatException + * If the content type part parsing encounters an error. + */ + ZipPackage(File file, PackageAccess access) { + super(access); + + ZipFile zipFile = null; + + try { + zipFile = ZipHelper.openZipFile(file); + } catch (IOException e) { + throw new InvalidOperationException( + "Can't open the specified file: '" + file + "'", e); + } + + this.zipArchive = new ZipFileZipEntrySource(zipFile); + } /** * Retrieves the parts from this package. We assume that the package has not diff --git a/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ZipHelper.java b/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ZipHelper.java index e808dc61e9..9598b05cbe 100644 --- a/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ZipHelper.java +++ b/src/ooxml/java/org/apache/poi/openxml4j/opc/internal/ZipHelper.java @@ -72,11 +72,12 @@ public final class ZipHelper { * Retrieve the Zip entry of the content types part. */ public static ZipEntry getContentTypeZipEntry(ZipPackage pkg) { - Enumeration entries = pkg.getZipArchive().getEntries(); + Enumeration<? extends ZipEntry> entries = pkg.getZipArchive().getEntries(); + // Enumerate through the Zip entries until we find the one named // '[Content_Types].xml'. while (entries.hasMoreElements()) { - ZipEntry entry = (ZipEntry) entries.nextElement(); + ZipEntry entry = entries.nextElement(); if (entry.getName().equals( ContentTypeManager.CONTENT_TYPES_PART_NAME)) return entry; @@ -141,6 +142,21 @@ public final class ZipHelper { } } + /** + * Opens the specified file as a zip, or returns null if no such file exists + * + * @param file + * The file to open. + * @return The zip archive freshly open. + */ + public static ZipFile openZipFile(File file) throws IOException { + if (!file.exists()) { + return null; + } + + return new ZipFile(file); + } + /** * Retrieve and open a zip file with the specified path. * diff --git a/src/ooxml/java/org/apache/poi/ss/usermodel/WorkbookFactory.java b/src/ooxml/java/org/apache/poi/ss/usermodel/WorkbookFactory.java index 41f70d92bb..56a9f89028 100644 --- a/src/ooxml/java/org/apache/poi/ss/usermodel/WorkbookFactory.java +++ b/src/ooxml/java/org/apache/poi/ss/usermodel/WorkbookFactory.java @@ -87,7 +87,7 @@ public class WorkbookFactory { NPOIFSFileSystem fs = new NPOIFSFileSystem(file); return new HSSFWorkbook(fs.getRoot(), true); } catch(OfficeXmlFileException e) { - OPCPackage pkg = OPCPackage.openOrCreate(file); + OPCPackage pkg = OPCPackage.open(file); return new XSSFWorkbook(pkg); } } diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSimpleShape.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSimpleShape.java index 6e4768399b..d6cfa7fba3 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSimpleShape.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFSimpleShape.java @@ -299,7 +299,7 @@ public abstract class XSLFSimpleShape extends XSLFShape { public void setLineWidth(double width) {
CTShapeProperties spPr = getSpPr();
if (width == 0.) {
- if (spPr.isSetLn())
+ if (spPr.isSetLn() && spPr.getLn().isSetW())
spPr.getLn().unsetW();
} else {
CTLineProperties ln = spPr.isSetLn() ? spPr.getLn() : spPr
@@ -353,7 +353,7 @@ public abstract class XSLFSimpleShape extends XSLFShape { public void setLineDash(LineDash dash) {
CTShapeProperties spPr = getSpPr();
if (dash == null) {
- if (spPr.isSetLn())
+ if (spPr.isSetLn() && spPr.getLn().isSetPrstDash())
spPr.getLn().unsetPrstDash();
} else {
CTPresetLineDashProperties val = CTPresetLineDashProperties.Factory
@@ -406,7 +406,7 @@ public abstract class XSLFSimpleShape extends XSLFShape { public void setLineCap(LineCap cap) {
CTShapeProperties spPr = getSpPr();
if (cap == null) {
- if (spPr.isSetLn())
+ if (spPr.isSetLn() && spPr.getLn().isSetCap())
spPr.getLn().unsetCap();
} else {
CTLineProperties ln = spPr.isSetLn() ? spPr.getLn() : spPr
diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java index 73c2e52d99..a932fb5cd3 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextParagraph.java @@ -425,7 +425,13 @@ public class XSLFTextParagraph implements Iterable<XSLFTextRun>{ }
};
fetchParagraphProperty(fetcher);
- return fetcher.getValue() == null ? getDefaultTabSize() : fetcher.getValue();
+ return fetcher.getValue() == null ? 0. : fetcher.getValue();
+ }
+
+ public void addTabStop(double value){
+ CTTextParagraphProperties pr = _p.isSetPPr() ? _p.getPPr() : _p.addNewPPr();
+ CTTextTabStopList tabStops = pr.isSetTabLst() ? pr.getTabLst() : pr.addNewTabLst();
+ tabStops.addNewTab().setPos(Units.toEMU(value));
}
/**
diff --git a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java index d7a10c20c8..c6f0591704 100644 --- a/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java +++ b/src/ooxml/java/org/apache/poi/xslf/usermodel/XSLFTextRun.java @@ -353,6 +353,39 @@ public class XSLFTextRun { }
/**
+ * Set the baseline for both the superscript and subscript fonts.
+ * <p>
+ * The size is specified using a percentage.
+ * Positive values indicate superscript, negative values indicate subscript.
+ * </p>
+ *
+ * @param baselineOffset
+ */
+ public void setBaselineOffset(double baselineOffset){
+ getRPr().setBaseline((int) baselineOffset * 1000);
+ }
+
+ /**
+ * Set whether the text in this run is formatted as superscript.
+ * Default base line offset is 30%
+ *
+ * @see #setBaselineOffset(double)
+ */
+ public void setSuperscript(boolean flag){
+ setBaselineOffset(flag ? 30. : 0.);
+ }
+
+ /**
+ * Set whether the text in this run is formatted as subscript.
+ * Default base line offset is -25%.
+ *
+ * @see #setBaselineOffset(double)
+ */
+ public void setSubscript(boolean flag){
+ setBaselineOffset(flag ? -25.0 : 0.);
+ }
+
+ /**
* @return whether a run of text will be formatted as a superscript text. Default is false.
*/
public boolean isSubscript() {
diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFCell.java b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFCell.java index bd57fc3ded..46ca95fcf7 100644 --- a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFCell.java +++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFCell.java @@ -728,10 +728,6 @@ public class SXSSFCell implements Cell } void ensureTypeOrFormulaType(int type) { - assert type==CELL_TYPE_NUMERIC|| - type==CELL_TYPE_STRING|| - type==CELL_TYPE_BOOLEAN|| - type==CELL_TYPE_ERROR; if(_value.getType()==type) { if(type==CELL_TYPE_STRING&&((StringValue)_value).isRichText()) diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFRow.java b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFRow.java index 1bd53bc8b2..a762de822b 100644 --- a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFRow.java +++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFRow.java @@ -212,7 +212,6 @@ public class SXSSFRow implements Row */ public Cell getCell(int cellnum, MissingCellPolicy policy) { - assert false; Cell cell = getCell(cellnum); if(policy == RETURN_NULL_AND_BLANK) { diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFSheet.java b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFSheet.java index afe8612002..77ae26f76a 100644 --- a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFSheet.java +++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFSheet.java @@ -17,19 +17,32 @@ package org.apache.poi.xssf.streaming; -import java.io.*; +import java.io.IOException; +import java.io.InputStream; import java.util.Iterator; -import java.util.TreeMap; import java.util.Map; +import java.util.TreeMap; +import org.apache.poi.hssf.util.PaneInformation; import org.apache.poi.ss.SpreadsheetVersion; -import org.apache.poi.ss.usermodel.*; - +import org.apache.poi.ss.usermodel.AutoFilter; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellRange; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.Comment; +import org.apache.poi.ss.usermodel.DataValidation; +import org.apache.poi.ss.usermodel.DataValidationHelper; +import org.apache.poi.ss.usermodel.Drawing; +import org.apache.poi.ss.usermodel.Footer; +import org.apache.poi.ss.usermodel.Header; +import org.apache.poi.ss.usermodel.PrintSetup; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.SheetConditionalFormatting; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.SheetUtil; import org.apache.poi.xssf.usermodel.XSSFSheet; - -import org.apache.poi.hssf.util.PaneInformation; -import org.apache.poi.ss.util.CellRangeAddress; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTSheetFormatPr; import org.openxmlformats.schemas.spreadsheetml.x2006.main.CTWorksheet; @@ -1263,7 +1276,27 @@ public class SXSSFSheet implements Sheet, Cloneable public SheetConditionalFormatting getSheetConditionalFormatting(){ return _sh.getSheetConditionalFormatting(); } - + + + public CellRangeAddress getRepeatingRows() { + return _sh.getRepeatingRows(); + } + + + public CellRangeAddress getRepeatingColumns() { + return _sh.getRepeatingColumns(); + } + + public void setRepeatingRows(CellRangeAddress rowRangeRef) { + _sh.setRepeatingRows(rowRangeRef); + } + + public void setRepeatingColumns(CellRangeAddress columnRangeRef) { + _sh.setRepeatingColumns(columnRangeRef); + } + + + //end of interface implementation /** * Specifies how many rows can be accessed at most via getRow(). @@ -1330,7 +1363,6 @@ public class SXSSFSheet implements Sheet, Cloneable if(entry.getValue()==row) return entry.getKey().intValue(); } - assert false; return -1; } } diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFWorkbook.java index a79f1aad02..73c4b934cf 100644 --- a/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SXSSFWorkbook.java @@ -42,6 +42,7 @@ import java.util.zip.ZipEntry; import org.apache.poi.ss.formula.udf.UDFFinder; import org.apache.poi.ss.usermodel.Row.MissingCellPolicy; +import org.apache.poi.ss.util.CellRangeAddress; /** * Streaming version of XSSFWorkbook implementing the "BigGridDemo" strategy. @@ -244,7 +245,6 @@ public class SXSSFWorkbook implements Workbook XSSFSheet getXSSFSheet(SXSSFSheet sheet) { XSSFSheet result=_sxFromXHash.get(sheet); - assert result!=null; return result; } @@ -543,7 +543,6 @@ public class SXSSFWorkbook implements Workbook */ public int getSheetIndex(Sheet sheet) { - assert sheet instanceof SXSSFSheet; return _wb.getSheetIndex(getXSSFSheet((SXSSFSheet)sheet)); } @@ -664,6 +663,9 @@ public class SXSSFWorkbook implements Workbook * @param endColumn 0 based end of repeating columns. * @param startRow 0 based start of repeating rows. * @param endRow 0 based end of repeating rows. + * + * @deprecated use {@link SXSSFSheet#setRepeatingRows(CellRangeAddress)} + * or {@link SXSSFSheet#setRepeatingColumns(CellRangeAddress)} */ public void setRepeatingRowsAndColumns(int sheetIndex, int startColumn, int endColumn, int startRow, int endRow) { diff --git a/src/ooxml/java/org/apache/poi/xssf/streaming/SheetDataWriter.java b/src/ooxml/java/org/apache/poi/xssf/streaming/SheetDataWriter.java index 1823fec798..f6c98317f5 100644 --- a/src/ooxml/java/org/apache/poi/xssf/streaming/SheetDataWriter.java +++ b/src/ooxml/java/org/apache/poi/xssf/streaming/SheetDataWriter.java @@ -204,7 +204,6 @@ public class SheetDataWriter { break;
}
default: {
- assert false;
throw new RuntimeException("Huh?");
}
}
@@ -279,6 +278,9 @@ public class SheetDataWriter { // the same rule applies to unicode surrogates and "not a character" symbols.
if( c < ' ' || Character.isLowSurrogate(c) || Character.isHighSurrogate(c) ||
('\uFFFE' <= c && c <= '\uFFFF')) {
+ if (counter > last) {
+ _out.write(chars, last, counter - last);
+ }
_out.write('?');
last = counter + 1;
}
diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java index fbed5b06dd..ccf2419873 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFSheet.java @@ -41,6 +41,7 @@ import org.apache.poi.openxml4j.opc.PackageRelationship; import org.apache.poi.openxml4j.opc.PackageRelationshipCollection; import org.apache.poi.ss.SpreadsheetVersion; import org.apache.poi.ss.formula.FormulaShifter; +import org.apache.poi.ss.formula.SheetNameFormatter; import org.apache.poi.ss.usermodel.*; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.CellRangeAddressList; @@ -3185,4 +3186,162 @@ public class XSSFSheet extends POIXMLDocumentPart implements Sheet { color.setIndexed(colorIndex); pr.setTabColor(color); } + + + public CellRangeAddress getRepeatingRows() { + return getRepeatingRowsOrColums(true); + } + + + public CellRangeAddress getRepeatingColumns() { + return getRepeatingRowsOrColums(false); + } + + public void setRepeatingRows(CellRangeAddress rowRangeRef) { + CellRangeAddress columnRangeRef = getRepeatingColumns(); + setRepeatingRowsAndColumns(rowRangeRef, columnRangeRef); + } + + + public void setRepeatingColumns(CellRangeAddress columnRangeRef) { + CellRangeAddress rowRangeRef = getRepeatingRows(); + setRepeatingRowsAndColumns(rowRangeRef, columnRangeRef); + } + + + private void setRepeatingRowsAndColumns( + CellRangeAddress rowDef, CellRangeAddress colDef) { + int col1 = -1; + int col2 = -1; + int row1 = -1; + int row2 = -1; + + if (rowDef != null) { + row1 = rowDef.getFirstRow(); + row2 = rowDef.getLastRow(); + if ((row1 == -1 && row2 != -1) + || row1 < -1 || row2 < -1 || row1 > row2) { + throw new IllegalArgumentException("Invalid row range specification"); + } + } + if (colDef != null) { + col1 = colDef.getFirstColumn(); + col2 = colDef.getLastColumn(); + if ((col1 == -1 && col2 != -1) + || col1 < -1 || col2 < -1 || col1 > col2) { + throw new IllegalArgumentException( + "Invalid column range specification"); + } + } + + int sheetIndex = getWorkbook().getSheetIndex(this); + + boolean removeAll = rowDef == null && colDef == null; + + XSSFName name = getWorkbook().getBuiltInName( + XSSFName.BUILTIN_PRINT_TITLE, sheetIndex); + if (removeAll) { + if (name != null) { + getWorkbook().removeName(name); + } + return; + } + if (name == null) { + name = getWorkbook().createBuiltInName( + XSSFName.BUILTIN_PRINT_TITLE, sheetIndex); + } + + String reference = getReferenceBuiltInRecord( + name.getSheetName(), col1, col2, row1, row2); + name.setRefersToFormula(reference); + + // If the print setup isn't currently defined, then add it + // in but without printer defaults + // If it's already there, leave it as-is! + if (worksheet.isSetPageSetup() && worksheet.isSetPageMargins()) { + // Everything we need is already there + } else { + // Have initial ones put in place + getPrintSetup().setValidSettings(false); + } + } + + private static String getReferenceBuiltInRecord( + String sheetName, int startC, int endC, int startR, int endR) { + // Excel example for built-in title: + // 'second sheet'!$E:$F,'second sheet'!$2:$3 + + CellReference colRef = + new CellReference(sheetName, 0, startC, true, true); + CellReference colRef2 = + new CellReference(sheetName, 0, endC, true, true); + CellReference rowRef = + new CellReference(sheetName, startR, 0, true, true); + CellReference rowRef2 = + new CellReference(sheetName, endR, 0, true, true); + + String escapedName = SheetNameFormatter.format(sheetName); + + String c = ""; + String r = ""; + + if(startC == -1 && endC == -1) { + } else { + c = escapedName + "!$" + colRef.getCellRefParts()[2] + + ":$" + colRef2.getCellRefParts()[2]; + } + + if (startR == -1 && endR == -1) { + + } else if (!rowRef.getCellRefParts()[1].equals("0") + && !rowRef2.getCellRefParts()[1].equals("0")) { + r = escapedName + "!$" + rowRef.getCellRefParts()[1] + + ":$" + rowRef2.getCellRefParts()[1]; + } + + StringBuffer rng = new StringBuffer(); + rng.append(c); + if(rng.length() > 0 && r.length() > 0) { + rng.append(','); + } + rng.append(r); + return rng.toString(); + } + + + private CellRangeAddress getRepeatingRowsOrColums(boolean rows) { + int sheetIndex = getWorkbook().getSheetIndex(this); + XSSFName name = getWorkbook().getBuiltInName( + XSSFName.BUILTIN_PRINT_TITLE, sheetIndex); + if (name == null ) { + return null; + } + String refStr = name.getRefersToFormula(); + if (refStr == null) { + return null; + } + String[] parts = refStr.split(","); + int maxRowIndex = SpreadsheetVersion.EXCEL2007.getLastRowIndex(); + int maxColIndex = SpreadsheetVersion.EXCEL2007.getLastColumnIndex(); + for (String part : parts) { + CellRangeAddress range = CellRangeAddress.valueOf(part); + if ((range.getFirstColumn() == 0 + && range.getLastColumn() == maxColIndex) + || (range.getFirstColumn() == -1 + && range.getLastColumn() == -1)) { + if (rows) { + return range; + } + } else if (range.getFirstRow() == 0 + && range.getLastRow() == maxRowIndex + || (range.getFirstRow() == -1 + && range.getLastRow() == -1)) { + if (!rows) { + return range; + } + } + } + return null; + } + } diff --git a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java index 0c40d26fb1..639a78d423 100644 --- a/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java +++ b/src/ooxml/java/org/apache/poi/xssf/usermodel/XSSFWorkbook.java @@ -19,6 +19,7 @@ package org.apache.poi.xssf.usermodel; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -51,6 +52,7 @@ import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.ss.usermodel.Row.MissingCellPolicy; +import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.ss.util.CellReference; import org.apache.poi.ss.util.WorkbookUtil; import org.apache.poi.util.*; @@ -172,9 +174,15 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Iterable<X /** * Constructs a XSSFWorkbook object given a OpenXML4J <code>Package</code> object, - * see <a href="http://openxml4j.org/">www.openxml4j.org</a>. + * see <a href="http://poi.apache.org/oxml4j/">http://poi.apache.org/oxml4j/</a>. + * + * Once you have finished working with the Workbook, you should close the package + * by calling pkg.close, to avoid leaving file handles open. + * + * Creating a XSSFWorkbook from a file-backed OPC Package has a lower memory + * footprint than an InputStream backed one. * - * @param pkg the OpenXML4J <code>Package</code> object. + * @param pkg the OpenXML4J <code>OPC Package</code> object. */ public XSSFWorkbook(OPCPackage pkg) throws IOException { super(pkg); @@ -183,6 +191,20 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Iterable<X load(XSSFFactory.getInstance()); } + /** + * Constructs a XSSFWorkbook object, by buffering the whole stream into memory + * and then opening an {@link OPCPackage} object for it. + * + * Using an {@link InputStream} requires more memory than using a File, so + * if a {@link File} is available then you should instead do something like + * <pre><code> + * OPCPackage pkg = OPCPackage.open(path); + * XSSFWorkbook wb = new XSSFWorkbook(pkg); + * // work with the wb object + * ...... + * pkg.close(); // gracefully closes the underlying zip file + * </code></pre> + */ public XSSFWorkbook(InputStream is) throws IOException { super(PackageHelper.open(is)); @@ -904,6 +926,20 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Iterable<X throw new IllegalArgumentException("Named range was not found: " + name); } + + /** + * As {@link #removeName(String)} is not necessarily unique + * (name + sheet index is unique), this method is more accurate. + * + * @param name the name to remove. + */ + void removeName(XSSFName name) { + if (!namedRanges.remove(name)) { + throw new IllegalArgumentException("Name was not found: " + name); + } + } + + /** * Delete the printarea for the sheet specified * @@ -1108,71 +1144,27 @@ public class XSSFWorkbook extends POIXMLDocument implements Workbook, Iterable<X * @param endColumn 0 based end of repeating columns. * @param startRow 0 based start of repeating rows. * @param endRow 0 based end of repeating rows. + * + * @deprecated use {@link XSSFSheet#setRepeatingRows(CellRangeAddress)} + * or {@link XSSFSheet#setRepeatingColumns(CellRangeAddress)} */ public void setRepeatingRowsAndColumns(int sheetIndex, int startColumn, int endColumn, int startRow, int endRow) { - // Check arguments - if ((startColumn == -1 && endColumn != -1) || startColumn < -1 || endColumn < -1 || startColumn > endColumn) - throw new IllegalArgumentException("Invalid column range specification"); - if ((startRow == -1 && endRow != -1) || startRow < -1 || endRow < -1 || startRow > endRow) - throw new IllegalArgumentException("Invalid row range specification"); - - XSSFSheet sheet = getSheetAt(sheetIndex); - boolean removingRange = startColumn == -1 && endColumn == -1 && startRow == -1 && endRow == -1; - - XSSFName name = getBuiltInName(XSSFName.BUILTIN_PRINT_TITLE, sheetIndex); - if (removingRange) { - if(name != null)namedRanges.remove(name); - return; - } - if (name == null) { - name = createBuiltInName(XSSFName.BUILTIN_PRINT_TITLE, sheetIndex); - } - - String reference = getReferenceBuiltInRecord(name.getSheetName(), startColumn, endColumn, startRow, endRow); - name.setRefersToFormula(reference); - - // If the print setup isn't currently defined, then add it - // in but without printer defaults - // If it's already there, leave it as-is! - CTWorksheet ctSheet = sheet.getCTWorksheet(); - if(ctSheet.isSetPageSetup() && ctSheet.isSetPageMargins()) { - // Everything we need is already there - } else { - // Have initial ones put in place - XSSFPrintSetup printSetup = sheet.getPrintSetup(); - printSetup.setValidSettings(false); - } - } - - private static String getReferenceBuiltInRecord(String sheetName, int startC, int endC, int startR, int endR) { - //windows excel example for built-in title: 'second sheet'!$E:$F,'second sheet'!$2:$3 - CellReference colRef = new CellReference(sheetName, 0, startC, true, true); - CellReference colRef2 = new CellReference(sheetName, 0, endC, true, true); - - String escapedName = SheetNameFormatter.format(sheetName); - - String c; - if(startC == -1 && endC == -1) c= ""; - else c = escapedName + "!$" + colRef.getCellRefParts()[2] + ":$" + colRef2.getCellRefParts()[2]; - - CellReference rowRef = new CellReference(sheetName, startR, 0, true, true); - CellReference rowRef2 = new CellReference(sheetName, endR, 0, true, true); - - String r = ""; - if(startR == -1 && endR == -1) r = ""; - else { - if (!rowRef.getCellRefParts()[1].equals("0") && !rowRef2.getCellRefParts()[1].equals("0")) { - r = escapedName + "!$" + rowRef.getCellRefParts()[1] + ":$" + rowRef2.getCellRefParts()[1]; - } - } - - StringBuffer rng = new StringBuffer(); - rng.append(c); - if(rng.length() > 0 && r.length() > 0) rng.append(','); - rng.append(r); - return rng.toString(); + XSSFSheet sheet = getSheetAt(sheetIndex); + + CellRangeAddress rows = null; + CellRangeAddress cols = null; + + if (startRow != -1) { + rows = new CellRangeAddress(startRow, endRow, -1, -1); + } + if (startColumn != -1) { + cols = new CellRangeAddress(-1, -1, startColumn, endColumn); + } + + sheet.setRepeatingRows(rows); + sheet.setRepeatingColumns(cols); } private static String getReferencePrintArea(String sheetName, int startC, int endC, int startR, int endR) { diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFColor.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFColor.java index a08faf0e38..b335983d09 100644 --- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFColor.java +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFColor.java @@ -17,11 +17,7 @@ package org.apache.poi.xslf.usermodel;
import junit.framework.TestCase;
-import org.openxmlformats.schemas.drawingml.x2006.main.CTColor;
-import org.openxmlformats.schemas.drawingml.x2006.main.CTHslColor;
-import org.openxmlformats.schemas.drawingml.x2006.main.CTSRgbColor;
-import org.openxmlformats.schemas.drawingml.x2006.main.STPresetColorVal;
-import org.openxmlformats.schemas.drawingml.x2006.main.STSchemeColorVal;
+import org.openxmlformats.schemas.drawingml.x2006.main.*;
import java.awt.Color;
@@ -149,4 +145,19 @@ public class TestXSLFColor extends TestCase { assertEquals(XSLFColor.presetColors.get(colorName), color.getColor());
}
}
+
+ public void testSys() {
+ CTColor xml = CTColor.Factory.newInstance();
+ CTSystemColor sys = xml.addNewSysClr();
+ sys.setVal(STSystemColorVal.GRAY_TEXT);
+ XSLFColor color = new XSLFColor(xml, null, null);
+ assertEquals(Color.black, color.getColor());
+
+ xml = CTColor.Factory.newInstance();
+ sys = xml.addNewSysClr();
+ sys.setLastClr(new byte[]{(byte)0xFF, 0, 0});
+ color = new XSLFColor(xml, null, null);
+ assertEquals(Color.red, color.getColor());
+ }
+
}
\ No newline at end of file diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java index f5839fd778..f0d57241db 100644 --- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFSimpleShape.java @@ -19,9 +19,7 @@ package org.apache.poi.xslf.usermodel; import junit.framework.TestCase;
import org.apache.poi.util.Units;
import org.apache.poi.xslf.XSLFTestDataSamples;
-import org.openxmlformats.schemas.drawingml.x2006.main.CTSchemeColor;
-import org.openxmlformats.schemas.drawingml.x2006.main.STLineCap;
-import org.openxmlformats.schemas.drawingml.x2006.main.STPresetLineDashVal;
+import org.openxmlformats.schemas.drawingml.x2006.main.*;
import java.awt.Color;
@@ -102,6 +100,20 @@ public class TestXSLFSimpleShape extends TestCase { assertEquals(null, shape.getLineColor());
// setting dash width to null unsets the SolidFill element
assertFalse(shape.getSpPr().getLn().isSetSolidFill());
+
+ XSLFSimpleShape ln2 = slide.createAutoShape();
+ ln2.setLineDash(LineDash.DOT);
+ assertEquals(LineDash.DOT, ln2.getLineDash());
+ ln2.setLineWidth(0.);
+ assertEquals(0., ln2.getLineWidth());
+
+ XSLFSimpleShape ln3 = slide.createAutoShape();
+ ln3.setLineWidth(1.);
+ assertEquals(1., ln3.getLineWidth());
+ ln3.setLineDash(null);
+ assertEquals(null, ln3.getLineDash());
+ ln3.setLineCap(null);
+ assertEquals(null, ln3.getLineDash());
}
public void testFill() {
@@ -231,4 +243,14 @@ public class TestXSLFSimpleShape extends TestCase { }
+ public void testShadowEffects(){
+ XMLSlideShow ppt = new XMLSlideShow();
+ XSLFSlide slide = ppt.createSlide();
+ CTStyleMatrix styleMatrix = slide.getTheme().getXmlObject().getThemeElements().getFmtScheme();
+ CTEffectStyleList lst = styleMatrix.getEffectStyleLst();
+ assertNotNull(lst);
+ for(CTEffectStyleItem ef : lst.getEffectStyleList()){
+ CTOuterShadowEffect obj = ef.getEffectLst().getOuterShdw();
+ }
+ }
}
\ No newline at end of file diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTableStyles.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTableStyles.java index 0feff27d25..3ce6c3b6d2 100644 --- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTableStyles.java +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTableStyles.java @@ -17,6 +17,7 @@ package org.apache.poi.xslf.usermodel;
import junit.framework.TestCase;
+import org.openxmlformats.schemas.drawingml.x2006.main.CTTableStyle;
/**
* @author Yegor Kozlov
@@ -30,4 +31,9 @@ public class TestXSLFTableStyles extends TestCase { assertEquals(0, tblStyles.getStyles().size());
}
+
+ public void testStyle(){
+ CTTableStyle obj = CTTableStyle.Factory.newInstance();
+ XSLFTableStyle style = new XSLFTableStyle(obj);
+ }
}
\ No newline at end of file diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextParagraph.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextParagraph.java index 2a44c058bb..00b4cbdcce 100644 --- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextParagraph.java +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextParagraph.java @@ -289,6 +289,17 @@ public class TestXSLFTextParagraph extends TestCase { p.setBullet(false);
assertFalse(p.isBullet());
+
+ p.setBulletAutoNumber(ListAutoNumber.ALPHA_LC_PARENT_BOTH, 1);
+
+ double tabStop = p.getTabStop(0);
+ assertEquals(0.0, tabStop);
+
+ p.addTabStop(100.);
+ assertEquals(100., p.getTabStop(0));
+
+ assertEquals(72.0, p.getDefaultTabSize());
+
}
public void testLineBreak(){
diff --git a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextRun.java b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextRun.java index 256c9eaecb..eb7553ecc4 100644 --- a/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextRun.java +++ b/src/ooxml/testcases/org/apache/poi/xslf/usermodel/TestXSLFTextRun.java @@ -56,5 +56,16 @@ public class TestXSLFTextRun extends TestCase { r.setFontSize(13.0);
assertEquals(13.0, r.getFontSize());
+ assertEquals(false, r.isSuperscript());
+ r.setSuperscript(true);
+ assertEquals(true, r.isSuperscript());
+ r.setSuperscript(false);
+ assertEquals(false, r.isSuperscript());
+
+ assertEquals(false, r.isSubscript());
+ r.setSubscript(true);
+ assertEquals(true, r.isSubscript());
+ r.setSubscript(false);
+ assertEquals(false, r.isSubscript());
}
}
diff --git a/src/ooxml/testcases/org/apache/poi/xssf/streaming/TestSXSSFWorkbook.java b/src/ooxml/testcases/org/apache/poi/xssf/streaming/TestSXSSFWorkbook.java index 72258e411c..ef9149cbf8 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/streaming/TestSXSSFWorkbook.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/streaming/TestSXSSFWorkbook.java @@ -154,6 +154,14 @@ public final class TestSXSSFWorkbook extends BaseTestWorkbook { tmp = wr.getTempFile(); assertTrue(tmp.getName().startsWith("poi-sxssf-sheet-xml")); assertTrue(tmp.getName().endsWith(".gz")); + + //Test escaping of Unicode control characters + wb = new SXSSFWorkbook(); + wb.createSheet("S1").createRow(0).createCell(0).setCellValue("value\u0019"); + XSSFWorkbook xssfWorkbook = (XSSFWorkbook) SXSSFITestDataProvider.instance.writeOutAndReadBack(wb); + Cell cell = xssfWorkbook.getSheet("S1").getRow(0).getCell(0); + assertEquals("value?", cell.getStringCellValue()); + } public void testGZipSheetdataWriter(){ diff --git a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFName.java b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFName.java index 1c640b4177..21b74ae11f 100644 --- a/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFName.java +++ b/src/ooxml/testcases/org/apache/poi/xssf/usermodel/TestXSSFName.java @@ -20,6 +20,7 @@ package org.apache.poi.xssf.usermodel; import org.apache.poi.xssf.XSSFTestDataSamples; import org.apache.poi.xssf.XSSFITestDataProvider; import org.apache.poi.ss.usermodel.BaseTestNamedRange; +import org.apache.poi.ss.util.CellRangeAddress; /** * @author Yegor Kozlov @@ -35,13 +36,15 @@ public final class TestXSSFName extends BaseTestNamedRange { // First test that setting RR&C for same sheet more than once only creates a // single Print_Titles built-in record XSSFWorkbook wb = new XSSFWorkbook(); - wb.createSheet("First Sheet"); + XSSFSheet sheet1 = wb.createSheet("First Sheet"); - wb.setRepeatingRowsAndColumns(0, -1, -1, -1, -1); + sheet1.setRepeatingRows(null); + sheet1.setRepeatingColumns(null); // set repeating rows and columns twice for the first sheet for (int i = 0; i < 2; i++) { - wb.setRepeatingRowsAndColumns(0, 0, 0, 0, 3); + sheet1.setRepeatingRows(CellRangeAddress.valueOf("1:4")); + sheet1.setRepeatingColumns(CellRangeAddress.valueOf("A:A")); //sheet.createFreezePane(0, 3); } assertEquals(1, wb.getNumberOfNames()); @@ -51,18 +54,18 @@ public final class TestXSSFName extends BaseTestNamedRange { assertEquals("'First Sheet'!$A:$A,'First Sheet'!$1:$4", nr1.getRefersToFormula()); //remove the columns part - wb.setRepeatingRowsAndColumns(0, -1, -1, 0, 3); + sheet1.setRepeatingColumns(null); assertEquals("'First Sheet'!$1:$4", nr1.getRefersToFormula()); //revert - wb.setRepeatingRowsAndColumns(0, 0, 0, 0, 3); + sheet1.setRepeatingColumns(CellRangeAddress.valueOf("A:A")); //remove the rows part - wb.setRepeatingRowsAndColumns(0, 0, 0, -1, -1); + sheet1.setRepeatingRows(null); assertEquals("'First Sheet'!$A:$A", nr1.getRefersToFormula()); //revert - wb.setRepeatingRowsAndColumns(0, 0, 0, 0, 3); + sheet1.setRepeatingRows(CellRangeAddress.valueOf("1:4")); // Save and re-open XSSFWorkbook nwb = XSSFTestDataSamples.writeOutAndReadBack(wb); @@ -75,8 +78,9 @@ public final class TestXSSFName extends BaseTestNamedRange { // check that setting RR&C on a second sheet causes a new Print_Titles built-in // name to be created - nwb.createSheet("SecondSheet"); - nwb.setRepeatingRowsAndColumns(1, 1, 2, 0, 0); + XSSFSheet sheet2 = nwb.createSheet("SecondSheet"); + sheet2.setRepeatingRows(CellRangeAddress.valueOf("1:1")); + sheet2.setRepeatingColumns(CellRangeAddress.valueOf("B:C")); assertEquals(2, nwb.getNumberOfNames()); XSSFName nr2 = nwb.getNameAt(1); @@ -84,6 +88,7 @@ public final class TestXSSFName extends BaseTestNamedRange { assertEquals(XSSFName.BUILTIN_PRINT_TITLE, nr2.getNameName()); assertEquals("SecondSheet!$B:$C,SecondSheet!$1:$1", nr2.getRefersToFormula()); - nwb.setRepeatingRowsAndColumns(1, -1, -1, -1, -1); + sheet2.setRepeatingRows(null); + sheet2.setRepeatingColumns(null); } } diff --git a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFDocument.java b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFDocument.java index 7f87bff921..09c1c1636f 100644 --- a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFDocument.java +++ b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFDocument.java @@ -337,4 +337,10 @@ public final class TestXWPFDocument extends TestCase { doc.getPackage().revert(); } + + public void testSettings(){ + XWPFSettings settings = new XWPFSettings(); + settings.setZoomPercent(50); + assertEquals(50, settings.getZoomPercent()); + } } diff --git a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFStyles.java b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFStyles.java index 3c13504cdf..5d13185cfc 100644 --- a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFStyles.java +++ b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFStyles.java @@ -26,7 +26,11 @@ import junit.framework.TestCase; import org.apache.poi.xwpf.XWPFTestDataSamples; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTFonts; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTLatentStyles; import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTStyle; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.STStyleType; +import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTLsdException; public class TestXWPFStyles extends TestCase { @@ -82,4 +86,36 @@ public class TestXWPFStyles extends TestCase { assertNotNull(styles); } + + /** + * YK: tests below don't make much sense, + * they exist only to copy xml beans to pi-ooxml-schemas.jar + */ + public void testLanguages(){ + XWPFDocument docOut = new XWPFDocument(); + XWPFStyles styles = docOut.createStyles(); + styles.setEastAsia("Chinese"); + + styles.setSpellingLanguage("English"); + + CTFonts def = CTFonts.Factory.newInstance(); + styles.setDefaultFonts(def); + } + + public void testType() { + CTStyle ctStyle = CTStyle.Factory.newInstance(); + XWPFStyle style = new XWPFStyle(ctStyle); + + style.setType(STStyleType.PARAGRAPH); + assertEquals(STStyleType.PARAGRAPH, style.getType()); + } + + public void testLatentStyles() { + CTLatentStyles latentStyles = CTLatentStyles.Factory.newInstance(); + CTLsdException ex = latentStyles.addNewLsdException(); + ex.setName("ex1"); + XWPFLatentStyles ls = new XWPFLatentStyles(latentStyles); + assertEquals(true, ls.isLatentStyle("ex1")); + + } } diff --git a/src/scratchpad/src/org/apache/poi/hdgf/chunks/Chunk.java b/src/scratchpad/src/org/apache/poi/hdgf/chunks/Chunk.java index fc880d5db3..b2a42536c3 100644 --- a/src/scratchpad/src/org/apache/poi/hdgf/chunks/Chunk.java +++ b/src/scratchpad/src/org/apache/poi/hdgf/chunks/Chunk.java @@ -161,70 +161,76 @@ public final class Chunk { continue; } - // Process - switch(type) { - // Types 0->7 = a flat at bit 0->7 - case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: - int val = contents[offset] & (1<<type); - command.value = Boolean.valueOf(val > 0); - break; - case 8: - command.value = Byte.valueOf(contents[offset]); - break; - case 9: - command.value = new Double( - LittleEndian.getDouble(contents, offset) - ); - break; - case 12: - // A Little Endian String - // Starts 8 bytes into the data segment - // Ends at end of data, or 00 00 - - // Ensure we have enough data - if(contents.length < 8) { - command.value = ""; + try { + // Process + switch(type) { + // Types 0->7 = a flat at bit 0->7 + case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: + int val = contents[offset] & (1<<type); + command.value = Boolean.valueOf(val > 0); break; - } - - // Find the end point - int startsAt = 8; - int endsAt = startsAt; - for(int j=startsAt; j<contents.length-1 && endsAt == startsAt; j++) { - if(contents[j] == 0 && contents[j+1] == 0) { - endsAt = j; + case 8: + command.value = Byte.valueOf(contents[offset]); + break; + case 9: + command.value = new Double( + LittleEndian.getDouble(contents, offset) + ); + break; + case 12: + // A Little Endian String + // Starts 8 bytes into the data segment + // Ends at end of data, or 00 00 + + // Ensure we have enough data + if(contents.length < 8) { + command.value = ""; + break; } - } - if(endsAt == startsAt) { - endsAt = contents.length; - } - - int strLen = (endsAt-startsAt) / 2; - command.value = StringUtil.getFromUnicodeLE(contents, startsAt, strLen); - break; - case 25: - command.value = Short.valueOf( - LittleEndian.getShort(contents, offset) - ); - break; - case 26: - command.value = Integer.valueOf( - LittleEndian.getInt(contents, offset) - ); - break; - // Types 11 and 21 hold the offset to the blocks - case 11: case 21: - if(offset < contents.length - 3) { - int bOffset = (int)LittleEndian.getUInt(contents, offset); - BlockOffsetCommand bcmd = (BlockOffsetCommand)command; - bcmd.setOffset(bOffset); - } - break; + // Find the end point + int startsAt = 8; + int endsAt = startsAt; + for(int j=startsAt; j<contents.length-1 && endsAt == startsAt; j++) { + if(contents[j] == 0 && contents[j+1] == 0) { + endsAt = j; + } + } + if(endsAt == startsAt) { + endsAt = contents.length; + } - default: - logger.log(POILogger.INFO, - "Command of type " + type + " not processed!"); + int strLen = endsAt - startsAt; + command.value = new String(contents, startsAt, strLen, header.getChunkCharset().name()); + break; + case 25: + command.value = Short.valueOf( + LittleEndian.getShort(contents, offset) + ); + break; + case 26: + command.value = Integer.valueOf( + LittleEndian.getInt(contents, offset) + ); + break; + + // Types 11 and 21 hold the offset to the blocks + case 11: case 21: + if(offset < contents.length - 3) { + int bOffset = (int)LittleEndian.getUInt(contents, offset); + BlockOffsetCommand bcmd = (BlockOffsetCommand)command; + bcmd.setOffset(bOffset); + } + break; + + default: + logger.log(POILogger.INFO, + "Command of type " + type + " not processed!"); + } + } + catch (Exception e) { + logger.log(POILogger.ERROR, "Unexpected error processing command, ignoring and continuing. Command: " + + command, e); } // Add to the array diff --git a/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeader.java b/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeader.java index 1565074de9..fc8c0a30eb 100644 --- a/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeader.java +++ b/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeader.java @@ -19,6 +19,8 @@ package org.apache.poi.hdgf.chunks; import org.apache.poi.util.LittleEndian; +import java.nio.charset.Charset; + /** * A chunk header */ @@ -80,6 +82,7 @@ public abstract class ChunkHeader { public abstract int getSizeInBytes(); public abstract boolean hasTrailer(); public abstract boolean hasSeparator(); + public abstract Charset getChunkCharset(); /** * Returns the ID/IX of the chunk diff --git a/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeaderV11.java b/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeaderV11.java index df68ea5849..b3d84aa503 100644 --- a/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeaderV11.java +++ b/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeaderV11.java @@ -17,6 +17,8 @@ package org.apache.poi.hdgf.chunks; +import java.nio.charset.Charset; + /** * A chunk header from v11+ */ @@ -42,4 +44,9 @@ public final class ChunkHeaderV11 extends ChunkHeaderV6 { return false; } + + @Override + public Charset getChunkCharset() { + return Charset.forName("UTF-16LE"); + } } diff --git a/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeaderV4V5.java b/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeaderV4V5.java index 7162f5056f..bba6a87ddd 100644 --- a/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeaderV4V5.java +++ b/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeaderV4V5.java @@ -17,6 +17,8 @@ package org.apache.poi.hdgf.chunks; +import java.nio.charset.Charset; + /** * A chunk header from v4 or v5 */ @@ -54,4 +56,9 @@ public final class ChunkHeaderV4V5 extends ChunkHeader { // V4 and V5 never has separators return false; } + + @Override + public Charset getChunkCharset() { + return Charset.forName("ASCII"); + } } diff --git a/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeaderV6.java b/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeaderV6.java index cfbae6e04c..96546c780b 100644 --- a/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeaderV6.java +++ b/src/scratchpad/src/org/apache/poi/hdgf/chunks/ChunkHeaderV6.java @@ -17,6 +17,8 @@ package org.apache.poi.hdgf.chunks; +import java.nio.charset.Charset; + /** * A chunk header from v6 */ @@ -59,4 +61,9 @@ public class ChunkHeaderV6 extends ChunkHeader { // V6 never has separators return false; } + + @Override + public Charset getChunkCharset() { + return Charset.forName("ASCII"); + } } diff --git a/src/scratchpad/src/org/apache/poi/hdgf/streams/ChunkStream.java b/src/scratchpad/src/org/apache/poi/hdgf/streams/ChunkStream.java index 34399ee501..5956334800 100644 --- a/src/scratchpad/src/org/apache/poi/hdgf/streams/ChunkStream.java +++ b/src/scratchpad/src/org/apache/poi/hdgf/streams/ChunkStream.java @@ -52,19 +52,25 @@ public final class ChunkStream extends Stream { int pos = 0; byte[] contents = getStore().getContents(); - while(pos < contents.length) { - // Ensure we have enough data to create a chunk from - int headerSize = ChunkHeader.getHeaderSize(chunkFactory.getVersion()); - if(pos+headerSize <= contents.length) { - Chunk chunk = chunkFactory.createChunk(contents, pos); - chunksA.add(chunk); + try { + while(pos < contents.length) { + // Ensure we have enough data to create a chunk from + int headerSize = ChunkHeader.getHeaderSize(chunkFactory.getVersion()); + if(pos+headerSize <= contents.length) { + Chunk chunk = chunkFactory.createChunk(contents, pos); + chunksA.add(chunk); - pos += chunk.getOnDiskSize(); - } else { - System.err.println("Needed " + headerSize + " bytes to create the next chunk header, but only found " + (contents.length-pos) + " bytes, ignoring rest of data"); - pos = contents.length; + pos += chunk.getOnDiskSize(); + } else { + System.err.println("Needed " + headerSize + " bytes to create the next chunk header, but only found " + (contents.length-pos) + " bytes, ignoring rest of data"); + pos = contents.length; + } } } + catch (Exception e) + { + System.err.println("Failed to create chunk at " + pos + ", ignoring rest of data." + e); + } chunks = chunksA.toArray(new Chunk[chunksA.size()]); } diff --git a/src/scratchpad/src/org/apache/poi/hmef/attribute/MAPIAttribute.java b/src/scratchpad/src/org/apache/poi/hmef/attribute/MAPIAttribute.java index 31c3cbb885..f4e9fab559 100644 --- a/src/scratchpad/src/org/apache/poi/hmef/attribute/MAPIAttribute.java +++ b/src/scratchpad/src/org/apache/poi/hmef/attribute/MAPIAttribute.java @@ -27,6 +27,7 @@ import org.apache.poi.hmef.Attachment; import org.apache.poi.hmef.HMEFMessage; import org.apache.poi.hsmf.datatypes.MAPIProperty; import org.apache.poi.hsmf.datatypes.Types; +import org.apache.poi.hsmf.datatypes.Types.MAPIType; import org.apache.poi.util.HexDump; import org.apache.poi.util.IOUtils; import org.apache.poi.util.LittleEndian; @@ -109,16 +110,22 @@ public class MAPIAttribute { // Is it either Multi-Valued or Variable-Length? boolean isMV = false; boolean isVL = false; - int type = typeAndMV; + int typeId = typeAndMV; if( (typeAndMV & Types.MULTIVALUED_FLAG) > 0 ) { isMV = true; - type -= Types.MULTIVALUED_FLAG; + typeId -= Types.MULTIVALUED_FLAG; } - if(type == Types.ASCII_STRING || type == Types.UNICODE_STRING || - type == Types.BINARY || type == Types.DIRECTORY) { + if(typeId == Types.ASCII_STRING.getId() || typeId == Types.UNICODE_STRING.getId() || + typeId == Types.BINARY.getId() || typeId == Types.DIRECTORY.getId()) { isVL = true; } + // Turn the type ID into a strongly typed thing + MAPIType type = Types.getById(typeId); + if (type == null) { + type = Types.createCustom(typeId); + } + // If it's a named property, rather than a standard // MAPI property, grab the details of it MAPIProperty prop = MAPIProperty.get(id); @@ -164,13 +171,13 @@ public class MAPIAttribute { // Create MAPIAttribute attr; if(type == Types.UNICODE_STRING || type == Types.ASCII_STRING) { - attr = new MAPIStringAttribute(prop, type, data); + attr = new MAPIStringAttribute(prop, typeId, data); } else if(type == Types.APP_TIME || type == Types.TIME) { - attr = new MAPIDateAttribute(prop, type, data); + attr = new MAPIDateAttribute(prop, typeId, data); } else if(id == MAPIProperty.RTF_COMPRESSED.id) { - attr = new MAPIRtfAttribute(prop, type, data); + attr = new MAPIRtfAttribute(prop, typeId, data); } else { - attr = new MAPIAttribute(prop, type, data); + attr = new MAPIAttribute(prop, typeId, data); } attrs.add(attr); } @@ -179,32 +186,17 @@ public class MAPIAttribute { // All done return attrs; } - private static int getLength(int type, InputStream inp) throws IOException { - switch(type) { - case Types.NULL: - return 0; - case Types.BOOLEAN: - case Types.SHORT: - return 2; - case Types.LONG: - case Types.FLOAT: - case Types.ERROR: - return 4; - case Types.LONG_LONG: - case Types.DOUBLE: - case Types.APP_TIME: - case Types.TIME: - case Types.CURRENCY: - return 8; - case Types.CLS_ID: - return 16; - case Types.ASCII_STRING: - case Types.UNICODE_STRING: - case Types.DIRECTORY: - case Types.BINARY: + private static int getLength(MAPIType type, InputStream inp) throws IOException { + if (type.isFixedLength()) { + return type.getLength(); + } + if (type == Types.ASCII_STRING || + type == Types.UNICODE_STRING || + type == Types.DIRECTORY || + type == Types.BINARY) { // Need to read the length, as it varies return LittleEndian.readInt(inp); - default: + } else { throw new IllegalArgumentException("Unknown type " + type); } } diff --git a/src/scratchpad/src/org/apache/poi/hmef/attribute/MAPIStringAttribute.java b/src/scratchpad/src/org/apache/poi/hmef/attribute/MAPIStringAttribute.java index d53e59c7d5..b48651c094 100644 --- a/src/scratchpad/src/org/apache/poi/hmef/attribute/MAPIStringAttribute.java +++ b/src/scratchpad/src/org/apache/poi/hmef/attribute/MAPIStringAttribute.java @@ -37,13 +37,13 @@ public final class MAPIStringAttribute extends MAPIAttribute { super(property, type, data); String tmpData = null; - if(type == Types.ASCII_STRING) { + if(type == Types.ASCII_STRING.getId()) { try { tmpData = new String(data, CODEPAGE); } catch(UnsupportedEncodingException e) { throw new RuntimeException("JVM Broken - core encoding " + CODEPAGE + " missing"); } - } else if(type == Types.UNICODE_STRING) { + } else if(type == Types.UNICODE_STRING.getId()) { tmpData = StringUtil.getFromUnicodeLE(data); } else { throw new IllegalArgumentException("Not a string type " + type); diff --git a/src/scratchpad/src/org/apache/poi/hslf/blip/PNG.java b/src/scratchpad/src/org/apache/poi/hslf/blip/PNG.java index 20016fd6b3..12b98f1802 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/blip/PNG.java +++ b/src/scratchpad/src/org/apache/poi/hslf/blip/PNG.java @@ -17,6 +17,7 @@ package org.apache.poi.hslf.blip; +import org.apache.poi.util.PngUtils; import org.apache.poi.hslf.model.Picture; import org.apache.poi.hslf.exceptions.HSLFException; @@ -35,22 +36,19 @@ public final class PNG extends Bitmap { /** * @return PNG data */ - public byte[] getData(){ - byte[] data = super.getData(); - try { - //PNG created on MAC may have a 16-byte prefix which prevents successful reading. - //Just cut it off!. - BufferedImage bi = ImageIO.read(new ByteArrayInputStream(data)); - if (bi == null){ - byte[] png = new byte[data.length-16]; - System.arraycopy(data, 16, png, 0, png.length); - data = png; - } - } catch (IOException e){ - throw new HSLFException(e); - } - return data; - } + public byte[] getData() { + byte[] data = super.getData(); + + //PNG created on MAC may have a 16-byte prefix which prevents successful reading. + //Just cut it off!. + if (PngUtils.matchesPngHeader(data, 16)) { + byte[] png = new byte[data.length-16]; + System.arraycopy(data, 16, png, 0, png.length); + data = png; + } + + return data; + } /** * @return type of this picture diff --git a/src/scratchpad/src/org/apache/poi/hslf/model/PPGraphics2D.java b/src/scratchpad/src/org/apache/poi/hslf/model/PPGraphics2D.java index 747c903970..86c67dcebd 100644 --- a/src/scratchpad/src/org/apache/poi/hslf/model/PPGraphics2D.java +++ b/src/scratchpad/src/org/apache/poi/hslf/model/PPGraphics2D.java @@ -218,6 +218,9 @@ public final class PPGraphics2D extends Graphics2D implements Cloneable { p.setPath(path); p.getFill().setForegroundColor(null); applyStroke(p); + if (_paint instanceof Color) { + p.setLineColor((Color)_paint); + } _group.addShape(p); } diff --git a/src/scratchpad/src/org/apache/poi/hsmf/MAPIMessage.java b/src/scratchpad/src/org/apache/poi/hsmf/MAPIMessage.java index 7c600e7014..8e26b48c94 100644 --- a/src/scratchpad/src/org/apache/poi/hsmf/MAPIMessage.java +++ b/src/scratchpad/src/org/apache/poi/hsmf/MAPIMessage.java @@ -39,9 +39,9 @@ import org.apache.poi.hsmf.datatypes.Chunks; import org.apache.poi.hsmf.datatypes.MAPIProperty; import org.apache.poi.hsmf.datatypes.NameIdChunks; import org.apache.poi.hsmf.datatypes.RecipientChunks; -import org.apache.poi.hsmf.datatypes.Types; import org.apache.poi.hsmf.datatypes.RecipientChunks.RecipientChunksSorter; import org.apache.poi.hsmf.datatypes.StringChunk; +import org.apache.poi.hsmf.datatypes.Types; import org.apache.poi.hsmf.exceptions.ChunkNotFoundException; import org.apache.poi.hsmf.parsers.POIFSChunkParser; import org.apache.poi.poifs.filesystem.DirectoryNode; @@ -214,7 +214,7 @@ public class MAPIMessage extends POIDocument { try { MAPIRtfAttribute rtf = new MAPIRtfAttribute( - MAPIProperty.RTF_COMPRESSED, Types.BINARY, chunk.getValue() + MAPIProperty.RTF_COMPRESSED, Types.BINARY.getId(), chunk.getValue() ); return rtf.getDataString(); } catch(IOException e) { diff --git a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/AttachmentChunks.java b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/AttachmentChunks.java index af2ea642a5..55f8a5f254 100644 --- a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/AttachmentChunks.java +++ b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/AttachmentChunks.java @@ -31,10 +31,13 @@ import static org.apache.poi.hsmf.datatypes.MAPIProperty.ATTACH_MIME_TAG; import static org.apache.poi.hsmf.datatypes.MAPIProperty.ATTACH_RENDERING; import static org.apache.poi.hsmf.datatypes.MAPIProperty.ATTACH_SIZE; +import java.io.IOException; import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import org.apache.poi.hsmf.MAPIMessage; + /** * Collection of convenence chunks for standard parts of the MSG file attachment. */ @@ -68,6 +71,36 @@ public class AttachmentChunks implements ChunkGroup { this.poifsName = poifsName; } + + /** + * Is this Attachment an embedded MAPI message? + */ + public boolean isEmbeddedMessage() { + return (attachmentDirectory != null); + } + /** + * Returns the embedded MAPI message, if the attachment + * is an embedded message, or null otherwise + */ + public MAPIMessage getEmbeddedMessage() throws IOException { + if (attachmentDirectory != null) { + return attachmentDirectory.getAsEmbededMessage(); + } + return null; + } + + /** + * Returns the embedded object, if the attachment is an + * object based embedding (image, document etc), or null + * if it's an embedded message + */ + public byte[] getEmbeddedAttachmentObject() { + if (attachData != null) { + return attachData.getValue(); + } + return null; + } + public Chunk[] getAll() { return allChunks.toArray(new Chunk[allChunks.size()]); } diff --git a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/ByteChunk.java b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/ByteChunk.java index fc84d3731b..d8b26b3952 100644 --- a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/ByteChunk.java +++ b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/ByteChunk.java @@ -20,6 +20,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import org.apache.poi.hsmf.datatypes.Types.MAPIType; import org.apache.poi.util.IOUtils; /** @@ -36,14 +37,14 @@ public class ByteChunk extends Chunk { /** * Creates a Byte Chunk. */ - public ByteChunk(String namePrefix, int chunkId, int type) { + public ByteChunk(String namePrefix, int chunkId, MAPIType type) { super(namePrefix, chunkId, type); } /** * Create a Byte Chunk, with the specified * type. */ - public ByteChunk(int chunkId, int type) { + public ByteChunk(int chunkId, MAPIType type) { super(chunkId, type); } diff --git a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/Chunk.java b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/Chunk.java index 7fcb252c96..a111eb32a5 100644 --- a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/Chunk.java +++ b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/Chunk.java @@ -21,19 +21,21 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import org.apache.poi.hsmf.datatypes.Types.MAPIType; + abstract public class Chunk { public static final String DEFAULT_NAME_PREFIX = "__substg1.0_"; protected int chunkId; - protected int type; + protected MAPIType type; protected String namePrefix; - protected Chunk(String namePrefix, int chunkId, int type) { + protected Chunk(String namePrefix, int chunkId, MAPIType type) { this.namePrefix = namePrefix; this.chunkId = chunkId; this.type = type; } - protected Chunk(int chunkId, int type) { + protected Chunk(int chunkId, MAPIType type) { this(DEFAULT_NAME_PREFIX, chunkId, type); } @@ -47,7 +49,7 @@ abstract public class Chunk { /** * Gets the numeric type of this chunk. */ - public int getType() { + public MAPIType getType() { return this.type; } @@ -55,8 +57,7 @@ abstract public class Chunk { * Creates a string to use to identify this chunk in the POI file system object. */ public String getEntryName() { - String type = Integer.toHexString(this.type); - while(type.length() < 4) type = "0" + type; + String type = this.type.asFileEnding(); String chunkId = Integer.toHexString(this.chunkId); while(chunkId.length() < 4) chunkId = "0" + chunkId; diff --git a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/DirectoryChunk.java b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/DirectoryChunk.java index 156c6f2602..5365a63259 100644 --- a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/DirectoryChunk.java +++ b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/DirectoryChunk.java @@ -21,6 +21,7 @@ import java.io.InputStream; import java.io.OutputStream; import org.apache.poi.hsmf.MAPIMessage; +import org.apache.poi.hsmf.datatypes.Types.MAPIType; import org.apache.poi.poifs.filesystem.DirectoryNode; /** @@ -33,7 +34,7 @@ import org.apache.poi.poifs.filesystem.DirectoryNode; public class DirectoryChunk extends Chunk { private DirectoryNode dir; - public DirectoryChunk(DirectoryNode dir, String namePrefix, int chunkId, int type) { + public DirectoryChunk(DirectoryNode dir, String namePrefix, int chunkId, MAPIType type) { super(namePrefix, chunkId, type); this.dir = dir; } diff --git a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/MAPIProperty.java b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/MAPIProperty.java index d5b274a498..a5ee6cdca5 100644 --- a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/MAPIProperty.java +++ b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/MAPIProperty.java @@ -20,8 +20,11 @@ package org.apache.poi.hsmf.datatypes; import static org.apache.poi.hsmf.datatypes.Types.ASCII_STRING; import static org.apache.poi.hsmf.datatypes.Types.BINARY; import static org.apache.poi.hsmf.datatypes.Types.BOOLEAN; +import static org.apache.poi.hsmf.datatypes.Types.CLS_ID; import static org.apache.poi.hsmf.datatypes.Types.DIRECTORY; import static org.apache.poi.hsmf.datatypes.Types.LONG; +import static org.apache.poi.hsmf.datatypes.Types.LONG_LONG; +import static org.apache.poi.hsmf.datatypes.Types.SHORT; import static org.apache.poi.hsmf.datatypes.Types.TIME; import java.util.Collection; @@ -29,6 +32,8 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import org.apache.poi.hsmf.datatypes.Types.MAPIType; + /** * Holds the list of MAPI Attributes, and allows lookup * by friendly name, ID and MAPI Property Name. @@ -49,7 +54,7 @@ public class MAPIProperty { public static final MAPIProperty AB_PROVIDERS = new MAPIProperty(0x3d01, BINARY, "AbProviders", "PR_AB_PROVIDERS"); public static final MAPIProperty AB_SEARCH_PATH = - new MAPIProperty(0x3d05, 4354, "AbSearchPath", "PR_AB_SEARCH_PATH"); + new MAPIProperty(0x3d05, Types.createCustom(4354), "AbSearchPath", "PR_AB_SEARCH_PATH"); public static final MAPIProperty AB_SEARCH_PATH_UPDATE = new MAPIProperty(0x3d11, BINARY, "AbSearchPathUpdate", "PR_AB_SEARCH_PATH_UPDATE"); public static final MAPIProperty ACCESS = @@ -75,15 +80,15 @@ public class MAPIProperty { public static final MAPIProperty ATTACH_ADDITIONAL_INFO = new MAPIProperty(0x370f, BINARY, "AttachAdditionalInfo", "PR_ATTACH_ADDITIONAL_INFO"); public static final MAPIProperty ATTACH_CONTENT_BASE = - new MAPIProperty(0x3711, -1, "AttachContentBase", "PR_ATTACH_CONTENT_BASE"); + new MAPIProperty(0x3711, Types.UNKNOWN, "AttachContentBase", "PR_ATTACH_CONTENT_BASE"); public static final MAPIProperty ATTACH_CONTENT_ID = - new MAPIProperty(0x3712, -1, "AttachContentId", "PR_ATTACH_CONTENT_ID"); + new MAPIProperty(0x3712, Types.UNKNOWN, "AttachContentId", "PR_ATTACH_CONTENT_ID"); public static final MAPIProperty ATTACH_CONTENT_LOCATION = - new MAPIProperty(0x3713, -1, "AttachContentLocation", "PR_ATTACH_CONTENT_LOCATION"); + new MAPIProperty(0x3713, Types.UNKNOWN, "AttachContentLocation", "PR_ATTACH_CONTENT_LOCATION"); public static final MAPIProperty ATTACH_DATA = new MAPIProperty(0x3701, BINARY, "AttachData", "PR_ATTACH_DATA_OBJ"); public static final MAPIProperty ATTACH_DISPOSITION = - new MAPIProperty(0x3716, -1, "AttachDisposition", "PR_ATTACH_DISPOSITION"); + new MAPIProperty(0x3716, Types.UNKNOWN, "AttachDisposition", "PR_ATTACH_DISPOSITION"); public static final MAPIProperty ATTACH_ENCODING = new MAPIProperty(0x3702, BINARY, "AttachEncoding", "PR_ATTACH_ENCODING"); public static final MAPIProperty ATTACH_EXTENSION = @@ -91,7 +96,7 @@ public class MAPIProperty { public static final MAPIProperty ATTACH_FILENAME = new MAPIProperty(0x3704, ASCII_STRING, "AttachFilename", "PR_ATTACH_FILENAME"); public static final MAPIProperty ATTACH_FLAGS = - new MAPIProperty(0x3714, -1, "AttachFlags", "PR_ATTACH_FLAGS"); + new MAPIProperty(0x3714, Types.UNKNOWN, "AttachFlags", "PR_ATTACH_FLAGS"); public static final MAPIProperty ATTACH_LONG_FILENAME = new MAPIProperty(0x3707, ASCII_STRING, "AttachLongFilename", "PR_ATTACH_LONG_FILENAME"); public static final MAPIProperty ATTACH_LONG_PATHNAME = @@ -99,11 +104,11 @@ public class MAPIProperty { public static final MAPIProperty ATTACH_METHOD = new MAPIProperty(0x3705, LONG, "AttachMethod", "PR_ATTACH_METHOD"); public static final MAPIProperty ATTACH_MIME_SEQUENCE = - new MAPIProperty(0x3710, -1, "AttachMimeSequence", "PR_ATTACH_MIME_SEQUENCE"); + new MAPIProperty(0x3710, Types.UNKNOWN, "AttachMimeSequence", "PR_ATTACH_MIME_SEQUENCE"); public static final MAPIProperty ATTACH_MIME_TAG = new MAPIProperty(0x370e, ASCII_STRING, "AttachMimeTag", "PR_ATTACH_MIME_TAG"); public static final MAPIProperty ATTACH_NETSCAPE_MAC_INFO = - new MAPIProperty(0x3715, -1, "AttachNetscapeMacInfo", "PR_ATTACH_NETSCAPE_MAC_INFO"); + new MAPIProperty(0x3715, Types.UNKNOWN, "AttachNetscapeMacInfo", "PR_ATTACH_NETSCAPE_MAC_INFO"); public static final MAPIProperty ATTACH_NUM = new MAPIProperty(0xe21, LONG, "AttachNum", "PR_ATTACH_NUM"); public static final MAPIProperty ATTACH_PATHNAME = @@ -125,19 +130,19 @@ public class MAPIProperty { public static final MAPIProperty AUTO_FORWARDED = new MAPIProperty(5, BOOLEAN, "AutoForwarded", "PR_AUTO_FORWARDED"); public static final MAPIProperty AUTO_RESPONSE_SUPPRESS = - new MAPIProperty(0x3fdf, -1, "AutoResponseSuppress", "PR_AUTO_RESPONSE_SUPPRESS"); + new MAPIProperty(0x3fdf, Types.UNKNOWN, "AutoResponseSuppress", "PR_AUTO_RESPONSE_SUPPRESS"); public static final MAPIProperty BIRTHDAY = new MAPIProperty(0x3a42, TIME, "Birthday", "PR_BIRTHDAY"); public static final MAPIProperty BODY = new MAPIProperty(0x1000, ASCII_STRING, "Body", "PR_BODY"); public static final MAPIProperty BODY_CONTENT_ID = - new MAPIProperty(0x1015, -1, "BodyContentId", "PR_BODY_CONTENT_ID"); + new MAPIProperty(0x1015, Types.UNKNOWN, "BodyContentId", "PR_BODY_CONTENT_ID"); public static final MAPIProperty BODY_CONTENT_LOCATION = - new MAPIProperty(0x1014, -1, "BodyContentLocation", "PR_BODY_CONTENT_LOCATION"); + new MAPIProperty(0x1014, Types.UNKNOWN, "BodyContentLocation", "PR_BODY_CONTENT_LOCATION"); public static final MAPIProperty BODY_CRC = new MAPIProperty(0xe1c, LONG, "BodyCrc", "PR_BODY_CRC"); public static final MAPIProperty BODY_HTML = - new MAPIProperty(0x1013, -1, "BodyHtml", "data"); + new MAPIProperty(0x1013, Types.UNKNOWN, "BodyHtml", "data"); public static final MAPIProperty BUSINESS_FAX_NUMBER = new MAPIProperty(0x3a24, ASCII_STRING, "BusinessFaxNumber", "PR_BUSINESS_FAX_NUMBER"); public static final MAPIProperty BUSINESS_HOME_PAGE = @@ -147,7 +152,7 @@ public class MAPIProperty { public static final MAPIProperty CAR_TELEPHONE_NUMBER = new MAPIProperty(0x3a1e, ASCII_STRING, "CarTelephoneNumber", "PR_CAR_TELEPHONE_NUMBER"); public static final MAPIProperty CHILDRENS_NAMES = - new MAPIProperty(0x3a58, 4126, "ChildrensNames", "PR_CHILDRENS_NAMES"); + new MAPIProperty(0x3a58, Types.createCustom(4126), "ChildrensNames", "PR_CHILDRENS_NAMES"); public static final MAPIProperty CLIENT_SUBMIT_TIME = new MAPIProperty(0x39, TIME, "ClientSubmitTime", "PR_CLIENT_SUBMIT_TIME"); public static final MAPIProperty COMMENT = @@ -161,15 +166,15 @@ public class MAPIProperty { public static final MAPIProperty COMPUTER_NETWORK_NAME = new MAPIProperty(0x3a49, ASCII_STRING, "ComputerNetworkName", "PR_COMPUTER_NETWORK_NAME"); public static final MAPIProperty CONTACT_ADDRTYPES = - new MAPIProperty(0x3a54, 4126, "ContactAddrtypes", "PR_CONTACT_ADDRTYPES"); + new MAPIProperty(0x3a54, Types.createCustom(4126), "ContactAddrtypes", "PR_CONTACT_ADDRTYPES"); public static final MAPIProperty CONTACT_DEFAULT_ADDRESS_INDEX = new MAPIProperty(0x3a55, LONG, "ContactDefaultAddressIndex", "PR_CONTACT_DEFAULT_ADDRESS_INDEX"); public static final MAPIProperty CONTACT_EMAIL_ADDRESSES = - new MAPIProperty(0x3a56, 4126, "ContactEmailAddresses", "PR_CONTACT_EMAIL_ADDRESSES"); + new MAPIProperty(0x3a56, Types.createCustom(4126), "ContactEmailAddresses", "PR_CONTACT_EMAIL_ADDRESSES"); public static final MAPIProperty CONTACT_ENTRY_IDS = - new MAPIProperty(0x3a53, 4354, "ContactEntryIds", "PR_CONTACT_ENTRYIDS"); + new MAPIProperty(0x3a53, Types.createCustom(4354), "ContactEntryIds", "PR_CONTACT_ENTRYIDS"); public static final MAPIProperty CONTACT_VERSION = - new MAPIProperty(0x3a52, 72, "ContactVersion", "PR_CONTACT_VERSION"); + new MAPIProperty(0x3a52, CLS_ID, "ContactVersion", "PR_CONTACT_VERSION"); public static final MAPIProperty CONTAINER_CLASS = new MAPIProperty(0x3613, ASCII_STRING, "ContainerClass", "PR_CONTAINER_CLASS"); public static final MAPIProperty CONTAINER_CONTENTS = @@ -179,7 +184,7 @@ public class MAPIProperty { public static final MAPIProperty CONTAINER_HIERARCHY = new MAPIProperty(0x360e, DIRECTORY, "ContainerHierarchy", "PR_CONTAINER_HIERARCHY"); public static final MAPIProperty CONTAINER_MODIFY_VERSION = - new MAPIProperty(0x3614, 20, "ContainerModifyVersion", "PR_CONTAINER_MODIFY_VERSION"); + new MAPIProperty(0x3614, LONG_LONG, "ContainerModifyVersion", "PR_CONTAINER_MODIFY_VERSION"); public static final MAPIProperty CONTENT_CONFIDENTIALITY_ALGORITHM_ID = new MAPIProperty(6, BINARY, "ContentConfidentialityAlgorithmId", "PR_CONTENT_CONFIDENTIALITY_ALGORITHM_ID"); public static final MAPIProperty CONTENT_CORRELATOR = @@ -197,7 +202,7 @@ public class MAPIProperty { public static final MAPIProperty CONTENT_UNREAD = new MAPIProperty(0x3603, LONG, "ContentUnread", "PR_CONTENT_UNREAD"); public static final MAPIProperty CONTENTS_SORT_ORDER = - new MAPIProperty(0x360d, 4099, "ContentsSortOrder", "PR_CONTENTS_SORT_ORDER"); + new MAPIProperty(0x360d, Types.createCustom(4099), "ContentsSortOrder", "PR_CONTENTS_SORT_ORDER"); public static final MAPIProperty CONTROL_FLAGS = new MAPIProperty(0x3f00, LONG, "ControlFlags", "PR_CONTROL_FLAGS"); public static final MAPIProperty CONTROL_ID = @@ -231,9 +236,9 @@ public class MAPIProperty { public static final MAPIProperty CREATION_TIME = new MAPIProperty(0x3007, TIME, "CreationTime", "PR_CREATION_TIME"); public static final MAPIProperty CREATION_VERSION = - new MAPIProperty(0xe19, 20, "CreationVersion", "PR_CREATION_VERSION"); + new MAPIProperty(0xe19, LONG_LONG, "CreationVersion", "PR_CREATION_VERSION"); public static final MAPIProperty CURRENT_VERSION = - new MAPIProperty(0xe00, 20, "CurrentVersion", "PR_CURRENT_VERSION"); + new MAPIProperty(0xe00, LONG_LONG, "CurrentVersion", "PR_CURRENT_VERSION"); public static final MAPIProperty CUSTOMER_ID = new MAPIProperty(0x3a4a, ASCII_STRING, "CustomerId", "PR_CUSTOMER_ID"); public static final MAPIProperty DEF_CREATE_DL = @@ -299,13 +304,13 @@ public class MAPIProperty { public static final MAPIProperty ENTRY_ID = new MAPIProperty(0xfff, BINARY, "EntryId", "PR_ENTRYID"); public static final MAPIProperty EXPAND_BEGIN_TIME = - new MAPIProperty(0x3618, -1, "ExpandBeginTime", "PR_EXPAND_BEGIN_TIME"); + new MAPIProperty(0x3618, Types.UNKNOWN, "ExpandBeginTime", "PR_EXPAND_BEGIN_TIME"); public static final MAPIProperty EXPAND_END_TIME = - new MAPIProperty(0x3619, -1, "ExpandEndTime", "PR_EXPAND_END_TIME"); + new MAPIProperty(0x3619, Types.UNKNOWN, "ExpandEndTime", "PR_EXPAND_END_TIME"); public static final MAPIProperty EXPANDED_BEGIN_TIME = - new MAPIProperty(0x361a, -1, "ExpandedBeginTime", "PR_EXPANDED_BEGIN_TIME"); + new MAPIProperty(0x361a, Types.UNKNOWN, "ExpandedBeginTime", "PR_EXPANDED_BEGIN_TIME"); public static final MAPIProperty EXPANDED_END_TIME = - new MAPIProperty(0x361b, -1, "ExpandedEndTime", "PR_EXPANDED_END_TIME"); + new MAPIProperty(0x361b, Types.UNKNOWN, "ExpandedEndTime", "PR_EXPANDED_END_TIME"); public static final MAPIProperty EXPIRY_TIME = new MAPIProperty(0x15, TIME, "ExpiryTime", "PR_EXPIRY_TIME"); public static final MAPIProperty EXPLICIT_CONVERSION = @@ -323,17 +328,17 @@ public class MAPIProperty { public static final MAPIProperty FORM_CATEGORY_SUB = new MAPIProperty(0x3305, ASCII_STRING, "FormCategorySub", "PR_FORM_CATEGORY_SUB"); public static final MAPIProperty FORM_CLSID = - new MAPIProperty(0x3302, 72, "FormClsid", "PR_FORM_ClsID"); + new MAPIProperty(0x3302, CLS_ID, "FormClsid", "PR_FORM_ClsID"); public static final MAPIProperty FORM_CONTACT_NAME = new MAPIProperty(0x3303, ASCII_STRING, "FormContactName", "PR_FORM_CONTACT_NAME"); public static final MAPIProperty FORM_DESIGNER_GUID = - new MAPIProperty(0x3309, 72, "FormDesignerGuid", "PR_FORM_DESIGNER_GUID"); + new MAPIProperty(0x3309, CLS_ID, "FormDesignerGuid", "PR_FORM_DESIGNER_GUID"); public static final MAPIProperty FORM_DESIGNER_NAME = new MAPIProperty(0x3308, ASCII_STRING, "FormDesignerName", "PR_FORM_DESIGNER_NAME"); public static final MAPIProperty FORM_HIDDEN = new MAPIProperty(0x3307, BOOLEAN, "FormHidden", "PR_FORM_HIDDEN"); public static final MAPIProperty FORM_HOST_MAP = - new MAPIProperty(0x3306, 4099, "FormHostMap", "PR_FORM_HOST_MAP"); + new MAPIProperty(0x3306, Types.createCustom(4099), "FormHostMap", "PR_FORM_HOST_MAP"); public static final MAPIProperty FORM_MESSAGE_BEHAVIOR = new MAPIProperty(0x330a, LONG, "FormMessageBehavior", "PR_FORM_MESSAGE_BEHAVIOR"); public static final MAPIProperty FORM_VERSION = @@ -341,7 +346,7 @@ public class MAPIProperty { public static final MAPIProperty FTP_SITE = new MAPIProperty(0x3a4c, ASCII_STRING, "FtpSite", "PR_FTP_SITE"); public static final MAPIProperty GENDER = - new MAPIProperty(0x3a4d, 2, "Gender", "PR_GENDER"); + new MAPIProperty(0x3a4d, SHORT, "Gender", "PR_GENDER"); public static final MAPIProperty GENERATION = new MAPIProperty(0x3a05, ASCII_STRING, "Generation", "PR_GENERATION"); public static final MAPIProperty GIVEN_NAME = @@ -373,9 +378,9 @@ public class MAPIProperty { public static final MAPIProperty HOME_TELEPHONE_NUMBER = new MAPIProperty(0x3a09, ASCII_STRING, "HomeTelephoneNumber", "PR_HOME_TELEPHONE_NUMBER"); public static final MAPIProperty INET_MAIL_OVERRIDE_CHARSET = - new MAPIProperty(0x5903, -1, "INetMailOverrideCharset", "Charset"); + new MAPIProperty(0x5903, Types.UNKNOWN, "INetMailOverrideCharset", "Charset"); public static final MAPIProperty INET_MAIL_OVERRIDE_FORMAT = - new MAPIProperty(0x5902, -1, "INetMailOverrideFormat", "Format"); + new MAPIProperty(0x5902, Types.UNKNOWN, "INetMailOverrideFormat", "Format"); public static final MAPIProperty ICON = new MAPIProperty(0xffd, BINARY, "Icon", "PR_ICON"); public static final MAPIProperty IDENTITY_DISPLAY = @@ -389,7 +394,7 @@ public class MAPIProperty { public static final MAPIProperty IMPORTANCE = new MAPIProperty(0x17, LONG, "Importance", "PR_IMPORTANCE"); public static final MAPIProperty IN_REPLY_TO_ID = - new MAPIProperty(0x1042, -1, "InReplyToId", "PR_IN_REPLY_TO_ID"); + new MAPIProperty(0x1042, Types.UNKNOWN, "InReplyToId", "PR_IN_REPLY_TO_ID"); public static final MAPIProperty INCOMPLETE_COPY = new MAPIProperty(0x35, BOOLEAN, "IncompleteCopy", "PR_INCOMPLETE_COPY"); public static final MAPIProperty INITIAL_DETAILS_PANE = @@ -403,7 +408,7 @@ public class MAPIProperty { public static final MAPIProperty INTERNET_ARTICLE_NUMBER = new MAPIProperty(0xe23, LONG, "InternetArticleNumber", "PR_INTERNET_ARTICLE_NUMBER"); public static final MAPIProperty INTERNET_CPID = - new MAPIProperty(0x3fde, -1, "InternetCPID", "PR_INTERNET_CPID"); + new MAPIProperty(0x3fde, Types.UNKNOWN, "InternetCPID", "PR_INTERNET_CPID"); public static final MAPIProperty INTERNET_CONTROL = new MAPIProperty(0x1031, ASCII_STRING, "InternetControl", "PR_INTERNET_CONTROL"); public static final MAPIProperty INTERNET_DISTRIBUTION = @@ -457,39 +462,39 @@ public class MAPIProperty { public static final MAPIProperty LATEST_DELIVERY_TIME = new MAPIProperty(0x19, TIME, "LatestDeliveryTime", "PR_LATEST_DELIVERY_TIME"); public static final MAPIProperty LIST_HELP = - new MAPIProperty(0x1043, -1, "ListHelp", "PR_LIST_HELP"); + new MAPIProperty(0x1043, Types.UNKNOWN, "ListHelp", "PR_LIST_HELP"); public static final MAPIProperty LIST_SUBSCRIBE = - new MAPIProperty(0x1044, -1, "ListSubscribe", "PR_LIST_SUBSCRIBE"); + new MAPIProperty(0x1044, Types.UNKNOWN, "ListSubscribe", "PR_LIST_SUBSCRIBE"); public static final MAPIProperty LIST_UNSUBSCRIBE = - new MAPIProperty(0x1045, -1, "ListUnsubscribe", "PR_LIST_UNSUBSCRIBE"); + new MAPIProperty(0x1045, Types.UNKNOWN, "ListUnsubscribe", "PR_LIST_UNSUBSCRIBE"); public static final MAPIProperty LOCALITY = new MAPIProperty(0x3a27, ASCII_STRING, "Locality", "PR_LOCALITY"); public static final MAPIProperty LOCALLY_DELIVERED = - new MAPIProperty(0x6745, -1, "LocallyDelivered", "ptagLocallyDelivered"); + new MAPIProperty(0x6745, Types.UNKNOWN, "LocallyDelivered", "ptagLocallyDelivered"); public static final MAPIProperty LOCATION = new MAPIProperty(0x3a0d, ASCII_STRING, "Location", "PR_LOCATION"); public static final MAPIProperty LOCK_BRANCH_ID = - new MAPIProperty(0x3800, -1, "LockBranchId", "PR_LOCK_BRANCH_ID"); + new MAPIProperty(0x3800, Types.UNKNOWN, "LockBranchId", "PR_LOCK_BRANCH_ID"); public static final MAPIProperty LOCK_DEPTH = - new MAPIProperty(0x3808, -1, "LockDepth", "PR_LOCK_DEPTH"); + new MAPIProperty(0x3808, Types.UNKNOWN, "LockDepth", "PR_LOCK_DEPTH"); public static final MAPIProperty LOCK_ENLISTMENT_CONTEXT = - new MAPIProperty(0x3804, -1, "LockEnlistmentContext", "PR_LOCK_ENLISTMENT_CONTEXT"); + new MAPIProperty(0x3804, Types.UNKNOWN, "LockEnlistmentContext", "PR_LOCK_ENLISTMENT_CONTEXT"); public static final MAPIProperty LOCK_EXPIRY_TIME = - new MAPIProperty(0x380a, -1, "LockExpiryTime", "PR_LOCK_EXPIRY_TIME"); + new MAPIProperty(0x380a, Types.UNKNOWN, "LockExpiryTime", "PR_LOCK_EXPIRY_TIME"); public static final MAPIProperty LOCK_PERSISTENT = - new MAPIProperty(0x3807, -1, "LockPersistent", "PR_LOCK_PERSISTENT"); + new MAPIProperty(0x3807, Types.UNKNOWN, "LockPersistent", "PR_LOCK_PERSISTENT"); public static final MAPIProperty LOCK_RESOURCE_DID = - new MAPIProperty(0x3802, -1, "LockResourceDid", "PR_LOCK_RESOURCE_DID"); + new MAPIProperty(0x3802, Types.UNKNOWN, "LockResourceDid", "PR_LOCK_RESOURCE_DID"); public static final MAPIProperty LOCK_RESOURCE_FID = - new MAPIProperty(0x3801, -1, "LockResourceFid", "PR_LOCK_RESOURCE_FID"); + new MAPIProperty(0x3801, Types.UNKNOWN, "LockResourceFid", "PR_LOCK_RESOURCE_FID"); public static final MAPIProperty LOCK_RESOURCE_MID = - new MAPIProperty(0x3803, -1, "LockResourceMid", "PR_LOCK_RESOURCE_MID"); + new MAPIProperty(0x3803, Types.UNKNOWN, "LockResourceMid", "PR_LOCK_RESOURCE_MID"); public static final MAPIProperty LOCK_SCOPE = - new MAPIProperty(0x3806, -1, "LockScope", "PR_LOCK_SCOPE"); + new MAPIProperty(0x3806, Types.UNKNOWN, "LockScope", "PR_LOCK_SCOPE"); public static final MAPIProperty LOCK_TIMEOUT = - new MAPIProperty(0x3809, -1, "LockTimeout", "PR_LOCK_TIMEOUT"); + new MAPIProperty(0x3809, Types.UNKNOWN, "LockTimeout", "PR_LOCK_TIMEOUT"); public static final MAPIProperty LOCK_TYPE = - new MAPIProperty(0x3805, -1, "LockType", "PR_LOCK_TYPE"); + new MAPIProperty(0x3805, Types.UNKNOWN, "LockType", "PR_LOCK_TYPE"); public static final MAPIProperty MAIL_PERMISSION = new MAPIProperty(0x3a0e, BOOLEAN, "MailPermission", "PR_MAIL_PERMISSION"); public static final MAPIProperty MANAGER_NAME = @@ -505,7 +510,7 @@ public class MAPIProperty { public static final MAPIProperty MESSAGE_CLASS = new MAPIProperty(0x1a, ASCII_STRING, "MessageClass", "PR_MESSAGE_CLASS"); public static final MAPIProperty MESSAGE_CODEPAGE = - new MAPIProperty(0x3ffd, -1, "MessageCodepage", "PR_MESSAGE_CODEPAGE"); + new MAPIProperty(0x3ffd, Types.UNKNOWN, "MessageCodepage", "PR_MESSAGE_CODEPAGE"); public static final MAPIProperty MESSAGE_DELIVERY_ID = new MAPIProperty(0x1b, BINARY, "MessageDeliveryId", "PR_MESSAGE_DELIVERY_ID"); public static final MAPIProperty MESSAGE_DELIVERY_TIME = @@ -537,7 +542,7 @@ public class MAPIProperty { public static final MAPIProperty MOBILE_TELEPHONE_NUMBER = new MAPIProperty(0x3a1c, ASCII_STRING, "MobileTelephoneNumber", "PR_MOBILE_TELEPHONE_NUMBER"); public static final MAPIProperty MODIFY_VERSION = - new MAPIProperty(0xe1a, 20, "ModifyVersion", "PR_MODIFY_VERSION"); + new MAPIProperty(0xe1a, LONG_LONG, "ModifyVersion", "PR_MODIFY_VERSION"); public static final MAPIProperty MSG_STATUS = new MAPIProperty(0xe17, LONG, "MsgStatus", "PR_MSG_STATUS"); public static final MAPIProperty NDR_DIAG_CODE = @@ -545,7 +550,7 @@ public class MAPIProperty { public static final MAPIProperty NDR_REASON_CODE = new MAPIProperty(0xc04, LONG, "NdrReasonCode", "PR_NDR_REASON_CODE"); public static final MAPIProperty NDR_STATUS_CODE = - new MAPIProperty(0xc20, -1, "NdrStatusCode", "PR_NDR_STATUS_CODE"); + new MAPIProperty(0xc20, Types.UNKNOWN, "NdrStatusCode", "PR_NDR_STATUS_CODE"); public static final MAPIProperty NEWSGROUP_NAME = new MAPIProperty(0xe24, ASCII_STRING, "NewsgroupName", "PR_NEWSGROUP_NAME"); public static final MAPIProperty NICKNAME = @@ -559,7 +564,7 @@ public class MAPIProperty { public static final MAPIProperty NORMALIZED_SUBJECT = new MAPIProperty(0xe1d, ASCII_STRING, "NormalizedSubject", "PR_NORMALIZED_SUBJECT"); public static final MAPIProperty NT_SECURITY_DESCRIPTOR = - new MAPIProperty(0xe27, -1, "NtSecurityDescriptor", "PR_NT_SECURITY_DESCRIPTOR"); + new MAPIProperty(0xe27, Types.UNKNOWN, "NtSecurityDescriptor", "PR_NT_SECURITY_DESCRIPTOR"); public static final MAPIProperty NULL = new MAPIProperty(1, LONG, "Null", "PR_NULL"); public static final MAPIProperty OBJECT_TYPE = @@ -573,11 +578,11 @@ public class MAPIProperty { public static final MAPIProperty OFFICE_TELEPHONE_NUMBER = new MAPIProperty(0x3a08, ASCII_STRING, "OfficeTelephoneNumber", "PR_OFFICE_TELEPHONE_NUMBER"); public static final MAPIProperty OOF_REPLY_TYPE = - new MAPIProperty(0x4080, -1, "OofReplyType", "PR_OOF_REPLY_TYPE"); + new MAPIProperty(0x4080, Types.UNKNOWN, "OofReplyType", "PR_OOF_REPLY_TYPE"); public static final MAPIProperty ORGANIZATIONAL_ID_NUMBER = new MAPIProperty(0x3a10, ASCII_STRING, "OrganizationalIdNumber", "PR_ORGANIZATIONAL_ID_NUMBER"); public static final MAPIProperty ORIG_ENTRY_ID = - new MAPIProperty(0x300f, -1, "OrigEntryId", "PR_ORIG_ENTRYID"); + new MAPIProperty(0x300f, Types.UNKNOWN, "OrigEntryId", "PR_ORIG_ENTRYID"); public static final MAPIProperty ORIG_MESSAGE_CLASS = new MAPIProperty(0x4b, ASCII_STRING, "OrigMessageClass", "PR_ORIG_MESSAGE_CLASS"); public static final MAPIProperty ORIGIN_CHECK = @@ -737,9 +742,9 @@ public class MAPIProperty { public static final MAPIProperty PROOF_OF_SUBMISSION_REQUESTED = new MAPIProperty(40, BOOLEAN, "ProofOfSubmissionRequested", "PR_PROOF_OF_SUBMISSION_REQUESTED"); public static final MAPIProperty PROP_ID_SECURE_MAX = - new MAPIProperty(0x67ff, -1, "PropIdSecureMax", "PROP_ID_SECURE_MAX"); + new MAPIProperty(0x67ff, Types.UNKNOWN, "PropIdSecureMax", "PROP_ID_SECURE_MAX"); public static final MAPIProperty PROP_ID_SECURE_MIN = - new MAPIProperty(0x67f0, -1, "PropIdSecureMin", "PROP_ID_SECURE_MIN"); + new MAPIProperty(0x67f0, Types.UNKNOWN, "PropIdSecureMin", "PROP_ID_SECURE_MIN"); public static final MAPIProperty PROVIDER_DISPLAY = new MAPIProperty(0x3006, ASCII_STRING, "ProviderDisplay", "PR_PROVIDER_DISPLAY"); public static final MAPIProperty PROVIDER_DLL_NAME = @@ -751,7 +756,7 @@ public class MAPIProperty { public static final MAPIProperty PROVIDER_UID = new MAPIProperty(0x300c, BINARY, "ProviderUid", "PR_PROVIDER_UID"); public static final MAPIProperty PUID = - new MAPIProperty(0x300e, -1, "Puid", "PR_PUID"); + new MAPIProperty(0x300e, Types.UNKNOWN, "Puid", "PR_PUID"); public static final MAPIProperty RADIO_TELEPHONE_NUMBER = new MAPIProperty(0x3a1d, ASCII_STRING, "RadioTelephoneNumber", "PR_RADIO_TELEPHONE_NUMBER"); public static final MAPIProperty RCVD_REPRESENTING_ADDRTYPE = @@ -783,11 +788,11 @@ public class MAPIProperty { public static final MAPIProperty RECEIVED_BY_NAME = new MAPIProperty(0x40, ASCII_STRING, "ReceivedByName", "PR_RECEIVED_BY_NAME"); public static final MAPIProperty RECIPIENT_DISPLAY_NAME = - new MAPIProperty(0x5ff6, -1, "RecipientDisplayName", null); + new MAPIProperty(0x5ff6, Types.UNKNOWN, "RecipientDisplayName", null); public static final MAPIProperty RECIPIENT_ENTRY_ID = - new MAPIProperty(0x5ff7, -1, "RecipientEntryId", null); + new MAPIProperty(0x5ff7, Types.UNKNOWN, "RecipientEntryId", null); public static final MAPIProperty RECIPIENT_FLAGS = - new MAPIProperty(0x5ffd, -1, "RecipientFlags", null); + new MAPIProperty(0x5ffd, Types.UNKNOWN, "RecipientFlags", null); public static final MAPIProperty RECEIVED_BY_SEARCH_KEY = new MAPIProperty(0x51, BINARY, "ReceivedBySearchKey", "PR_RECEIVED_BY_SEARCH_KEY"); public static final MAPIProperty RECIPIENT_CERTIFICATE = @@ -887,7 +892,7 @@ public class MAPIProperty { public static final MAPIProperty SEND_INTERNET_ENCODING = new MAPIProperty(0x3a71, LONG, "SendInternetEncoding", "PR_SEND_INTERNET_ENCODING"); public static final MAPIProperty SEND_RECALL_REPORT = - new MAPIProperty(0x6803, -1, "SendRecallReport", "messages"); + new MAPIProperty(0x6803, Types.UNKNOWN, "SendRecallReport", "messages"); public static final MAPIProperty SEND_RICH_INFO = new MAPIProperty(0x3a40, BOOLEAN, "SendRichInfo", "PR_SEND_RICH_INFO"); public static final MAPIProperty SENDER_ADDRTYPE = @@ -915,7 +920,7 @@ public class MAPIProperty { public static final MAPIProperty SENTMAIL_ENTRY_ID = new MAPIProperty(0xe0a, BINARY, "SentmailEntryId", "PR_SENTMAIL_ENTRYID"); public static final MAPIProperty SERVICE_DELETE_FILES = - new MAPIProperty(0x3d10, 4126, "ServiceDeleteFiles", "PR_SERVICE_DELETE_FILES"); + new MAPIProperty(0x3d10, Types.createCustom(4126), "ServiceDeleteFiles", "PR_SERVICE_DELETE_FILES"); public static final MAPIProperty SERVICE_DLL_NAME = new MAPIProperty(0x3d0a, ASCII_STRING, "ServiceDllName", "PR_SERVICE_DLL_NAME"); public static final MAPIProperty SERVICE_ENTRY_NAME = @@ -925,7 +930,7 @@ public class MAPIProperty { public static final MAPIProperty SERVICE_NAME = new MAPIProperty(0x3d09, ASCII_STRING, "ServiceName", "PR_SERVICE_NAME"); public static final MAPIProperty SERVICE_SUPPORT_FILES = - new MAPIProperty(0x3d0f, 4126, "ServiceSupportFiles", "PR_SERVICE_SUPPORT_FILES"); + new MAPIProperty(0x3d0f, Types.createCustom(4126), "ServiceSupportFiles", "PR_SERVICE_SUPPORT_FILES"); public static final MAPIProperty SERVICE_UID = new MAPIProperty(0x3d0c, BINARY, "ServiceUid", "PR_SERVICE_UID"); public static final MAPIProperty SERVICES = @@ -933,7 +938,7 @@ public class MAPIProperty { public static final MAPIProperty SEVEN_BIT_DISPLAY_NAME = new MAPIProperty(0x39ff, ASCII_STRING, "SevenBitDisplayName", "PR_SEVEN_BIT_DISPLAY_NAME"); public static final MAPIProperty SMTP_ADDRESS = - new MAPIProperty(0x39fe, -1, "SmtpAddress", "PR_SMTP_ADDRESS"); + new MAPIProperty(0x39fe, Types.UNKNOWN, "SmtpAddress", "PR_SMTP_ADDRESS"); public static final MAPIProperty SPOOLER_STATUS = new MAPIProperty(0xe10, LONG, "SpoolerStatus", "PR_SPOOLER_STATUS"); public static final MAPIProperty SPOUSE_NAME = @@ -1001,7 +1006,7 @@ public class MAPIProperty { public static final MAPIProperty USER_CERTIFICATE = new MAPIProperty(0x3a22, BINARY, "UserCertificate", "PR_USER_CERTIFICATE"); public static final MAPIProperty USER_X509_CERTIFICATE = - new MAPIProperty(0x3a70, 4354, "UserX509Certificate", "PR_USER_X509_CERTIFICATE"); + new MAPIProperty(0x3a70, Types.createCustom(4354), "UserX509Certificate", "PR_USER_X509_CERTIFICATE"); public static final MAPIProperty VALID_FOLDER_MASK = new MAPIProperty(0x35df, LONG, "ValidFolderMask", "PR_VALID_FOLDER_MASK"); public static final MAPIProperty VIEWS_ENTRY_ID = @@ -1018,20 +1023,22 @@ public class MAPIProperty { new MAPIProperty(0x3f06, LONG, "Ypos", "PR_YPOS"); public static final MAPIProperty UNKNOWN = - new MAPIProperty(-1, -1, "Unknown", null); + new MAPIProperty(-1, Types.UNKNOWN, "Unknown", null); // 0x8??? ones are outlook specific, and not standard MAPI + // TODO See http://msdn.microsoft.com/en-us/library/ee157150%28v=exchg.80%29 for some + // info on how we might decode them properly in the future private static final int ID_FIRST_CUSTOM = 0x8000; private static final int ID_LAST_CUSTOM = 0xFFFE; /* --------------------------------------------------------------------- */ public final int id; - public final int usualType; + public final MAPIType usualType; public final String name; public final String mapiProperty; - private MAPIProperty(int id, int usualType, String name, String mapiProperty) { + private MAPIProperty(int id, MAPIType usualType, String name, String mapiProperty) { this.id = id; this.usualType = usualType; this.name = name; @@ -1077,12 +1084,12 @@ public class MAPIProperty { return Collections.unmodifiableCollection( attributes.values() ); } - public static MAPIProperty createCustom(int id, int type, String name) { + public static MAPIProperty createCustom(int id, MAPIType type, String name) { return new CustomMAPIProperty(id, type, name, null); } private static class CustomMAPIProperty extends MAPIProperty { - private CustomMAPIProperty(int id, int usualType, String name, String mapiProperty) { + private CustomMAPIProperty(int id, MAPIType usualType, String name, String mapiProperty) { super(id, usualType, name, mapiProperty); } } diff --git a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/MessagePropertiesChunk.java b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/MessagePropertiesChunk.java new file mode 100644 index 0000000000..ab9b1bfaf5 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/MessagePropertiesChunk.java @@ -0,0 +1,89 @@ +/* ==================================================================== + 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.hsmf.datatypes; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.apache.poi.util.LittleEndian; + +/** + * A {@link PropertiesChunk} for a Message or Embedded-Message. + * This has a 32 byte header + */ +public class MessagePropertiesChunk extends PropertiesChunk { + private long nextRecipientId; + private long nextAttachmentId; + private long recipientCount; + private long attachmentCount; + + public MessagePropertiesChunk() { + super(); + } + + public long getNextRecipientId() { + return nextRecipientId; + } + public long getNextAttachmentId() { + return nextAttachmentId; + } + + public long getRecipientCount() { + return recipientCount; + } + public long getAttachmentCount() { + return attachmentCount; + } + + @Override + public void readValue(InputStream stream) throws IOException { + // 8 bytes of reserved zeros + LittleEndian.readLong(stream); + + // Nexts and counts + nextRecipientId = LittleEndian.readUInt(stream); + nextAttachmentId = LittleEndian.readUInt(stream); + recipientCount = LittleEndian.readUInt(stream); + attachmentCount = LittleEndian.readUInt(stream); + + // 8 bytes of reserved zeros + LittleEndian.readLong(stream); + + // Now properties + readProperties(stream); + } + + @Override + public void writeValue(OutputStream out) throws IOException { + // 8 bytes of reserved zeros + out.write(new byte[8]); + + // Nexts and counts + LittleEndian.putUInt(nextRecipientId, out); + LittleEndian.putUInt(nextAttachmentId, out); + LittleEndian.putUInt(recipientCount, out); + LittleEndian.putUInt(attachmentCount, out); + + // 8 bytes of reserved zeros + out.write(new byte[8]); + + // Now properties + writeProperties(out); + } +} diff --git a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/MessageSubmissionChunk.java b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/MessageSubmissionChunk.java index 902549c1f0..397717c7fa 100644 --- a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/MessageSubmissionChunk.java +++ b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/MessageSubmissionChunk.java @@ -24,6 +24,7 @@ import java.util.Calendar; import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.apache.poi.hsmf.datatypes.Types.MAPIType; import org.apache.poi.util.IOUtils; /** @@ -44,7 +45,7 @@ public class MessageSubmissionChunk extends Chunk { /** * Creates a Byte Chunk. */ - public MessageSubmissionChunk(String namePrefix, int chunkId, int type) { + public MessageSubmissionChunk(String namePrefix, int chunkId, MAPIType type) { super(namePrefix, chunkId, type); } @@ -52,7 +53,7 @@ public class MessageSubmissionChunk extends Chunk { * Create a Byte Chunk, with the specified * type. */ - public MessageSubmissionChunk(int chunkId, int type) { + public MessageSubmissionChunk(int chunkId, MAPIType type) { super(chunkId, type); } diff --git a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/NameIdChunks.java b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/NameIdChunks.java index cbcce93d12..bb78ea308e 100644 --- a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/NameIdChunks.java +++ b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/NameIdChunks.java @@ -26,7 +26,7 @@ import java.util.List; * NameID part of an outlook file */ public final class NameIdChunks implements ChunkGroup { - public static final String PREFIX = "__nameid_version1.0"; + public static final String NAME = "__nameid_version1.0"; /** Holds all the chunks that were found. */ private List<Chunk> allChunks = new ArrayList<Chunk>(); diff --git a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/PropertiesChunk.java b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/PropertiesChunk.java new file mode 100644 index 0000000000..b83ae7eb46 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/PropertiesChunk.java @@ -0,0 +1,87 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hsmf.datatypes; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * A Chunk which holds fixed-length properties, and pointer + * to the variable length ones (which get their own chunk). + * There are two kinds of PropertiesChunks, which differ only in + * their headers. + */ +public abstract class PropertiesChunk extends Chunk { + public static final String NAME = "__properties_version1.0"; + + /** + * Holds properties, indexed by type. Properties can be multi-valued + */ + private Map<MAPIProperty, List<PropertyValue>> properties = + new HashMap<MAPIProperty, List<PropertyValue>>(); + + /** + * Creates a Properties Chunk. + */ + protected PropertiesChunk() { + super(NAME, -1, Types.UNKNOWN); + } + + @Override + public String getEntryName() { + return NAME; + } + + /** + * Returns all the properties in the chunk + */ + public Map<MAPIProperty, List<PropertyValue>> getProperties() { + return properties; + } + + /** + * Returns all values for the given property, of null if none exist + */ + public List<PropertyValue> getValues(MAPIProperty property) { + return properties.get(property); + } + + /** + * Returns the (first/only) value for the given property, or + * null if none exist + */ + public PropertyValue getValue(MAPIProperty property) { + List<PropertyValue> values = properties.get(property); + if (values != null && values.size() > 0) { + return values.get(0); + } + return null; + } + + protected void readProperties(InputStream value) throws IOException { + // TODO + } + + protected void writeProperties(OutputStream out) throws IOException { + // TODO + } +} diff --git a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/PropertyValue.java b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/PropertyValue.java new file mode 100644 index 0000000000..1468c094d9 --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/PropertyValue.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.hsmf.datatypes; + +import org.apache.poi.util.LittleEndian; + +/** + * An instance of a {@link MAPIProperty} inside a {@link PropertiesChunk}. + * Where the {@link Types} type is a fixed length one, this will contain the + * actual value. + * Where the {@link Types} type is a variable length one, this will contain + * the length of the property, and the value will be in the associated {@link Chunk}. + */ +public class PropertyValue { + private MAPIProperty property; + private long flags; + protected byte[] data; + + public PropertyValue(MAPIProperty property, long flags, byte[] data) { + this.property = property; + this.flags = flags; + this.data = data; + } + + public MAPIProperty getProperty() { + return property; + } + + /** + * Get the raw value flags. + * TODO Also provide getters for the flag meanings + */ + public long getFlags() { + return flags; + } + + public Object getValue() { + return data; + } + public void setRawValue(byte[] value) { + this.data = value; + } + + // TODO classes for the other important value types + public static class LongLongPropertyValue extends PropertyValue { + public LongLongPropertyValue(MAPIProperty property, long flags, byte[] data) { + super(property, flags, data); + } + + public Long getValue() { + return LittleEndian.getLong(data); + } + public void setValue(long value) { + if (data.length != 8) { + data = new byte[8]; + } + LittleEndian.putLong(data, 0, value); + } + } +} diff --git a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/StoragePropertiesChunk.java b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/StoragePropertiesChunk.java new file mode 100644 index 0000000000..166d38ba9c --- /dev/null +++ b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/StoragePropertiesChunk.java @@ -0,0 +1,53 @@ +/* ==================================================================== + 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.hsmf.datatypes; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import org.apache.poi.util.LittleEndian; + +/** + * A {@link PropertiesChunk} for a Storage Properties, such as + * Attachments and Recipients. + * This only has a 8 byte header + */ +public class StoragePropertiesChunk extends PropertiesChunk { + public StoragePropertiesChunk() { + super(); + } + + @Override + public void readValue(InputStream stream) throws IOException { + // 8 bytes of reserved zeros + LittleEndian.readLong(stream); + + // Now properties + readProperties(stream); + } + + @Override + public void writeValue(OutputStream out) throws IOException { + // 8 bytes of reserved zeros + out.write(new byte[8]); + + // Now properties + writeProperties(out); + } +}
\ No newline at end of file diff --git a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/StringChunk.java b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/StringChunk.java index 133389bbcc..2051f44c28 100644 --- a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/StringChunk.java +++ b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/StringChunk.java @@ -22,7 +22,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; -import org.apache.poi.hsmf.datatypes.Types; +import org.apache.poi.hsmf.datatypes.Types.MAPIType; import org.apache.poi.util.IOUtils; import org.apache.poi.util.StringUtil; @@ -38,7 +38,7 @@ public class StringChunk extends Chunk { /** * Creates a String Chunk. */ - public StringChunk(String namePrefix, int chunkId, int type) { + public StringChunk(String namePrefix, int chunkId, MAPIType type) { super(namePrefix, chunkId, type); } @@ -46,7 +46,7 @@ public class StringChunk extends Chunk { * Create a String Chunk, with the specified * type. */ - public StringChunk(int chunkId, int type) { + public StringChunk(int chunkId, MAPIType type) { super(chunkId, type); } @@ -81,14 +81,11 @@ public class StringChunk extends Chunk { } private void parseString() { String tmpValue; - switch(type) { - case Types.ASCII_STRING: + if (type == Types.ASCII_STRING) { tmpValue = parseAs7BitData(rawValue, encoding7Bit); - break; - case Types.UNICODE_STRING: + } else if (type == Types.UNICODE_STRING) { tmpValue = StringUtil.getFromUnicodeLE(rawValue); - break; - default: + } else { throw new IllegalArgumentException("Invalid type " + type + " for String Chunk"); } @@ -100,19 +97,16 @@ public class StringChunk extends Chunk { out.write(rawValue); } private void storeString() { - switch(type) { - case Types.ASCII_STRING: + if (type == Types.ASCII_STRING) { try { rawValue = value.getBytes(encoding7Bit); } catch (UnsupportedEncodingException e) { throw new RuntimeException("Encoding not found - " + encoding7Bit, e); } - break; - case Types.UNICODE_STRING: + } else if (type == Types.UNICODE_STRING) { rawValue = new byte[value.length()*2]; StringUtil.putUnicodeLE(value, rawValue, 0); - break; - default: + } else { throw new IllegalArgumentException("Invalid type " + type + " for String Chunk"); } } diff --git a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/Types.java b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/Types.java index bd76e14f9c..a4732f081f 100644 --- a/src/scratchpad/src/org/apache/poi/hsmf/datatypes/Types.java +++ b/src/scratchpad/src/org/apache/poi/hsmf/datatypes/Types.java @@ -17,56 +17,121 @@ package org.apache.poi.hsmf.datatypes; +import java.util.HashMap; +import java.util.Map; + /** * The types list and details are available from * http://msdn.microsoft.com/en-us/library/microsoft.exchange.data.contenttypes.tnef.tnefpropertytype%28v=EXCHG.140%29.aspx */ public final class Types { + private static Map<Integer, MAPIType> builtInTypes = new HashMap<Integer, MAPIType>(); + private static Map<Integer, MAPIType> customTypes = new HashMap<Integer, Types.MAPIType>(); + /** Unspecified */ - public static final int UNSPECIFIED = 0x0000; + public static final MAPIType UNSPECIFIED = new MAPIType(0x0000, "Unspecified", -1); + /** Unknown */ + public static final MAPIType UNKNOWN = new MAPIType(-1, "Unknown", -1); /** Null - NULL property value */ - public static final int NULL = 0x0001; + public static final MAPIType NULL = new MAPIType(0x0001, "Null", 0); /** I2 - signed 16-bit value */ - public static final int SHORT = 0x0002; + public static final MAPIType SHORT = new MAPIType(0x0002, "Short", 2); /** Long - signed 32-bit value */ - public static final int LONG = 0x0003; + public static final MAPIType LONG = new MAPIType(0x0003, "Long", 4); /** R4 - 4-byte floating point value */ - public static final int FLOAT = 0x0004; + public static final MAPIType FLOAT = new MAPIType(0x0004, "Float", 4); /** Double - floating point double */ - public static final int DOUBLE = 0x0005; + public static final MAPIType DOUBLE = new MAPIType(0x0005, "Double", 8); /** Currency - signed 64-bit integer that represents a base ten decimal with four digits to the right of the decimal point */ - public static final int CURRENCY = 0x0006; + public static final MAPIType CURRENCY = new MAPIType(0x0006, "Currency", 8); /** AppTime - application time value */ - public static final int APP_TIME = 0x0007; + public static final MAPIType APP_TIME = new MAPIType(0x0007, "Application Time", 8); /** Error - 32-bit error value */ - public static final int ERROR = 0x000A; + public static final MAPIType ERROR = new MAPIType(0x000A, "Error", 4); /** Boolean - 16-bit Boolean value. '0' is false. Non-zero is true */ - public static final int BOOLEAN = 0x000B; + public static final MAPIType BOOLEAN = new MAPIType(0x000B, "Boolean", 2); /** Object/Directory - embedded object in a property */ - public static final int DIRECTORY = 0x000D; + public static final MAPIType DIRECTORY = new MAPIType(0x000D, "Directory", -1); /** I8 - 8-byte signed integer */ - public static final int LONG_LONG = 0x0014; + public static final MAPIType LONG_LONG = new MAPIType(0x0014, "Long Long", 8); /** SysTime - FILETIME 64-bit integer specifying the number of 100ns periods since Jan 1, 1601 */ - public static final int TIME = 0x0040; + public static final MAPIType TIME = new MAPIType(0x0040, "Time", 8); /** ClassId - OLE GUID */ - public static final int CLS_ID = 0x0048; + public static final MAPIType CLS_ID = new MAPIType(0x0048, "CLS ID GUID", 16); /** Binary - counted byte array */ - public static final int BINARY = 0x0102; + public static final MAPIType BINARY = new MAPIType(0x0102, "Binary", -1); /** * An 8-bit string, probably in CP1252, but don't quote us... * Normally used for everything before Outlook 3.0, and some * fields in Outlook 3.0. */ - public static final int ASCII_STRING = 0x001E; + public static final MAPIType ASCII_STRING = new MAPIType(0x001E, "ASCII String", -1); /** A string, from Outlook 3.0 onwards. Normally unicode */ - public static final int UNICODE_STRING = 0x001F; + public static final MAPIType UNICODE_STRING = new MAPIType(0x001F, "Unicode String", -1); /** MultiValued - Value part contains multiple values */ public static final int MULTIVALUED_FLAG = 0x1000; + public static final class MAPIType { + private final int id; + private final String name; + private final int length; + + /** + * Creates a standard, built-in type + */ + private MAPIType(int id, String name, int length) { + this.id = id; + this.name = name; + this.length = length; + builtInTypes.put(id, this); + } + /** + * Creates a custom type + */ + private MAPIType(int id, int length) { + this.id = id; + this.name = asCustomName(id); + this.length = length; + customTypes.put(id, this); + } + + /** + * Returns the length, in bytes, of values of this type, or + * -1 if it is a variable length type. + */ + public int getLength() { + return length; + } + /** + * Is this type a fixed-length type, or a variable-length one? + */ + public boolean isFixedLength() { + return (length != -1); + } + + public int getId() { + return id; + } + public String getName() { + return name; + } + + /** + * Return the 4 character hex encoded version, + * as used in file endings + */ + public String asFileEnding() { + return Types.asFileEnding(id); + } + } + + public static MAPIType getById(int typeId) { + return builtInTypes.get(typeId); + } public static String asFileEnding(int type) { String str = Integer.toHexString(type).toUpperCase(); @@ -75,45 +140,36 @@ public final class Types { } return str; } - public static String asName(int type) { - switch(type) { - case BINARY: - return "Binary"; - case ASCII_STRING: - return "ASCII String"; - case UNICODE_STRING: - return "Unicode String"; - case UNSPECIFIED: - return "Unspecified"; - case NULL: - return "Null"; - case SHORT: - return "Short"; - case LONG: - return "Long"; - case LONG_LONG: - return "Long Long"; - case FLOAT: - return "Float"; - case DOUBLE: - return "Double"; - case CURRENCY: - return "Currency"; - case APP_TIME: - return "Application Time"; - case ERROR: - return "Error"; - case TIME: - return "Time"; - case BOOLEAN: - return "Boolean"; - case CLS_ID: - return "CLS ID GUID"; - case DIRECTORY: - return "Directory"; - case -1: - return "Unknown"; + public static String asName(int typeId) { + MAPIType type = builtInTypes.get(typeId); + if (type != null) { + return type.name; + } + return asCustomName(typeId); + } + private static String asCustomName(int typeId) { + return "0x" + Integer.toHexString(typeId); + } + + public static MAPIType createCustom(int typeId) { + // Check they're not being silly, and asking for a built-in one... + if (getById(typeId) != null) { + return getById(typeId); + } + + // Try to get an existing definition of this + MAPIType type = customTypes.get(typeId); + + // If none, do a thread-safe creation + if (type == null) { + synchronized (customTypes) { + type = customTypes.get(typeId); + if (type == null) { + type = new MAPIType(typeId, -1); + } + } } - return "0x" + Integer.toHexString(type); + + return type; } } diff --git a/src/scratchpad/src/org/apache/poi/hsmf/dev/HSMFDump.java b/src/scratchpad/src/org/apache/poi/hsmf/dev/HSMFDump.java index 43edf92121..436298de45 100644 --- a/src/scratchpad/src/org/apache/poi/hsmf/dev/HSMFDump.java +++ b/src/scratchpad/src/org/apache/poi/hsmf/dev/HSMFDump.java @@ -23,7 +23,8 @@ import java.io.IOException; import org.apache.poi.hsmf.datatypes.Chunk; import org.apache.poi.hsmf.datatypes.ChunkGroup; import org.apache.poi.hsmf.datatypes.MAPIProperty; -import org.apache.poi.hsmf.datatypes.Types; +import org.apache.poi.hsmf.datatypes.PropertiesChunk; +import org.apache.poi.hsmf.datatypes.PropertyValue; import org.apache.poi.hsmf.parsers.POIFSChunkParser; import org.apache.poi.poifs.filesystem.POIFSFileSystem; @@ -43,18 +44,35 @@ public class HSMFDump { for(Chunk chunk : chunks.getChunks()) { MAPIProperty attr = MAPIProperty.get(chunk.getChunkId()); - String idName = attr.id + " - " + attr.name; - if(attr == MAPIProperty.UNKNOWN) { - idName = chunk.getChunkId() + " - (unknown)"; + if (chunk instanceof PropertiesChunk) { + PropertiesChunk props = (PropertiesChunk)chunk; + System.out.println( + " Properties - " + props.getProperties().size() + ":" + ); + + for (MAPIProperty prop : props.getProperties().keySet()) { + System.out.println( + " * " + prop + ); + for (PropertyValue v : props.getValues(prop)) { + System.out.println( + " = " + v.toString() + ); + } + } + } else { + String idName = attr.id + " - " + attr.name; + if(attr == MAPIProperty.UNKNOWN) { + idName = chunk.getChunkId() + " - (unknown)"; + } + + System.out.println( + " " + idName + " - " + chunk.getType().getName() + ); + System.out.println( + " " + chunk.toString() + ); } - - System.out.println( - " " + idName + " - " + - Types.asName(chunk.getType()) - ); - System.out.println( - " " + chunk.toString() - ); } System.out.println(); } diff --git a/src/scratchpad/src/org/apache/poi/hsmf/dev/TypesLister.java b/src/scratchpad/src/org/apache/poi/hsmf/dev/TypesLister.java index fda6513d61..39b0ebce5a 100644 --- a/src/scratchpad/src/org/apache/poi/hsmf/dev/TypesLister.java +++ b/src/scratchpad/src/org/apache/poi/hsmf/dev/TypesLister.java @@ -23,7 +23,6 @@ import java.util.Collections; import java.util.Comparator; import org.apache.poi.hsmf.datatypes.MAPIProperty; -import org.apache.poi.hsmf.datatypes.Types; /** * Lists the different MAPI types @@ -56,9 +55,15 @@ public class TypesLister { String id = Integer.toHexString(attr.id); while(id.length() < 4) { id = "0"+id; } + int typeId = attr.usualType.getId(); + String typeIdStr = Integer.toString(typeId); + if (typeId > 0) { + typeIdStr = typeIdStr + " / 0x" + Integer.toHexString(typeId); + } + out.println("0x" + id + " - " + attr.name); - out.println(" " + attr.id + " - " + Types.asName(attr.usualType) + - " (" + attr.usualType + ") - " + attr.mapiProperty); + out.println(" " + attr.id + " - " + attr.usualType.getName() + + " (" + typeIdStr + ") - " + attr.mapiProperty); } } diff --git a/src/scratchpad/src/org/apache/poi/hsmf/parsers/POIFSChunkParser.java b/src/scratchpad/src/org/apache/poi/hsmf/parsers/POIFSChunkParser.java index 6f542c5ed7..6800b0ac77 100644 --- a/src/scratchpad/src/org/apache/poi/hsmf/parsers/POIFSChunkParser.java +++ b/src/scratchpad/src/org/apache/poi/hsmf/parsers/POIFSChunkParser.java @@ -27,11 +27,15 @@ import org.apache.poi.hsmf.datatypes.ChunkGroup; import org.apache.poi.hsmf.datatypes.Chunks; import org.apache.poi.hsmf.datatypes.DirectoryChunk; import org.apache.poi.hsmf.datatypes.MAPIProperty; +import org.apache.poi.hsmf.datatypes.MessagePropertiesChunk; import org.apache.poi.hsmf.datatypes.MessageSubmissionChunk; import org.apache.poi.hsmf.datatypes.NameIdChunks; +import org.apache.poi.hsmf.datatypes.PropertiesChunk; import org.apache.poi.hsmf.datatypes.RecipientChunks; +import org.apache.poi.hsmf.datatypes.StoragePropertiesChunk; import org.apache.poi.hsmf.datatypes.StringChunk; import org.apache.poi.hsmf.datatypes.Types; +import org.apache.poi.hsmf.datatypes.Types.MAPIType; import org.apache.poi.poifs.filesystem.DirectoryNode; import org.apache.poi.poifs.filesystem.DocumentInputStream; import org.apache.poi.poifs.filesystem.DocumentNode; @@ -65,7 +69,7 @@ public final class POIFSChunkParser { if(dir.getName().startsWith(AttachmentChunks.PREFIX)) { group = new AttachmentChunks(dir.getName()); } - if(dir.getName().startsWith(NameIdChunks.PREFIX)) { + if(dir.getName().startsWith(NameIdChunks.NAME)) { group = new NameIdChunks(); } if(dir.getName().startsWith(RecipientChunks.PREFIX)) { @@ -97,7 +101,7 @@ public final class POIFSChunkParser { if(entry instanceof DocumentNode) { process(entry, grouping); } else if(entry instanceof DirectoryNode) { - if(entry.getName().endsWith(Types.asFileEnding(Types.DIRECTORY))) { + if(entry.getName().endsWith(Types.DIRECTORY.asFileEnding())) { process(entry, grouping); } } @@ -109,81 +113,98 @@ public final class POIFSChunkParser { */ protected static void process(Entry entry, ChunkGroup grouping) { String entryName = entry.getName(); + Chunk chunk = null; - if(entryName.length() < 9) { - // Name in the wrong format - return; - } - if(entryName.indexOf('_') == -1) { - // Name in the wrong format - return; - } - - // Split it into its parts - int splitAt = entryName.lastIndexOf('_'); - String namePrefix = entryName.substring(0, splitAt+1); - String ids = entryName.substring(splitAt+1); - - // Make sure we got what we expected, should be of - // the form __<name>_<id><type> - if(namePrefix.equals("Olk10SideProps") || - namePrefix.equals("Olk10SideProps_")) { - // This is some odd Outlook 2002 thing, skip - return; - } else if(splitAt <= entryName.length()-8) { - // In the right form for a normal chunk - // We'll process this further in a little bit + // Is it a properties chunk? (They have special names) + if (entryName.equals(PropertiesChunk.NAME)) { + if (grouping instanceof Chunks) { + // These should be the properties for the message itself + chunk = new MessagePropertiesChunk(); + } else { + // Will be properties on an attachment or recipient + chunk = new StoragePropertiesChunk(); + } } else { - // Underscores not the right place, something's wrong - throw new IllegalArgumentException("Invalid chunk name " + entryName); - } - - // Now try to turn it into id + type - try { - int chunkId = Integer.parseInt(ids.substring(0, 4), 16); - int type = Integer.parseInt(ids.substring(4, 8), 16); + // Check it's a regular chunk + if(entryName.length() < 9) { + // Name in the wrong format + return; + } + if(entryName.indexOf('_') == -1) { + // Name in the wrong format + return; + } - Chunk chunk = null; + // Split it into its parts + int splitAt = entryName.lastIndexOf('_'); + String namePrefix = entryName.substring(0, splitAt+1); + String ids = entryName.substring(splitAt+1); - // Special cases based on the ID - if(chunkId == MAPIProperty.MESSAGE_SUBMISSION_ID.id) { - chunk = new MessageSubmissionChunk(namePrefix, chunkId, type); - } - else { - // Nothing special about this ID - // So, do the usual thing which is by type - switch(type) { - case Types.BINARY: - chunk = new ByteChunk(namePrefix, chunkId, type); - break; - case Types.DIRECTORY: - if(entry instanceof DirectoryNode) { - chunk = new DirectoryChunk((DirectoryNode)entry, namePrefix, chunkId, type); + // Make sure we got what we expected, should be of + // the form __<name>_<id><type> + if(namePrefix.equals("Olk10SideProps") || + namePrefix.equals("Olk10SideProps_")) { + // This is some odd Outlook 2002 thing, skip + return; + } else if(splitAt <= entryName.length()-8) { + // In the right form for a normal chunk + // We'll process this further in a little bit + } else { + // Underscores not the right place, something's wrong + throw new IllegalArgumentException("Invalid chunk name " + entryName); + } + + // Now try to turn it into id + type + try { + int chunkId = Integer.parseInt(ids.substring(0, 4), 16); + int typeId = Integer.parseInt(ids.substring(4, 8), 16); + + MAPIType type = Types.getById(typeId); + if (type == null) { + type = Types.createCustom(typeId); + } + + // Special cases based on the ID + if(chunkId == MAPIProperty.MESSAGE_SUBMISSION_ID.id) { + chunk = new MessageSubmissionChunk(namePrefix, chunkId, type); + } + else { + // Nothing special about this ID + // So, do the usual thing which is by type + if (type == Types.BINARY) { + chunk = new ByteChunk(namePrefix, chunkId, type); + } + else if (type == Types.DIRECTORY) { + if(entry instanceof DirectoryNode) { + chunk = new DirectoryChunk((DirectoryNode)entry, namePrefix, chunkId, type); + } + } + else if (type == Types.ASCII_STRING || + type == Types.UNICODE_STRING) { + chunk = new StringChunk(namePrefix, chunkId, type); + } + else { + // Type of an unsupported type! Skipping... } - break; - case Types.ASCII_STRING: - case Types.UNICODE_STRING: - chunk = new StringChunk(namePrefix, chunkId, type); - break; } + } catch(NumberFormatException e) { + // Name in the wrong format + return; } + } - if(chunk != null) { - if(entry instanceof DocumentNode) { - try { - DocumentInputStream inp = new DocumentInputStream((DocumentNode)entry); - chunk.readValue(inp); - grouping.record(chunk); - } catch(IOException e) { - System.err.println("Error reading from part " + entry.getName() + " - " + e.toString()); - } - } else { + if(chunk != null) { + if(entry instanceof DocumentNode) { + try { + DocumentInputStream inp = new DocumentInputStream((DocumentNode)entry); + chunk.readValue(inp); grouping.record(chunk); + } catch(IOException e) { + System.err.println("Error reading from part " + entry.getName() + " - " + e.toString()); } - } - } catch(NumberFormatException e) { - // Name in the wrong format - return; + } else { + grouping.record(chunk); + } } } } diff --git a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Picture.java b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Picture.java index ff7af7cf21..670755dbf5 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Picture.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Picture.java @@ -34,6 +34,7 @@ import org.apache.poi.ddf.EscherProperty; import org.apache.poi.ddf.EscherRecord; import org.apache.poi.hwpf.model.PICF; import org.apache.poi.hwpf.model.PICFAndOfficeArtData; +import org.apache.poi.util.PngUtils; import org.apache.poi.util.POILogFactory; import org.apache.poi.util.POILogger; import org.apache.poi.util.StringUtil; @@ -191,6 +192,15 @@ public final class Picture { // Raw data is not compressed. content = rawContent; + + //PNG created on MAC may have a 16-byte prefix which prevents successful reading. + //Just cut it off!. + if (PngUtils.matchesPngHeader(content, 16)) + { + byte[] png = new byte[content.length-16]; + System.arraycopy(content, 16, png, 0, png.length); + content = png; + } } } diff --git a/src/scratchpad/testcases/org/apache/poi/hdgf/TestHDGFCore.java b/src/scratchpad/testcases/org/apache/poi/hdgf/TestHDGFCore.java index 25a513872b..c617341698 100644 --- a/src/scratchpad/testcases/org/apache/poi/hdgf/TestHDGFCore.java +++ b/src/scratchpad/testcases/org/apache/poi/hdgf/TestHDGFCore.java @@ -17,6 +17,7 @@ package org.apache.poi.hdgf; +import org.apache.poi.hdgf.extractor.VisioTextExtractor; import org.apache.poi.hdgf.streams.PointerContainingStream; import org.apache.poi.hdgf.streams.TrailerStream; import org.apache.poi.poifs.filesystem.POIFSFileSystem; @@ -88,4 +89,28 @@ public final class TestHDGFCore extends TestCase { HDGFDiagram hdgf = new HDGFDiagram(fs); assertNotNull(hdgf); } + + public void testV6NonUtf16LE() throws Exception { + fs = new POIFSFileSystem(_dgTests.openResourceAsStream("v6-non-utf16le.vsd")); + + HDGFDiagram hdgf = new HDGFDiagram(fs); + assertNotNull(hdgf); + + VisioTextExtractor textExtractor = new VisioTextExtractor(hdgf); + String text = textExtractor.getText().replace("\u0000", "").trim(); + + assertEquals("Table\n\n\nPropertySheet\n\n\n\nPropertySheetField", text); + } + + public void testUtf16LE() throws Exception { + fs = new POIFSFileSystem(_dgTests.openResourceAsStream("Test_Visio-Some_Random_Text.vsd")); + + HDGFDiagram hdgf = new HDGFDiagram(fs); + assertNotNull(hdgf); + + VisioTextExtractor textExtractor = new VisioTextExtractor(hdgf); + String text = textExtractor.getText().trim(); + + assertEquals("text\nView\nTest View\nI am a test view\nSome random text, on a page", text); + } } diff --git a/src/scratchpad/testcases/org/apache/poi/hslf/model/TestPicture.java b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestPicture.java index 871893210a..3444ae5fe8 100644 --- a/src/scratchpad/testcases/org/apache/poi/hslf/model/TestPicture.java +++ b/src/scratchpad/testcases/org/apache/poi/hslf/model/TestPicture.java @@ -20,10 +20,13 @@ package org.apache.poi.hslf.model; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import javax.imageio.ImageIO; import junit.framework.TestCase; import org.apache.poi.ddf.EscherBSERecord; +import org.apache.poi.hslf.HSLFSlideShow; import org.apache.poi.hslf.usermodel.PictureData; import org.apache.poi.hslf.usermodel.SlideShow; import org.apache.poi.POIDataSamples; @@ -88,4 +91,41 @@ public final class TestPicture extends TestCase { Graphics2D graphics = img.createGraphics(); pict.draw(graphics); } + + public void testMacImages() throws Exception { + HSLFSlideShow hss = new HSLFSlideShow(_slTests.openResourceAsStream("53446.ppt")); + + PictureData[] pictures = hss.getPictures(); + assertEquals(15, pictures.length); + + int[][] expectedSizes = { + null, // WMF + { 427, 428 }, // PNG + { 371, 370 }, // PNG + { 288, 183 }, // PNG + { 285, 97 }, // PNG + { 288, 168 }, // PNG + null, // WMF + null, // WMF + { 199, 259 }, // PNG + { 432, 244 }, // PNG + { 261, 258 }, // PNG + null, // WMF + null, // WMF + null, // WMF + null // EMF + }; + + for (int i = 0; i < pictures.length; i++) { + BufferedImage image = ImageIO.read(new ByteArrayInputStream(pictures[i].getData())); + + if (pictures[i].getType() != Picture.WMF && pictures[i].getType() != Picture.EMF) { + assertNotNull(image); + + int[] dimensions = expectedSizes[i]; + assertEquals(dimensions[0], image.getWidth()); + assertEquals(dimensions[1], image.getHeight()); + } + } + } } diff --git a/src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestChunkData.java b/src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestChunkData.java index 2303ccc0d0..ea6f8be81b 100644 --- a/src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestChunkData.java +++ b/src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestChunkData.java @@ -30,24 +30,29 @@ public final class TestChunkData extends TestCase { public void testChunkCreate() { Chunk chunk; - chunk = new StringChunk(0x0200, 0x001E); + chunk = new StringChunk(0x0200, Types.createCustom(0x001E)); assertEquals("__substg1.0_0200001E", chunk.getEntryName()); assertEquals(0x0200, chunk.getChunkId()); - assertEquals(0x001E, chunk.getType()); + assertEquals(0x001E, chunk.getType().getId()); - chunk = new StringChunk("__substg1.0_", 0x0200, 0x001E); + chunk = new StringChunk("__substg1.0_", 0x0200, Types.createCustom(0x001E)); assertEquals("__substg1.0_0200001E", chunk.getEntryName()); assertEquals(0x0200, chunk.getChunkId()); - assertEquals(0x001E, chunk.getType()); + assertEquals(0x001E, chunk.getType().getId()); + + chunk = new StringChunk("__substg1.0_", 0x0200, Types.getById(0x001E)); + assertEquals("__substg1.0_0200001E", chunk.getEntryName()); + assertEquals(0x0200, chunk.getChunkId()); + assertEquals(0x001E, chunk.getType().getId()); /* test the lower and upper limits of the chunk ids */ - chunk = new StringChunk(0x0000, 0x001E); + chunk = new StringChunk(0x0000, Types.createCustom(0x001E)); assertEquals("__substg1.0_0000001E", chunk.getEntryName()); - chunk = new StringChunk(0xFFFF, 0x001E); + chunk = new StringChunk(0xFFFF, Types.createCustom(0x001E)); assertEquals("__substg1.0_FFFF001E", chunk.getEntryName()); - chunk = new StringChunk(0xFFFF, 0x001F); + chunk = new StringChunk(0xFFFF, Types.createCustom(0x001F)); assertEquals("__substg1.0_FFFF001F", chunk.getEntryName()); } diff --git a/src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestMAPIProperty.java b/src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestMAPIProperty.java index 56c8859cca..55369e8dcd 100644 --- a/src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestMAPIProperty.java +++ b/src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestMAPIProperty.java @@ -37,16 +37,16 @@ public final class TestMAPIProperty extends TestCase { assertEquals(true, all.contains(MAPIProperty.DISPLAY_CC)); // Won't contain custom - assertEquals(false, all.contains(MAPIProperty.createCustom(1, 1, ""))); + assertEquals(false, all.contains(MAPIProperty.createCustom(1, Types.UNSPECIFIED, ""))); // Won't contain unknown assertEquals(false, all.contains(MAPIProperty.UNKNOWN)); } public void testCustom() throws Exception { - MAPIProperty c1 = MAPIProperty.createCustom(1, 1, ""); - MAPIProperty c2a = MAPIProperty.createCustom(2, 1, "2"); - MAPIProperty c2b = MAPIProperty.createCustom(2, 1, "2"); + MAPIProperty c1 = MAPIProperty.createCustom(1, Types.UNSPECIFIED, ""); + MAPIProperty c2a = MAPIProperty.createCustom(2, Types.UNSPECIFIED, "2"); + MAPIProperty c2b = MAPIProperty.createCustom(2, Types.UNSPECIFIED, "2"); // New object each time assertNotSame(c1, c2a); diff --git a/src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestTypes.java b/src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestTypes.java index 152ca4dee0..8a58639d79 100644 --- a/src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestTypes.java +++ b/src/scratchpad/testcases/org/apache/poi/hsmf/datatypes/TestTypes.java @@ -28,13 +28,21 @@ import junit.framework.TestCase; */ public final class TestTypes extends TestCase { public void testTypeIds() { - assertEquals(0x1e, Types.ASCII_STRING); - assertEquals(0x1f, Types.UNICODE_STRING); + assertEquals(0x1e, Types.ASCII_STRING.getId()); + assertEquals(0x1f, Types.UNICODE_STRING.getId()); - assertEquals(0x0102, Types.BINARY); - assertEquals(0x000B, Types.BOOLEAN); - assertEquals(0x0003, Types.LONG); - assertEquals(0x0040, Types.TIME); + assertEquals(0x0102, Types.BINARY.getId()); + assertEquals(0x000B, Types.BOOLEAN.getId()); + assertEquals(0x0003, Types.LONG.getId()); + assertEquals(0x0040, Types.TIME.getId()); + + assertEquals(Types.ASCII_STRING, Types.getById(0x1e)); + assertEquals(Types.UNICODE_STRING, Types.getById(0x1f)); + + assertEquals(Types.BINARY, Types.getById(0x0102)); + assertEquals(Types.BOOLEAN, Types.getById(0x000B)); + assertEquals(Types.LONG, Types.getById(0x0003)); + assertEquals(Types.TIME, Types.getById(0x0040)); } public void testTypeFormatting() { @@ -45,7 +53,7 @@ public final class TestTypes extends TestCase { } public void testName() { - assertEquals("ASCII String", Types.asName(Types.ASCII_STRING)); - assertEquals("Boolean", Types.asName(Types.BOOLEAN)); + assertEquals("ASCII String", Types.ASCII_STRING.getName()); + assertEquals("Boolean", Types.BOOLEAN.getName()); } } diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/TestHWPFPictures.java b/src/scratchpad/testcases/org/apache/poi/hwpf/TestHWPFPictures.java index f650f0d890..ae7cc3df24 100644 --- a/src/scratchpad/testcases/org/apache/poi/hwpf/TestHWPFPictures.java +++ b/src/scratchpad/testcases/org/apache/poi/hwpf/TestHWPFPictures.java @@ -17,10 +17,14 @@ package org.apache.poi.hwpf; +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; import java.util.List; import junit.framework.TestCase; +import org.apache.poi.POIDataSamples; import org.apache.poi.hwpf.model.PicturesTable; import org.apache.poi.hwpf.usermodel.Picture; import org.apache.poi.POIDataSamples; @@ -128,6 +132,30 @@ public final class TestHWPFPictures extends TestCase { assertBytesSame(picBytes, pic.getContent()); } + public void testMacImages() throws Exception { + HWPFDocument docC = HWPFTestDataSamples.openSampleFile("53446.doc"); + PicturesTable picturesTable = docC.getPicturesTable(); + List<Picture> pictures = picturesTable.getAllPictures(); + + assertEquals(4, pictures.size()); + + int[][] expectedSizes = { + { 185, 42 }, // PNG + { 260, 114 }, // PNG + { 185, 42 }, // PNG + { 260, 114 }, // PNG + }; + + for (int i = 0; i < pictures.size(); i++) { + BufferedImage image = ImageIO.read(new ByteArrayInputStream(pictures.get(i).getContent())); + assertNotNull(image); + + int[] dimensions = expectedSizes[i]; + assertEquals(dimensions[0], image.getWidth()); + assertEquals(dimensions[1], image.getHeight()); + } + } + /** * Pending the missing files being uploaded to * bug #44937 diff --git a/src/testcases/org/apache/poi/hssf/model/TestSheet.java b/src/testcases/org/apache/poi/hssf/model/TestSheet.java index d3248edffe..d898a999ad 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestSheet.java +++ b/src/testcases/org/apache/poi/hssf/model/TestSheet.java @@ -35,6 +35,8 @@ import org.apache.poi.ss.formula.FormulaShifter; import org.apache.poi.ss.util.CellRangeAddress; import org.apache.poi.util.HexRead; +import java.io.FileOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -773,4 +775,43 @@ public final class TestSheet extends TestCase { assertEquals(WindowTwoRecord.sid, ((Record)sheetRecords.get(2)).getSid()); assertEquals(EOFRecord.sid, ((Record)sheetRecords.get(3)).getSid()); } + + public void testSheetDimensions() throws IOException{ + InternalSheet sheet = InternalSheet.createSheet(); + DimensionsRecord dimensions = (DimensionsRecord)sheet.findFirstRecordBySid(DimensionsRecord.sid); + assertEquals(0, dimensions.getFirstCol()); + assertEquals(0, dimensions.getFirstRow()); + assertEquals(1, dimensions.getLastCol()); // plus pne + assertEquals(1, dimensions.getLastRow()); // plus pne + + RowRecord rr = new RowRecord(0); + sheet.addRow(rr); + + assertEquals(0, dimensions.getFirstCol()); + assertEquals(0, dimensions.getFirstRow()); + assertEquals(1, dimensions.getLastCol()); + assertEquals(1, dimensions.getLastRow()); + + CellValueRecordInterface cvr; + + cvr = new BlankRecord(); + cvr.setColumn((short)0); + cvr.setRow(0); + sheet.addValueRecord(0, cvr); + + assertEquals(0, dimensions.getFirstCol()); + assertEquals(0, dimensions.getFirstRow()); + assertEquals(1, dimensions.getLastCol()); + assertEquals(1, dimensions.getLastRow()); + + cvr = new BlankRecord(); + cvr.setColumn((short)1); + cvr.setRow(0); + sheet.addValueRecord(0, cvr); + + assertEquals(0, dimensions.getFirstCol()); + assertEquals(0, dimensions.getFirstRow()); + assertEquals(2, dimensions.getLastCol()); //YK: failed until Bugzilla 53414 was fixed + assertEquals(1, dimensions.getLastRow()); + } } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPictureData.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPictureData.java index 99f4ad8068..b9ff69543a 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPictureData.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFPictureData.java @@ -71,6 +71,29 @@ public final class TestHSSFPictureData extends TestCase{ } } } + + public void testMacPicture() throws IOException { + HSSFWorkbook wb = HSSFTestDataSamples.openSampleWorkbook("53446.xls"); + + @SuppressWarnings("unchecked") + List<HSSFPictureData> lst = (List<HSSFPictureData>)(List<?>)wb.getAllPictures(); + assertEquals(1, lst.size()); + + HSSFPictureData pict = lst.get(0); + String ext = pict.suggestFileExtension(); + if (!ext.equals("png")) { + fail("Expected a PNG."); + } + + //try to read image data using javax.imageio.* (JDK 1.4+) + byte[] data = pict.getData(); + BufferedImage png = ImageIO.read(new ByteArrayInputStream(data)); + assertNotNull(png); + assertEquals(78, png.getWidth()); + assertEquals(76, png.getHeight()); + assertEquals(HSSFWorkbook.PICTURE_TYPE_PNG, pict.getFormat()); + assertEquals("image/png", pict.getMimeType()); + } public void testNotNullPictures() throws IOException { diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java index d05f048771..a640ce971d 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFSheet.java @@ -57,6 +57,22 @@ public final class TestHSSFSheet extends BaseTestSheet { super(HSSFITestDataProvider.instance); } + + /** + * Test for Bugzilla #29747. + * Moved from TestHSSFWorkbook#testSetRepeatingRowsAndColumns(). + */ + public void testSetRepeatingRowsAndColumnsBug29747() { + HSSFWorkbook wb = new HSSFWorkbook(); + wb.createSheet(); + wb.createSheet(); + HSSFSheet sheet2 = wb.createSheet(); + sheet2.setRepeatingRows(CellRangeAddress.valueOf("1:2")); + NameRecord nameRecord = wb.getWorkbook().getNameRecord(0); + assertEquals(3, nameRecord.getSheetNumber()); + } + + public void testTestGetSetMargin() { baseTestGetSetMargin(new double[]{0.75, 0.75, 1.0, 1.0, 0.3, 0.3}); } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFWorkbook.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFWorkbook.java index 68462a2e2f..b1e36af747 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFWorkbook.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFWorkbook.java @@ -56,17 +56,6 @@ public final class TestHSSFWorkbook extends BaseTestWorkbook { return wb.getWorkbook(); } - public void testSetRepeatingRowsAndColumns() { - // Test bug 29747 - HSSFWorkbook b = new HSSFWorkbook( ); - b.createSheet(); - b.createSheet(); - b.createSheet(); - b.setRepeatingRowsAndColumns( 2, 0,1,-1,-1 ); - NameRecord nameRecord = b.getWorkbook().getNameRecord( 0 ); - assertEquals(3, nameRecord.getSheetNumber()); - } - public void testWindowOneDefaults() { HSSFWorkbook b = new HSSFWorkbook( ); try { @@ -501,7 +490,8 @@ public final class TestHSSFWorkbook extends BaseTestWorkbook { assertEquals("Sheet2!$A$1:$IV$1", HSSFFormulaParser.toFormulaString(wb, nr.getNameDefinition())); // 1:1 try { - wb.setRepeatingRowsAndColumns(3, 4, 5, 8, 11); + wb.getSheetAt(3).setRepeatingRows(CellRangeAddress.valueOf("9:12")); + wb.getSheetAt(3).setRepeatingColumns(CellRangeAddress.valueOf("E:F")); } catch (RuntimeException e) { if (e.getMessage().equals("Builtin (7) already exists for sheet (4)")) { // there was a problem in the code which locates the existing print titles name record diff --git a/src/testcases/org/apache/poi/ss/formula/TestWorkbookEvaluator.java b/src/testcases/org/apache/poi/ss/formula/TestWorkbookEvaluator.java index 8303e08600..712335f85c 100644 --- a/src/testcases/org/apache/poi/ss/formula/TestWorkbookEvaluator.java +++ b/src/testcases/org/apache/poi/ss/formula/TestWorkbookEvaluator.java @@ -42,6 +42,9 @@ import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.CellValue; import org.apache.poi.ss.usermodel.ErrorConstants; import org.apache.poi.ss.usermodel.FormulaEvaluator; +import org.apache.poi.ss.usermodel.Name; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; import org.apache.poi.ss.usermodel.Workbook; /** @@ -235,4 +238,47 @@ public class TestWorkbookEvaluator extends TestCase { assertEquals(Cell.CELL_TYPE_ERROR, cv.getCellType()); assertEquals(ErrorEval.CIRCULAR_REF_ERROR.getErrorCode(), cv.getErrorValue()); } + + + /** + * formulas with defined names. + */ + public void testNamesInFormulas() { + Workbook wb = new HSSFWorkbook(); + Sheet sheet = wb.createSheet("Sheet1"); + + Name name1 = wb.createName(); + name1.setNameName("aConstant"); + name1.setRefersToFormula("3.14"); + + Name name2 = wb.createName(); + name2.setNameName("aFormula"); + name2.setRefersToFormula("SUM(Sheet1!$A$1:$A$3)"); + + Name name3 = wb.createName(); + name3.setNameName("aSet"); + name3.setRefersToFormula("Sheet1!$A$2:$A$4"); + + + Row row0 = sheet.createRow(0); + Row row1 = sheet.createRow(1); + Row row2 = sheet.createRow(2); + Row row3 = sheet.createRow(3); + row0.createCell(0).setCellValue(2); + row1.createCell(0).setCellValue(5); + row2.createCell(0).setCellValue(3); + row3.createCell(0).setCellValue(7); + + row0.createCell(2).setCellFormula("aConstant"); + row1.createCell(2).setCellFormula("aFormula"); + row2.createCell(2).setCellFormula("SUM(aSet)"); + row3.createCell(2).setCellFormula("aConstant+aFormula+SUM(aSet)"); + + FormulaEvaluator fe = wb.getCreationHelper().createFormulaEvaluator(); + assertEquals(3.14, fe.evaluate(row0.getCell(2)).getNumberValue()); + assertEquals(10.0, fe.evaluate(row1.getCell(2)).getNumberValue()); + assertEquals(15.0, fe.evaluate(row2.getCell(2)).getNumberValue()); + assertEquals(28.14, fe.evaluate(row3.getCell(2)).getNumberValue()); + } + } diff --git a/src/testcases/org/apache/poi/ss/usermodel/BaseTestSheet.java b/src/testcases/org/apache/poi/ss/usermodel/BaseTestSheet.java index 7a3c213bab..d879e1b6a1 100644 --- a/src/testcases/org/apache/poi/ss/usermodel/BaseTestSheet.java +++ b/src/testcases/org/apache/poi/ss/usermodel/BaseTestSheet.java @@ -712,4 +712,78 @@ public abstract class BaseTestSheet extends TestCase { assertNull(sheet.getPaneInformation()); } + + public void testGetRepeatingRowsAndColumns() { + Workbook wb = _testDataProvider.openSampleWorkbook( + "RepeatingRowsCols." + + _testDataProvider.getStandardFileNameExtension()); + + checkRepeatingRowsAndColumns(wb.getSheetAt(0), null, null); + checkRepeatingRowsAndColumns(wb.getSheetAt(1), "1:1", null); + checkRepeatingRowsAndColumns(wb.getSheetAt(2), null, "A:A"); + checkRepeatingRowsAndColumns(wb.getSheetAt(3), "2:3", "A:B"); + } + + + public void testSetRepeatingRowsAndColumnsBug47294(){ + Workbook wb = _testDataProvider.createWorkbook(); + Sheet sheet1 = wb.createSheet(); + sheet1.setRepeatingRows(CellRangeAddress.valueOf("1:4")); + assertEquals("1:4", sheet1.getRepeatingRows().formatAsString()); + + //must handle sheets with quotas, see Bugzilla #47294 + Sheet sheet2 = wb.createSheet("My' Sheet"); + sheet2.setRepeatingRows(CellRangeAddress.valueOf("1:4")); + assertEquals("1:4", sheet2.getRepeatingRows().formatAsString()); + } + + public void testSetRepeatingRowsAndColumns() { + Workbook wb = _testDataProvider.createWorkbook(); + Sheet sheet1 = wb.createSheet("Sheet1"); + Sheet sheet2 = wb.createSheet("Sheet2"); + Sheet sheet3 = wb.createSheet("Sheet3"); + + checkRepeatingRowsAndColumns(sheet1, null, null); + + sheet1.setRepeatingRows(CellRangeAddress.valueOf("4:5")); + sheet2.setRepeatingColumns(CellRangeAddress.valueOf("A:C")); + sheet3.setRepeatingRows(CellRangeAddress.valueOf("1:4")); + sheet3.setRepeatingColumns(CellRangeAddress.valueOf("A:A")); + + checkRepeatingRowsAndColumns(sheet1, "4:5", null); + checkRepeatingRowsAndColumns(sheet2, null, "A:C"); + checkRepeatingRowsAndColumns(sheet3, "1:4", "A:A"); + + // write out, read back, and test refrain... + wb = _testDataProvider.writeOutAndReadBack(wb); + sheet1 = wb.getSheetAt(0); + sheet2 = wb.getSheetAt(1); + sheet3 = wb.getSheetAt(2); + + checkRepeatingRowsAndColumns(sheet1, "4:5", null); + checkRepeatingRowsAndColumns(sheet2, null, "A:C"); + checkRepeatingRowsAndColumns(sheet3, "1:4", "A:A"); + + // check removing repeating rows and columns + sheet3.setRepeatingRows(null); + checkRepeatingRowsAndColumns(sheet3, null, "A:A"); + + sheet3.setRepeatingColumns(null); + checkRepeatingRowsAndColumns(sheet3, null, null); + } + + private void checkRepeatingRowsAndColumns( + Sheet s, String expectedRows, String expectedCols) { + if (expectedRows == null) { + assertNull(s.getRepeatingRows()); + } else { + assertEquals(expectedRows, s.getRepeatingRows().formatAsString()); + } + if (expectedCols == null) { + assertNull(s.getRepeatingColumns()); + } else { + assertEquals(expectedCols, s.getRepeatingColumns().formatAsString()); + } + } + } diff --git a/src/testcases/org/apache/poi/ss/usermodel/BaseTestWorkbook.java b/src/testcases/org/apache/poi/ss/usermodel/BaseTestWorkbook.java index 347eded973..15181245bc 100644 --- a/src/testcases/org/apache/poi/ss/usermodel/BaseTestWorkbook.java +++ b/src/testcases/org/apache/poi/ss/usermodel/BaseTestWorkbook.java @@ -359,14 +359,27 @@ public abstract class BaseTestWorkbook extends TestCase { assertSame(row, cell.getRow()); } + + /** + * Test is kept to ensure stub for deprecated business method passes test. + * + * @Deprecated remove this test when + * {@link Workbook#setRepeatingRowsAndColumns(int, int, int, int, int)} + * is removed + */ + @Deprecated public void testSetRepeatingRowsAnsColumns(){ Workbook wb = _testDataProvider.createWorkbook(); Sheet sheet1 = wb.createSheet(); wb.setRepeatingRowsAndColumns(wb.getSheetIndex(sheet1), 0, 0, 0, 3); + assertEquals("1:4", sheet1.getRepeatingRows().formatAsString()); + assertEquals("A:A", sheet1.getRepeatingColumns().formatAsString()); //must handle sheets with quotas, see Bugzilla #47294 Sheet sheet2 = wb.createSheet("My' Sheet"); wb.setRepeatingRowsAndColumns(wb.getSheetIndex(sheet2), 0, 0, 0, 3); + assertEquals("1:4", sheet2.getRepeatingRows().formatAsString()); + assertEquals("A:A", sheet1.getRepeatingColumns().formatAsString()); } /** diff --git a/src/testcases/org/apache/poi/ss/usermodel/TestDataFormatter.java b/src/testcases/org/apache/poi/ss/usermodel/TestDataFormatter.java index 499fe5442b..b8c3a78a26 100644 --- a/src/testcases/org/apache/poi/ss/usermodel/TestDataFormatter.java +++ b/src/testcases/org/apache/poi/ss/usermodel/TestDataFormatter.java @@ -47,6 +47,24 @@ public class TestDataFormatter extends TestCase { } /** + * At the moment, we don't decode the locale strings into + * a specific locale, but we should format things as if + * the locale (eg '[$-1010409]') isn't there + */ + public void testLocaleBasedFormats() { + DataFormatter dfUS = new DataFormatter(Locale.US); + + // Standard formats + assertEquals("63", dfUS.formatRawCellContents(63.0, -1, "[$-1010409]General")); + assertEquals("63", dfUS.formatRawCellContents(63.0, -1, "[$-1010409]@")); + + // Regular numeric style formats + assertEquals("63", dfUS.formatRawCellContents(63.0, -1, "[$-1010409]##")); + assertEquals("63", dfUS.formatRawCellContents(63.0, -1, "[$-1010409]00")); + + } + + /** * Ensure that colours get correctly * zapped from within the format strings */ diff --git a/test-data/diagram/v6-non-utf16le.vsd b/test-data/diagram/v6-non-utf16le.vsd Binary files differnew file mode 100644 index 0000000000..8d4fdb89fe --- /dev/null +++ b/test-data/diagram/v6-non-utf16le.vsd diff --git a/test-data/document/53446.doc b/test-data/document/53446.doc Binary files differnew file mode 100644 index 0000000000..4844cc937a --- /dev/null +++ b/test-data/document/53446.doc diff --git a/test-data/slideshow/53446.ppt b/test-data/slideshow/53446.ppt Binary files differnew file mode 100644 index 0000000000..55333fc359 --- /dev/null +++ b/test-data/slideshow/53446.ppt diff --git a/test-data/spreadsheet/53446.xls b/test-data/spreadsheet/53446.xls Binary files differnew file mode 100644 index 0000000000..b33bd7aeca --- /dev/null +++ b/test-data/spreadsheet/53446.xls diff --git a/test-data/spreadsheet/RepeatingRowsCols.xls b/test-data/spreadsheet/RepeatingRowsCols.xls Binary files differnew file mode 100644 index 0000000000..95f4e50192 --- /dev/null +++ b/test-data/spreadsheet/RepeatingRowsCols.xls diff --git a/test-data/spreadsheet/RepeatingRowsCols.xlsx b/test-data/spreadsheet/RepeatingRowsCols.xlsx Binary files differnew file mode 100644 index 0000000000..a8a5edf7db --- /dev/null +++ b/test-data/spreadsheet/RepeatingRowsCols.xlsx |