From 8ec2aaafd661f439a4fbfd3dddd66839e21a58e7 Mon Sep 17 00:00:00 2001 From: Ugo Cei Date: Wed, 13 Feb 2008 17:34:43 +0000 Subject: [PATCH] Merged revisions 618680-620582 via svnmerge from https://svn.apache.org/repos/asf/poi/trunk ........ r618680 | nick | 2008-02-05 16:51:29 +0100 (Tue, 05 Feb 2008) | 1 line Add java.lang.Iterable style methods for iterating over rows and cells, but don't actually implement that, as it wasn't in jdk 1.3 or jdk 1.4, with is rather tedious ........ r618690 | nick | 2008-02-05 17:21:21 +0100 (Tue, 05 Feb 2008) | 1 line Tweaks to the iterator use guides for hssf ........ r618692 | nick | 2008-02-05 17:32:45 +0100 (Tue, 05 Feb 2008) | 1 line Few tweaks to the patching guide ........ r618940 | yegor | 2008-02-06 10:47:13 +0100 (Wed, 06 Feb 2008) | 1 line 3.0.2-FINAL released ........ r619001 | nick | 2008-02-06 15:35:05 +0100 (Wed, 06 Feb 2008) | 1 line Implement hashCode and equals for HSSFFont and HSSFCellStyle ........ r619310 | yegor | 2008-02-07 09:56:59 +0100 (Thu, 07 Feb 2008) | 1 line support for excel hypelrinks ........ r619382 | nick | 2008-02-07 13:39:12 +0100 (Thu, 07 Feb 2008) | 1 line Patch from bug #44373 - Have HSSFDateUtil.isADateFormat support more date formats ........ r619502 | nick | 2008-02-07 17:53:23 +0100 (Thu, 07 Feb 2008) | 1 line Handle timezones better with cell.setCellValue(Calendar), so now 20:00-03:00, 20:00+00:00 and 20:00+03:00 will all be recorded as 20:00, and not 17:00 / 20:00 / 23:00 (pass a Date not a Calendar for old behaviour) - patch from bug #38641 ........ r619509 | nick | 2008-02-07 18:12:09 +0100 (Thu, 07 Feb 2008) | 1 line Add (disabled) failing testcase for bug #44375 ........ r619848 | klute | 2008-02-08 12:55:43 +0100 (Fri, 08 Feb 2008) | 1 line - Fixed bug 44375 - HPSF now copes with a broken dictionary in Document Summary Information stream. RuntimeExceptions that occured when trying to read bogus data are now caught. Dictionary entries up to but not including the bogus one are preserved, the rest is ignored. ........ r619849 | klute | 2008-02-08 12:56:11 +0100 (Fri, 08 Feb 2008) | 1 line - Fixed bug 44375 - HPSF now copes with a broken dictionary in Document Summary Information stream. RuntimeExceptions that occured when trying to read bogus data are now caught. Dictionary entries up to but not including the bogus one are preserved, the rest is ignored. ........ r619851 | nick | 2008-02-08 13:02:10 +0100 (Fri, 08 Feb 2008) | 1 line Copy Rainer's change note from changes.xml to status.xml, as it's needed in both ........ r619968 | nick | 2008-02-08 19:17:29 +0100 (Fri, 08 Feb 2008) | 1 line Tweak layout ........ r620341 | nick | 2008-02-10 22:54:13 +0100 (Sun, 10 Feb 2008) | 1 line Test for bug #42564 - appears to already be fixed ........ r620557 | nick | 2008-02-11 18:55:09 +0100 (Mon, 11 Feb 2008) | 1 line Another test relating to bug #42564 - this one is still failing ........ r620582 | nick | 2008-02-11 20:14:04 +0100 (Mon, 11 Feb 2008) | 1 line If we have a document with a hpsf stream that exists, but is of the wrong type, then log a warning but continue ........ git-svn-id: https://svn.apache.org/repos/asf/poi/branches/ooxml@627544 13f79535-47bb-0310-9956-ffa450edef68 --- src/documentation/content/xdocs/changes.xml | 5 + .../content/xdocs/getinvolved/index.xml | 30 +- .../content/xdocs/hssf/quick-guide.xml | 115 ++- src/documentation/content/xdocs/index.xml | 17 +- src/documentation/content/xdocs/status.xml | 5 + .../hssf/usermodel/examples/Hyperlinks.java | 91 +++ src/java/org/apache/poi/POIDocument.java | 20 +- src/java/org/apache/poi/hpsf/Property.java | 134 ++-- src/java/org/apache/poi/hpsf/Section.java | 2 +- .../org/apache/poi/hpsf/VariantSupport.java | 50 +- .../poi/hssf/record/ExtendedFormatRecord.java | 52 ++ .../apache/poi/hssf/record/FontRecord.java | 33 + .../poi/hssf/record/HyperlinkRecord.java | 662 +++++++++++------- .../apache/poi/hssf/usermodel/HSSFCell.java | 30 +- .../poi/hssf/usermodel/HSSFCellStyle.java | 25 + .../poi/hssf/usermodel/HSSFDateUtil.java | 38 +- .../apache/poi/hssf/usermodel/HSSFFont.java | 23 + .../poi/hssf/usermodel/HSSFHyperlink.java | 137 +++- .../apache/poi/hssf/usermodel/HSSFRow.java | 3 +- .../apache/poi/hssf/usermodel/HSSFSheet.java | 3 +- .../poi/hwpf/data/ProblemExtracting.doc | Bin 0 -> 424448 bytes .../hwpf/extractor/TestWordExtractorBugs.java | 45 ++ .../org/apache/poi/hpsf/basic/TestBasic.java | 41 -- .../poi/hpsf/basic/TestReadAllFiles.java | 110 +++ .../org/apache/poi/hpsf/data/TestBug44375.xls | Bin 0 -> 16896 bytes .../org/apache/poi/hssf/data/42564-2.xls | Bin 0 -> 27136 bytes .../org/apache/poi/hssf/data/42564.xls | Bin 0 -> 13824 bytes .../poi/hssf/record/TestHyperlinkRecord.java | 391 ++++++++--- .../apache/poi/hssf/usermodel/TestBugs.java | 42 ++ .../poi/hssf/usermodel/TestCellStyle.java | 29 + .../poi/hssf/usermodel/TestHSSFCell.java | 14 +- .../poi/hssf/usermodel/TestHSSFDateUtil.java | 44 +- .../poi/hssf/usermodel/TestHSSFHyperlink.java | 191 +++++ 33 files changed, 1861 insertions(+), 521 deletions(-) create mode 100755 src/examples/src/org/apache/poi/hssf/usermodel/examples/Hyperlinks.java create mode 100644 src/scratchpad/testcases/org/apache/poi/hwpf/data/ProblemExtracting.doc create mode 100644 src/scratchpad/testcases/org/apache/poi/hwpf/extractor/TestWordExtractorBugs.java create mode 100644 src/testcases/org/apache/poi/hpsf/basic/TestReadAllFiles.java create mode 100644 src/testcases/org/apache/poi/hpsf/data/TestBug44375.xls create mode 100644 src/testcases/org/apache/poi/hssf/data/42564-2.xls create mode 100644 src/testcases/org/apache/poi/hssf/data/42564.xls create mode 100755 src/testcases/org/apache/poi/hssf/usermodel/TestHSSFHyperlink.java diff --git a/src/documentation/content/xdocs/changes.xml b/src/documentation/content/xdocs/changes.xml index df0e5e51a..34345df5d 100644 --- a/src/documentation/content/xdocs/changes.xml +++ b/src/documentation/content/xdocs/changes.xml @@ -36,6 +36,11 @@ + 44375 - Cope with a broken dictionary in Document Summary Information stream. RuntimeExceptions that occured when trying to read bogus data are now caught. Dictionary entries up to but not including the bogus one are preserved, the rest is ignored. + 38641 - Handle timezones better with cell.setCellValue(Calendar), so now 20:00-03:00, 20:00+00:00 and 20:00+03:00 will all be recorded as 20:00, and not 17:00 / 20:00 / 23:00 (pass a Date not a Calendar for old behaviour) + 44373 - Have HSSFDateUtil.isADateFormat recognize more formats as being dates + 37923 - Support for Excel hyperlinks + Implement hashCode() and equals(obj) on HSSFFont and HSSFCellStyle 44345 - Implement CountA, CountIf, Index, Rows and Columns functions 44336 - Properly escape sheet names as required when figuring out the text of formulas 44326 - Improvements to how SystemOutLogger and CommonsLogger log messages with exceptions, and avoid an infinite loop with certain log messages with exceptions diff --git a/src/documentation/content/xdocs/getinvolved/index.xml b/src/documentation/content/xdocs/getinvolved/index.xml index 1c182a508..71ba94134 100644 --- a/src/documentation/content/xdocs/getinvolved/index.xml +++ b/src/documentation/content/xdocs/getinvolved/index.xml @@ -96,7 +96,8 @@ Create patches by getting the latest sources from Subversion. Alter or add files as appropriate. Then, from the poi directiory, type svn diff > mypatch.patch. This will capture all of your changes - in a patch file of the appropriate format. Next, if you've added any + in a patch file of the appropriate format. However, svn diff won't + capture any new files you may have added. So, if you've added any files, create an archive (tar.bz2 preferred as its the smallest) in a path-preserving archive format, relative to your poi directory. You'll attach both files in the next step. @@ -117,6 +118,33 @@ Standards). Patches that are of low quality may be rejected or the contributer may be asked to bring them up to spec.

