diff options
25 files changed, 1179 insertions, 143 deletions
diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index 561ab0d4d2..4ed05d00f1 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -36,7 +36,7 @@ </devs> <!-- Don't forget to update status.xml too! --> - <release version="3.5.1-alpha1" date="2008-04-??"> + <release version="3.5.1-beta1" date="2008-07-11"> <action dev="POI-DEVELOPERS" type="add">45018 - Support for fetching embeded documents from within an OOXML file</action> <action dev="POI-DEVELOPERS" type="add">Port support for setting a policy on missing / blank cells when fetching, to XSSF too</action> <action dev="POI-DEVELOPERS" type="add">Common text extraction factory, which returns the correct POITextExtractor for the supplied data</action> @@ -45,8 +45,15 @@ <action dev="POI-DEVELOPERS" type="add">Created a common interface for handling PowerPoint files, irrespective of if they are .ppt or .pptx</action> <action dev="POI-DEVELOPERS" type="add">Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx</action> </release> - <release version="3.2-alpha1" date="2008-??-??"> - <action dev="POI-DEVELOPERS" type="add"><!-- to keep forrest dtd quiet--></action> + <release version="3.1.1-alpha1" date="2008-??-??"> + <action dev="POI-DEVELOPERS" type="fix">45338 - Fix HSSFWorkbook to give you the same HSSFFont every time, and then fix it to find newly added fonts</action> + <action dev="POI-DEVELOPERS" type="fix">45336 - Fix HSSFColor.getTripletHash()</action> + <action dev="POI-DEVELOPERS" type="fix">45334 - Fixed formula parser to handle dots in identifiers</action> + <action dev="POI-DEVELOPERS" type="fix">45252 - Improvement for HWPF Range.replaceText()</action> + <action dev="POI-DEVELOPERS" type="fix">45001 - Further fix for HWPF Range.delete() and unicode characters</action> + <action dev="POI-DEVELOPERS" type="add">45175 - Support for variable length operands in org.apache.poi.hwpf.sprm.SprmOperation</action> + <action dev="POI-DEVELOPERS" type="fix">Avoid spurious missing lines with the MissingRecordAware event code, and odd files that contain RowRecords in the middle of the cell Records.</action> + <action dev="POI-DEVELOPERS" type="add">Support for parsing formulas during EventUserModel processing, via the new EventWorkbookBuilder</action> </release> <release version="3.1-final" date="2008-06-29"> <action dev="POI-DEVELOPERS" type="fix">30978 - Fixed re-serialization of tRefErr3d and tAreaErr3d</action> diff --git a/src/documentation/content/xdocs/faq.xml b/src/documentation/content/xdocs/faq.xml index e74b6f0901..22c1a4cb4d 100644 --- a/src/documentation/content/xdocs/faq.xml +++ b/src/documentation/content/xdocs/faq.xml @@ -22,6 +22,36 @@ <faqs title="Frequently Asked Questions"> <faq> <question> + My code uses some new HSSF feature, compiles fine but fails when live with a "MethodNotFoundException" + </question> + <answer> + <p>You almost certainly have an older version of POI earlier + on your classpath. Quite a few runtimes and other packages + will ship an older version of POI, so this is an easy problem + to hit without realising.</p> + <p>The best way to identify the offending earlier jar file is + with a few lines of java. These will load one of the core POI + classes, and report where it came from.</p> + <source> +ClassLoader classloader = org.apache.poi.poifs.filesystem.POIFSFileSystem.class.getClassLoader(); +URL res = classloader.getResource("org/apache/poi/poifs/filesystem/POIFSFileSystem.class"> +String path = res.getPath(); +System.out.println("Core POI came from " + path); + </source> + </answer> + </faq> + <faq> + <question> + My code uses the scratchpad, compiles fine but fails to run with a "MethodNotFoundException" + </question> + <answer> + <p>You almost certainly have an older version earlier on your + classpath. See the answer to the similar question above for + how to track this down.</p> + </answer> + </faq> + <faq> + <question> Why is reading a simple sheet taking so long? </question> <answer> diff --git a/src/documentation/content/xdocs/index.xml b/src/documentation/content/xdocs/index.xml index b881cc0a2a..b632fb93ff 100644 --- a/src/documentation/content/xdocs/index.xml +++ b/src/documentation/content/xdocs/index.xml @@ -31,35 +31,24 @@ </header> <body> - <section><title>Office Open XML Support</title> + <section><title>POI 3.5.1 beta 1, and Office Open XML Support (2008-07-11)</title> <p>We are currently working to support the new Office Open XML file formats, such as XLSX and PPTX, which were introduced in Office 2007.</p> - <p>Support for these is currently only available in an svn branch, - but we hope to have a full release including it by the summer. - People interested should follow the + <p>Development for this is in a svn branch, but we are please to + announce our first preview release containing this support. + Users interested in the OOXML support should download the + <link href="http://www.apache.org/dyn/closer.cgi/poi/dev/">POI 3.5.1 beta 1</link> + the source and binaries from their + <link href="http://www.apache.org/dyn/closer.cgi/poi/dev/">local mirror</link>. + People interested should also follow the <link href="mailinglists.html">dev list</link> to track progress.</p> </section> - <section><title>POI 3.1-BETA2 Released (2008-05-28)</title> + <section><title>POI 3.1-FINAL Released (2008-06-29)</title> <p> - The POI team is pleased to announce the release of 3.1 BETA2 which is one of the final steps before 3.1 FINAL. - The status of this release is a beta, meaning that we encourage users to try it out. - If you find any bugs, please report them to the POI <link href="https://issues.apache.org/bugzilla/buglist.cgi?product=POI">bug database</link> or to - the <link href="./mailinglists.html">POI Developer List</link>. - </p><p> A full list of changes is available in - <link href="./changes.html">the changelog</link>, and - <link href="http://www.apache.org/dyn/closer.cgi/poi/dev/">download</link> - the source and binaries from your - <link href="http://www.apache.org/dyn/closer.cgi/poi/dev/">local mirror</link>. - </p> - <p> - The release is also available from the central Maven repository - under Group ID "org.apache.poi" and Version "3.1-beta2". - </p> - </section> - <section><title>POI 3.0.2 Released</title> - <p>The POI team is pleased to announce POI 3.0.2, the latest release of Apache POI. - There have been many important bug fixes since the 3.0.1 release and a lot of new features. A full list of changes is available in + The POI team is pleased to announce the release of 3.1 FINAL, the latest release of Apache POI. + There have been many important bug fixes since the 3.0.2 release and a lot of new features. + </p><p> A full list of changes is available in <link href="./changes.html">the changelog</link>, and <link href="http://www.apache.org/dyn/closer.cgi/poi/release/">download</link> the source and binaries from your @@ -67,13 +56,8 @@ </p> <p> The release is also available from the central Maven repository - under Group ID "org.apache.poi" and Version "3.0.2-FINAL". + under Group ID "org.apache.poi" and Version "3.1-FINAL". </p> - <p>We would also like to confirm that verions 3.0 and 3.0.1 of Apache - POI do - <em>not</em> contain any viruses. Users of broken virus checkers - which do detect a 94 byte file, sci_cec.db, as containing one are - advised to contact their vendor for a fix.</p> </section> <section><title>Purpose</title> diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index ca47e0f209..f96dfc33ab 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -33,7 +33,7 @@ <!-- Don't forget to update changes.xml too! --> <changes> - <release version="3.5.1-alpha1" date="2008-04-??"> + <release version="3.5.1-beta1" date="2008-07-11"> <action dev="POI-DEVELOPERS" type="add">45018 - Support for fetching embeded documents from within an OOXML file</action> <action dev="POI-DEVELOPERS" type="add">Port support for setting a policy on missing / blank cells when fetching, to XSSF too</action> <action dev="POI-DEVELOPERS" type="add">Common text extraction factory, which returns the correct POITextExtractor for the supplied data</action> @@ -42,8 +42,15 @@ <action dev="POI-DEVELOPERS" type="add">Created a common interface for handling PowerPoint files, irrespective of if they are .ppt or .pptx</action> <action dev="POI-DEVELOPERS" type="add">Created a common interface for handling Excel files, irrespective of if they are .xls or .xlsx</action> </release> - <release version="3.2-alpha1" date="2008-??-??"> - <action dev="POI-DEVELOPERS" type="add"><!-- to keep forrest dtd quiet--></action> + <release version="3.1.1-alpha1" date="2008-??-??"> + <action dev="POI-DEVELOPERS" type="fix">45338 - Fix HSSFWorkbook to give you the same HSSFFont every time, and then fix it to find newly added fonts</action> + <action dev="POI-DEVELOPERS" type="fix">45336 - Fix HSSFColor.getTripletHash()</action> + <action dev="POI-DEVELOPERS" type="fix">45334 - Fixed formula parser to handle dots in identifiers</action> + <action dev="POI-DEVELOPERS" type="fix">45252 - Improvement for HWPF Range.replaceText()</action> + <action dev="POI-DEVELOPERS" type="fix">45001 - Further fix for HWPF Range.delete() and unicode characters</action> + <action dev="POI-DEVELOPERS" type="add">45175 - Support for variable length operands in org.apache.poi.hwpf.sprm.SprmOperation</action> + <action dev="POI-DEVELOPERS" type="fix">Avoid spurious missing lines with the MissingRecordAware event code, and odd files that contain RowRecords in the middle of the cell Records.</action> + <action dev="POI-DEVELOPERS" type="add">Support for parsing formulas during EventUserModel processing, via the new EventWorkbookBuilder</action> </release> <release version="3.1-final" date="2008-06-29"> <action dev="POI-DEVELOPERS" type="fix">30978 - Fixed re-serialization of tRefErr3d and tAreaErr3d</action> diff --git a/src/examples/src/org/apache/poi/hssf/eventusermodel/examples/XLS2CSVmra.java b/src/examples/src/org/apache/poi/hssf/eventusermodel/examples/XLS2CSVmra.java index 9bebd3a837..1c9b220356 100644 --- a/src/examples/src/org/apache/poi/hssf/eventusermodel/examples/XLS2CSVmra.java +++ b/src/examples/src/org/apache/poi/hssf/eventusermodel/examples/XLS2CSVmra.java @@ -30,9 +30,11 @@ import org.apache.poi.hssf.eventusermodel.HSSFEventFactory; import org.apache.poi.hssf.eventusermodel.HSSFListener; import org.apache.poi.hssf.eventusermodel.HSSFRequest; import org.apache.poi.hssf.eventusermodel.MissingRecordAwareHSSFListener; +import org.apache.poi.hssf.eventusermodel.EventWorkbookBuilder.SheetRecordCollectingListener; import org.apache.poi.hssf.eventusermodel.dummyrecord.LastCellOfRowDummyRecord; import org.apache.poi.hssf.eventusermodel.dummyrecord.MissingCellDummyRecord; import org.apache.poi.hssf.model.FormulaParser; +import org.apache.poi.hssf.record.BOFRecord; import org.apache.poi.hssf.record.BlankRecord; import org.apache.poi.hssf.record.BoolErrRecord; import org.apache.poi.hssf.record.CellValueRecordInterface; @@ -46,6 +48,7 @@ import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.SSTRecord; import org.apache.poi.hssf.record.StringRecord; import org.apache.poi.hssf.usermodel.HSSFDateUtil; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.poifs.filesystem.POIFSFileSystem; /** @@ -64,6 +67,10 @@ public class XLS2CSVmra implements HSSFListener { /** Should we output the formula, or the value it has? */ private boolean outputFormulaValues = true; + /** For parsing Formulas */ + private SheetRecordCollectingListener workbookBuildingListener; + private HSSFWorkbook stubWorkbook; + // Records we pick up as we process private SSTRecord sstRecord; private FormatTrackingHSSFListener formatListener; @@ -108,7 +115,13 @@ public class XLS2CSVmra implements HSSFListener { HSSFEventFactory factory = new HSSFEventFactory(); HSSFRequest request = new HSSFRequest(); - request.addListenerForAllRecords(formatListener); + + if(outputFormulaValues) { + request.addListenerForAllRecords(formatListener); + } else { + workbookBuildingListener = new SheetRecordCollectingListener(formatListener); + request.addListenerForAllRecords(workbookBuildingListener); + } factory.processWorkbookEvents(request, fs); } @@ -124,6 +137,16 @@ public class XLS2CSVmra implements HSSFListener { switch (record.getSid()) { + case BOFRecord.sid: + BOFRecord br = (BOFRecord)record; + if(br.getType() == BOFRecord.TYPE_WORKSHEET) { + // Create sub workbook if required + if(workbookBuildingListener != null && stubWorkbook == null) { + stubWorkbook = workbookBuildingListener.getStubHSSFWorkbook(); + } + } + break; + case SSTRecord.sid: sstRecord = (SSTRecord) record; break; @@ -161,7 +184,7 @@ public class XLS2CSVmra implements HSSFListener { } } else { thisStr = '"' + - FormulaParser.toFormulaString(null, frec.getParsedExpression()) + '"'; + FormulaParser.toFormulaString(stubWorkbook, frec.getParsedExpression()) + '"'; } break; case StringRecord.sid: diff --git a/src/java/org/apache/poi/hssf/eventusermodel/EventWorkbookBuilder.java b/src/java/org/apache/poi/hssf/eventusermodel/EventWorkbookBuilder.java new file mode 100644 index 0000000000..0ae5f3f260 --- /dev/null +++ b/src/java/org/apache/poi/hssf/eventusermodel/EventWorkbookBuilder.java @@ -0,0 +1,199 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ +package org.apache.poi.hssf.eventusermodel; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.poi.hssf.model.FormulaParser; +import org.apache.poi.hssf.model.Workbook; +import org.apache.poi.hssf.record.BoundSheetRecord; +import org.apache.poi.hssf.record.EOFRecord; +import org.apache.poi.hssf.record.ExternSheetRecord; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.SSTRecord; +import org.apache.poi.hssf.record.SupBookRecord; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; + +/** + * When working with the EventUserModel, if you want to + * process formulas, you need an instance of + * {@link Workbook} to pass to a {@link HSSFWorkbook}, + * to finally give to {@link FormulaParser}, + * and this will build you stub ones. + * Since you're working with the EventUserModel, you + * wouldn't want to get a full {@link Workbook} and + * {@link HSSFWorkbook}, as they would eat too much memory. + * Instead, you should collect a few key records as they + * go past, then call this once you have them to build a + * stub {@link Workbook}, and from that a stub + * {@link HSSFWorkbook}, to use with the {@link FormulaParser}. + * + * The records you should collect are: + * * {@link ExternSheetRecord} + * * {@link BoundSheetRecord} + * You should probably also collect {@link SSTRecord}, + * but it's not required to pass this in. + * + * To help, this class includes a HSSFListener wrapper + * that will do the collecting for you. + */ +public class EventWorkbookBuilder { + /** + * Wraps up your stub {@link Workbook} as a stub + * {@link HSSFWorkbook}, ready for passing to + * {@link FormulaParser} + * @param workbook A stub {@link Workbook} + */ + public static HSSFWorkbook createStubHSSFWorkbook(Workbook workbook) { + return new StubHSSFWorkbook(workbook); + } + + /** + * Creates a stub Workbook from the supplied records, + * suitable for use with the {@link FormulaParser} + * @param externs The ExternSheetRecords in your file + * @param bounds The BoundSheetRecords in your file + * @param sst The SSTRecord in your file. + * @return A stub Workbook suitable for use with {@link FormulaParser} + */ + public static Workbook createStubWorkbook(ExternSheetRecord[] externs, + BoundSheetRecord[] bounds, SSTRecord sst) { + List wbRecords = new ArrayList(); + + // Core Workbook records go first + if(bounds != null) { + for(int i=0; i<bounds.length; i++) { + wbRecords.add(bounds[i]); + } + } + if(sst != null) { + wbRecords.add(sst); + } + + // Now we can have the ExternSheetRecords, + // preceded by a SupBookRecord + if(externs != null) { + wbRecords.add(SupBookRecord.createInternalReferences( + (short)externs.length)); + for(int i=0; i<externs.length; i++) { + wbRecords.add(externs[i]); + } + } + + // Finally we need an EoF record + wbRecords.add(new EOFRecord()); + + return Workbook.createWorkbook(wbRecords); + } + + /** + * Creates a stub workbook from the supplied records, + * suitable for use with the {@link FormulaParser} + * @param externs The ExternSheetRecords in your file + * @param bounds The BoundSheetRecords in your file + * @return A stub Workbook suitable for use with {@link FormulaParser} + */ + public static Workbook createStubWorkbook(ExternSheetRecord[] externs, + BoundSheetRecord[] bounds) { + return createStubWorkbook(externs, bounds, null); + } + + + /** + * A wrapping HSSFListener which will collect + * {@link BoundSheetRecord}s and {@link ExternSheetRecord}s as + * they go past, so you can create a Stub {@link Workbook} from + * them once required. + */ + public static class SheetRecordCollectingListener implements HSSFListener { + private HSSFListener childListener; + private List boundSheetRecords = new ArrayList(); + private List externSheetRecords = new ArrayList(); + private SSTRecord sstRecord = null; + + public SheetRecordCollectingListener(HSSFListener childListener) { + this.childListener = childListener; + } + + + public BoundSheetRecord[] getBoundSheetRecords() { + return (BoundSheetRecord[])boundSheetRecords.toArray( + new BoundSheetRecord[boundSheetRecords.size()] + ); + } + public ExternSheetRecord[] getExternSheetRecords() { + return (ExternSheetRecord[])externSheetRecords.toArray( + new ExternSheetRecord[externSheetRecords.size()] + ); + } + public SSTRecord getSSTRecord() { + return sstRecord; + } + + public HSSFWorkbook getStubHSSFWorkbook() { + return createStubHSSFWorkbook( + getStubWorkbook() + ); + } + public Workbook getStubWorkbook() { + return createStubWorkbook( + getExternSheetRecords(), getBoundSheetRecords(), + getSSTRecord() + ); + } + + + /** + * Process this record ourselves, and then + * pass it on to our child listener + */ + public void processRecord(Record record) { + // Handle it ourselves + processRecordInternally(record); + + // Now pass on to our child + childListener.processRecord(record); + } + + /** + * Process the record ourselves, but do not + * pass it on to the child Listener. + */ + public void processRecordInternally(Record record) { + if(record instanceof BoundSheetRecord) { + boundSheetRecords.add(record); + } + else if(record instanceof ExternSheetRecord) { + externSheetRecords.add(record); + } + else if(record instanceof SSTRecord) { + sstRecord = (SSTRecord)record; + } + } + } + + /** + * Let us at the {@link Workbook} constructor on + * {@link HSSFWorkbook} + */ + private static class StubHSSFWorkbook extends HSSFWorkbook { + private StubHSSFWorkbook(Workbook wb) { + super(wb); + } + } +}
\ No newline at end of file diff --git a/src/java/org/apache/poi/hssf/eventusermodel/MissingRecordAwareHSSFListener.java b/src/java/org/apache/poi/hssf/eventusermodel/MissingRecordAwareHSSFListener.java index a727d064b9..0bdcb1d3d9 100644 --- a/src/java/org/apache/poi/hssf/eventusermodel/MissingRecordAwareHSSFListener.java +++ b/src/java/org/apache/poi/hssf/eventusermodel/MissingRecordAwareHSSFListener.java @@ -46,8 +46,14 @@ import org.apache.poi.hssf.record.RowRecord; */ public class MissingRecordAwareHSSFListener implements HSSFListener { private HSSFListener childListener; - private int lastSeenRow = -1; - private int lastSeenColumn = -1; + + // Need to have different counters for cell rows and + // row rows, as you sometimes get a RowRecord in the + // middle of some cells, and that'd break everything + private int lastRowRow = -1; + + private int lastCellRow = -1; + private int lastCellColumn = -1; /** * Constructs a new MissingRecordAwareHSSFListener, which @@ -71,14 +77,16 @@ public class MissingRecordAwareHSSFListener implements HSSFListener { if (bof.getType() == bof.TYPE_WORKBOOK) { // Reset the row and column counts - new workbook - lastSeenRow = -1; - lastSeenColumn = -1; + lastRowRow = -1; + lastCellRow = -1; + lastCellColumn = -1; //System.out.println("Encountered workbook"); } else if (bof.getType() == bof.TYPE_WORKSHEET) { // Reset the row and column counts - new sheet - lastSeenRow = -1; - lastSeenColumn = -1; + lastRowRow = -1; + lastCellRow = -1; + lastCellColumn = -1; //System.out.println("Encountered sheet reference"); } break; @@ -92,15 +100,15 @@ public class MissingRecordAwareHSSFListener implements HSSFListener { // + rowrec.getFirstCol() + " last column at " + rowrec.getLastCol()); // If there's a jump in rows, fire off missing row records - if(lastSeenRow+1 < rowrec.getRowNumber()) { - for(int i=(lastSeenRow+1); i<rowrec.getRowNumber(); i++) { + if(lastRowRow+1 < rowrec.getRowNumber()) { + for(int i=(lastRowRow+1); i<rowrec.getRowNumber(); i++) { MissingRowDummyRecord dr = new MissingRowDummyRecord(i); childListener.processRecord(dr); } } // Record this as the last row we saw - lastSeenRow = rowrec.getRowNumber(); + lastRowRow = rowrec.getRowNumber(); break; @@ -157,45 +165,49 @@ public class MissingRecordAwareHSSFListener implements HSSFListener { break; } - // Do we need to fire dummy end-of-row records? - if(thisRow != lastSeenRow) { - for(int i=lastSeenRow; i<thisRow; i++) { + // If we're on cells, and this cell isn't in the same + // row as the last one, then fire the + // dummy end-of-row records? + if(thisRow != lastCellRow && lastCellRow > -1) { + for(int i=lastCellRow; i<thisRow; i++) { int cols = -1; - if(i == lastSeenRow) { - cols = lastSeenColumn; + if(i == lastCellRow) { + cols = lastCellColumn; } LastCellOfRowDummyRecord r = new LastCellOfRowDummyRecord(i, cols); childListener.processRecord(r); } } - // If we've finished with the columns, then fire the final - // dummy end-of-row record - if(lastSeenRow != -1 && lastSeenColumn != -1 && thisRow == -1) { - LastCellOfRowDummyRecord r = new LastCellOfRowDummyRecord(lastSeenRow, lastSeenColumn); + + // If we've just finished with the cells, then fire the + // final dummy end-of-row record + if(lastCellRow != -1 && lastCellColumn != -1 && thisRow == -1) { + LastCellOfRowDummyRecord r = new LastCellOfRowDummyRecord(lastCellRow, lastCellColumn); childListener.processRecord(r); - lastSeenRow = -1; - lastSeenColumn = -1; + lastCellRow = -1; + lastCellColumn = -1; } // If we've moved onto a new row, the ensure we re-set // the column counter - if(thisRow != lastSeenRow) { - lastSeenColumn = -1; + if(thisRow != lastCellRow) { + lastCellColumn = -1; } - // Do we need to fire dummy cell records? - if(lastSeenColumn != (thisColumn-1)) { - for(int i=lastSeenColumn+1; i<thisColumn; i++) { + // If there's a gap in the cells, then fire + // the dummy cell records? + if(lastCellColumn != (thisColumn-1)) { + for(int i=lastCellColumn+1; i<thisColumn; i++) { MissingCellDummyRecord r = new MissingCellDummyRecord(thisRow, i); childListener.processRecord(r); } } - // Update cell and row counts if doing cells + // Update cell and row counts as needed if(thisColumn != -1) { - lastSeenRow = thisRow; - lastSeenColumn = thisColumn; + lastCellColumn = thisColumn; + lastCellRow = thisRow; } childListener.processRecord(record); diff --git a/src/java/org/apache/poi/hssf/model/FormulaParser.java b/src/java/org/apache/poi/hssf/model/FormulaParser.java index 501b030ecc..2cc2a80531 100644 --- a/src/java/org/apache/poi/hssf/model/FormulaParser.java +++ b/src/java/org/apache/poi/hssf/model/FormulaParser.java @@ -22,11 +22,13 @@ import java.util.List; import java.util.Stack; import java.util.regex.Pattern; -//import PTG's .. since we need everything, import * +//import PTGs .. since we need everything, import * import org.apache.poi.hssf.record.formula.*; import org.apache.poi.hssf.record.formula.function.FunctionMetadata; import org.apache.poi.hssf.record.formula.function.FunctionMetadataRegistry; import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.hssf.util.AreaReference; +import org.apache.poi.hssf.util.CellReference; /** * This class parses a formula string into a List of tokens in RPN order. @@ -178,8 +180,14 @@ public final class FormulaParser { GetChar(); } - /** Get an Identifier */ - private String GetName() { + /** + * Parses a sheet name, named range name, or simple cell reference.<br/> + * Note - identifiers in Excel can contain dots, so this method may return a String + * which may need to be converted to an area reference. For example, this method + * may return a value like "A1..B2", in which case the caller must convert it to + * an area reference like "A1:B2" + */ + private String parseIdentifier() { StringBuffer Token = new StringBuffer(); if (!IsAlpha(look) && look != '\'') { throw expected("Name"); @@ -201,7 +209,9 @@ public final class FormulaParser { } else { - while (IsAlNum(look)) { + // allow for any sequence of dots and identifier chars + // special case of two consecutive dots is best treated in the calling code + while (IsAlNum(look) || look == '.') { Token.append(look); GetChar(); } @@ -220,15 +230,22 @@ public final class FormulaParser { return value.length() == 0 ? null : value.toString(); } - private ParseNode parseFunctionOrIdentifier() { - String name = GetName(); + private ParseNode parseFunctionReferenceOrName() { + String name = parseIdentifier(); if (look == '('){ //This is a function return function(name); } - return new ParseNode(parseIdentifier(name)); + return new ParseNode(parseNameOrReference(name)); } - private Ptg parseIdentifier(String name) { + + private Ptg parseNameOrReference(String name) { + + AreaReference areaRef = parseArea(name); + if (areaRef != null) { + // will happen if dots are used instead of colon + return new AreaPtg(areaRef.formatAsString()); + } if (look == ':' || look == '.') { // this is a AreaReference GetChar(); @@ -238,23 +255,28 @@ public final class FormulaParser { } String first = name; - String second = GetName(); + String second = parseIdentifier(); return new AreaPtg(first+":"+second); } if (look == '!') { Match('!'); String sheetName = name; - String first = GetName(); + String first = parseIdentifier(); short externIdx = (short)book.getExternalSheetIndex(book.getSheetIndex(sheetName)); + areaRef = parseArea(name); + if (areaRef != null) { + // will happen if dots are used instead of colon + return new Area3DPtg(areaRef.formatAsString(), externIdx); + } if (look == ':') { Match(':'); - String second=GetName(); + String second=parseIdentifier(); if (look == '!') { //The sheet name was included in both of the areas. Only really //need it once Match('!'); - String third=GetName(); + String third=parseIdentifier(); if (!sheetName.equals(second)) throw new RuntimeException("Unhandled double sheet reference."); @@ -271,9 +293,7 @@ public final class FormulaParser { // This can be either a cell ref or a named range // Try to spot which it is - boolean cellRef = CELL_REFERENCE_PATTERN.matcher(name).matches(); - - if (cellRef) { + if (isValidCellReference(name)) { return new RefPtg(name); } @@ -288,6 +308,41 @@ public final class FormulaParser { } /** + * @return <code>null</code> if name cannot be split at a dot + */ + private AreaReference parseArea(String name) { + int dotPos = name.indexOf('.'); + if (dotPos < 0) { + return null; + } + int dotCount = 1; + while (dotCount<name.length() && name.charAt(dotPos+dotCount) == '.') { + dotCount++; + if (dotCount>3) { + // four or more consecutive dots does not convert to ':' + return null; + } + } + String partA = name.substring(0, dotPos); + if (!isValidCellReference(partA)) { + return null; + } + String partB = name.substring(dotPos+dotCount); + if (!isValidCellReference(partB)) { + return null; + } + CellReference topLeft = new CellReference(partA); + CellReference bottomRight = new CellReference(partB); + return new AreaReference(topLeft, bottomRight); + } + + private static boolean isValidCellReference(String str) { + // TODO - exact rules for recognising cell references may be too complicated for regex + return CELL_REFERENCE_PATTERN.matcher(str).matches(); + } + + + /** * Note - Excel function names are 'case aware but not case sensitive'. This method may end * up creating a defined name record in the workbook if the specified name is not an internal * Excel function, and has not been encountered before. @@ -465,7 +520,7 @@ public final class FormulaParser { return new ParseNode(parseStringLiteral()); } if (IsAlpha(look) || look == '\''){ - return parseFunctionOrIdentifier(); + return parseFunctionReferenceOrName(); } // else - assume number return new ParseNode(parseNumber()); @@ -510,7 +565,7 @@ public final class FormulaParser { private ErrPtg parseErrorLiteral() { Match('#'); - String part1 = GetName().toUpperCase(); + String part1 = parseIdentifier().toUpperCase(); switch(part1.charAt(0)) { case 'V': diff --git a/src/java/org/apache/poi/hssf/record/formula/functions/Errortype.java b/src/java/org/apache/poi/hssf/record/formula/functions/Errortype.java index 51268c9aa0..dd72eb5c79 100644 --- a/src/java/org/apache/poi/hssf/record/formula/functions/Errortype.java +++ b/src/java/org/apache/poi/hssf/record/formula/functions/Errortype.java @@ -1,25 +1,78 @@ -/* -* 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. -*/ -/* - * Created on May 15, 2005 - * - */ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + package org.apache.poi.hssf.record.formula.functions; -public class Errortype extends NotImplementedFunction { +import org.apache.poi.hssf.record.formula.eval.ErrorEval; +import org.apache.poi.hssf.record.formula.eval.Eval; +import org.apache.poi.hssf.record.formula.eval.EvaluationException; +import org.apache.poi.hssf.record.formula.eval.NumberEval; +import org.apache.poi.hssf.record.formula.eval.OperandResolver; +import org.apache.poi.hssf.usermodel.HSSFErrorConstants; + +/** + * Implementation for the ERROR.TYPE() Excel function.<p/> + * + * <b>Syntax:</b><br/> + * <b>ERROR.TYPE</b>(<b>errorValue</b>)<p/> + * + * Returns a number corresponding to the error type of the supplied argument.<p/> + * + * <table border="1" cellpadding="1" cellspacing="1" summary="Return values for ERROR.TYPE()"> + * <tr><td>errorValue</td><td>Return Value</td></tr> + * <tr><td>#NULL!</td><td>1</td></tr> + * <tr><td>#DIV/0!</td><td>2</td></tr> + * <tr><td>#VALUE!</td><td>3</td></tr> + * <tr><td>#REF!</td><td>4</td></tr> + * <tr><td>#NAME?</td><td>5</td></tr> + * <tr><td>#NUM!</td><td>6</td></tr> + * <tr><td>#N/A!</td><td>7</td></tr> + * <tr><td>everything else</td><td>#N/A!</td></tr> + * </table> + * + * Note - the results of ERROR.TYPE() are different to the constants defined in + * <tt>HSSFErrorConstants</tt>. + * + * @author Josh Micich + */ +public final class Errortype implements Function { + + public Eval evaluate(Eval[] args, int srcCellRow, short srcCellCol) { + + try { + OperandResolver.getSingleValue(args[0], srcCellRow, srcCellCol); + return ErrorEval.NA; + } catch (EvaluationException e) { + int result = translateErrorCodeToErrorTypeValue(e.getErrorEval().getErrorCode()); + return new NumberEval(result); + } + } + + private int translateErrorCodeToErrorTypeValue(int errorCode) { + switch (errorCode) { + case HSSFErrorConstants.ERROR_NULL: return 1; + case HSSFErrorConstants.ERROR_DIV_0: return 2; + case HSSFErrorConstants.ERROR_VALUE: return 3; + case HSSFErrorConstants.ERROR_REF: return 4; + case HSSFErrorConstants.ERROR_NAME: return 5; + case HSSFErrorConstants.ERROR_NUM: return 6; + case HSSFErrorConstants.ERROR_NA : return 7; + } + throw new IllegalArgumentException("Invalid error code (" + errorCode + ")"); + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java index 3aac4b5992..b80ccb7905 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFWorkbook.java @@ -24,6 +24,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Stack; @@ -105,6 +106,12 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm */ private ArrayList names; + + /** + * this holds the HSSFFont objects attached to this workbook. + * We only create these from the low level records as required. + */ + private Hashtable fonts; /** * holds whether or not to preserve other nodes in the POIFS. Used @@ -1021,9 +1028,10 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm if(fontindex == Short.MAX_VALUE){ throw new IllegalArgumentException("Maximum number of fonts was exceeded"); } - HSSFFont retval = new HSSFFont(fontindex, font); - - return retval; + + // Ask getFontAt() to build it for us, + // so it gets properly cached + return getFontAt(fontindex); } /** @@ -1033,15 +1041,11 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm String name, boolean italic, boolean strikeout, short typeOffset, byte underline) { -// System.out.println( boldWeight + ", " + color + ", " + fontHeight + ", " + name + ", " + italic + ", " + strikeout + ", " + typeOffset + ", " + underline ); - for (short i = 0; i < workbook.getNumberOfFontRecords(); i++) - { - if (i == 4) - continue; - - FontRecord font = workbook.getFontRecordAt(i); - HSSFFont hssfFont = new HSSFFont(i, font); -// System.out.println( hssfFont.getBoldweight() + ", " + hssfFont.getColor() + ", " + hssfFont.getFontHeight() + ", " + hssfFont.getFontName() + ", " + hssfFont.getItalic() + ", " + hssfFont.getStrikeout() + ", " + hssfFont.getTypeOffset() + ", " + hssfFont.getUnderline() ); + for (short i=0; i<=getNumberOfFonts(); i++) { + // Remember - there is no 4! + if(i == 4) continue; + + HSSFFont hssfFont = getFontAt(i); if (hssfFont.getBoldweight() == boldWeight && hssfFont.getColor() == color && hssfFont.getFontHeight() == fontHeight @@ -1051,12 +1055,10 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm && hssfFont.getTypeOffset() == typeOffset && hssfFont.getUnderline() == underline) { -// System.out.println( "Found font" ); return hssfFont; } } -// System.out.println( "No font found" ); return null; } @@ -1071,15 +1073,26 @@ public class HSSFWorkbook extends POIDocument implements org.apache.poi.ss.userm } /** - * get the font at the given index number + * Get the font at the given index number * @param idx index number * @return HSSFFont at the index */ public HSSFFont getFontAt(short idx) { + if(fonts == null) fonts = new Hashtable(); + + // So we don't confuse users, give them back + // the same object every time, but create + // them lazily + Short sIdx = Short.valueOf(idx); + if(fonts.containsKey(sIdx)) { + return (HSSFFont)fonts.get(sIdx); + } + FontRecord font = workbook.getFontRecordAt(idx); HSSFFont retval = new HSSFFont(idx, font); + fonts.put(sIdx, retval); return retval; } diff --git a/src/java/org/apache/poi/hssf/util/HSSFColor.java b/src/java/org/apache/poi/hssf/util/HSSFColor.java index 2c51b3d208..d13baecf80 100644 --- a/src/java/org/apache/poi/hssf/util/HSSFColor.java +++ b/src/java/org/apache/poi/hssf/util/HSSFColor.java @@ -155,8 +155,12 @@ public class HSSFColor implements Color { String hexString = color.getHexString(); if (result.containsKey(hexString)) { - throw new RuntimeException("Dup color hexString (" + hexString - + ") for color (" + color.getClass().getName() + ")"); + HSSFColor other = (HSSFColor)result.get(hexString); + throw new RuntimeException( + "Dup color hexString (" + hexString + + ") for color (" + color.getClass().getName() + ") - " + + " already taken by (" + other.getClass().getName() + ")" + ); } result.put(hexString, color); } @@ -1511,9 +1515,9 @@ public class HSSFColor implements Color { public final static short index = 0x19; public final static short[] triplet = { - 153, 51, 102 + 127, 0, 0 }; - public final static String hexString = "9999:3333:6666"; + public final static String hexString = "8000:0:0"; public short getIndex() { diff --git a/src/scratchpad/src/org/apache/poi/hwpf/model/TextPiece.java b/src/scratchpad/src/org/apache/poi/hwpf/model/TextPiece.java index bc33954dff..227200ab5d 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/model/TextPiece.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/model/TextPiece.java @@ -91,15 +91,18 @@ public class TextPiece extends PropertyNode implements Comparable public void adjustForDelete(int start, int length) { + // length is expected to be the number of code-points, + // not the number of characters + int numChars = length; if (usesUnicode()) { start /= 2; - length /= 2; + numChars = (length / 2); } int myStart = getStart(); int myEnd = getEnd(); - int end = start + length; + int end = start + numChars; /* do we have to delete from this text piece? */ if (start <= myEnd && end >= myStart) { @@ -108,9 +111,14 @@ public class TextPiece extends PropertyNode implements Comparable int overlapStart = Math.max(myStart, start); int overlapEnd = Math.min(myEnd, end); ((StringBuffer)_buf).delete(overlapStart, overlapEnd); - - super.adjustForDelete(start, length); } + + // We need to invoke this even if text from this piece is not being + // deleted because the adjustment must propagate to all subsequent + // text pieces i.e., if text from tp[n] is being deleted, then + // tp[n + 1], tp[n + 2], etc. will need to be adjusted. + // The superclass is expected to use a separate sentry for this. + super.adjustForDelete(start, length); } public int characterLength() diff --git a/src/scratchpad/src/org/apache/poi/hwpf/sprm/SprmOperation.java b/src/scratchpad/src/org/apache/poi/hwpf/sprm/SprmOperation.java index cacbbaaa38..764fc06c4d 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/sprm/SprmOperation.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/sprm/SprmOperation.java @@ -101,7 +101,14 @@ public class SprmOperation case 3: return LittleEndian.getInt(_grpprl, _gOffset); case 6: - throw new UnsupportedOperationException("This SPRM contains a variable length operand"); + byte operandLength = _grpprl[_gOffset + 1]; //surely shorter than an int... + + byte [] codeBytes = new byte[LittleEndian.INT_SIZE]; //initialized to zeros by JVM + for(int i = 0; i < operandLength; i++) + if(_gOffset + i < _grpprl.length) + codeBytes[i] = _grpprl[_gOffset + 1 + i]; + + return LittleEndian.getInt(codeBytes, 0); case 7: byte threeByteInt[] = new byte[4]; threeByteInt[0] = _grpprl[_gOffset]; diff --git a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Range.java b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Range.java index 80e9b7526c..0ef944f136 100644 --- a/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Range.java +++ b/src/scratchpad/src/org/apache/poi/hwpf/usermodel/Range.java @@ -333,7 +333,7 @@ public class Range _doc.getCharacterTable().adjustForInsert(_charStart, adjustedLength); _doc.getParagraphTable().adjustForInsert(_parStart, adjustedLength); _doc.getSectionTable().adjustForInsert(_sectionStart, adjustedLength); - adjustForInsert(text.length()); + adjustForInsert(adjustedLength); // update the FIB.CCPText field adjustFIB(text.length()); @@ -656,8 +656,15 @@ public class Range ); } + // this Range isn't a proper parent of the subRange() so we'll have to keep + // track of an updated endOffset on our own + int previousEndOffset = subRange.getEndOffset(); + subRange.insertBefore(pValue); + if (subRange.getEndOffset() != previousEndOffset) + _end += (subRange.getEndOffset() - previousEndOffset); + // re-create the sub-range so we can delete it subRange = new Range( (absPlaceHolderIndex + pValue.length()), @@ -671,10 +678,31 @@ public class Range (pValue.length() * 2)), getDocument() ); + // deletes are automagically propagated subRange.delete(); } /** + * Replace (all instances of) a piece of text with another... + * + * @param pPlaceHolder The text to be replaced (e.g., "${organization}") + * @param pValue The replacement text (e.g., "Apache Software Foundation") + */ + public void replaceText(String pPlaceHolder, String pValue) + { + boolean keepLooking = true; + while (keepLooking) { + + String text = text(); + int offset = text.indexOf(pPlaceHolder); + if (offset >= 0) + replaceText(pPlaceHolder, pValue, offset); + else + keepLooking = false; + } + } + + /** * Gets the character run at index. The index is relative to this range. * * @param index The index of the character run to get. @@ -915,7 +943,7 @@ public class Range /** * adjust this range after an insert happens. - * @param length the length to adjust for + * @param length the length to adjust for (expected to be a count of code-points, not necessarily chars) */ private void adjustForInsert(int length) { diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/data/testRangeDelete.doc b/src/scratchpad/testcases/org/apache/poi/hwpf/data/testRangeDelete.doc Binary files differnew file mode 100644 index 0000000000..896108397c --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hwpf/data/testRangeDelete.doc diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestRangeDelete.java b/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestRangeDelete.java new file mode 100644 index 0000000000..1becc234c3 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestRangeDelete.java @@ -0,0 +1,196 @@ + +/* ==================================================================== + 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.hwpf.usermodel; + +import java.io.ByteArrayOutputStream; +import java.io.FileInputStream; +import java.util.List; + +import org.apache.poi.hwpf.HWPFDocument; +import org.apache.poi.hwpf.model.PicturesTable; +import org.apache.poi.hwpf.usermodel.Picture; + +import junit.framework.TestCase; + +/** + * Test to see if Range.delete() works even if the Range contains a + * CharacterRun that uses Unicode characters. + */ +public class TestRangeDelete extends TestCase { + + // u201c and u201d are "smart-quotes" + private String originalText = + "It is used to confirm that text delete works even if Unicode characters (such as \u201c\u2014\u201d (U+2014), \u201c\u2e8e\u201d (U+2E8E), or \u201c\u2714\u201d (U+2714)) are present. Everybody should be thankful to the ${organization} ${delete} and all the POI contributors for their assistance in this matter.\r"; + private String searchText = "${delete}"; + private String expectedText1 = " This is an MS-Word 97 formatted document created using NeoOffice v. 2.2.4 Patch 0 (OpenOffice.org v. 2.2.1).\r"; + private String expectedText2 = + "It is used to confirm that text delete works even if Unicode characters (such as \u201c\u2014\u201d (U+2014), \u201c\u2e8e\u201d (U+2E8E), or \u201c\u2714\u201d (U+2714)) are present. Everybody should be thankful to the ${organization} and all the POI contributors for their assistance in this matter.\r"; + private String expectedText3 = "Thank you, ${organization} !\r"; + + private String illustrativeDocFile; + + protected void setUp() throws Exception { + + String dirname = System.getProperty("HWPF.testdata.path"); + + illustrativeDocFile = dirname + "/testRangeDelete.doc"; + } + + /** + * Test just opening the files + */ + public void testOpen() throws Exception { + + HWPFDocument docA = new HWPFDocument(new FileInputStream(illustrativeDocFile)); + } + + /** + * Test (more "confirm" than test) that we have the general structure that we expect to have. + */ + public void testDocStructure() throws Exception { + + HWPFDocument daDoc = new HWPFDocument(new FileInputStream(illustrativeDocFile)); + + Range range = daDoc.getRange(); + + assertEquals(1, range.numSections()); + Section section = range.getSection(0); + + assertEquals(5, section.numParagraphs()); + Paragraph para = section.getParagraph(2); + + assertEquals(5, para.numCharacterRuns()); + + assertEquals(originalText, para.text()); + } + + /** + * Test that we can delete text (one instance) from our Range with Unicode text. + */ + public void testRangeDeleteOne() throws Exception { + + HWPFDocument daDoc = new HWPFDocument(new FileInputStream(illustrativeDocFile)); + + Range range = daDoc.getRange(); + assertEquals(1, range.numSections()); + + Section section = range.getSection(0); + assertEquals(5, section.numParagraphs()); + + Paragraph para = section.getParagraph(2); + + String text = para.text(); + assertEquals(originalText, text); + + int offset = text.indexOf(searchText); + assertEquals(192, offset); + + int absOffset = para.getStartOffset() + offset; + if (para.usesUnicode()) + absOffset = para.getStartOffset() + (offset * 2); + + Range subRange = new Range(absOffset, (absOffset + searchText.length()), para.getDocument()); + if (subRange.usesUnicode()) + subRange = new Range(absOffset, (absOffset + (searchText.length() * 2)), para.getDocument()); + + assertEquals(searchText, subRange.text()); + + subRange.delete(); + + // we need to let the model re-calculate the Range before we evaluate it + range = daDoc.getRange(); + + assertEquals(1, range.numSections()); + section = range.getSection(0); + + assertEquals(5, section.numParagraphs()); + para = section.getParagraph(2); + + text = para.text(); + assertEquals(expectedText2, text); + + // this can lead to a StringBufferOutOfBoundsException, so we will add it + // even though we don't have an assertion for it + Range daRange = daDoc.getRange(); + daRange.text(); + } + + /** + * Test that we can delete text (all instances of) from our Range with Unicode text. + */ + public void testRangeDeleteAll() throws Exception { + + HWPFDocument daDoc = new HWPFDocument(new FileInputStream(illustrativeDocFile)); + + Range range = daDoc.getRange(); + assertEquals(1, range.numSections()); + + Section section = range.getSection(0); + assertEquals(5, section.numParagraphs()); + + Paragraph para = section.getParagraph(2); + + String text = para.text(); + assertEquals(originalText, text); + + boolean keepLooking = true; + while (keepLooking) { + + int offset = range.text().indexOf(searchText); + if (offset >= 0) { + + int absOffset = range.getStartOffset() + offset; + if (range.usesUnicode()) + absOffset = range.getStartOffset() + (offset * 2); + + Range subRange = new Range( + absOffset, (absOffset + searchText.length()), range.getDocument()); + if (subRange.usesUnicode()) + subRange = new Range( + absOffset, (absOffset + (searchText.length() * 2)), range.getDocument()); + + assertEquals(searchText, subRange.text()); + + subRange.delete(); + + } else + keepLooking = false; + } + + // we need to let the model re-calculate the Range before we use it + range = daDoc.getRange(); + + assertEquals(1, range.numSections()); + section = range.getSection(0); + + assertEquals(5, section.numParagraphs()); + + para = section.getParagraph(1); + text = para.text(); + assertEquals(expectedText1, text); + + para = section.getParagraph(2); + text = para.text(); + assertEquals(expectedText2, text); + + para = section.getParagraph(3); + text = para.text(); + assertEquals(expectedText3, text); + } +} diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestRangeReplacement.java b/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestRangeReplacement.java index 4b2b9ce370..bda615e943 100644 --- a/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestRangeReplacement.java +++ b/src/scratchpad/testcases/org/apache/poi/hwpf/usermodel/TestRangeReplacement.java @@ -39,8 +39,9 @@ public class TestRangeReplacement extends TestCase { "It is used to confirm that text replacement works even if Unicode characters (such as \u201c\u2014\u201d (U+2014), \u201c\u2e8e\u201d (U+2E8E), or \u201c\u2714\u201d (U+2714)) are present. Everybody should be thankful to the ${organization} and all the POI contributors for their assistance in this matter.\r"; private String searchText = "${organization}"; private String replacementText = "Apache Software Foundation"; - private String expectedText = + private String expectedText2 = "It is used to confirm that text replacement works even if Unicode characters (such as \u201c\u2014\u201d (U+2014), \u201c\u2e8e\u201d (U+2E8E), or \u201c\u2714\u201d (U+2714)) are present. Everybody should be thankful to the Apache Software Foundation and all the POI contributors for their assistance in this matter.\r"; + private String expectedText3 = "Thank you, Apache Software Foundation!\r"; private String illustrativeDocFile; @@ -84,7 +85,7 @@ public class TestRangeReplacement extends TestCase { /** * Test that we can replace text in our Range with Unicode text. */ - public void testRangeReplacement() throws Exception { + public void testRangeReplacementOne() throws Exception { HWPFDocument daDoc = new HWPFDocument(new FileInputStream(illustrativeDocFile)); @@ -104,16 +105,46 @@ public class TestRangeReplacement extends TestCase { para.replaceText(searchText, replacementText, offset); - // we need to let the model re-calculate the Range before we evaluate it - range = daDoc.getRange(); - assertEquals(1, range.numSections()); section = range.getSection(0); + assertEquals(4, section.numParagraphs()); + para = section.getParagraph(2); + + text = para.text(); + assertEquals(expectedText2, text); + } + + /** + * Test that we can replace text in our Range with Unicode text. + */ + public void testRangeReplacementAll() throws Exception { + + HWPFDocument daDoc = new HWPFDocument(new FileInputStream(illustrativeDocFile)); + + Range range = daDoc.getRange(); + assertEquals(1, range.numSections()); + + Section section = range.getSection(0); + assertEquals(5, section.numParagraphs()); + + Paragraph para = section.getParagraph(2); + + String text = para.text(); + assertEquals(originalText, text); + + range.replaceText(searchText, replacementText); + + assertEquals(1, range.numSections()); + section = range.getSection(0); assertEquals(5, section.numParagraphs()); + para = section.getParagraph(2); + text = para.text(); + assertEquals(expectedText2, text); + para = section.getParagraph(3); text = para.text(); - assertEquals(expectedText, text); + assertEquals(expectedText3, text); } } diff --git a/src/testcases/org/apache/poi/hssf/data/3dFormulas.xls b/src/testcases/org/apache/poi/hssf/data/3dFormulas.xls Binary files differnew file mode 100644 index 0000000000..82519ed839 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/data/3dFormulas.xls diff --git a/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls b/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls Binary files differindex ce94050789..7be92c5fa4 100644 --- a/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls +++ b/src/testcases/org/apache/poi/hssf/data/FormulaEvalTestData.xls diff --git a/src/testcases/org/apache/poi/hssf/data/MRExtraLines.xls b/src/testcases/org/apache/poi/hssf/data/MRExtraLines.xls Binary files differnew file mode 100644 index 0000000000..e82e4f6f40 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/data/MRExtraLines.xls diff --git a/src/testcases/org/apache/poi/hssf/eventusermodel/TestEventWorkbookBuilder.java b/src/testcases/org/apache/poi/hssf/eventusermodel/TestEventWorkbookBuilder.java new file mode 100644 index 0000000000..adf084331a --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/eventusermodel/TestEventWorkbookBuilder.java @@ -0,0 +1,160 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.eventusermodel; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import junit.framework.TestCase; + +import org.apache.poi.hssf.HSSFTestDataSamples; +import org.apache.poi.hssf.eventusermodel.EventWorkbookBuilder.SheetRecordCollectingListener; +import org.apache.poi.hssf.model.FormulaParser; +import org.apache.poi.hssf.model.Workbook; +import org.apache.poi.hssf.record.FormulaRecord; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.formula.Ref3DPtg; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.hssf.util.SheetReferences; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +/** + * Tests for {@link EventWorkbookBuilder} + */ +public final class TestEventWorkbookBuilder extends TestCase { + private MockHSSFListener mockListen; + private SheetRecordCollectingListener listener; + + public void setUp() { + HSSFRequest req = new HSSFRequest(); + mockListen = new MockHSSFListener(); + listener = new SheetRecordCollectingListener(mockListen); + req.addListenerForAllRecords(listener); + + HSSFEventFactory factory = new HSSFEventFactory(); + try { + InputStream is = HSSFTestDataSamples.openSampleFileStream("3dFormulas.xls"); + POIFSFileSystem fs = new POIFSFileSystem(is); + factory.processWorkbookEvents(req, fs); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void testBasics() throws Exception { + assertNotNull(listener.getSSTRecord()); + assertNotNull(listener.getBoundSheetRecords()); + assertNotNull(listener.getExternSheetRecords()); + } + + public void testGetStubWorkbooks() throws Exception { + assertNotNull(listener.getStubWorkbook()); + assertNotNull(listener.getStubHSSFWorkbook()); + + assertNotNull(listener.getStubWorkbook().getSheetReferences()); + assertNotNull(listener.getStubHSSFWorkbook().getSheetReferences()); + } + + public void testContents() throws Exception { + assertEquals(2, listener.getSSTRecord().getNumStrings()); + assertEquals(3, listener.getBoundSheetRecords().length); + assertEquals(1, listener.getExternSheetRecords().length); + + assertEquals(3, listener.getStubWorkbook().getNumSheets()); + + SheetReferences ref = listener.getStubWorkbook().getSheetReferences(); + assertEquals("Sh3", ref.getSheetName(0)); + assertEquals("Sheet1", ref.getSheetName(1)); + assertEquals("S2", ref.getSheetName(2)); + } + + public void testFormulas() throws Exception { + FormulaRecord fr; + + // Check our formula records + assertEquals(6, mockListen._frecs.size()); + + Workbook stubWB = listener.getStubWorkbook(); + assertNotNull(stubWB); + HSSFWorkbook stubHSSF = listener.getStubHSSFWorkbook(); + assertNotNull(stubHSSF); + + // Check these stubs have the right stuff on them + assertEquals("Sheet1", stubWB.getSheetName(0)); + assertEquals("S2", stubWB.getSheetName(1)); + assertEquals("Sh3", stubWB.getSheetName(2)); + + // Check we can get the formula without breaking + for(int i=0; i<mockListen._frecs.size(); i++) { + fr = (FormulaRecord)mockListen._frecs.get(i); + FormulaParser.toFormulaString(stubHSSF, fr.getParsedExpression()); + } + + // Peer into just one formula, and check that + // all the ptgs give back the right things + List ptgs = ((FormulaRecord)mockListen._frecs.get(0)).getParsedExpression(); + assertEquals(1, ptgs.size()); + assertTrue(ptgs.get(0) instanceof Ref3DPtg); + + Ref3DPtg ptg = (Ref3DPtg)ptgs.get(0); + assertEquals("Sheet1!A1", ptg.toFormulaString(stubHSSF)); + + + // Now check we get the right formula back for + // a few sample ones + + // Sheet 1 A2 is on same sheet + fr = (FormulaRecord)mockListen._frecs.get(0); + assertEquals(1, fr.getRow()); + assertEquals(0, fr.getColumn()); + assertEquals("Sheet1!A1", FormulaParser.toFormulaString(stubHSSF, fr.getParsedExpression())); + + // Sheet 1 A5 is to another sheet + fr = (FormulaRecord)mockListen._frecs.get(3); + assertEquals(4, fr.getRow()); + assertEquals(0, fr.getColumn()); + assertEquals("'S2'!A1", FormulaParser.toFormulaString(stubHSSF, fr.getParsedExpression())); + + // Sheet 1 A7 is to another sheet, range + fr = (FormulaRecord)mockListen._frecs.get(5); + assertEquals(6, fr.getRow()); + assertEquals(0, fr.getColumn()); + assertEquals("SUM('Sh3'!A1:A4)", FormulaParser.toFormulaString(stubHSSF, fr.getParsedExpression())); + + + // Now, load via Usermodel and re-check + InputStream is = HSSFTestDataSamples.openSampleFileStream("3dFormulas.xls"); + POIFSFileSystem fs = new POIFSFileSystem(is); + HSSFWorkbook wb = new HSSFWorkbook(fs); + assertEquals("Sheet1!A1", wb.getSheetAt(0).getRow(1).getCell(0).getCellFormula()); + assertEquals("SUM('Sh3'!A1:A4)", wb.getSheetAt(0).getRow(6).getCell(0).getCellFormula()); + } + + private static final class MockHSSFListener implements HSSFListener { + public MockHSSFListener() {} + private final List _records = new ArrayList(); + private final List _frecs = new ArrayList(); + + public void processRecord(Record record) { + _records.add(record); + if(record instanceof FormulaRecord) { + _frecs.add(record); + } + } + } +}
\ No newline at end of file diff --git a/src/testcases/org/apache/poi/hssf/eventusermodel/TestMissingRecordAwareHSSFListener.java b/src/testcases/org/apache/poi/hssf/eventusermodel/TestMissingRecordAwareHSSFListener.java index 16a406cba1..37e594940c 100644 --- a/src/testcases/org/apache/poi/hssf/eventusermodel/TestMissingRecordAwareHSSFListener.java +++ b/src/testcases/org/apache/poi/hssf/eventusermodel/TestMissingRecordAwareHSSFListener.java @@ -16,8 +16,6 @@ ==================================================================== */ package org.apache.poi.hssf.eventusermodel; -import java.io.File; -import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; @@ -29,6 +27,7 @@ import org.apache.poi.hssf.HSSFTestDataSamples; import org.apache.poi.hssf.eventusermodel.dummyrecord.LastCellOfRowDummyRecord; import org.apache.poi.hssf.eventusermodel.dummyrecord.MissingCellDummyRecord; import org.apache.poi.hssf.eventusermodel.dummyrecord.MissingRowDummyRecord; +import org.apache.poi.hssf.record.BOFRecord; import org.apache.poi.hssf.record.LabelSSTRecord; import org.apache.poi.hssf.record.Record; import org.apache.poi.hssf.record.RowRecord; @@ -40,8 +39,7 @@ public final class TestMissingRecordAwareHSSFListener extends TestCase { private Record[] r; - public void setUp() { - + public void openNormal() { HSSFRequest req = new HSSFRequest(); MockHSSFListener mockListen = new MockHSSFListener(); MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(mockListen); @@ -55,10 +53,31 @@ public final class TestMissingRecordAwareHSSFListener extends TestCase { } catch (IOException e) { throw new RuntimeException(e); } + r = mockListen.getRecords(); + assertTrue(r.length > 100); + } + public void openAlt() { + HSSFRequest req = new HSSFRequest(); + MockHSSFListener mockListen = new MockHSSFListener(); + MissingRecordAwareHSSFListener listener = new MissingRecordAwareHSSFListener(mockListen); + req.addListenerForAllRecords(listener); + + HSSFEventFactory factory = new HSSFEventFactory(); + try { + InputStream is = HSSFTestDataSamples.openSampleFileStream("MRExtraLines.xls"); + POIFSFileSystem fs = new POIFSFileSystem(is); + factory.processWorkbookEvents(req, fs); + } catch (IOException e) { + throw new RuntimeException(e); + } + + r = mockListen.getRecords(); + assertTrue(r.length > 100); } public void testMissingRowRecords() throws Exception { + openNormal(); // We have rows 0, 1, 2, 20 and 21 int row0 = -1; @@ -108,6 +127,7 @@ public final class TestMissingRecordAwareHSSFListener extends TestCase { } public void testEndOfRowRecords() throws Exception { + openNormal(); // Find the cell at 0,0 int cell00 = -1; @@ -194,7 +214,7 @@ public final class TestMissingRecordAwareHSSFListener extends TestCase { assertTrue(r[cell00+57] instanceof LastCellOfRowDummyRecord); // Check the numbers of the last seen columns - LastCellOfRowDummyRecord[] lrs = new LastCellOfRowDummyRecord[23]; + LastCellOfRowDummyRecord[] lrs = new LastCellOfRowDummyRecord[24]; int lrscount = 0; for(int i=0; i<r.length; i++) { if(r[i] instanceof LastCellOfRowDummyRecord) { @@ -229,6 +249,7 @@ public final class TestMissingRecordAwareHSSFListener extends TestCase { public void testMissingCellRecords() throws Exception { + openNormal(); // Find the cell at 0,0 int cell00 = -1; @@ -326,10 +347,38 @@ public final class TestMissingRecordAwareHSSFListener extends TestCase { assertEquals(22, mc.getRow()); assertEquals(10, mc.getColumn()); } + + // Make sure we don't put in any extra new lines + // that aren't already there + public void testNoExtraNewLines() throws Exception { + // Load a different file + openAlt(); + + + // This file has has something in lines 1-33 + List lcor = new ArrayList(); + for(int i=0; i<r.length; i++) { + if(r[i] instanceof LastCellOfRowDummyRecord) + lcor.add( (LastCellOfRowDummyRecord)r[i] ); + } + + // Check we got the 33 rows + assertEquals(33, lcor.size()); + LastCellOfRowDummyRecord[] rowEnds = (LastCellOfRowDummyRecord[]) + lcor.toArray(new LastCellOfRowDummyRecord[lcor.size()]); + assertEquals(33, rowEnds.length); + + // And check they have the right stuff in them, + // no repeats + for(int i=0; i<rowEnds.length; i++) { + assertEquals(i, rowEnds[i].getRow()); + } + } private static final class MockHSSFListener implements HSSFListener { public MockHSSFListener() {} private final List _records = new ArrayList(); + private boolean logToStdOut = false; public void processRecord(Record record) { _records.add(record); @@ -346,9 +395,20 @@ public final class TestMissingRecordAwareHSSFListener extends TestCase { LastCellOfRowDummyRecord lc = (LastCellOfRowDummyRecord)record; log("Got end-of row, row was " + lc.getRow() + ", last column was " + lc.getLastColumnNumber()); } + + if(record instanceof BOFRecord) { + BOFRecord r = (BOFRecord)record; + if(r.getType() == BOFRecord.TYPE_WORKSHEET) { + log("On new sheet"); + } + } + if(record instanceof RowRecord) { + RowRecord rr = (RowRecord)record; + log("Starting row #" + rr.getRowNumber()); + } } - private static void log(String msg) { - if(false) { // successful tests should be quiet + private void log(String msg) { + if(logToStdOut) { System.out.println(msg); } } diff --git a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java index 987ad0b096..115a7e081b 100644 --- a/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java +++ b/src/testcases/org/apache/poi/hssf/model/TestFormulaParser.java @@ -759,7 +759,7 @@ public final class TestFormulaParser extends TestCase { } } - public void testParseErrorExpecteMsg() { + public void testParseErrorExpectedMsg() { try { parseFormula("round(3.14;2)"); @@ -768,4 +768,27 @@ public final class TestFormulaParser extends TestCase { assertEquals("Parse error near char 10 ';' in specified formula 'round(3.14;2)'. Expected ',' or ')'", e.getMessage()); } } + + /** + * this function name has a dot in it. + */ + public void testParseErrorTypeFunction() { + + Ptg[] ptgs; + try { + ptgs = parseFormula("error.type(A1)"); + + + } catch (IllegalArgumentException e) { + if (e.getMessage().equals("Invalid Formula cell reference: 'error'")) { + throw new AssertionFailedError("Identified bug 45334"); + } + throw e; + } + assertEquals(2, ptgs.length); + assertEquals(FuncPtg.class, ptgs[1].getClass()); + FuncPtg funcPtg = (FuncPtg) ptgs[1]; + assertEquals("ERROR.TYPE", funcPtg.getName()); + } + } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java index 79ef47be38..cf48573682 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestBugs.java @@ -1052,4 +1052,88 @@ public final class TestBugs extends TestCase { assertTrue(nd.get(0) instanceof DeletedArea3DPtg); } } + + /** + * Test that fonts get added properly + */ + public void test45338() throws Exception { + HSSFWorkbook wb = new HSSFWorkbook(); + assertEquals(4, wb.getNumberOfFonts()); + + HSSFSheet s = wb.createSheet(); + s.createRow(0); + s.createRow(1); + HSSFCell c1 = s.getRow(0).createCell((short)0); + HSSFCell c2 = s.getRow(1).createCell((short)0); + + assertEquals(4, wb.getNumberOfFonts()); + + HSSFFont f1 = wb.getFontAt((short)0); + assertEquals(400, f1.getBoldweight()); + + // Check that asking for the same font + // multiple times gives you the same thing. + // Otherwise, our tests wouldn't work! + assertEquals( + wb.getFontAt((short)0), + wb.getFontAt((short)0) + ); + assertEquals( + wb.getFontAt((short)2), + wb.getFontAt((short)2) + ); + assertTrue( + wb.getFontAt((short)0) + != + wb.getFontAt((short)2) + ); + + // Look for a new font we have + // yet to add + assertNull( + wb.findFont( + (short)11, (short)123, (short)22, + "Thingy", false, true, (short)2, (byte)2 + ) + ); + + HSSFFont nf = wb.createFont(); + assertEquals(5, wb.getNumberOfFonts()); + + assertEquals(5, nf.getIndex()); + assertEquals(nf, wb.getFontAt((short)5)); + + nf.setBoldweight((short)11); + nf.setColor((short)123); + nf.setFontHeight((short)22); + nf.setFontName("Thingy"); + nf.setItalic(false); + nf.setStrikeout(true); + nf.setTypeOffset((short)2); + nf.setUnderline((byte)2); + + assertEquals(5, wb.getNumberOfFonts()); + assertEquals(nf, wb.getFontAt((short)5)); + + // Find it now + assertNotNull( + wb.findFont( + (short)11, (short)123, (short)22, + "Thingy", false, true, (short)2, (byte)2 + ) + ); + assertEquals( + 5, + wb.findFont( + (short)11, (short)123, (short)22, + "Thingy", false, true, (short)2, (byte)2 + ).getIndex() + ); + assertEquals(nf, + wb.findFont( + (short)11, (short)123, (short)22, + "Thingy", false, true, (short)2, (byte)2 + ) + ); + } } diff --git a/src/testcases/org/apache/poi/hssf/util/TestHSSFColor.java b/src/testcases/org/apache/poi/hssf/util/TestHSSFColor.java new file mode 100644 index 0000000000..42571eb323 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/util/TestHSSFColor.java @@ -0,0 +1,52 @@ +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.util; + +import java.util.Hashtable; + +import junit.framework.TestCase; + +public final class TestHSSFColor extends TestCase { + public void testBasics() { + assertNotNull(HSSFColor.YELLOW.class); + assertTrue(HSSFColor.YELLOW.index > 0); + assertTrue(HSSFColor.YELLOW.index2 > 0); + } + + public void testContents() { + assertEquals(3, HSSFColor.YELLOW.triplet.length); + assertEquals(255, HSSFColor.YELLOW.triplet[0]); + assertEquals(255, HSSFColor.YELLOW.triplet[1]); + assertEquals(0, HSSFColor.YELLOW.triplet[2]); + + assertEquals("FFFF:FFFF:0", HSSFColor.YELLOW.hexString); + } + + public void testTrippletHash() { + Hashtable tripplets = HSSFColor.getTripletHash(); + + assertEquals( + HSSFColor.MAROON.class, + tripplets.get(HSSFColor.MAROON.hexString).getClass() + ); + assertEquals( + HSSFColor.YELLOW.class, + tripplets.get(HSSFColor.YELLOW.hexString).getClass() + ); + } +} |