From 73bd034c79196d325f9b16716ff634a2430748da Mon Sep 17 00:00:00 2001 From: Nick Burch Date: Sun, 30 Nov 2014 00:16:23 +0000 Subject: [PATCH] Start on a Text Extractor for the pre-OLE2 Excel formats like Excel 4, for TIKA-1490 git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1642490 13f79535-47bb-0310-9956-ffa450edef68 --- maven/mvn-deploy.sh | 2 +- .../poi/hssf/extractor/OldExcelExtractor.java | 97 ++++++++++ .../apache/poi/hssf/record/LabelRecord.java | 4 +- .../poi/hssf/record/OldLabelRecord.java | 168 ++++++++++++++++++ .../apache/poi/hssf/dev/TestBiffViewer.java | 1 + .../hssf/extractor/TestOldExcelExtractor.java | 52 ++++++ test-data/spreadsheet/testEXCEL_4.xls | Bin 0 -> 39942 bytes 7 files changed, 320 insertions(+), 4 deletions(-) create mode 100644 src/java/org/apache/poi/hssf/extractor/OldExcelExtractor.java create mode 100644 src/java/org/apache/poi/hssf/record/OldLabelRecord.java create mode 100644 src/testcases/org/apache/poi/hssf/extractor/TestOldExcelExtractor.java create mode 100644 test-data/spreadsheet/testEXCEL_4.xls diff --git a/maven/mvn-deploy.sh b/maven/mvn-deploy.sh index 2d609e491f..9d1c18a37d 100755 --- a/maven/mvn-deploy.sh +++ b/maven/mvn-deploy.sh @@ -39,7 +39,7 @@ # 2. cd build/dist # 3. ./mvn-deploy.sh -M2_REPOSITORY=M2_REPOSITORY=https://repository.apache.org/service/local/staging/deploy/maven2 +M2_REPOSITORY=https://repository.apache.org/service/local/staging/deploy/maven2 VERSION=@VERSION@ DSTAMP=@DSTAMP@ diff --git a/src/java/org/apache/poi/hssf/extractor/OldExcelExtractor.java b/src/java/org/apache/poi/hssf/extractor/OldExcelExtractor.java new file mode 100644 index 0000000000..3c2d067282 --- /dev/null +++ b/src/java/org/apache/poi/hssf/extractor/OldExcelExtractor.java @@ -0,0 +1,97 @@ +/* ==================================================================== + 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.extractor; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.poi.hssf.record.LabelRecord; +import org.apache.poi.hssf.record.OldLabelRecord; +import org.apache.poi.hssf.record.RecordInputStream; + +/** + * A text extractor for very old (pre-OLE2) Excel files, + * such as Excel 4 files. + *

+ * Returns much (but not all) of the textual content of the file, + * suitable for indexing by something like Apache Lucene, or used + * by Apache Tika, but not really intended for display to the user. + *