+

If you use a unix shell, you may find the following following + sequence of commands useful for building the files to attach.

+ +# Run this in the root of the checkout, i.e. the directory holding +# build.xml and poi.pom + +# Build the directory to hold new files +mkdir /tmp/poi-patch/ +mkdir /tmp/poi-patch/new-files/ + +# Get changes to existing files +svn diff > /tmp/poi-patch/diff.txt + +# Capture any new files, as svn diff won't include them +# Preserve the path +svn status | grep "^\?" | awk '{printf "cp --parents %s /tmp/poi-patch/new-files/\n", $2 }' | sh -s + +# tar up the new files +cd /tmp/poi-patch/new-files/ +tar jcvf ../new-files.tar.bz2 +cd .. + +# Upload these to bugzilla +echo "Please upload to bugzilla:" +echo " /tmp/poi-patch/diff.txt" +echo " /tmp/poi-patch/new-files.tar.bz2" + diff --git a/src/documentation/content/xdocs/hssf/quick-guide.xml b/src/documentation/content/xdocs/hssf/quick-guide.xml index b2f729fed..66da60489 100644 --- a/src/documentation/content/xdocs/hssf/quick-guide.xml +++ b/src/documentation/content/xdocs/hssf/quick-guide.xml @@ -69,6 +69,7 @@
  • Named Ranges and Named Cells
  • How to set cell comments
  • How to adjust column width to fit the contents
  • +
  • Hyperlinks
  • Features @@ -236,16 +237,51 @@
    -
    Iterate over rows and cells (including Java 5 foreach loops) +
    Iterate over rows and cells

    Sometimes, you'd like to just iterate over all the rows in - a sheet, or all the cells in a row. If you are using Java - 5 or later, then this is especially handy, as it'll allow the - new foreach loop support to work.

    + a sheet, or all the cells in a row. This is possible with + a simple for loop.

    Luckily, this is very easy. HSSFRow defines a CellIterator inner class to handle iterating over the cells (get one with a call to row.cellIterator()), and HSSFSheet provides a rowIterator() method to give an iterator over all the rows.

    +

    (Unfortunately, due to the broken and + backwards-incompatible way that Java 5 foreach loops were + implemented, it isn't possible to use them on a codebase + that supports Java 1.4, as POI does)

    + + HSSFSheet sheet = wb.getSheetAt(0); + for (Iterator rit = sheet.rowIterator(); rit.hasNext(); ) { + HSSFRow row = (HSSFRow)rit.next(); + for (Iterator cit = row.cellIterator(); cit.hasNext(); ) { + HSSFCell cell = (HSSFCell)cit.next(); + // Do something here + } + } + + + HSSFSheet sheet = wb.getSheetAt(0); + for (Iterator<HSSFRow> rit = (Iterator<HSSFRow>)sheet.rowIterator(); rit.hasNext(); ) { + HSSFRow row = rit.next(); + for (Iterator<HSSFCell> cit = (Iterator<HSSFCell>)row.cellIterator(); cit.hasNext(); ) { + HSSFCell cell = cit.next(); + // Do something here + } + } + +
    +
    Iterate over rows and cells using Java 1.5 foreach loops - OOXML Branch Only +

    Sometimes, you'd like to just iterate over all the rows in + a sheet, or all the cells in a row. If you are using Java + 5 or later, then this is especially handy, as it'll allow the + new foreach loop support to work.

    +

    Luckily, this is very easy. Both HSSFSheet and HSSFRow + implement java.lang.Iterable to allow foreach + loops. For HSSFRow this allows access to the + CellIterator inner class to handle iterating over + the cells, and for HSSFSheet gives the + rowIterator() to iterator over all the rows.

    HSSFSheet sheet = wb.getSheetAt(0); for (HSSFRow row : sheet.rowIterator()) { @@ -254,6 +290,7 @@ } } + This only works on the OOXML branch of POI
    Text Extraction @@ -1286,6 +1323,76 @@ Examples: (either via -Djava.awt.headless=true startup parameter or via System.setProperty("java.awt.headless", "true")).
    + +
    How to read hyperlinks + + HSSFSheet sheet = workbook.getSheetAt(0); + + HSSFCell cell = sheet.getRow(0).getCell((short)0); + HSSFHyperlink link = cell.getHyperlink(); + if(link != null){ + System.out.println(link.getAddress()); + } + +
    +
    How to create hyperlinks + + HSSFWorkbook wb = new HSSFWorkbook(); + + //cell style for hyperlinks + //by default hypelrinks are blue and underlined + HSSFCellStyle hlink_style = wb.createCellStyle(); + HSSFFont hlink_font = wb.createFont(); + hlink_font.setUnderline(HSSFFont.U_SINGLE); + hlink_font.setColor(HSSFColor.BLUE.index); + hlink_style.setFont(hlink_font); + + HSSFCell cell; + HSSFSheet sheet = wb.createSheet("Hyperlinks"); + + //URL + cell = sheet.createRow(0).createCell((short)0); + cell.setCellValue("URL Link"); + HSSFHyperlink link = new HSSFHyperlink(HSSFHyperlink.LINK_URL); + link.setAddress("http://poi.apache.org/"); + cell.setHyperlink(link); + cell.setCellStyle(hlink_style); + + //link to a file in the current directory + cell = sheet.createRow(1).createCell((short)0); + cell.setCellValue("File Link"); + link = new HSSFHyperlink(HSSFHyperlink.LINK_FILE); + link.setAddress("link1.xls"); + cell.setHyperlink(link); + cell.setCellStyle(hlink_style); + + //e-mail link + cell = sheet.createRow(2).createCell((short)0); + cell.setCellValue("Email Link"); + link = new HSSFHyperlink(HSSFHyperlink.LINK_EMAIL); + //note, if subject contains white spaces, make sure they are url-encoded + link.setAddress("mailto:poi@apache.org?subject=Hyperlinks"); + cell.setHyperlink(link); + cell.setCellStyle(hlink_style); + + //link to a place in this workbook + + //create a target sheet and cell + HSSFSheet sheet2 = wb.createSheet("Target Sheet"); + sheet2.createRow(0).createCell((short)0).setCellValue("Target Cell"); + + cell = sheet.createRow(3).createCell((short)0); + cell.setCellValue("Worksheet Link"); + link = new HSSFHyperlink(HSSFHyperlink.LINK_DOCUMENT); + link.setAddress("'Target Sheet'!A1"); + cell.setHyperlink(link); + cell.setCellStyle(hlink_style); + + FileOutputStream out = new FileOutputStream("hssf-links.xls"); + wb.write(out); + out.close(); + +
    diff --git a/src/documentation/content/xdocs/index.xml b/src/documentation/content/xdocs/index.xml index ba019eb98..b9e8f6d71 100644 --- a/src/documentation/content/xdocs/index.xml +++ b/src/documentation/content/xdocs/index.xml @@ -31,16 +31,17 @@ -
    POI 3.0.2 BETA2 Release -

    The latest release of Apache POI is 3.0.2 BETA2 which was promoted to "Beta" on 12 January 2008. It contains a mixture of - new features and bug fixes, compared to 3.0.1. A full list of changes - is available in - the changelog, and - download +

    February 06 2008 - POI 3.0.2 Released +

    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 changelog, and + download the source and binaries from your - local mirror. - The release is also available from the central Maven repository under Group ID "org.apache.poi". + local mirror.

    +

    + The release is also available from the central Maven repository under Group ID "org.apache.poi" and Version "3.0.2-FINAL". +

    POI 3.0.1 Release diff --git a/src/documentation/content/xdocs/status.xml b/src/documentation/content/xdocs/status.xml index 9a7a3f43c..91641b13b 100644 --- a/src/documentation/content/xdocs/status.xml +++ b/src/documentation/content/xdocs/status.xml @@ -33,6 +33,11 @@ + 44375 - Cope with a broken dictionary in Document Summary Information stream. RuntimeExceptions that occured when trying to read bogus data are now caught. Dictionary entries up to but not including the bogus one are preserved, the rest is ignored. + 38641 - Handle timezones better with cell.setCellValue(Calendar), so now 20:00-03:00, 20:00+00:00 and 20:00+03:00 will all be recorded as 20:00, and not 17:00 / 20:00 / 23:00 (pass a Date not a Calendar for old behaviour) + 44373 - Have HSSFDateUtil.isADateFormat recognize more formats as being dates + 37923 - Support for Excel hyperlinks + Implement hashCode() and equals(obj) on HSSFFont and HSSFCellStyle 44345 - Implement CountA, CountIf, Index, Rows and Columns functions 44336 - Properly escape sheet names as required when figuring out the text of formulas 44326 - Improvements to how SystemOutLogger and CommonsLogger log messages with exceptions, and avoid an infinite loop with certain log messages with exceptions diff --git a/src/examples/src/org/apache/poi/hssf/usermodel/examples/Hyperlinks.java b/src/examples/src/org/apache/poi/hssf/usermodel/examples/Hyperlinks.java new file mode 100755 index 000000000..24b3f186f --- /dev/null +++ b/src/examples/src/org/apache/poi/hssf/usermodel/examples/Hyperlinks.java @@ -0,0 +1,91 @@ + +/* ==================================================================== + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +==================================================================== */ + +package org.apache.poi.hssf.usermodel.examples; + +import org.apache.poi.hssf.usermodel.*; +import org.apache.poi.hssf.util.HSSFColor; + +import java.io.IOException; +import java.io.FileOutputStream; + +/** + * Demonstrates how to create hyperlinks. + * + * @author Yegor Kozlov (yegor at apach.org) + */ +public class Hyperlinks { + + public static void main(String[] args) throws IOException { + HSSFWorkbook wb = new HSSFWorkbook(); + + //cell style for hyperlinks + //by default hypelrinks are blue and underlined + HSSFCellStyle hlink_style = wb.createCellStyle(); + HSSFFont hlink_font = wb.createFont(); + hlink_font.setUnderline(HSSFFont.U_SINGLE); + hlink_font.setColor(HSSFColor.BLUE.index); + hlink_style.setFont(hlink_font); + + HSSFCell cell; + HSSFSheet sheet = wb.createSheet("Hyperlinks"); + + //URL + cell = sheet.createRow(0).createCell((short)0); + cell.setCellValue("URL Link"); + HSSFHyperlink link = new HSSFHyperlink(HSSFHyperlink.LINK_URL); + link.setAddress("http://poi.apache.org/"); + cell.setHyperlink(link); + cell.setCellStyle(hlink_style); + + //link to a file in the current directory + cell = sheet.createRow(1).createCell((short)0); + cell.setCellValue("File Link"); + link = new HSSFHyperlink(HSSFHyperlink.LINK_FILE); + link.setAddress("link1.xls"); + cell.setHyperlink(link); + cell.setCellStyle(hlink_style); + + //e-mail link + cell = sheet.createRow(2).createCell((short)0); + cell.setCellValue("Email Link"); + link = new HSSFHyperlink(HSSFHyperlink.LINK_EMAIL); + //note, if subject contains white spaces, make sure they are url-encoded + link.setAddress("mailto:poi@apache.org?subject=Hyperlinks"); + cell.setHyperlink(link); + cell.setCellStyle(hlink_style); + + //link to a place in this workbook + + //create a target sheet and cell + HSSFSheet sheet2 = wb.createSheet("Target Sheet"); + sheet2.createRow(0).createCell((short)0).setCellValue("Target Cell"); + + cell = sheet.createRow(3).createCell((short)0); + cell.setCellValue("Worksheet Link"); + link = new HSSFHyperlink(HSSFHyperlink.LINK_DOCUMENT); + link.setAddress("'Target Sheet'!A1"); + cell.setHyperlink(link); + cell.setCellStyle(hlink_style); + + FileOutputStream out = new FileOutputStream("hssf-links.xls"); + wb.write(out); + out.close(); + + } +} diff --git a/src/java/org/apache/poi/POIDocument.java b/src/java/org/apache/poi/POIDocument.java index ece7a3f13..8d91c06e7 100644 --- a/src/java/org/apache/poi/POIDocument.java +++ b/src/java/org/apache/poi/POIDocument.java @@ -67,14 +67,28 @@ public abstract class POIDocument { /** * Find, and create objects for, the standard - * Documment Information Properties (HPSF) + * Documment Information Properties (HPSF). + * If a given property set is missing or corrupt, + * it will remain null; */ protected void readProperties() { + PropertySet ps; + // DocumentSummaryInformation - dsInf = (DocumentSummaryInformation)getPropertySet(DocumentSummaryInformation.DEFAULT_STREAM_NAME); + ps = getPropertySet(DocumentSummaryInformation.DEFAULT_STREAM_NAME); + if(ps != null && ps instanceof DocumentSummaryInformation) { + dsInf = (DocumentSummaryInformation)ps; + } else if(ps != null) { + logger.log(POILogger.WARN, "DocumentSummaryInformation property set came back with wrong class - ", ps.getClass()); + } // SummaryInformation - sInf = (SummaryInformation)getPropertySet(SummaryInformation.DEFAULT_STREAM_NAME); + ps = getPropertySet(SummaryInformation.DEFAULT_STREAM_NAME); + if(ps instanceof SummaryInformation) { + sInf = (SummaryInformation)ps; + } else if(ps != null) { + logger.log(POILogger.WARN, "SummaryInformation property set came back with wrong class - ", ps.getClass()); + } } /** diff --git a/src/java/org/apache/poi/hpsf/Property.java b/src/java/org/apache/poi/hpsf/Property.java index 3cfb58e6d..16b4f7e41 100644 --- a/src/java/org/apache/poi/hpsf/Property.java +++ b/src/java/org/apache/poi/hpsf/Property.java @@ -23,6 +23,8 @@ import java.util.Map; import org.apache.poi.util.HexDump; import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; /** *

    A property in a {@link Section} of a {@link PropertySet}.

    @@ -113,7 +115,8 @@ public class Property * * @param id the property's ID. * @param type the property's type, see {@link Variant}. - * @param value the property's value. Only certain types are allowed, see {@link Variant}. + * @param value the property's value. Only certain types are allowed, see + * {@link Variant}. */ public Property(final long id, final long type, final Object value) { @@ -210,68 +213,80 @@ public class Property o += LittleEndian.INT_SIZE; final Map m = new HashMap((int) nrEntries, (float) 1.0); - for (int i = 0; i < nrEntries; i++) + + try { - /* The key. */ - final Long id = new Long(LittleEndian.getUInt(src, o)); - o += LittleEndian.INT_SIZE; - - /* The value (a string). The length is the either the - * number of (two-byte) characters if the character set is Unicode - * or the number of bytes if the character set is not Unicode. - * The length includes terminating 0x00 bytes which we have to strip - * off to create a Java string. */ - long sLength = LittleEndian.getUInt(src, o); - o += LittleEndian.INT_SIZE; - - /* Read the string. */ - final StringBuffer b = new StringBuffer(); - switch (codepage) + for (int i = 0; i < nrEntries; i++) { - case -1: + /* The key. */ + final Long id = new Long(LittleEndian.getUInt(src, o)); + o += LittleEndian.INT_SIZE; + + /* The value (a string). The length is the either the + * number of (two-byte) characters if the character set is Unicode + * or the number of bytes if the character set is not Unicode. + * The length includes terminating 0x00 bytes which we have to strip + * off to create a Java string. */ + long sLength = LittleEndian.getUInt(src, o); + o += LittleEndian.INT_SIZE; + + /* Read the string. */ + final StringBuffer b = new StringBuffer(); + switch (codepage) { - /* Without a codepage the length is equal to the number of - * bytes. */ - b.append(new String(src, o, (int) sLength)); - break; - } - case Constants.CP_UNICODE: - { - /* The length is the number of characters, i.e. the number - * of bytes is twice the number of the characters. */ - final int nrBytes = (int) (sLength * 2); - final byte[] h = new byte[nrBytes]; - for (int i2 = 0; i2 < nrBytes; i2 += 2) + case -1: { - h[i2] = src[o + i2 + 1]; - h[i2 + 1] = src[o + i2]; + /* Without a codepage the length is equal to the number of + * bytes. */ + b.append(new String(src, o, (int) sLength)); + break; + } + case Constants.CP_UNICODE: + { + /* The length is the number of characters, i.e. the number + * of bytes is twice the number of the characters. */ + final int nrBytes = (int) (sLength * 2); + final byte[] h = new byte[nrBytes]; + for (int i2 = 0; i2 < nrBytes; i2 += 2) + { + h[i2] = src[o + i2 + 1]; + h[i2 + 1] = src[o + i2]; + } + b.append(new String(h, 0, nrBytes, + VariantSupport.codepageToEncoding(codepage))); + break; + } + default: + { + /* For encodings other than Unicode the length is the number + * of bytes. */ + b.append(new String(src, o, (int) sLength, + VariantSupport.codepageToEncoding(codepage))); + break; } - b.append(new String(h, 0, nrBytes, - VariantSupport.codepageToEncoding(codepage))); - break; } - default: - { - /* For encodings other than Unicode the length is the number - * of bytes. */ - b.append(new String(src, o, (int) sLength, - VariantSupport.codepageToEncoding(codepage))); - break; - } - } - /* Strip 0x00 characters from the end of the string: */ - while (b.length() > 0 && b.charAt(b.length() - 1) == 0x00) - b.setLength(b.length() - 1); - if (codepage == Constants.CP_UNICODE) - { - if (sLength % 2 == 1) - sLength++; - o += (sLength + sLength); + /* Strip 0x00 characters from the end of the string: */ + while (b.length() > 0 && b.charAt(b.length() - 1) == 0x00) + b.setLength(b.length() - 1); + if (codepage == Constants.CP_UNICODE) + { + if (sLength % 2 == 1) + sLength++; + o += (sLength + sLength); + } + else + o += sLength; + m.put(id, b.toString()); } - else - o += sLength; - m.put(id, b.toString()); + } + catch (RuntimeException ex) + { + final POILogger l = POILogFactory.getLogger(getClass()); + l.log(POILogger.WARN, + "The property set's dictionary contains bogus data. " + + "All dictionary entries starting with the one with ID " + + id + " will be ignored.", ex); } return m; } @@ -320,11 +335,10 @@ public class Property /** - *

    Compares two properties.

    - * - *

    Please beware that a property with ID == 0 is a special case: It does not have a type, and its value is the section's - * dictionary. Another special case are strings: Two properties may have - * the different types Variant.VT_LPSTR and Variant.VT_LPWSTR;

    + *

    Compares two properties.

    Please beware that a property with + * ID == 0 is a special case: It does not have a type, and its value is the + * section's dictionary. Another special case are strings: Two properties + * may have the different types Variant.VT_LPSTR and Variant.VT_LPWSTR;

    * * @see Object#equals(java.lang.Object) */ diff --git a/src/java/org/apache/poi/hpsf/Section.java b/src/java/org/apache/poi/hpsf/Section.java index 3b041ed9c..76824e7b7 100644 --- a/src/java/org/apache/poi/hpsf/Section.java +++ b/src/java/org/apache/poi/hpsf/Section.java @@ -210,7 +210,7 @@ public class Section /* Pass 1: Read the property list. */ int pass1Offset = o1; - List propertyList = new ArrayList(propertyCount); + final List propertyList = new ArrayList(propertyCount); PropertyListEntry ple; for (int i = 0; i < properties.length; i++) { diff --git a/src/java/org/apache/poi/hpsf/VariantSupport.java b/src/java/org/apache/poi/hpsf/VariantSupport.java index 1986cac30..703e8abe8 100644 --- a/src/java/org/apache/poi/hpsf/VariantSupport.java +++ b/src/java/org/apache/poi/hpsf/VariantSupport.java @@ -109,25 +109,51 @@ public class VariantSupport extends Variant } + /** + *

    HPSF is able to read these {@link Variant} types.

    + */ + final static public int[] SUPPORTED_TYPES = { Variant.VT_EMPTY, + Variant.VT_I2, Variant.VT_I4, Variant.VT_I8, Variant.VT_R8, + Variant.VT_FILETIME, Variant.VT_LPSTR, Variant.VT_LPWSTR, + Variant.VT_CF, Variant.VT_BOOL }; + + + + /** + *

    Checks whether HPSF supports the specified variant type. Unsupported + * types should be implemented included in the {@link #SUPPORTED_TYPES} + * array.

    + * + * @see Variant + * @param variantType the variant type to check + * @return true if HPFS supports this type, else + * false + */ + public boolean isSupportedType(final int variantType) + { + for (int i = 0; i < SUPPORTED_TYPES.length; i++) + if (variantType == SUPPORTED_TYPES[i]) + return true; + return false; + } + + /** *

    Reads a variant type from a byte array.

    - * + * * @param src The byte array - * @param offset The offset in the byte array where the variant - * starts - * @param length The length of the variant including the variant - * type field + * @param offset The offset in the byte array where the variant starts + * @param length The length of the variant including the variant type field * @param type The variant type to read - * @param codepage The codepage to use to write non-wide strings - * @return A Java object that corresponds best to the variant - * field. For example, a VT_I4 is returned as a {@link Long}, a - * VT_LPSTR as a {@link String}. + * @param codepage The codepage to use for non-wide strings + * @return A Java object that corresponds best to the variant field. For + * example, a VT_I4 is returned as a {@link Long}, a VT_LPSTR as a + * {@link String}. * @exception ReadingNotSupportedException if a property is to be written - * who's variant type HPSF does not yet support + * who's variant type HPSF does not yet support * @exception UnsupportedEncodingException if the specified codepage is not - * supported. - * + * supported. * @see Variant */ public static Object read(final byte[] src, final int offset, diff --git a/src/java/org/apache/poi/hssf/record/ExtendedFormatRecord.java b/src/java/org/apache/poi/hssf/record/ExtendedFormatRecord.java index 221668cb4..c668d5f87 100644 --- a/src/java/org/apache/poi/hssf/record/ExtendedFormatRecord.java +++ b/src/java/org/apache/poi/hssf/record/ExtendedFormatRecord.java @@ -1814,4 +1814,56 @@ public class ExtendedFormatRecord { return sid; } + + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + field_1_font_index; + result = prime * result + field_2_format_index; + result = prime * result + field_3_cell_options; + result = prime * result + field_4_alignment_options; + result = prime * result + field_5_indention_options; + result = prime * result + field_6_border_options; + result = prime * result + field_7_palette_options; + result = prime * result + field_8_adtl_palette_options; + result = prime * result + field_9_fill_palette_options; + return result; + } + + /** + * Will consider two different records with the same + * contents as equals, as the various indexes + * that matter are embedded in the records + */ + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (obj instanceof ExtendedFormatRecord) { + final ExtendedFormatRecord other = (ExtendedFormatRecord) obj; + if (field_1_font_index != other.field_1_font_index) + return false; + if (field_2_format_index != other.field_2_format_index) + return false; + if (field_3_cell_options != other.field_3_cell_options) + return false; + if (field_4_alignment_options != other.field_4_alignment_options) + return false; + if (field_5_indention_options != other.field_5_indention_options) + return false; + if (field_6_border_options != other.field_6_border_options) + return false; + if (field_7_palette_options != other.field_7_palette_options) + return false; + if (field_8_adtl_palette_options != other.field_8_adtl_palette_options) + return false; + if (field_9_fill_palette_options != other.field_9_fill_palette_options) + return false; + return true; + } + return false; + } + + } diff --git a/src/java/org/apache/poi/hssf/record/FontRecord.java b/src/java/org/apache/poi/hssf/record/FontRecord.java index 35d365a67..42e058f47 100644 --- a/src/java/org/apache/poi/hssf/record/FontRecord.java +++ b/src/java/org/apache/poi/hssf/record/FontRecord.java @@ -538,4 +538,37 @@ public class FontRecord { return sid; } + + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime + * result + + ((field_11_font_name == null) ? 0 : field_11_font_name + .hashCode()); + result = prime * result + field_1_font_height; + result = prime * result + field_2_attributes; + result = prime * result + field_3_color_palette_index; + result = prime * result + field_4_bold_weight; + result = prime * result + field_5_super_sub_script; + result = prime * result + field_6_underline; + result = prime * result + field_7_family; + result = prime * result + field_8_charset; + result = prime * result + field_9_zero; + result = prime * result + field_10_font_name_len; + return result; + } + + /** + * Only returns two for the same exact object - + * creating a second FontRecord with the same + * properties won't be considered equal, as + * the record's position in the record stream + * matters. + */ + public boolean equals(Object obj) { + if (this == obj) + return true; + return false; + } } diff --git a/src/java/org/apache/poi/hssf/record/HyperlinkRecord.java b/src/java/org/apache/poi/hssf/record/HyperlinkRecord.java index 0dcd45a72..798d4e1ff 100644 --- a/src/java/org/apache/poi/hssf/record/HyperlinkRecord.java +++ b/src/java/org/apache/poi/hssf/record/HyperlinkRecord.java @@ -1,29 +1,27 @@ /* ==================================================================== - Copyright 2002-2004 Apache Software Foundation + 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 - Licensed 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 - 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. - ==================================================================== */ + 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; import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; +import java.util.Arrays; import org.apache.poi.util.LittleEndian; import org.apache.poi.util.StringUtil; +import org.apache.poi.util.HexDump; /** * The HyperlinkRecord wraps an HLINK-record @@ -31,146 +29,283 @@ import org.apache.poi.util.StringUtil; * Supports only external links for now (eg http://) * * @author Mark Hissink Muller (in.remaining()/2) ? (in.remaining()/2) : field_7_url_len; - field_12_url = in.readUnicodeLEString(strlen); + } - - /* (non-Javadoc) - * @see org.apache.poi.hssf.record.Record#getSid() - */ + public short getSid() { return HyperlinkRecord.sid; @@ -244,55 +378,75 @@ public class HyperlinkRecord extends Record implements CellValueRecordInterface public int serialize(int offset, byte[] data) { - LittleEndian.putShort(data, 0 + offset, sid); - LittleEndian.putShort(data, 2 + offset, - ( short )(getRecordSize()-4)); - LittleEndian.putShort(data, 4 + offset, field_1_unknown); - LittleEndian.putUShort(data, 6 + offset, field_2_row); - LittleEndian.putShort(data, 8 + offset, field_3_column); - LittleEndian.putShort(data, 10 + offset, field_4_xf_index); - - offset += 12; - for(int i=0; i 0){ + System.arraycopy(tail, 0, data, pos, tail.length); pos += tail.length; + } + } else if (Arrays.equals(FILE_MONIKER, moniker)){ + LittleEndian.putShort(data, pos, file_opts); pos += 2; + LittleEndian.putInt(data, pos, address.length()); pos += 4; + byte[] bytes = address.getBytes(); + System.arraycopy(bytes, 0, data, pos, bytes.length); pos += bytes.length; + if(tail.length > 0){ + System.arraycopy(tail, 0, data, pos, tail.length); pos += tail.length; + } + } + } else if((link_opts & HLINK_PLACE) != 0){ + LittleEndian.putInt(data, pos, address.length()); pos += 4; + StringUtil.putUnicodeLE(address, data, pos); pos += address.length()*2; } - - LittleEndian.putInt(data, offset, field_11_url_opts); - offset += 4; - StringUtil.putUnicodeLE(field_12_url, data, offset); - return getRecordSize(); } public int getRecordSize() { - // We have: - // 4 shorts - // junk - // 3 ints - // label - // junk - // int - // url - return 4 + 4*2 + field_5_unknown.length + - 3*4 + field_9_label.length()*2 + - field_10_unknown.length + 4 + - field_12_url.length()*2; + int size = 4; + size += 2 + 2 + 2 + 2; //rwFirst, rwLast, colFirst, colLast + size += guid.length; + size += 4; //label_opts + size += 4; //link_opts + if ((link_opts & HLINK_LABEL) != 0){ + size += 4; //link length + size += label.length()*2; + } + if ((link_opts & HLINK_URL) != 0){ + size += moniker.length; //moniker length + if(Arrays.equals(URL_MONIKER, moniker)){ + size += 4; //address length + size += address.length()*2; + size += tail.length; + } else if (Arrays.equals(FILE_MONIKER, moniker)){ + size += 2; //file_opts + size += 4; //address length + size += address.length(); + size += tail.length; + } + } else if((link_opts & HLINK_PLACE) != 0){ + size += 4; //address length + size += address.length()*2; + } + return size; } public String toString() @@ -300,71 +454,89 @@ public class HyperlinkRecord extends Record implements CellValueRecordInterface StringBuffer buffer = new StringBuffer(); buffer.append("[HYPERLINK RECORD]\n"); - buffer.append(" .row = ").append(Integer.toHexString(getRow())).append("\n"); - buffer.append(" .column = ").append(Integer.toHexString(getColumn())).append("\n"); - buffer.append(" .xfindex = ").append(Integer.toHexString(getXFIndex())).append("\n"); - buffer.append(" .label = ").append(field_9_label).append("\n"); - buffer.append(" .url = ").append(field_12_url).append("\n"); + buffer.append(" .rwFirst = ").append(Integer.toHexString(getFirstRow())).append("\n"); + buffer.append(" .rwLast = ").append(Integer.toHexString(getLastRow())).append("\n"); + buffer.append(" .colFirst = ").append(Integer.toHexString(getFirstColumn())).append("\n"); + buffer.append(" .colLast = ").append(Integer.toHexString(getLastColumn())).append("\n"); + buffer.append(" .guid = ").append(HexDump.toHex(guid)).append("\n"); + buffer.append(" .label_opts = ").append(label_opts).append("\n"); + buffer.append(" .label = ").append(getLabel()).append("\n"); + if((link_opts & HLINK_URL) != 0){ + buffer.append(" .moniker = ").append(HexDump.toHex(moniker)).append("\n"); + } + buffer.append(" .address = ").append(getAddress()).append("\n"); buffer.append("[/HYPERLINK RECORD]\n"); return buffer.toString(); } /** - * @return Returns the label. + * Initialize a new url link */ - public String getLabel() - { - if(field_9_label.length() == 0) { - return ""; - } else { - // Trim off \0 - return field_9_label.substring(0, field_9_label.length() - 1); - } + public void newUrlLink(){ + rwFirst = 0; + rwLast = 0; + colFirst = 0; + colLast = 0; + guid = STD_MONIKER; + label_opts = 0x2; + link_opts = HLINK_URL | HLINK_ABS | HLINK_LABEL; + label = "" + '\u0000'; + moniker = URL_MONIKER; + address = "" + '\u0000'; + tail = URL_TAIL; } /** - * @param label The label to set. + * Initialize a new file link */ - public void setLabel(String label) - { - this.field_9_label = label + '\u0000'; - this.field_8_label_len = field_9_label.length(); + public void newFileLink(){ + rwFirst = 0; + rwLast = 0; + colFirst = 0; + colLast = 0; + guid = STD_MONIKER; + label_opts = 0x2; + link_opts = HLINK_URL | HLINK_LABEL; + file_opts = 0; + label = "" + '\u0000'; + moniker = FILE_MONIKER; + address = "" + '\0'; + tail = FILE_TAIL; } /** - * @return Returns the Url. + * Initialize a new document link */ - public URL getUrl() throws MalformedURLException - { - return new URL(getUrlString()); - } - public String getUrlString() - { - if(field_12_url.length() == 0) { - return ""; - } else { - // Trim off \0 - return field_12_url.substring(0, field_12_url.length() - 1); - } + public void newDocumentLink(){ + rwFirst = 0; + rwLast = 0; + colFirst = 0; + colLast = 0; + guid = STD_MONIKER; + label_opts = 0x2; + link_opts = HLINK_LABEL | HLINK_PLACE; + label = "" + '\u0000'; + moniker = FILE_MONIKER; + address = "" + '\0'; + tail = new byte[]{}; } - /** - * @param url The url to set. - */ - public void setUrl(URL url) - { - setUrl(url.toString()); - } - /** - * @param url The url to set. - */ - public void setUrl(String url) - { - this.field_12_url = url + '\u0000'; - this.field_7_url_len = field_12_url.length(); + public Object clone() { + HyperlinkRecord rec = new HyperlinkRecord(); + rec.rwFirst = rwFirst; + rec.rwLast = rwLast; + rec.colFirst = colFirst; + rec.colLast = colLast; + rec.guid = guid; + rec.label_opts = label_opts; + rec.link_opts = link_opts; + rec.file_opts = file_opts; + rec.label = label; + rec.address = address; + rec.moniker = moniker; + rec.tail = tail; + return rec; } - public int getOptions(){ - return field_11_url_opts; - } + } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java index 19ead33f0..67f455797 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFCell.java @@ -550,6 +550,13 @@ public class HSSFCell implements Cell /** * set a date value for the cell. Excel treats dates as numeric so you will need to format the cell as * a date. + * + * This will set the cell value based on the Calendar's timezone. As Excel + * does not support timezones this means that both 20:00+03:00 and + * 20:00-03:00 will be reported as the same value (20:00) even that there + * are 6 hours difference between the two times. This difference can be + * preserved by using setCellValue(value.getTime()) which will + * automatically shift the times to the default timezone. * * @param value the date value to set this cell to. For formulas we'll set the * precalculated value, for numerics we'll set its value. For othertypes we @@ -557,7 +564,7 @@ public class HSSFCell implements Cell */ public void setCellValue(Calendar value) { - setCellValue(value.getTime()); + setCellValue( HSSFDateUtil.getExcelDate(value, this.book.isUsing1904DateWindowing()) ); } /** @@ -1071,7 +1078,7 @@ public class HSSFCell implements Cell Record rec = ( Record ) it.next(); if (rec instanceof HyperlinkRecord){ HyperlinkRecord link = (HyperlinkRecord)rec; - if(link.getColumn() == record.getColumn() && link.getRow() == record.getRow()){ + if(link.getFirstColumn() == record.getColumn() && link.getFirstRow() == record.getRow()){ return new HSSFHyperlink(link); } } @@ -1085,6 +1092,25 @@ public class HSSFCell implements Cell * @param link hypelrink associated with this cell */ public void setHyperlink(HSSFHyperlink link){ + link.setFirstRow(record.getRow()); + link.setLastRow(record.getRow()); + link.setFirstColumn(record.getColumn()); + link.setLastColumn(record.getColumn()); + switch(link.getType()){ + case HSSFHyperlink.LINK_EMAIL: + case HSSFHyperlink.LINK_URL: + link.setLabel("url"); + break; + case HSSFHyperlink.LINK_FILE: + link.setLabel("file"); + break; + case HSSFHyperlink.LINK_DOCUMENT: + link.setLabel("place"); + break; + } + + int eofLoc = sheet.findFirstRecordLocBySid( EOFRecord.sid ); + sheet.getRecords().add( eofLoc, link.record ); } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFCellStyle.java b/src/java/org/apache/poi/hssf/usermodel/HSSFCellStyle.java index a0837cdd0..6d066fc8d 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFCellStyle.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFCellStyle.java @@ -914,4 +914,29 @@ public class HSSFCellStyle implements CellStyle { return format.getFillForeground(); } + + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((format == null) ? 0 : format.hashCode()); + result = prime * result + index; + return result; + } + + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (obj instanceof HSSFCellStyle) { + final HSSFCellStyle other = (HSSFCellStyle) obj; + if (format == null) { + if (other.format != null) + return false; + } else if (!format.equals(other.format)) + return false; + if (index != other.index) + return false; + return true; + } + return false; + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFDateUtil.java b/src/java/org/apache/poi/hssf/usermodel/HSSFDateUtil.java index fb3a92df8..0e3d1ee54 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFDateUtil.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFDateUtil.java @@ -70,9 +70,25 @@ public class HSSFDateUtil public static double getExcelDate(Date date, boolean use1904windowing) { Calendar calStart = new GregorianCalendar(); calStart.setTime(date); // If date includes hours, minutes, and seconds, set them to 0 - - if ((!use1904windowing && calStart.get(Calendar.YEAR) < 1900) || - (use1904windowing && calStart.get(Calendar.YEAR) < 1904)) + return internalGetExcelDate(calStart, use1904windowing); + } + /** + * Given a Date in the form of a Calendar, converts it into a double + * representing its internal Excel representation, which is the + * number of days since 1/1/1900. Fractional days represent hours, + * minutes, and seconds. + * + * @return Excel representation of Date (-1 if error - test for error by checking for less than 0.1) + * @param date the Calendar holding the date to convert + * @param use1904windowing Should 1900 or 1904 date windowing be used? + */ + public static double getExcelDate(Calendar date, boolean use1904windowing) { + // Don't alter the supplied Calendar as we do our work + return internalGetExcelDate( (Calendar)date.clone(), use1904windowing ); + } + private static double internalGetExcelDate(Calendar date, boolean use1904windowing) { + if ((!use1904windowing && date.get(Calendar.YEAR) < 1900) || + (use1904windowing && date.get(Calendar.YEAR) < 1904)) { return BAD_DATE; } else { @@ -83,12 +99,12 @@ public class HSSFDateUtil // be 4 hours. // E.g. 2004-03-28 04:00 CEST - 2004-03-28 00:00 CET is 3 hours // and 2004-10-31 04:00 CET - 2004-10-31 00:00 CEST is 5 hours - double fraction = (((calStart.get(Calendar.HOUR_OF_DAY) * 60 - + calStart.get(Calendar.MINUTE) - ) * 60 + calStart.get(Calendar.SECOND) - ) * 1000 + calStart.get(Calendar.MILLISECOND) + double fraction = (((date.get(Calendar.HOUR_OF_DAY) * 60 + + date.get(Calendar.MINUTE) + ) * 60 + date.get(Calendar.SECOND) + ) * 1000 + date.get(Calendar.MILLISECOND) ) / ( double ) DAY_MILLISECONDS; - calStart = dayStart(calStart); + Calendar calStart = dayStart(date); double value = fraction + absoluteDay(calStart, use1904windowing); @@ -208,9 +224,9 @@ public class HSSFDateUtil // who knows what that starting bit is all about fs = fs.replaceAll("\\[\\$\\-.*?\\]", ""); - // Otherwise, check it's only made up of: - // y m d - / , - if(fs.matches("^[ymd\\-/, ]+$")) { + // Otherwise, check it's only made up, in any case, of: + // y m d h s - / , . : + if(fs.matches("^[yYmMdDhHsS\\-/,. :]+$")) { return true; } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFFont.java b/src/java/org/apache/poi/hssf/usermodel/HSSFFont.java index 5c1d4dd8c..2141c74f0 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFFont.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFFont.java @@ -306,5 +306,28 @@ public class HSSFFont implements Font "}"; } + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((font == null) ? 0 : font.hashCode()); + result = prime * result + index; + return result; + } + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (obj instanceof HSSFFont) { + final HSSFFont other = (HSSFFont) obj; + if (font == null) { + if (other.font != null) + return false; + } else if (!font.equals(other.font)) + return false; + if (index != other.index) + return false; + return true; + } + return false; + } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFHyperlink.java b/src/java/org/apache/poi/hssf/usermodel/HSSFHyperlink.java index e1bd28af6..7f1c2639c 100755 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFHyperlink.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFHyperlink.java @@ -27,9 +27,9 @@ import java.util.List; import java.util.Iterator; /** - * Represents a hyperlink. + * Represents an Excel hyperlink. * - * @author Yegor Kozlov + * @author Yegor Kozlov (yegor at apache dot org) */ public class HSSFHyperlink { @@ -49,67 +49,145 @@ public class HSSFHyperlink { public static final int LINK_EMAIL = 3; /** - * Unknown type + * Link to a file */ - public static final int LINK_UNKNOWN = 4; + public static final int LINK_FILE = 4; /** * Low-level record object that stores the actual hyperlink data */ - private HyperlinkRecord record = null; + protected HyperlinkRecord record = null; + /** + * If we create a new hypelrink remember its type + */ + protected int link_type; + + /** + * Construct a new hyperlink + * + * @param type the type of hyperlink to create + */ + public HSSFHyperlink( int type ) + { + this.link_type = type; + record = new HyperlinkRecord(); + switch(type){ + case LINK_URL: + case LINK_EMAIL: + record.newUrlLink(); + break; + case LINK_FILE: + record.newFileLink(); + break; + case LINK_DOCUMENT: + record.newDocumentLink(); + break; + } + } + + /** + * Initialize the hyperlink by a HyperlinkRecord record + * + * @param record + */ protected HSSFHyperlink( HyperlinkRecord record ) { this.record = record; } /** - * Return the row of the cell that contains the hyperlink + * Return the row of the first cell that contains the hyperlink * * @return the 0-based row of the cell that contains the hyperlink */ - public int getRow(){ - return record.getRow(); + public int getFirstRow(){ + return record.getFirstRow(); } /** - * Set the row of the cell that contains the hyperlink + * Set the row of the first cell that contains the hyperlink * - * @param row the 0-based row of the cell that contains the hyperlink + * @param row the 0-based row of the first cell that contains the hyperlink */ - public void setRow(int row){ - record.setRow(row); + public void setFirstRow(int row){ + record.setFirstRow(row); } /** - * Return the column of the cell that contains the hyperlink + * Return the row of the last cell that contains the hyperlink * - * @return the 0-based column of the cell that contains the hyperlink + * @return the 0-based row of the last cell that contains the hyperlink */ - public short getColumn(){ - return record.getColumn(); + public int getLastRow(){ + return record.getLastRow(); } /** - * Set the column of the cell that contains the hyperlink + * Set the row of the last cell that contains the hyperlink * - * @param col the 0-based column of the cell that contains the hyperlink + * @param row the 0-based row of the last cell that contains the hyperlink */ - public void setColumn(short col){ - record.setColumn(col); + public void setLastRow(int row){ + record.setLastRow(row); } /** - * Hypelink address. Depending on the hyperlink type it can be URL, e-mail, etc. + * Return the column of the first cell that contains the hyperlink + * + * @return the 0-based column of the first cell that contains the hyperlink + */ + public short getFirstColumn(){ + return record.getFirstColumn(); + } + + /** + * Set the column of the first cell that contains the hyperlink + * + * @param col the 0-based column of the first cell that contains the hyperlink + */ + public void setFirstColumn(short col){ + record.setFirstColumn(col); + } + + /** + * Return the column of the last cell that contains the hyperlink + * + * @return the 0-based column of the last cell that contains the hyperlink + */ + public short getLastColumn(){ + return record.getLastColumn(); + } + + /** + * Set the column of the last cell that contains the hyperlink + * + * @param col the 0-based column of the last cell that contains the hyperlink + */ + public void setLastColumn(short col){ + record.setLastColumn(col); + } + + /** + * Hypelink address. Depending on the hyperlink type it can be URL, e-mail, patrh to a file, etc. * * @return the address of this hyperlink */ public String getAddress(){ - return record.getUrlString(); + return record.getAddress(); } /** - * Return text to display for this hyperlink + * Hypelink address. Depending on the hyperlink type it can be URL, e-mail, patrh to a file, etc. + * + * @param address the address of this hyperlink + */ + public void setAddress(String address){ + record.setAddress(address); + } + + /** + * Return text label for this hyperlink * * @return text to display */ @@ -117,12 +195,21 @@ public class HSSFHyperlink { return record.getLabel(); } + /** + * Sets text label for this hyperlink + * + * @param label text label for this hyperlink + */ + public void setLabel(String label){ + record.setLabel(label); + } + /** * Return the type of this hyperlink * * @return the type of this hyperlink */ - public int getType(){ - throw new RuntimeException("Not implemented"); + protected int getType(){ + return link_type; } } diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java b/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java index 3ac5ae3a5..0c9807b5a 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFRow.java @@ -471,7 +471,6 @@ public class HSSFRow * @return cell iterator of the physically defined cells. Note element 4 may * actually be row cell depending on how many are defined! */ - public Iterator cellIterator() { return new CellIterator(); @@ -481,7 +480,7 @@ public class HSSFRow * foreach loops */ public Iterator iterator() { - return cellIterator(); + return cellIterator(); } private class CellIterator implements Iterator diff --git a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java index bb1f5794f..0a64cee35 100644 --- a/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java +++ b/src/java/org/apache/poi/hssf/usermodel/HSSFSheet.java @@ -724,8 +724,9 @@ public class HSSFSheet implements org.apache.poi.ss.usermodel.Sheet * foreach loops */ public Iterator iterator() { - return rowIterator(); + return rowIterator(); } + /** * used internally in the API to get the low level Sheet record represented by this diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/data/ProblemExtracting.doc b/src/scratchpad/testcases/org/apache/poi/hwpf/data/ProblemExtracting.doc new file mode 100644 index 0000000000000000000000000000000000000000..b9800894391d6e8d49abf5eac85dc6f8c497e227 GIT binary patch literal 424448 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 DN0;z? literal 0 HcmV?d00001 diff --git a/src/scratchpad/testcases/org/apache/poi/hwpf/extractor/TestWordExtractorBugs.java b/src/scratchpad/testcases/org/apache/poi/hwpf/extractor/TestWordExtractorBugs.java new file mode 100644 index 000000000..b87f586d3 --- /dev/null +++ b/src/scratchpad/testcases/org/apache/poi/hwpf/extractor/TestWordExtractorBugs.java @@ -0,0 +1,45 @@ +/* +* 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.extractor; + +import java.io.FileInputStream; + +import junit.framework.TestCase; + +/** + * Tests for bugs with the WordExtractor + * + * @author Nick Burch (nick at torchbox dot com) + */ +public class TestWordExtractorBugs extends TestCase { + private String dirname; + protected void setUp() throws Exception { + dirname = System.getProperty("HWPF.testdata.path"); + } + + public void testProblemMetadata() throws Exception { + String filename = dirname + "/ProblemExtracting.doc"; + WordExtractor extractor = + new WordExtractor(new FileInputStream(filename)); + + // Check it gives text without error + extractor.getText(); + extractor.getParagraphText(); + extractor.getTextFromPieces(); + } + +} diff --git a/src/testcases/org/apache/poi/hpsf/basic/TestBasic.java b/src/testcases/org/apache/poi/hpsf/basic/TestBasic.java index 521ce819e..1932f0f11 100644 --- a/src/testcases/org/apache/poi/hpsf/basic/TestBasic.java +++ b/src/testcases/org/apache/poi/hpsf/basic/TestBasic.java @@ -20,7 +20,6 @@ package org.apache.poi.hpsf.basic; import java.io.ByteArrayInputStream; import java.io.File; -import java.io.FileFilter; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -227,46 +226,6 @@ public class TestBasic extends TestCase - /** - *

    This test methods reads all property set streams from all POI - * filesystems in the "data" directory.

    - */ - public void testReadAllFiles() - { - final File dataDir = - new File(System.getProperty("HPSF.testdata.path")); - final File[] fileList = dataDir.listFiles(new FileFilter() - { - public boolean accept(final File f) - { - return f.isFile(); - } - }); - try - { - for (int i = 0; i < fileList.length; i++) - { - File f = fileList[i]; - /* Read the POI filesystem's property set streams: */ - final POIFile[] psf1 = Util.readPropertySets(f); - - for (int j = 0; j < psf1.length; j++) - { - final InputStream in = - new ByteArrayInputStream(psf1[j].getBytes()); - PropertySetFactory.create(in); - } - } - } - catch (Throwable t) - { - final String s = org.apache.poi.hpsf.Util.toString(t); - fail(s); - } - } - - - /** *

    Runs the test cases stand-alone.

    * diff --git a/src/testcases/org/apache/poi/hpsf/basic/TestReadAllFiles.java b/src/testcases/org/apache/poi/hpsf/basic/TestReadAllFiles.java new file mode 100644 index 000000000..f68b4c3cc --- /dev/null +++ b/src/testcases/org/apache/poi/hpsf/basic/TestReadAllFiles.java @@ -0,0 +1,110 @@ +/* ==================================================================== + 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.hpsf.basic; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.FileFilter; +import java.io.InputStream; + +import junit.framework.TestCase; + +import org.apache.poi.hpsf.PropertySetFactory; + + + +/** + *

    Tests some HPSF functionality by reading all property sets from all files + * in the "data" directory. If you want to ensure HPSF can deal with a certain + * OLE2 file, just add it to the "data" directory and run this test case.

    + * + * @author Rainer Klute (klute@rainer-klute.de) + * @since 2008-02-08 + * @version $Id: TestBasic.java 489730 2006-12-22 19:18:16Z bayard $ + */ +public class TestReadAllFiles extends TestCase +{ + + /** + *

    Test case constructor.

    + * + * @param name The test case's name. + */ + public TestReadAllFiles(final String name) + { + super(name); + } + + + + /** + *

    This test methods reads all property set streams from all POI + * filesystems in the "data" directory.

    + */ + public void testReadAllFiles() + { + final File dataDir = + new File(System.getProperty("HPSF.testdata.path")); + final File[] fileList = dataDir.listFiles(new FileFilter() + { + public boolean accept(final File f) + { + return f.isFile(); + } + }); + try + { + for (int i = 0; i < fileList.length; i++) + { + final File f = fileList[i]; + /* Read the POI filesystem's property set streams: */ + final POIFile[] psf1 = Util.readPropertySets(f); + + for (int j = 0; j < psf1.length; j++) + { + final InputStream in = + new ByteArrayInputStream(psf1[j].getBytes()); + PropertySetFactory.create(in); + } + } + } + catch (Throwable t) + { + final String s = org.apache.poi.hpsf.Util.toString(t); + fail(s); + } + } + + + + /** + *

    Runs the test cases stand-alone.

    + * + * @param args Command-line arguments (ignored) + * + * @exception Throwable if any sort of exception or error occurs + */ + public static void main(final String[] args) throws Throwable + { + System.setProperty("HPSF.testdata.path", + "./src/testcases/org/apache/poi/hpsf/data"); + junit.textui.TestRunner.run(TestReadAllFiles.class); + } + +} diff --git a/src/testcases/org/apache/poi/hpsf/data/TestBug44375.xls b/src/testcases/org/apache/poi/hpsf/data/TestBug44375.xls new file mode 100644 index 0000000000000000000000000000000000000000..0ebd762934c6e7fcf75bc714de6fc468685b695c GIT binary patch literal 16896 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 literal 0 HcmV?d00001 diff --git a/src/testcases/org/apache/poi/hssf/record/TestHyperlinkRecord.java b/src/testcases/org/apache/poi/hssf/record/TestHyperlinkRecord.java index 70548fe95..3d2ca406c 100644 --- a/src/testcases/org/apache/poi/hssf/record/TestHyperlinkRecord.java +++ b/src/testcases/org/apache/poi/hssf/record/TestHyperlinkRecord.java @@ -18,114 +18,311 @@ package org.apache.poi.hssf.record; import java.io.ByteArrayInputStream; import java.net.URL; +import java.util.Arrays; import junit.framework.TestCase; +/** + * Test HyperlinkRecord + * + * @author Nick Burch + * @author Yegor Kozlov + */ public class TestHyperlinkRecord extends TestCase { - protected void setUp() throws Exception { - super.setUp(); - } - private byte[] data = new byte[] { - -72, 1, 110, 0, - // ??, Row, col, xf - 6, 0, 3, 0, 2, 0, 2, 0, - - // ?? - -48, -55, -22, 121, -7, -70, -50, 17, - -116, -126, 0, -86, 0, 75, -87, 11, - 2, 0, 0, 0, - - // URL length - 23, 0, 0, 0, - - // Label length - 4, 0, 0, 0, - - // Label - 76, 0, 44, 0, 65, 0, 0, 0, - - // ?? - -32, -55, -22, 121, -7, -70, -50, 17, - -116, -126, 0, -86, 0, 75, -87, 11, - 46, 0, 0, 0, - - // URL - 104, 0, 116, 0, 116, 0, 112, 0, 58, 0, 47, 0, 47, 0, 119, - 0, 119, 0, 119, 0, 46, 0, 108, 0, 97, 0, 107, 0, 105, - 0, 110, 0, 103, 0, 115, 0, 46, 0, 99, 0, 111, 0, - 109, 0, - 0, 0 }; - - private byte[] data2 = new byte[] { - -72, 1, -126, 0, - // ??, Row, col, xf - 2, 0, 2, 0, 4, 0, 4, 0, + //link to http://www.lakings.com/ + byte[] data1 = { 0x02, 0x00, //First row of the hyperlink + 0x02, 0x00, //Last row of the hyperlink + 0x00, 0x00, //First column of the hyperlink + 0x00, 0x00, //Last column of the hyperlink - // ?? - -48, -55, -22, 121, -7, -70, -50, 17, - -116, -126, 0, -86, 0, 75, -87, 11, - 2, 0, 0, 0, - - // URL and Label lengths - 23, 0, 0, 0, - 15, 0, 0, 0, + //16-byte GUID. Seems to be always the same. Does not depend on the hyperlink type + (byte)0xD0, (byte)0xC9, (byte)0xEA, 0x79, (byte)0xF9, (byte)0xBA, (byte)0xCE, 0x11, + (byte)0x8C, (byte)0x82, 0x00, (byte)0xAA, 0x00, 0x4B, (byte)0xA9, 0x0B, - // Label - 83, 0, 116, 0, 97, 0, 99, 0, 105, 0, - 101, 0, 64, 0, 65, 0, 66, 0, 67, 0, - 46, 0, 99, 0, 111, 0, 109, 0, 0, 0, + 0x02, 0x00, 0x00, 0x00, //integer, always 2 - // ?? - -32, -55, -22, 121, -7, -70, -50, 17, - -116, -126, 0, -86, 0, 75, -87, 11, - 44, 0, 0, 0, + // flags. Define the type of the hyperlink: + // HyperlinkRecord.HLINK_URL | HyperlinkRecord.HLINK_ABS | HyperlinkRecord.HLINK_LABEL + 0x17, 0x00, 0x00, 0x00, - // URL - 109, 0, 97, 0, 105, 0, 108, 0, 116, 0, - 111, 0, 58, 0, 83, 0, 116, 0, 97, 0, - 99, 0, 105, 0, 101, 0, 64, 0, 65, 0, - 66, 0, 67, 0, 46, 0, 99, 0, 111, 0, - 109, 0, 0, 0 }; + 0x08, 0x00, 0x00, 0x00, //length of the label including the trailing '\0' - public void testRecordParsing() throws Exception { - RecordInputStream inp = new RecordInputStream( - new ByteArrayInputStream(data) - ); - inp.nextRecord(); + //label: + 0x4D, 0x00, 0x79, 0x00, 0x20, 0x00, 0x4C, 0x00, 0x69, 0x00, 0x6E, 0x00, 0x6B, 0x00, 0x00, 0x00, - HyperlinkRecord r = new HyperlinkRecord(inp); - - assertEquals(3, r.getRow()); - assertEquals(2, r.getColumn()); - assertEquals(2, r.getXFIndex()); - - assertEquals("L,A", r.getLabel()); - assertEquals("http://www.lakings.com", r.getUrlString()); - assertEquals(new URL("http://www.lakings.com"), r.getUrl()); - - // Check it serialises as expected - assertEquals(data.length, r.getRecordSize()); - byte[] d = r.serialize(); - assertEquals(data.length, d.length); - for(int i=0; i diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.java index 80785ca18..6c604d1b4 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFCell.java @@ -320,8 +320,8 @@ extends TestCase { assertEquals("Foo", link.getLabel()); assertEquals("http://poi.apache.org/", link.getAddress()); - assertEquals(4, link.getRow()); - assertEquals(0, link.getColumn()); + assertEquals(4, link.getFirstRow()); + assertEquals(0, link.getFirstColumn()); } /** @@ -339,16 +339,16 @@ extends TestCase { assertNotNull(link1); assertEquals("Foo", link1.getLabel()); assertEquals("http://poi.apache.org/", link1.getAddress()); - assertEquals(4, link1.getRow()); - assertEquals(0, link1.getColumn()); + assertEquals(4, link1.getFirstRow()); + assertEquals(0, link1.getFirstColumn()); HSSFCell cell2 = sheet.getRow(8).getCell((short)1); HSSFHyperlink link2 = cell2.getHyperlink(); assertNotNull(link2); assertEquals("Bar", link2.getLabel()); - assertEquals("http://poi.apache.org/", link2.getAddress()); - assertEquals(8, link2.getRow()); - assertEquals(1, link2.getColumn()); + assertEquals("http://poi.apache.org/hssf/", link2.getAddress()); + assertEquals(8, link2.getFirstRow()); + assertEquals(1, link2.getFirstColumn()); } diff --git a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java index 6b37f749e..38078d9df 100644 --- a/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java +++ b/src/testcases/org/apache/poi/hssf/usermodel/TestHSSFDateUtil.java @@ -196,6 +196,29 @@ public class TestHSSFDateUtil } } + /** + * Tests that we deal with timezones properly + */ + public void testCalendarConversion() { + GregorianCalendar date = new GregorianCalendar(2002, 0, 1, 12, 1, 1); + Date expected = date.getTime(); + double expectedExcel = HSSFDateUtil.getExcelDate(expected); + + // Iteratating over the hours exposes any rounding issues. + for (int hour = -12; hour <= 12; hour++) + { + String id = "GMT" + (hour < 0 ? "" : "+") + hour + ":00"; + date.setTimeZone(TimeZone.getTimeZone(id)); + date.set(Calendar.HOUR_OF_DAY, 12); + double excelDate = HSSFDateUtil.getExcelDate(date, false); + Date javaDate = HSSFDateUtil.getJavaDate(excelDate); + + // Should match despite timezone + assertEquals("Checking timezone " + id, expected.getTime(), javaDate.getTime()); + } + } + + /** * Tests that we correctly detect date formats as such */ @@ -228,6 +251,7 @@ public class TestHSSFDateUtil "yyyy-mm-dd", "yyyy/mm/dd", "yy/mm/dd", "yy/mmm/dd", "dd/mm/yy", "dd/mm/yyyy", "dd/mmm/yy", "dd-mm-yy", "dd-mm-yyyy", + "DD-MM-YY", "DD-mm-YYYY", "dd\\-mm\\-yy", // Sometimes escaped // These crazy ones are valid @@ -242,9 +266,18 @@ public class TestHSSFDateUtil assertTrue( HSSFDateUtil.isADateFormat(formatId, formats[i]) ); } + // Then time based ones too + formats = new String[] { + "yyyy-mm-dd hh:mm:ss", "yyyy/mm/dd HH:MM:SS", + "mm/dd HH:MM", "yy/mmm/dd SS", + }; + for(int i=0; i