+ */ +public class OldExcelExtractor { + private InputStream input; + private boolean _includeSheetNames = true; + + public OldExcelExtractor(InputStream input) { + this.input = input; + } + public OldExcelExtractor(File f) throws IOException { + this.input = new FileInputStream(f); + } + + public static void main(String[] args) throws Exception { + if (args.length < 1) { + System.err.println("Use:"); + System.err.println(" OldExcelExtractor "); + System.exit(1); + } + OldExcelExtractor extractor = new OldExcelExtractor(new File(args[0])); + System.out.println(extractor.getText()); + } + + /** + * Should sheet names be included? Default is true + */ + public void setIncludeSheetNames(boolean includeSheetNames) { + _includeSheetNames = includeSheetNames; + } + + /** + * Retrieves the text contents of the file, as best we can + * for these old file formats + */ + public String getText() { + StringBuffer text = new StringBuffer(); + + RecordInputStream ris = new RecordInputStream(input); + while (ris.hasNextRecord()) { + int sid = ris.getNextSid(); + ris.nextRecord(); + + switch (sid) { + case LabelRecord.sid: + OldLabelRecord lr = new OldLabelRecord(ris); + text.append(lr.getValue()); + text.append('\n'); + break; + default: + ris.readFully(new byte[ris.remaining()]); + } + + // label - 5.63 - TODO Needs codepages + // number - 5.71 + // rk - 5.87 + // string - 5.102 + + } + + return text.toString(); + } +} diff --git a/src/java/org/apache/poi/hssf/record/LabelRecord.java b/src/java/org/apache/poi/hssf/record/LabelRecord.java index 4d2570272b..c7a585672d 100644 --- a/src/java/org/apache/poi/hssf/record/LabelRecord.java +++ b/src/java/org/apache/poi/hssf/record/LabelRecord.java @@ -25,9 +25,7 @@ import org.apache.poi.util.POILogger; * Label Record (0x0204) - read only support for strings stored directly in the cell.. Don't * use this (except to read), use LabelSST instead

* REFERENCE: PG 325 Microsoft Excel 97 Developer's Kit (ISBN: 1-57231-498-2)

- * @author Andrew C. Oliver (acoliver at apache dot org) - * @author Jason Height (jheight at chariot dot net dot au) - * @version 2.0-pre + * * @see org.apache.poi.hssf.record.LabelSSTRecord */ public final class LabelRecord extends Record implements CellValueRecordInterface { diff --git a/src/java/org/apache/poi/hssf/record/OldLabelRecord.java b/src/java/org/apache/poi/hssf/record/OldLabelRecord.java new file mode 100644 index 0000000000..0fcd1cb4c6 --- /dev/null +++ b/src/java/org/apache/poi/hssf/record/OldLabelRecord.java @@ -0,0 +1,168 @@ +/* ==================================================================== + 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; + +import org.apache.poi.util.HexDump; +import org.apache.poi.util.POILogFactory; +import org.apache.poi.util.POILogger; + +/** + * Biff2 - Biff 4 Label Record (0x0004 / 0x0204) - read only support for + * strings stored directly in the cell, from the older file formats that + * didn't use {@link LabelSSTRecord} + */ +public final class OldLabelRecord extends Record implements CellValueRecordInterface { + private final static POILogger logger = POILogFactory.getLogger(OldLabelRecord.class); + + public final static short biff2_sid = 0x0004; + public final static short biff345_sid = 0x0204; + + private short sid; + private int field_1_row; + private short field_2_column; + private int field_3_cell_attrs; // Biff 2 + private short field_3_xf_index; // Biff 3+ + private short field_4_string_len; + private byte[] field_5_bytes; + //private XXXXX codepage; // TODO + + /** + * @param in the RecordInputstream to read the record from + */ + public OldLabelRecord(RecordInputStream in) + { + sid = in.getSid(); + + field_1_row = in.readUShort(); + field_2_column = in.readShort(); + + if (in.getSid() == biff2_sid) { + field_3_cell_attrs = in.readUShort() << 8; + field_3_cell_attrs += in.readUByte(); + field_4_string_len = (short)in.readUByte(); + } else { + field_3_xf_index = in.readShort(); + field_4_string_len = in.readShort(); + } + + // Can only decode properly later when you know the codepage + field_5_bytes = new byte[field_4_string_len]; + in.read(field_5_bytes, 0, field_4_string_len); + + if (in.remaining() > 0) { + logger.log(POILogger.INFO, + "LabelRecord data remains: " + in.remaining() + + " : " + HexDump.toHex(in.readRemainder()) + ); + } + } + + public boolean isBiff2() { + return sid == biff2_sid; + } + + public int getRow() + { + return field_1_row; + } + + public short getColumn() + { + return field_2_column; + } + + public short getXFIndex() + { + return field_3_xf_index; + } + public int getCellAttrs() + { + return field_3_cell_attrs; + } + + /** + * get the number of characters this string contains + * @return number of characters + */ + public short getStringLength() + { + return field_4_string_len; + } + + /** + * Get the String of the cell + */ + public String getValue() + { + // We really need the codepage here to do this right... + return new String(field_5_bytes); + } + + /** + * Not supported + */ + public int serialize(int offset, byte [] data) { + throw new RecordFormatException("Old Label Records are supported READ ONLY"); + } + public int getRecordSize() { + throw new RecordFormatException("Old Label Records are supported READ ONLY"); + } + + public short getSid() + { + return sid; + } + + public String toString() + { + StringBuffer sb = new StringBuffer(); + sb.append("[OLD LABEL]\n"); + sb.append(" .row = ").append(HexDump.shortToHex(getRow())).append("\n"); + sb.append(" .column = ").append(HexDump.shortToHex(getColumn())).append("\n"); + if (isBiff2()) { + sb.append(" .cellattrs = ").append(HexDump.shortToHex(getCellAttrs())).append("\n"); + } else { + sb.append(" .xfindex = ").append(HexDump.shortToHex(getXFIndex())).append("\n"); + } + sb.append(" .string_len= ").append(HexDump.shortToHex(field_4_string_len)).append("\n"); + sb.append(" .value = ").append(getValue()).append("\n"); + sb.append("[/OLD LABEL]\n"); + return sb.toString(); + } + + /** + * NO-OP! + */ + public void setColumn(short col) + { + } + + /** + * NO-OP! + */ + public void setRow(int row) + { + } + + /** + * no op! + */ + public void setXFIndex(short xf) + { + } +} diff --git a/src/testcases/org/apache/poi/hssf/dev/TestBiffViewer.java b/src/testcases/org/apache/poi/hssf/dev/TestBiffViewer.java index ff8e18937e..b7013c1503 100644 --- a/src/testcases/org/apache/poi/hssf/dev/TestBiffViewer.java +++ b/src/testcases/org/apache/poi/hssf/dev/TestBiffViewer.java @@ -38,6 +38,7 @@ public class TestBiffViewer extends BaseXLSIteratingTest { SILENT_EXCLUDED.add("46904.xls"); SILENT_EXCLUDED.add("35897-type4.xls"); // unsupported crypto api header SILENT_EXCLUDED.add("xor-encryption-abc.xls"); // unsupported XOR-encryption + SILENT_EXCLUDED.add("testEXCEL_4.xls"); // Biff 4 / Excel 4, pre-OLE2 } @Override diff --git a/src/testcases/org/apache/poi/hssf/extractor/TestOldExcelExtractor.java b/src/testcases/org/apache/poi/hssf/extractor/TestOldExcelExtractor.java new file mode 100644 index 0000000000..a5c7dbedc2 --- /dev/null +++ b/src/testcases/org/apache/poi/hssf/extractor/TestOldExcelExtractor.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.extractor; + +import java.io.InputStream; + +import junit.framework.TestCase; + +import org.apache.poi.hssf.HSSFTestDataSamples; + +/** + * Unit tests for the Excel 4 (and older) text extractor + */ +public final class TestOldExcelExtractor extends TestCase { + private static OldExcelExtractor createExtractor(String sampleFileName) { + InputStream is = HSSFTestDataSamples.openSampleFileStream(sampleFileName); + + try { + return new OldExcelExtractor(is); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public void testSimple() { + OldExcelExtractor extractor = createExtractor("testEXCEL_4.xls"); + + // Check we can call getText without error + String text = extractor.getText(); + + // Check we find a few words we expect in there + assertTrue(text, text.contains("Size")); + assertTrue(text, text.contains("Returns")); + } + + // TODO Rest of the tests +} diff --git a/test-data/spreadsheet/testEXCEL_4.xls b/test-data/spreadsheet/testEXCEL_4.xls new file mode 100644 index 0000000000000000000000000000000000000000..02f58f73bc14af14155af34ca1bac31a26b06852 GIT binary patch literal 39942 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 IA65B(0O2coEC2ui literal 0 HcmV?d00001 -- 2.39.5