From 27b4bdc3f7834983e017ce765bd3a753b0462dfc Mon Sep 17 00:00:00 2001 From: Andreas Beeker Date: Mon, 15 Nov 2021 00:03:44 +0000 Subject: [PATCH] Move org.apache.poi.hssf.dev classes from poi:main to poi:test Use CloseShieldOutputStream instead of CloseIgnoringInputStream git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1895041 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/poi/stress/HSSFFileHandler.java | 7 +- .../crypt/temp/AesZipFileZipEntrySource.java | 29 +- .../apache/poi/hssf/dev/BiffDrawingToXml.java | 162 ---- .../org/apache/poi/hssf/dev/BiffViewer.java | 713 ------------------ .../org/apache/poi/hssf/dev/EFBiffViewer.java | 74 -- .../apache/poi/hssf/dev/FormulaViewer.java | 200 ----- .../java/org/apache/poi/hssf/dev/ReSave.java | 71 -- .../org/apache/poi/hssf/dev/RecordLister.java | 193 ----- .../poi/hssf/record/RecordInputStream.java | 12 +- .../poi/util/CloseIgnoringInputStream.java | 38 - poi/src/main/java9/module-info.class | Bin 3421 -> 3385 bytes poi/src/main/java9/module-info.java | 1 - .../poi/hssf/dev/BiffDumpingStream.java | 121 +++ .../org/apache/poi/hssf/dev/BiffViewer.java | 342 +++++++++ .../poi/hssf/dev/TestBiffDrawingToXml.java | 77 +- .../apache/poi/hssf/dev/TestBiffViewer.java | 24 +- .../apache/poi/hssf/dev/TestEFBiffViewer.java | 32 +- .../poi/hssf/dev/TestFormulaViewer.java | 132 +++- .../org/apache/poi/hssf/dev/TestReSave.java | 94 --- .../apache/poi/hssf/dev/TestRecordLister.java | 142 +++- .../org/apache/poi/hssf/dev/package-info.java | 0 21 files changed, 799 insertions(+), 1665 deletions(-) delete mode 100644 poi/src/main/java/org/apache/poi/hssf/dev/BiffDrawingToXml.java delete mode 100644 poi/src/main/java/org/apache/poi/hssf/dev/BiffViewer.java delete mode 100644 poi/src/main/java/org/apache/poi/hssf/dev/EFBiffViewer.java delete mode 100644 poi/src/main/java/org/apache/poi/hssf/dev/FormulaViewer.java delete mode 100644 poi/src/main/java/org/apache/poi/hssf/dev/ReSave.java delete mode 100644 poi/src/main/java/org/apache/poi/hssf/dev/RecordLister.java delete mode 100644 poi/src/main/java/org/apache/poi/util/CloseIgnoringInputStream.java create mode 100644 poi/src/test/java/org/apache/poi/hssf/dev/BiffDumpingStream.java create mode 100644 poi/src/test/java/org/apache/poi/hssf/dev/BiffViewer.java delete mode 100644 poi/src/test/java/org/apache/poi/hssf/dev/TestReSave.java rename poi/src/{main => test}/java/org/apache/poi/hssf/dev/package-info.java (100%) diff --git a/poi-integration/src/test/java/org/apache/poi/stress/HSSFFileHandler.java b/poi-integration/src/test/java/org/apache/poi/stress/HSSFFileHandler.java index 62e0d7cbc3..b009d2ea0b 100644 --- a/poi-integration/src/test/java/org/apache/poi/stress/HSSFFileHandler.java +++ b/poi-integration/src/test/java/org/apache/poi/stress/HSSFFileHandler.java @@ -33,7 +33,6 @@ import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.ss.usermodel.Cell; import org.apache.poi.ss.usermodel.Row; import org.apache.poi.ss.usermodel.Sheet; -import org.apache.commons.io.output.NullPrintStream; import org.junit.jupiter.api.Test; public class HSSFFileHandler extends SpreadsheetHandler { @@ -88,10 +87,8 @@ public class HSSFFileHandler extends SpreadsheetHandler { PrintStream oldOut = System.out; String fileWithParent = file.getParentFile().getName() + "/" + file.getName(); try { - System.setOut(new NullPrintStream()); - - BiffViewer.main(new String[]{file.getAbsolutePath()}); - + BiffViewer bv = new BiffViewer(); + bv.parse(file, null); assertFalse( EXPECTED_ADDITIONAL_FAILURES.contains(fileWithParent), "Expected Extraction to fail for file " + file + " and handler " + this + ", but did not fail!" ); } catch (OldExcelFormatException e) { // old excel formats are not supported here diff --git a/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/temp/AesZipFileZipEntrySource.java b/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/temp/AesZipFileZipEntrySource.java index f66808354a..0e06557812 100644 --- a/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/temp/AesZipFileZipEntrySource.java +++ b/poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/temp/AesZipFileZipEntrySource.java @@ -21,7 +21,6 @@ package org.apache.poi.poifs.crypt.temp; import java.io.File; import java.io.FileOutputStream; -import java.io.FilterOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Enumeration; @@ -35,6 +34,7 @@ import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream; import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream; import org.apache.commons.compress.archivers.zip.ZipFile; +import org.apache.commons.io.output.CloseShieldOutputStream; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.poi.openxml4j.util.ZipEntrySource; @@ -47,7 +47,7 @@ import org.apache.poi.util.RandomSingleton; import org.apache.poi.util.TempFile; /** - * An example ZipEntrySource that has encrypted temp files to ensure that + * An example {@code ZipEntrySource} that has encrypted temp files to ensure that * sensitive data is not stored in raw format on disk. */ @Beta @@ -60,14 +60,14 @@ public final class AesZipFileZipEntrySource implements ZipEntrySource { private final ZipFile zipFile; private final Cipher ci; private boolean closed; - + private AesZipFileZipEntrySource(File tmpFile, Cipher ci) throws IOException { this.tmpFile = tmpFile; this.zipFile = new ZipFile(tmpFile); this.ci = ci; this.closed = false; } - + /** * Note: the file sizes are rounded up to the next cipher block size, * so don't rely on file sizes of these custom encrypted zip file entries! @@ -87,7 +87,7 @@ public final class AesZipFileZipEntrySource implements ZipEntrySource { InputStream is = zipFile.getInputStream(entry); return new CipherInputStream(is, ci); } - + @Override public void close() throws IOException { if(!closed) { @@ -118,7 +118,7 @@ public final class AesZipFileZipEntrySource implements ZipEntrySource { private static void copyToFile(InputStream is, File tmpFile, byte[] keyBytes, byte[] ivBytes) throws IOException { SecretKeySpec skeySpec = new SecretKeySpec(keyBytes, CipherAlgorithm.aes128.jceId); Cipher ciEnc = CryptoFunctions.getCipher(skeySpec, CipherAlgorithm.aes128, ChainingMode.cbc, ivBytes, Cipher.ENCRYPT_MODE, PADDING); - + try (ZipArchiveInputStream zis = new ZipArchiveInputStream(is); FileOutputStream fos = new FileOutputStream(tmpFile); ZipArchiveOutputStream zos = new ZipArchiveOutputStream(fos)) { @@ -133,16 +133,11 @@ public final class AesZipFileZipEntrySource implements ZipEntrySource { zeNew.setTime(ze.getTime()); // zeNew.setMethod(ze.getMethod()); zos.putArchiveEntry(zeNew); - FilterOutputStream fos2 = new FilterOutputStream(zos) { - // don't close underlying ZipOutputStream - @Override - public void close() { - } - }; - CipherOutputStream cos = new CipherOutputStream(fos2, ciEnc); - IOUtils.copy(zis, cos); - cos.close(); - fos2.close(); + + // don't close underlying ZipOutputStream + try (CipherOutputStream cos = new CipherOutputStream(CloseShieldOutputStream.wrap(zos), ciEnc)) { + IOUtils.copy(zis, cos); + } zos.closeArchiveEntry(); } } @@ -153,5 +148,5 @@ public final class AesZipFileZipEntrySource implements ZipEntrySource { Cipher ciDec = CryptoFunctions.getCipher(skeySpec, CipherAlgorithm.aes128, ChainingMode.cbc, ivBytes, Cipher.DECRYPT_MODE, PADDING); return new AesZipFileZipEntrySource(tmpFile, ciDec); } - + } \ No newline at end of file diff --git a/poi/src/main/java/org/apache/poi/hssf/dev/BiffDrawingToXml.java b/poi/src/main/java/org/apache/poi/hssf/dev/BiffDrawingToXml.java deleted file mode 100644 index 3fc3f5d3be..0000000000 --- a/poi/src/main/java/org/apache/poi/hssf/dev/BiffDrawingToXml.java +++ /dev/null @@ -1,162 +0,0 @@ -/* - * ==================================================================== - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ==================================================================== - */ - -package org.apache.poi.hssf.dev; - -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.List; - -import org.apache.poi.ddf.EscherRecord; -import org.apache.poi.hssf.model.InternalWorkbook; -import org.apache.poi.hssf.record.DrawingGroupRecord; -import org.apache.poi.hssf.usermodel.HSSFPatriarch; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.util.StringUtil; - -/** - * Utility for representing drawings contained in a binary Excel file as a XML tree - */ -public class BiffDrawingToXml { - private static final String SHEET_NAME_PARAM = "-sheet-name"; - private static final String SHEET_INDEXES_PARAM = "-sheet-indexes"; - private static final String EXCLUDE_WORKBOOK_RECORDS = "-exclude-workbook"; - - private static int getAttributeIndex(String attribute, String[] params) { - for (int i = 0; i < params.length; i++) { - String param = params[i]; - if (attribute.equals(param)) { - return i; - } - } - return -1; - } - - private static boolean isExcludeWorkbookRecords(String[] params) { - return -1 != getAttributeIndex(EXCLUDE_WORKBOOK_RECORDS, params); - } - - private static List getIndexesByName(String[] params, HSSFWorkbook workbook) { - List list = new ArrayList<>(); - int pos = getAttributeIndex(SHEET_NAME_PARAM, params); - if (-1 != pos) { - if (pos >= params.length) { - throw new IllegalArgumentException("sheet name param value was not specified"); - } - String sheetName = params[pos + 1]; - int sheetPos = workbook.getSheetIndex(sheetName); - if (-1 == sheetPos){ - throw new IllegalArgumentException("specified sheet name has not been found in xls file"); - } - list.add(sheetPos); - } - return list; - } - - private static List getIndexesByIdArray(String[] params) { - List list = new ArrayList<>(); - int pos = getAttributeIndex(SHEET_INDEXES_PARAM, params); - if (-1 != pos) { - if (pos >= params.length) { - throw new IllegalArgumentException("sheet list value was not specified"); - } - String sheetParam = params[pos + 1]; - String[] sheets = sheetParam.split(","); - for (String sheet : sheets) { - list.add(Integer.parseInt(sheet)); - } - } - return list; - } - - private static List getSheetsIndexes(String[] params, HSSFWorkbook workbook) { - List list = new ArrayList<>(); - list.addAll(getIndexesByIdArray(params)); - list.addAll(getIndexesByName(params, workbook)); - if (0 == list.size()) { - int size = workbook.getNumberOfSheets(); - for (int i = 0; i < size; i++) { - list.add(i); - } - } - return list; - } - - private static String getInputFileName(String[] params) { - return params[params.length - 1]; - } - - private static String getOutputFileName(String input) { - if (input.contains("xls")) { - return input.replace(".xls", ".xml"); - } - return input + ".xml"; - } - - public static void main(String[] params) throws IOException { - if (0 == params.length) { - System.out.println("Usage: BiffDrawingToXml [options] inputWorkbook"); - System.out.println("Options:"); - System.out.println(" -exclude-workbook exclude workbook-level records"); - System.out.println(" -sheet-indexes output sheets with specified indexes"); - System.out.println(" -sheet-namek output sheets with specified name"); - return; - } - String input = getInputFileName(params); - String output = getOutputFileName(input); - try (FileInputStream inp = new FileInputStream(input); - FileOutputStream outputStream = new FileOutputStream(output)) { - writeToFile(outputStream, inp, isExcludeWorkbookRecords(params), params); - } - } - - public static void writeToFile(OutputStream fos, InputStream xlsWorkbook, boolean excludeWorkbookRecords, String[] params) throws IOException { - try (HSSFWorkbook workbook = new HSSFWorkbook(xlsWorkbook)) { - InternalWorkbook internalWorkbook = workbook.getInternalWorkbook(); - DrawingGroupRecord r = (DrawingGroupRecord) internalWorkbook.findFirstRecordBySid(DrawingGroupRecord.sid); - - StringBuilder builder = new StringBuilder(); - builder.append("\n"); - String tab = "\t"; - if (!excludeWorkbookRecords && r != null) { - r.decode(); - List escherRecords = r.getEscherRecords(); - for (EscherRecord record : escherRecords) { - builder.append(record.toXml(tab)); - } - } - List sheets = getSheetsIndexes(params, workbook); - for (Integer i : sheets) { - HSSFPatriarch p = workbook.getSheetAt(i).getDrawingPatriarch(); - if (p != null) { - builder.append(tab).append("\n"); - builder.append(p.getBoundAggregate().toXml(tab + "\t")); - builder.append(tab).append("\n"); - } - } - builder.append("\n"); - fos.write(builder.toString().getBytes(StringUtil.UTF8)); - } - } - -} diff --git a/poi/src/main/java/org/apache/poi/hssf/dev/BiffViewer.java b/poi/src/main/java/org/apache/poi/hssf/dev/BiffViewer.java deleted file mode 100644 index 006194d0be..0000000000 --- a/poi/src/main/java/org/apache/poi/hssf/dev/BiffViewer.java +++ /dev/null @@ -1,713 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hssf.dev; - -import java.io.DataInputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.Writer; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.List; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.poi.hssf.record.*; -import org.apache.poi.hssf.record.Record; -import org.apache.poi.hssf.record.RecordInputStream.LeftoverDataException; -import org.apache.poi.hssf.record.chart.*; -import org.apache.poi.hssf.record.pivottable.DataItemRecord; -import org.apache.poi.hssf.record.pivottable.ExtendedPivotTableViewFieldsRecord; -import org.apache.poi.hssf.record.pivottable.PageItemRecord; -import org.apache.poi.hssf.record.pivottable.StreamIDRecord; -import org.apache.poi.hssf.record.pivottable.ViewDefinitionRecord; -import org.apache.poi.hssf.record.pivottable.ViewFieldsRecord; -import org.apache.poi.hssf.record.pivottable.ViewSourceRecord; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; -import org.apache.poi.poifs.filesystem.POIFSFileSystem; -import org.apache.poi.util.HexDump; -import org.apache.poi.util.IOUtils; -import org.apache.poi.util.LittleEndian; -import org.apache.poi.util.RecordFormatException; -import org.apache.poi.util.StringUtil; -import org.apache.poi.util.SuppressForbidden; - -import static org.apache.logging.log4j.util.Unbox.box; - -/** - * Utility for reading in BIFF8 records and displaying data from them. - * @see #main - */ -public final class BiffViewer { - private static final char[] NEW_LINE_CHARS = System.getProperty("line.separator").toCharArray(); - private static final Logger LOG = LogManager.getLogger(BiffViewer.class); - - private BiffViewer() { - // no instances of this class - } - - /** - * Create an array of records from an input stream - * - * @param is the InputStream from which the records will be obtained - * @param ps the PrintWriter to output the record data - * @param recListener the record listener to notify about read records - * @param dumpInterpretedRecords if {@code true}, the read records will be written to the PrintWriter - * - * @exception RecordFormatException on error processing the InputStream - */ - private static void createRecords(InputStream is, PrintWriter ps, BiffRecordListener recListener, boolean dumpInterpretedRecords) - throws RecordFormatException { - RecordInputStream recStream = new RecordInputStream(is); - while (true) { - boolean hasNext; - try { - hasNext = recStream.hasNextRecord(); - } catch (LeftoverDataException e) { - LOG.atError().withThrowable(e).log("Discarding {} bytes and continuing", box(recStream.remaining())); - recStream.readRemainder(); - hasNext = recStream.hasNextRecord(); - } - if (!hasNext) { - break; - } - recStream.nextRecord(); - if (recStream.getSid() == 0) { - continue; - } - Record record; - if (dumpInterpretedRecords) { - record = createRecord (recStream); - if (record.getSid() == ContinueRecord.sid) { - continue; - } - - for (String header : recListener.getRecentHeaders()) { - ps.println(header); - } - ps.print(record); - } else { - recStream.readRemainder(); - } - ps.println(); - } - } - - - /** - * Essentially a duplicate of RecordFactory. Kept separate as not to screw - * up non-debug operations. - * - */ - private static Record createRecord(RecordInputStream in) { - switch (in.getSid()) { - case AreaFormatRecord.sid: return new AreaFormatRecord(in); - case AreaRecord.sid: return new AreaRecord(in); - case ArrayRecord.sid: return new ArrayRecord(in); - case AxisLineFormatRecord.sid: return new AxisLineFormatRecord(in); - case AxisOptionsRecord.sid: return new AxisOptionsRecord(in); - case AxisParentRecord.sid: return new AxisParentRecord(in); - case AxisRecord.sid: return new AxisRecord(in); - case AxisUsedRecord.sid: return new AxisUsedRecord(in); - case AutoFilterInfoRecord.sid: return new AutoFilterInfoRecord(in); - case BOFRecord.sid: return new BOFRecord(in); - case BackupRecord.sid: return new BackupRecord(in); - case BarRecord.sid: return new BarRecord(in); - case BeginRecord.sid: return new BeginRecord(in); - case BlankRecord.sid: return new BlankRecord(in); - case BookBoolRecord.sid: return new BookBoolRecord(in); - case BoolErrRecord.sid: return new BoolErrRecord(in); - case BottomMarginRecord.sid: return new BottomMarginRecord(in); - case BoundSheetRecord.sid: return new BoundSheetRecord(in); - case CFHeaderRecord.sid: return new CFHeaderRecord(in); - case CFHeader12Record.sid: return new CFHeader12Record(in); - case CFRuleRecord.sid: return new CFRuleRecord(in); - case CFRule12Record.sid: return new CFRule12Record(in); - // TODO Add CF Ex, and remove from UnknownRecord - case CalcCountRecord.sid: return new CalcCountRecord(in); - case CalcModeRecord.sid: return new CalcModeRecord(in); - case CategorySeriesAxisRecord.sid:return new CategorySeriesAxisRecord(in); - case ChartFormatRecord.sid: return new ChartFormatRecord(in); - case ChartRecord.sid: return new ChartRecord(in); - case CodepageRecord.sid: return new CodepageRecord(in); - case ColumnInfoRecord.sid: return new ColumnInfoRecord(in); - case ContinueRecord.sid: return new ContinueRecord(in); - case CountryRecord.sid: return new CountryRecord(in); - case DBCellRecord.sid: return new DBCellRecord(in); - case DSFRecord.sid: return new DSFRecord(in); - case DatRecord.sid: return new DatRecord(in); - case DataFormatRecord.sid: return new DataFormatRecord(in); - case DateWindow1904Record.sid: return new DateWindow1904Record(in); - case DConRefRecord.sid: return new DConRefRecord(in); - case DefaultColWidthRecord.sid: return new DefaultColWidthRecord(in); - case DefaultDataLabelTextPropertiesRecord.sid: return new DefaultDataLabelTextPropertiesRecord(in); - case DefaultRowHeightRecord.sid: return new DefaultRowHeightRecord(in); - case DeltaRecord.sid: return new DeltaRecord(in); - case DimensionsRecord.sid: return new DimensionsRecord(in); - case DrawingGroupRecord.sid: return new DrawingGroupRecord(in); - case DrawingRecordForBiffViewer.sid: return new DrawingRecordForBiffViewer(in); - case DrawingSelectionRecord.sid: return new DrawingSelectionRecord(in); - case DVRecord.sid: return new DVRecord(in); - case DVALRecord.sid: return new DVALRecord(in); - case EOFRecord.sid: return new EOFRecord(in); - case EndRecord.sid: return new EndRecord(in); - case ExtSSTRecord.sid: return new ExtSSTRecord(in); - case ExtendedFormatRecord.sid: return new ExtendedFormatRecord(in); - case ExternSheetRecord.sid: return new ExternSheetRecord(in); - case ExternalNameRecord.sid: return new ExternalNameRecord(in); - case FeatRecord.sid: return new FeatRecord(in); - case FeatHdrRecord.sid: return new FeatHdrRecord(in); - case FilePassRecord.sid: return new FilePassRecord(in); - case FileSharingRecord.sid: return new FileSharingRecord(in); - case FnGroupCountRecord.sid: return new FnGroupCountRecord(in); - case FontBasisRecord.sid: return new FontBasisRecord(in); - case FontIndexRecord.sid: return new FontIndexRecord(in); - case FontRecord.sid: return new FontRecord(in); - case FooterRecord.sid: return new FooterRecord(in); - case FormatRecord.sid: return new FormatRecord(in); - case FormulaRecord.sid: return new FormulaRecord(in); - case FrameRecord.sid: return new FrameRecord(in); - case GridsetRecord.sid: return new GridsetRecord(in); - case GutsRecord.sid: return new GutsRecord(in); - case HCenterRecord.sid: return new HCenterRecord(in); - case HeaderRecord.sid: return new HeaderRecord(in); - case HideObjRecord.sid: return new HideObjRecord(in); - case HorizontalPageBreakRecord.sid: return new HorizontalPageBreakRecord(in); - case HyperlinkRecord.sid: return new HyperlinkRecord(in); - case IndexRecord.sid: return new IndexRecord(in); - case InterfaceEndRecord.sid: return InterfaceEndRecord.create(in); - case InterfaceHdrRecord.sid: return new InterfaceHdrRecord(in); - case IterationRecord.sid: return new IterationRecord(in); - case LabelRecord.sid: return new LabelRecord(in); - case LabelSSTRecord.sid: return new LabelSSTRecord(in); - case LeftMarginRecord.sid: return new LeftMarginRecord(in); - case LegendRecord.sid: return new LegendRecord(in); - case LineFormatRecord.sid: return new LineFormatRecord(in); - case LinkedDataRecord.sid: return new LinkedDataRecord(in); - case MMSRecord.sid: return new MMSRecord(in); - case MergeCellsRecord.sid: return new MergeCellsRecord(in); - case MulBlankRecord.sid: return new MulBlankRecord(in); - case MulRKRecord.sid: return new MulRKRecord(in); - case NameRecord.sid: return new NameRecord(in); - case NameCommentRecord.sid: return new NameCommentRecord(in); - case NoteRecord.sid: return new NoteRecord(in); - case NumberRecord.sid: return new NumberRecord(in); - case ObjRecord.sid: return new ObjRecord(in); - case ObjectLinkRecord.sid: return new ObjectLinkRecord(in); - case PaletteRecord.sid: return new PaletteRecord(in); - case PaneRecord.sid: return new PaneRecord(in); - case PasswordRecord.sid: return new PasswordRecord(in); - case PasswordRev4Record.sid: return new PasswordRev4Record(in); - case PlotAreaRecord.sid: return new PlotAreaRecord(in); - case PlotGrowthRecord.sid: return new PlotGrowthRecord(in); - case PrecisionRecord.sid: return new PrecisionRecord(in); - case PrintGridlinesRecord.sid: return new PrintGridlinesRecord(in); - case PrintHeadersRecord.sid: return new PrintHeadersRecord(in); - case PrintSetupRecord.sid: return new PrintSetupRecord(in); - case ProtectRecord.sid: return new ProtectRecord(in); - case ProtectionRev4Record.sid: return new ProtectionRev4Record(in); - case RKRecord.sid: return new RKRecord(in); - case RecalcIdRecord.sid: return new RecalcIdRecord(in); - case RefModeRecord.sid: return new RefModeRecord(in); - case RefreshAllRecord.sid: return new RefreshAllRecord(in); - case RightMarginRecord.sid: return new RightMarginRecord(in); - case RowRecord.sid: return new RowRecord(in); - case SCLRecord.sid: return new SCLRecord(in); - case SSTRecord.sid: return new SSTRecord(in); - case SaveRecalcRecord.sid: return new SaveRecalcRecord(in); - case SelectionRecord.sid: return new SelectionRecord(in); - case SeriesIndexRecord.sid: return new SeriesIndexRecord(in); - case SeriesListRecord.sid: return new SeriesListRecord(in); - case SeriesRecord.sid: return new SeriesRecord(in); - case SeriesTextRecord.sid: return new SeriesTextRecord(in); - case SeriesChartGroupIndexRecord.sid:return new SeriesChartGroupIndexRecord(in); - case SharedFormulaRecord.sid: return new SharedFormulaRecord(in); - case SheetPropertiesRecord.sid: return new SheetPropertiesRecord(in); - case StringRecord.sid: return new StringRecord(in); - case StyleRecord.sid: return new StyleRecord(in); - case SupBookRecord.sid: return new SupBookRecord(in); - case TabIdRecord.sid: return new TabIdRecord(in); - case TableStylesRecord.sid: return new TableStylesRecord(in); - case TableRecord.sid: return new TableRecord(in); - case TextObjectRecord.sid: return new TextObjectRecord(in); - case TextRecord.sid: return new TextRecord(in); - case TickRecord.sid: return new TickRecord(in); - case TopMarginRecord.sid: return new TopMarginRecord(in); - case UncalcedRecord.sid: return new UncalcedRecord(in); - case UnitsRecord.sid: return new UnitsRecord(in); - case UseSelFSRecord.sid: return new UseSelFSRecord(in); - case VCenterRecord.sid: return new VCenterRecord(in); - case ValueRangeRecord.sid: return new ValueRangeRecord(in); - case VerticalPageBreakRecord.sid: return new VerticalPageBreakRecord(in); - case WSBoolRecord.sid: return new WSBoolRecord(in); - case WindowOneRecord.sid: return new WindowOneRecord(in); - case WindowProtectRecord.sid: return new WindowProtectRecord(in); - case WindowTwoRecord.sid: return new WindowTwoRecord(in); - case WriteAccessRecord.sid: return new WriteAccessRecord(in); - case WriteProtectRecord.sid: return new WriteProtectRecord(in); - - // chart - case CatLabRecord.sid: return new CatLabRecord(in); - case ChartEndBlockRecord.sid: return new ChartEndBlockRecord(in); - case ChartEndObjectRecord.sid: return new ChartEndObjectRecord(in); - case ChartFRTInfoRecord.sid: return new ChartFRTInfoRecord(in); - case ChartStartBlockRecord.sid: return new ChartStartBlockRecord(in); - case ChartStartObjectRecord.sid: return new ChartStartObjectRecord(in); - - // pivot table - case StreamIDRecord.sid: return new StreamIDRecord(in); - case ViewSourceRecord.sid: return new ViewSourceRecord(in); - case PageItemRecord.sid: return new PageItemRecord(in); - case ViewDefinitionRecord.sid: return new ViewDefinitionRecord(in); - case ViewFieldsRecord.sid: return new ViewFieldsRecord(in); - case DataItemRecord.sid: return new DataItemRecord(in); - case ExtendedPivotTableViewFieldsRecord.sid: return new ExtendedPivotTableViewFieldsRecord(in); - } - return new UnknownRecord(in); - } - - private static final class CommandArgs { - - private final boolean _biffhex; - private final boolean _noint; - private final boolean _out; - private final boolean _rawhex; - private final boolean _noHeader; - private final File _file; - - private CommandArgs(boolean biffhex, boolean noint, boolean out, boolean rawhex, boolean noHeader, File file) { - _biffhex = biffhex; - _noint = noint; - _out = out; - _rawhex = rawhex; - _file = file; - _noHeader = noHeader; - } - - public static CommandArgs parse(String[] args) throws CommandParseException { - int nArgs = args.length; - boolean biffhex = false; - boolean noint = false; - boolean out = false; - boolean rawhex = false; - boolean noheader = false; - File file = null; - for (int i=0; i - * - * Usage:

- * - * BiffViewer [--biffhex] [--noint] [--noescher] [--out] <fileName>

- * BiffViewer --rawhex [--out] <fileName> - * - * - * - * - * - * - * - * - * - *
BiffViewer options
--biffhexshow hex dump of each BIFF record
--nointdo not output interpretation of BIFF records
--outsend output to <fileName>.out
--rawhexoutput raw hex dump of whole workbook stream
--escherturn on deserialization of escher records (default is off)
--noheaderdo not print record header (default is on)
- * - * @param args the command line arguments - * - * @throws IOException if the file doesn't exist or contained errors - * @throws CommandParseException if the command line contained errors - */ - public static void main(String[] args) throws IOException, CommandParseException { - // args = new String[] { "--out", "", }; - CommandArgs cmdArgs = CommandArgs.parse(args); - - try (POIFSFileSystem fs = new POIFSFileSystem(cmdArgs.getFile(), true); - InputStream is = getPOIFSInputStream(fs); - PrintWriter pw = getOutputStream(cmdArgs.shouldOutputToFile() ? cmdArgs.getFile().getAbsolutePath() : null) - ) { - if (cmdArgs.shouldOutputRawHexOnly()) { - byte[] data = IOUtils.toByteArray(is); - HexDump.dump(data, 0, System.out, 0); - } else { - boolean dumpInterpretedRecords = cmdArgs.shouldDumpRecordInterpretations(); - boolean dumpHex = cmdArgs.shouldDumpBiffHex(); - runBiffViewer(pw, is, dumpInterpretedRecords, dumpHex, dumpInterpretedRecords, - cmdArgs.suppressHeader()); - } - } - } - - static PrintWriter getOutputStream(String outputPath) throws FileNotFoundException { - // Use the system default encoding when sending to System Out - OutputStream os = System.out; - Charset cs = Charset.defaultCharset(); - if (outputPath != null) { - cs = StringUtil.UTF8; - os = new FileOutputStream(outputPath + ".out"); - } - return new PrintWriter(new OutputStreamWriter(os, cs)); - } - - - static InputStream getPOIFSInputStream(POIFSFileSystem fs) throws IOException { - String workbookName = HSSFWorkbook.getWorkbookDirEntryName(fs.getRoot()); - return fs.createDocumentInputStream(workbookName); - } - - static void runBiffViewer(PrintWriter pw, InputStream is, - boolean dumpInterpretedRecords, boolean dumpHex, boolean zeroAlignHexDump, - boolean suppressHeader) { - BiffRecordListener recListener = new BiffRecordListener(dumpHex ? pw : null, zeroAlignHexDump, suppressHeader); - is = new BiffDumpingStream(is, recListener); - createRecords(is, pw, recListener, dumpInterpretedRecords); - } - - private static final class BiffRecordListener implements IBiffRecordListener { - private final Writer _hexDumpWriter; - private List _headers; - private final boolean _zeroAlignEachRecord; - private final boolean _noHeader; - private BiffRecordListener(Writer hexDumpWriter, boolean zeroAlignEachRecord, boolean noHeader) { - _hexDumpWriter = hexDumpWriter; - _zeroAlignEachRecord = zeroAlignEachRecord; - _noHeader = noHeader; - _headers = new ArrayList<>(); - } - - @Override - public void processRecord(int globalOffset, int recordCounter, int sid, int dataSize, - byte[] data) { - String header = formatRecordDetails(globalOffset, sid, dataSize, recordCounter); - if(!_noHeader) { - _headers.add(header); - } - Writer w = _hexDumpWriter; - if (w != null) { - try { - w.write(header); - w.write(NEW_LINE_CHARS); - hexDumpAligned(w, data, dataSize+4, globalOffset, _zeroAlignEachRecord); - w.flush(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - } - private List getRecentHeaders() { - List result = _headers; - _headers = new ArrayList<>(); - return result; - } - private static String formatRecordDetails(int globalOffset, int sid, int size, int recordCounter) { - return "Offset=" + HexDump.intToHex(globalOffset) + "(" + globalOffset + ")" + - " recno=" + recordCounter + - " sid=" + HexDump.shortToHex(sid) + - " size=" + HexDump.shortToHex(size) + "(" + size + ")"; - } - } - - private interface IBiffRecordListener { - - void processRecord(int globalOffset, int recordCounter, int sid, int dataSize, byte[] data); - - } - - /** - * Wraps a plain {@link InputStream} and allows BIFF record information to be tapped off - * - */ - private static final class BiffDumpingStream extends InputStream { - private final DataInputStream _is; - private final IBiffRecordListener _listener; - private final byte[] _data; - private int _recordCounter; - private int _overallStreamPos; - private int _currentPos; - private int _currentSize; - private boolean _innerHasReachedEOF; - - private BiffDumpingStream(InputStream is, IBiffRecordListener listener) { - _is = new DataInputStream(is); - _listener = listener; - _data = new byte[RecordInputStream.MAX_RECORD_DATA_SIZE + 4]; - _recordCounter = 0; - _overallStreamPos = 0; - _currentSize = 0; - _currentPos = 0; - } - - @Override - public int read() throws IOException { - if (_currentPos >= _currentSize) { - fillNextBuffer(); - } - if (_currentPos >= _currentSize) { - return -1; - } - int result = _data[_currentPos] & 0x00FF; - _currentPos ++; - _overallStreamPos ++; - formatBufferIfAtEndOfRec(); - return result; - } - @Override - public int read(byte[] b, int off, int len) throws IOException { - if (b == null || off < 0 || len < 0 || b.length < off+len) { - throw new IllegalArgumentException(); - } - if (_currentPos >= _currentSize) { - fillNextBuffer(); - } - if (_currentPos >= _currentSize) { - return -1; - } - final int result = Math.min(len, _currentSize - _currentPos); - System.arraycopy(_data, _currentPos, b, off, result); - _currentPos += result; - _overallStreamPos += result; - formatBufferIfAtEndOfRec(); - return result; - } - - @Override - @SuppressForbidden("just delegating the call") - public int available() throws IOException { - return _currentSize - _currentPos + _is.available(); - } - private void fillNextBuffer() throws IOException { - if (_innerHasReachedEOF) { - return; - } - int b0 = _is.read(); - if (b0 == -1) { - _innerHasReachedEOF = true; - return; - } - _data[0] = (byte) b0; - _is.readFully(_data, 1, 3); - int len = LittleEndian.getShort(_data, 2); - _is.readFully(_data, 4, len); - _currentPos = 0; - _currentSize = len + 4; - _recordCounter++; - } - private void formatBufferIfAtEndOfRec() { - if (_currentPos != _currentSize) { - return; - } - int dataSize = _currentSize-4; - int sid = LittleEndian.getShort(_data, 0); - int globalOffset = _overallStreamPos-_currentSize; - _listener.processRecord(globalOffset, _recordCounter, sid, dataSize, _data); - } - @Override - public void close() throws IOException { - _is.close(); - } - } - - private static final int DUMP_LINE_LEN = 16; - private static final char[] COLUMN_SEPARATOR = " | ".toCharArray(); - /** - * Hex-dumps a portion of a byte array in typical format, also preserving dump-line alignment - * @param globalOffset (somewhat arbitrary) used to calculate the addresses printed at the - * start of each line - */ - private static void hexDumpAligned(Writer w, byte[] data, int dumpLen, int globalOffset, - boolean zeroAlignEachRecord) { - int baseDataOffset = 0; - - // perhaps this code should be moved to HexDump - int globalStart = globalOffset + baseDataOffset; - int globalEnd = globalOffset + baseDataOffset + dumpLen; - int startDelta = globalStart % DUMP_LINE_LEN; - int endDelta = globalEnd % DUMP_LINE_LEN; - if (zeroAlignEachRecord) { - endDelta -= startDelta; - if (endDelta < 0) { - endDelta += DUMP_LINE_LEN; - } - startDelta = 0; - } - int startLineAddr; - int endLineAddr; - if (zeroAlignEachRecord) { - endLineAddr = globalEnd - endDelta - (globalStart - startDelta); - startLineAddr = 0; - } else { - startLineAddr = globalStart - startDelta; - endLineAddr = globalEnd - endDelta; - } - - int lineDataOffset = baseDataOffset - startDelta; - int lineAddr = startLineAddr; - - // output (possibly incomplete) first line - if (startLineAddr == endLineAddr) { - hexDumpLine(w, data, lineAddr, lineDataOffset, startDelta, endDelta); - return; - } - hexDumpLine(w, data, lineAddr, lineDataOffset, startDelta, DUMP_LINE_LEN); - - // output all full lines in the middle - while (true) { - lineAddr += DUMP_LINE_LEN; - lineDataOffset += DUMP_LINE_LEN; - if (lineAddr >= endLineAddr) { - break; - } - hexDumpLine(w, data, lineAddr, lineDataOffset, 0, DUMP_LINE_LEN); - } - - - // output (possibly incomplete) last line - if (endDelta != 0) { - hexDumpLine(w, data, lineAddr, lineDataOffset, 0, endDelta); - } - } - - private static void hexDumpLine(Writer w, byte[] data, int lineStartAddress, int lineDataOffset, int startDelta, int endDelta) { - final char[] buf = new char[8+2*COLUMN_SEPARATOR.length+DUMP_LINE_LEN*3-1+DUMP_LINE_LEN+NEW_LINE_CHARS.length]; - - if (startDelta >= endDelta) { - throw new IllegalArgumentException("Bad start/end delta"); - } - int idx=0; - try { - writeHex(buf, idx, lineStartAddress, 8); - idx = arraycopy(COLUMN_SEPARATOR, buf, idx+8); - // raw hex data - for (int i=0; i< DUMP_LINE_LEN; i++) { - if (i>0) { - buf[idx++] = ' '; - } - if (i >= startDelta && i < endDelta) { - writeHex(buf, idx, data[lineDataOffset+i], 2); - } else { - buf[idx] = ' '; - buf[idx+1] = ' '; - } - idx += 2; - } - idx = arraycopy(COLUMN_SEPARATOR, buf, idx); - - // interpreted ascii - for (int i=0; i< DUMP_LINE_LEN; i++) { - char ch = ' '; - if (i >= startDelta && i < endDelta) { - ch = getPrintableChar(data[lineDataOffset+i]); - } - buf[idx++] = ch; - } - - idx = arraycopy(NEW_LINE_CHARS, buf, idx); - - w.write(buf, 0, idx); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - private static int arraycopy(char[] in, char[] out, int pos) { - int idx = pos; - for (char c : in) { - out[idx++] = c; - } - return idx; - } - - private static char getPrintableChar(byte b) { - char ib = (char) (b & 0x00FF); - if (ib < 32 || ib > 126) { - return '.'; - } - return ib; - } - - private static void writeHex(char[] buf, int startInBuf, int value, int nDigits) { - int acc = value; - for(int i=nDigits-1; i>=0; i--) { - int digit = acc & 0x0F; - buf[startInBuf+i] = (char) (digit < 10 ? ('0' + digit) : ('A' + digit - 10)); - acc >>>= 4; - } - } -} diff --git a/poi/src/main/java/org/apache/poi/hssf/dev/EFBiffViewer.java b/poi/src/main/java/org/apache/poi/hssf/dev/EFBiffViewer.java deleted file mode 100644 index 8fe58a11c4..0000000000 --- a/poi/src/main/java/org/apache/poi/hssf/dev/EFBiffViewer.java +++ /dev/null @@ -1,74 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hssf.dev; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; - -import org.apache.poi.hssf.eventusermodel.HSSFEventFactory; -import org.apache.poi.hssf.eventusermodel.HSSFRequest; -import org.apache.poi.poifs.filesystem.POIFSFileSystem; - -public class EFBiffViewer -{ - String file; - - /** Creates a new instance of EFBiffViewer */ - - @SuppressWarnings("WeakerAccess") - public EFBiffViewer() { - } - - public void run() throws IOException { - try (POIFSFileSystem fs = new POIFSFileSystem(new File(file), true); - InputStream din = BiffViewer.getPOIFSInputStream(fs)) { - HSSFRequest req = new HSSFRequest(); - - req.addListenerForAllRecords(System.out::println); - HSSFEventFactory factory = new HSSFEventFactory(); - - factory.processEvents(req, din); - } - } - - public void setFile(String file) - { - this.file = file; - } - - public static void main(String [] args) throws IOException - { - if ((args.length == 1) && !args[ 0 ].equals("--help")) - { - EFBiffViewer viewer = new EFBiffViewer(); - - viewer.setFile(args[ 0 ]); - viewer.run(); - } - else - { - System.out.println("EFBiffViewer"); - System.out.println( - "Outputs biffview of records based on HSSFEventFactory"); - System.out - .println("usage: java org.apache.poi.hssf.dev.EBBiffViewer " - + "filename"); - } - } -} diff --git a/poi/src/main/java/org/apache/poi/hssf/dev/FormulaViewer.java b/poi/src/main/java/org/apache/poi/hssf/dev/FormulaViewer.java deleted file mode 100644 index 20aa00f63b..0000000000 --- a/poi/src/main/java/org/apache/poi/hssf/dev/FormulaViewer.java +++ /dev/null @@ -1,200 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hssf.dev; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; - -import org.apache.poi.hssf.model.HSSFFormulaParser; -import org.apache.poi.hssf.record.FormulaRecord; -import org.apache.poi.hssf.record.RecordFactory; -import org.apache.poi.poifs.filesystem.POIFSFileSystem; -import org.apache.poi.ss.formula.ptg.ExpPtg; -import org.apache.poi.ss.formula.ptg.FuncPtg; -import org.apache.poi.ss.formula.ptg.Ptg; - -/** - * FormulaViewer - finds formulas in a BIFF8 file and attempts to read them/display - * data from them. Only works if Formulas are enabled in "RecordFactory" - */ -public class FormulaViewer -{ - private String file; - private boolean list; - - /** Creates new FormulaViewer */ - - public FormulaViewer() - { - } - - /** - * Method run - * - * @throws IOException if the file contained errors - */ - public void run() throws IOException { - try (POIFSFileSystem fs = new POIFSFileSystem(new File(file), true)) { - try (InputStream is = BiffViewer.getPOIFSInputStream(fs)) { - List records = RecordFactory.createRecords(is); - - for (org.apache.poi.hssf.record.Record record : records) { - if (record.getSid() == FormulaRecord.sid) { - if (list) { - listFormula((FormulaRecord) record); - } else { - parseFormulaRecord((FormulaRecord) record); - } - } - } - } - } - } - - private void listFormula(FormulaRecord record) { - Ptg[] tokens= record.getParsedExpression(); - int numptgs = tokens.length; - final Ptg lastToken = tokens[numptgs-1]; - - if (lastToken instanceof ExpPtg) return; - - String buf = String.join("~", - lastToken.toFormulaString(), - mapToken(lastToken), - (numptgs > 1 ? mapToken(tokens[numptgs - 2]) : "VALUE"), - String.valueOf(lastToken instanceof FuncPtg ? numptgs-1 : -1) - ); - - System.out.println(buf); - } - - private static String mapToken(Ptg token) { - switch (token.getPtgClass()) { - case Ptg.CLASS_REF : - return "REF"; - case Ptg.CLASS_VALUE : - return "VALUE"; - case Ptg.CLASS_ARRAY : - return "ARRAY"; - default: - throwInvalidRVAToken(token); - return ""; - } - } - - /** - * Method parseFormulaRecord - * - * @param record the record to be parsed - */ - public void parseFormulaRecord(FormulaRecord record) - { - System.out.println("=============================="); - System.out.print("row = " + record.getRow()); - System.out.println(", col = " + record.getColumn()); - System.out.println("value = " + record.getValue()); - System.out.print("xf = " + record.getXFIndex()); - System.out.print(", number of ptgs = " - + record.getParsedExpression().length); - System.out.println(", options = " + record.getOptions()); - System.out.println("RPN List = "+formulaString(record)); - System.out.println("Formula text = "+ composeFormula(record)); - } - - private String formulaString(FormulaRecord record) { - - StringBuilder buf = new StringBuilder(); - Ptg[] tokens = record.getParsedExpression(); - for (Ptg token : tokens) { - buf.append( token.toFormulaString()); - switch (token.getPtgClass()) { - case Ptg.CLASS_REF : - buf.append("(R)"); - break; - case Ptg.CLASS_VALUE : - buf.append("(V)"); - break; - case Ptg.CLASS_ARRAY : - buf.append("(A)"); - break; - default: - throwInvalidRVAToken(token); - } - buf.append(' '); - } - return buf.toString(); - } - - private static void throwInvalidRVAToken(Ptg token) { - throw new IllegalStateException("Invalid RVA type (" + token.getPtgClass() + "). This should never happen."); - } - - - private static String composeFormula(FormulaRecord record) - { - return HSSFFormulaParser.toFormulaString(null, record.getParsedExpression()); - } - - /** - * Method setFile - * - * @param file the file to process - */ - - public void setFile(String file) - { - this.file = file; - } - - public void setList(boolean list) { - this.list=list; - } - - /** - * Method main - * - * pass me a filename and I'll try and parse the formulas from it - * - * @param args pass one argument with the filename or --help - * @throws IOException if the file can't be read or contained errors - */ - public static void main(String[] args) throws IOException - { - if ((args == null) || (args.length >2 ) - || args[ 0 ].equals("--help")) - { - System.out.println( - "FormulaViewer .8 proof that the devil lies in the details (or just in BIFF8 files in general)"); - System.out.println("usage: Give me a big fat file name"); - } else if (args[0].equals("--listFunctions")) { // undocumented attribute to research functions!~ - FormulaViewer viewer = new FormulaViewer(); - viewer.setFile(args[1]); - viewer.setList(true); - viewer.run(); - } - else - { - FormulaViewer viewer = new FormulaViewer(); - - viewer.setFile(args[ 0 ]); - viewer.run(); - } - } -} diff --git a/poi/src/main/java/org/apache/poi/hssf/dev/ReSave.java b/poi/src/main/java/org/apache/poi/hssf/dev/ReSave.java deleted file mode 100644 index 2254ed58fe..0000000000 --- a/poi/src/main/java/org/apache/poi/hssf/dev/ReSave.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.apache.poi.hssf.dev; - -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.OutputStream; - -import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream; -import org.apache.poi.hssf.usermodel.HSSFSheet; -import org.apache.poi.hssf.usermodel.HSSFWorkbook; - -/** - * Utility to test that POI produces readable output - * after re-saving xls files. - * - * Usage: ReSave [-dg] input.xls - * -dg initialize drawings, causes to re-build escher aggregates in all sheets - * -bos only write to memory instead of a file - */ -public class ReSave { - public static void main(String[] args) throws Exception { - boolean initDrawing = false; - boolean saveToMemory = false; - for(String filename : args) { - if(filename.equals("-dg")) { - initDrawing = true; - } else if(filename.equals("-bos")) { - saveToMemory = true; - } else { - System.out.print("reading " + filename + "..."); - try (FileInputStream is = new FileInputStream(filename); - HSSFWorkbook wb = new HSSFWorkbook(is)) { - System.out.println("done"); - - for(int i = 0; i < wb.getNumberOfSheets(); i++){ - HSSFSheet sheet = wb.getSheetAt(i); - if(initDrawing) { - /*HSSFPatriarch dg =*/ sheet.getDrawingPatriarch(); - } - } - - String outputFile = filename.replace(".xls", "-saved.xls"); - if (!saveToMemory) { - System.out.print("saving to " + outputFile + "..."); - } - - try (OutputStream os = saveToMemory ? new UnsynchronizedByteArrayOutputStream() : new FileOutputStream(outputFile)) { - wb.write(os); - } - System.out.println("done"); - } - } - } - } -} diff --git a/poi/src/main/java/org/apache/poi/hssf/dev/RecordLister.java b/poi/src/main/java/org/apache/poi/hssf/dev/RecordLister.java deleted file mode 100644 index fd6c8ef111..0000000000 --- a/poi/src/main/java/org/apache/poi/hssf/dev/RecordLister.java +++ /dev/null @@ -1,193 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.hssf.dev; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; - -import org.apache.poi.hssf.record.ContinueRecord; -import org.apache.poi.hssf.record.Record; -import org.apache.poi.hssf.record.RecordFactory; -import org.apache.poi.hssf.record.RecordInputStream; -import org.apache.poi.poifs.filesystem.POIFSFileSystem; - -/** - * This is a low-level debugging class, which simply prints - * out what records come in what order. - * Most people will want to use {@link BiffViewer} or - * {@link EFBiffViewer}, but this can be handy when - * trying to make sense of {@link ContinueRecord} - * special cases. - * - * Output is of the form: - * SID - Length - Type (if known) - * byte0 byte1 byte2 byte3 .... byte(n-4) byte(n-3) byte(n-2) byte(n-1) - */ -public class RecordLister -{ - String file; - - public RecordLister() - { - } - - public void run() throws IOException { - try (POIFSFileSystem fs = new POIFSFileSystem(new File(file), true); - InputStream din = BiffViewer.getPOIFSInputStream(fs)) { - RecordInputStream rinp = new RecordInputStream(din); - - while (rinp.hasNextRecord()) { - int sid = rinp.getNextSid(); - rinp.nextRecord(); - - int size = rinp.available(); - Class clz = RecordFactory.getRecordClass(sid); - - System.out.print( - formatSID(sid) + - " - " + - formatSize(size) + - " bytes" - ); - if (clz != null) { - System.out.print(" \t"); - System.out.print(clz.getName().replace("org.apache.poi.hssf.record.", "")); - } - System.out.println(); - - byte[] data = rinp.readRemainder(); - if (data.length > 0) { - System.out.print(" "); - System.out.println(formatData(data)); - } - } - } - } - - private static String formatSID(int sid) { - String hex = Integer.toHexString(sid); - String dec = Integer.toString(sid); - - StringBuilder s = new StringBuilder(); - s.append("0x"); - for(int i=hex.length(); i<4; i++) { - s.append('0'); - } - s.append(hex); - - s.append(" ("); - for(int i=dec.length(); i<4; i++) { - s.append('0'); - } - s.append(dec); - s.append(")"); - - return s.toString(); - } - private static String formatSize(int size) { - String hex = Integer.toHexString(size); - String dec = Integer.toString(size); - - StringBuilder s = new StringBuilder(); - for(int i=hex.length(); i<3; i++) { - s.append('0'); - } - s.append(hex); - - s.append(" ("); - for(int i=dec.length(); i<3; i++) { - s.append('0'); - } - s.append(dec); - s.append(")"); - - return s.toString(); - } - private static String formatData(byte[] data) { - if(data == null || data.length == 0) - return ""; - - // If possible, do first 4 and last 4 bytes - StringBuilder s = new StringBuilder(); - if(data.length > 9) { - s.append(byteToHex(data[0])); - s.append(' '); - s.append(byteToHex(data[1])); - s.append(' '); - s.append(byteToHex(data[2])); - s.append(' '); - s.append(byteToHex(data[3])); - s.append(' '); - - s.append(" .... "); - - s.append(' '); - s.append(byteToHex(data[data.length-4])); - s.append(' '); - s.append(byteToHex(data[data.length-3])); - s.append(' '); - s.append(byteToHex(data[data.length-2])); - s.append(' '); - s.append(byteToHex(data[data.length-1])); - } else { - for (byte aData : data) { - s.append(byteToHex(aData)); - s.append(' '); - } - } - - return s.toString(); - } - private static String byteToHex(byte b) { - int i = b; - if(i<0) { - i += 256; - } - String s = Integer.toHexString(i); - if(i < 16) { - return "0" + s; - } - return s; - } - - public void setFile(String file) - { - this.file = file; - } - - public static void main(String [] args) throws IOException - { - if ((args.length == 1) && !args[ 0 ].equals("--help")) - { - RecordLister viewer = new RecordLister(); - - viewer.setFile(args[ 0 ]); - viewer.run(); - } - else - { - System.out.println("RecordLister"); - System.out.println( - "Outputs the summary of the records in file order"); - System.out - .println("usage: java org.apache.poi.hssf.dev.RecordLister " - + "filename"); - } - } -} diff --git a/poi/src/main/java/org/apache/poi/hssf/record/RecordInputStream.java b/poi/src/main/java/org/apache/poi/hssf/record/RecordInputStream.java index 6cb3aacb5e..62535e50b8 100644 --- a/poi/src/main/java/org/apache/poi/hssf/record/RecordInputStream.java +++ b/poi/src/main/java/org/apache/poi/hssf/record/RecordInputStream.java @@ -22,7 +22,6 @@ import java.io.InputStream; import java.util.Locale; import org.apache.commons.io.output.UnsynchronizedByteArrayOutputStream; -import org.apache.poi.hssf.dev.BiffViewer; import org.apache.poi.hssf.record.crypto.Biff8DecryptingStream; import org.apache.poi.hssf.usermodel.HSSFWorkbook; import org.apache.poi.poifs.crypt.EncryptionInfo; @@ -52,7 +51,7 @@ public final class RecordInputStream implements LittleEndianInput { private static final byte[] EMPTY_BYTE_ARRAY = { }; /** - * For use in {@link BiffViewer} which may construct {@link Record}s that don't completely + * For use in BiffViewer which may construct {@link Record}s that don't completely * read all available data. This exception should never be thrown otherwise. */ public static final class LeftoverDataException extends RuntimeException { @@ -141,15 +140,6 @@ public final class RecordInputStream implements LittleEndianInput { _nextSid = readNextSid(); } - static LittleEndianInput getLEI(InputStream is) { - if (is instanceof LittleEndianInput) { - // accessing directly is an optimisation - return (LittleEndianInput) is; - } - // less optimal, but should work OK just the same. Often occurs in junit tests. - return new LittleEndianInputStream(is); - } - /** * @return the number of bytes available in the current BIFF record * @see #remaining() diff --git a/poi/src/main/java/org/apache/poi/util/CloseIgnoringInputStream.java b/poi/src/main/java/org/apache/poi/util/CloseIgnoringInputStream.java deleted file mode 100644 index 82247b13af..0000000000 --- a/poi/src/main/java/org/apache/poi/util/CloseIgnoringInputStream.java +++ /dev/null @@ -1,38 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ - -package org.apache.poi.util; - -import java.io.FilterInputStream; -import java.io.InputStream; - -/** - * A wrapper around an {@link InputStream}, which - * ignores close requests made to it. - * - * Useful with {@link org.apache.poi.poifs.filesystem.POIFSFileSystem}, where you want - * to control the close yourself. - */ -public class CloseIgnoringInputStream extends FilterInputStream { - public CloseIgnoringInputStream(InputStream in) { - super(in); - } - - public void close() { - // Does nothing and ignores closing the wrapped stream - } -} diff --git a/poi/src/main/java9/module-info.class b/poi/src/main/java9/module-info.class index 7ca4ecd882a04d09234b79b8f0809aa79580adb5..a27868cdf4ab031ce77f7eff01d32dadd450c52a 100644 GIT binary patch literal 3385 zcmai$_nzBC5XXNTAV)4e-t^umA(3*)C3FIT90?EzF%UZJNo!kYSrU?L$JC?u-XZkP zBk%_gz*F!fd>D;f6iasQ^SQ5W?ab_yomu_!-(PXYIW!1D1_)hX)!Xkl1iKsEFA~LaX>aelj zI8ee8fvp*N%WpJ%FET9Ob+r{c^nJR7WdiF8g=}q=utH#U!K&kzuu5QQ&tyZzwSy%{ zfhFZYg^@n$#AO|qu;zU0u3xP>Ue%z_={fTBfH5^~OqH;n6gh&fKnWXrPi!64V?QWi zv%vB>yKXeSs2K!)7;9T%tH3hy&rDNdQaU8Ir#@$%nW>)=JJXEhm&|6QLrT4NC3Xw! zKHp|ne;{O5d@qh9E>A0xwP)KEi7V5vv(NR}I8;{bhZ0u_l!i8_1re$36WCm^U}kj9 z5h88vx^>TQdlFLx=A*>?L4hs9=4rvyE$zkK;13Dx7%7uU($xaSh2>1U*=Te^ZTTS; z>)Ha}dH+Fbt`}HeWVY+wjRG5rmU}xK$(OE{?2xXTs#*sEnn zqjxi{nhIlyV*=a9NpaUn+@3#rS=TH&P!cCDU@Pndu`hAw_%H*f<;Ss_b+yFF5n*OZ zdCv%IeG#*!-9U$ww0raCnuBP@4mD0$VB=6Gs39v+zy0pdm#-_31SWANpYY5&m5)ZG z#6tqRinmFun6r+j!cJ&SOFS}O(&BWgfM&*H0z1dpjAG^4DmlYXA!9y~MuY-3(jI>rUUBpd_z%C$(m`={n=J-UE<69^_gma zo%gXF4BiPQ@QuKpp`+G6#^$l!C3hBrSAWd*n>w&M$f61NToV$LohM!!I(O3H&B7HRMlv zAM|55UM|n{;^?dWkxx|5Cwg&gsNg)$<^e%3&*|R+Ttr_M;9^`tkN?F&emsdw=YdHq z=3h&h%b6>gCFW}4TC5{ZVgqv%^9Z)kC$NpVgSm@&8S@I}9_C)A!Q9V0z?^0tW?sX* zj(G#~Cg#n|qs-fwGtA@6JD7Jd?`EE2o@U<1e1Q2N^I_(r%*UBeGH01Kv%;(~=a??j zV+PES88chVv&^TN&oZB9&NE+PzQTNs`3CbX<~z*ym>)1dVt&H>jQIugD<-It=kP7Q V$B$Imb5!Y{>6gH-^jqNf{{aAB*t!4! literal 3421 zcmai$_mAg`xBE`vuUP2&C0t7+~gbrHy+SXc@ge2Qp>e74fy?35~ zKX?Ehgva188aL%y+3Y#{*}gM(H091*{qx^ne*-v(KTEjZz`{z{j$JkB`O3iZR#=5u9y0N;iAPL4YT_{ynd*M4JYkhhOqp;^ zR0)buBh(2Ef=8GpGzmVTMF5%O8B*e-%9wsgg*={$|cFb$}p~5 zPUN@^WksQv{To=2<<6RN63*$CoPry+T49h_Zs_~UO+EfTVPI{cMpd~6Ruzc6(7=*` zrIwR64j5QmiJUl5hrP6-(lagkVZH7Jb&EeIrdd;5>n{#|R#nnW!^pt0>1xwz2T41M z!Z=k`154QhyBD7=v1Kmh7VMrUy*S6$?RKJKPMGoy>?+(j(y(h`kS4v9<}FpLwO&a1 zMRhuj9XAbQ1EyxT5hc7~Q^Bg8+f^vx*Oq!+NzEnc<$$cIx$5)tyvv zwF({R>$#k%6&6eP?6UO)6|*D24P`P zxiS^n8OrU3@*@>Gz()qr8yy&Mke{rV(Zy{ zd&&!(cqVoy49unGO3s{!=h==qtR<-vRGsW7>K%}G>vYzrdNz6r(mlnFNc+?{pkyXV zRcrhd^ep5xiqqP1+&C<#M{j?Sja0qlJy8^gT*-9Cz{bKHBs_tfI-i);nmF<8YV35! z>)aREbro_zy1OjRHLpHxSn9RpaRvK^$IrkD-5CyjY)6uRl3to5;b|Q6?P5TmAbnBW z=PO@N+DK+QDc>jh%*)BSX|tol575s|PDyo~f{Q~_$UJRkchwwEtF;5UZ^zGa&e%Yy zV5;S`qO?9|;O*?XD#&)OK6*c~^`Ue$JjRX`Ysu)yUMccZCELyd+vVG<>-f#_%d2?g zbfeK@4eZve4$q-|WN_D>h{KLoRou#ZH03iQIK}mt3d80|eXp+WkDhE-e70F>gx!(8 zJ`M&=b1XakCsn{cWM1fIM=l?CykO||q`W9_50^DJdgFNThGHMBR3-<$5BKP48Mwml zS}i9!!=YKg;8*M3-#pCYFY|C7&gbQSae*u^#Qa&HgbU@@g8o|eu}Hoz7A|2f#WH3I z%Y`e1hp>`A0aHlARl?Q6HNv&Rb;9++4Z@AWO~TE>EyAtBZNlxs9m1W$UBZim7Yi>D zUMk!zyiB-9xL0TiZQ(v)S-4+#KsX^hD7;*Fh44z@Rl=)<*9fl_UMIX>c!The@J8Wb z;Z4Gug-3*w!drw#g~x=q3U3qME<7&0LwKj~gzzpQxSA(X!QHrrD|?dbeJ}qqaEgB$ GxbJ^K=i(&* diff --git a/poi/src/main/java9/module-info.java b/poi/src/main/java9/module-info.java index 6e7fe11df8..389b4b5a85 100644 --- a/poi/src/main/java9/module-info.java +++ b/poi/src/main/java9/module-info.java @@ -48,7 +48,6 @@ module org.apache.poi.poi { exports org.apache.poi.hpsf.extractor; exports org.apache.poi.hpsf.wellknown; exports org.apache.poi.hssf; - exports org.apache.poi.hssf.dev; exports org.apache.poi.hssf.eventmodel; exports org.apache.poi.hssf.eventusermodel; exports org.apache.poi.hssf.eventusermodel.dummyrecord; diff --git a/poi/src/test/java/org/apache/poi/hssf/dev/BiffDumpingStream.java b/poi/src/test/java/org/apache/poi/hssf/dev/BiffDumpingStream.java new file mode 100644 index 0000000000..0591b359cb --- /dev/null +++ b/poi/src/test/java/org/apache/poi/hssf/dev/BiffDumpingStream.java @@ -0,0 +1,121 @@ +/* ==================================================================== + 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.dev; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.poi.hssf.record.RecordInputStream; +import org.apache.poi.util.LittleEndian; +import org.apache.poi.util.SuppressForbidden; + +/** + * Wraps a plain {@link InputStream} and allows BIFF record information to be tapped off + */ +final class BiffDumpingStream extends InputStream { + + interface IBiffRecordListener { + void processRecord(int globalOffset, int recordCounter, int sid, int dataSize, byte[] data) throws IOException; + } + + + private final DataInputStream _is; + private final IBiffRecordListener _listener; + private final byte[] _data; + private int _recordCounter; + private int _overallStreamPos; + private int _currentPos; + private int _currentSize; + private boolean _innerHasReachedEOF; + private final byte[] oneByte = new byte[1]; + + BiffDumpingStream(InputStream is, IBiffRecordListener listener) { + _is = new DataInputStream(is); + _listener = listener; + _data = new byte[RecordInputStream.MAX_RECORD_DATA_SIZE + 4]; + _recordCounter = 0; + _overallStreamPos = 0; + _currentSize = 0; + _currentPos = 0; + } + + @Override + public int read() throws IOException { + int ret = read(oneByte, 0, 1); + return (ret == -1) ? -1 : oneByte[0] & 0x00FF; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + if (b == null || off < 0 || len < 0 || b.length < off + len) { + throw new IllegalArgumentException(); + } + if (_currentPos >= _currentSize) { + fillNextBuffer(); + } + if (_currentPos >= _currentSize) { + return -1; + } + final int result = Math.min(len, _currentSize - _currentPos); + System.arraycopy(_data, _currentPos, b, off, result); + _currentPos += result; + _overallStreamPos += result; + formatBufferIfAtEndOfRec(); + return result; + } + + @Override + @SuppressForbidden("just delegating the call") + public int available() throws IOException { + return _currentSize - _currentPos + _is.available(); + } + + private void fillNextBuffer() throws IOException { + if (_innerHasReachedEOF) { + return; + } + int b0 = _is.read(); + if (b0 == -1) { + _innerHasReachedEOF = true; + return; + } + _data[0] = (byte) b0; + _is.readFully(_data, 1, 3); + int len = LittleEndian.getShort(_data, 2); + _is.readFully(_data, 4, len); + _currentPos = 0; + _currentSize = len + 4; + _recordCounter++; + } + + private void formatBufferIfAtEndOfRec() throws IOException { + if (_currentPos != _currentSize) { + return; + } + int dataSize = _currentSize - 4; + int sid = LittleEndian.getShort(_data, 0); + int globalOffset = _overallStreamPos - _currentSize; + _listener.processRecord(globalOffset, _recordCounter, sid, dataSize, _data); + } + + @Override + public void close() throws IOException { + _is.close(); + } +} diff --git a/poi/src/test/java/org/apache/poi/hssf/dev/BiffViewer.java b/poi/src/test/java/org/apache/poi/hssf/dev/BiffViewer.java new file mode 100644 index 0000000000..274c140f17 --- /dev/null +++ b/poi/src/test/java/org/apache/poi/hssf/dev/BiffViewer.java @@ -0,0 +1,342 @@ +/* ==================================================================== + 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.dev; + +import static org.apache.logging.log4j.util.Unbox.box; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.Writer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.io.output.CloseShieldOutputStream; +import org.apache.commons.io.output.NullOutputStream; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.poi.hssf.dev.BiffDumpingStream.IBiffRecordListener; +import org.apache.poi.hssf.record.ContinueRecord; +import org.apache.poi.hssf.record.HSSFRecordTypes; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.RecordInputStream; +import org.apache.poi.hssf.record.RecordInputStream.LeftoverDataException; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.util.HexDump; +import org.apache.poi.util.IOUtils; +import org.apache.poi.util.RecordFormatException; +import org.apache.poi.util.StringUtil; + +/** + * Utility for reading in BIFF8 records and displaying data from them. + */ +public final class BiffViewer { + private static final char[] NEW_LINE_CHARS = System.getProperty("line.separator").toCharArray(); + private static final Logger LOG = LogManager.getLogger(BiffViewer.class); + private static final String ESCHER_SERIALIZE = "poi.deserialize.escher"; + private static final int DUMP_LINE_LEN = 16; + private static final char[] COLUMN_SEPARATOR = " | ".toCharArray(); + + private boolean biffHex; + private boolean interpretRecords = true; + private boolean rawHexOnly; + private boolean noHeader = true; + private boolean zeroAlignRecord = true; + private final List _headers = new ArrayList<>(); + + + /** + * show hex dump of each BIFF record + */ + public void setDumpBiffHex(boolean biffhex) { + this.biffHex = biffhex; + } + + /** + * output interpretation of BIFF records + */ + public void setInterpretRecords(boolean interpretRecords) { + this.interpretRecords = interpretRecords; + } + + /** + * output raw hex dump of whole workbook stream + */ + public void setOutputRawHexOnly(boolean rawhex) { + this.rawHexOnly = rawhex; + } + + /** + * do not print record header - default is on + */ + public void setSuppressHeader(boolean noHeader) { + this.noHeader = noHeader; + } + + /** + * turn on deserialization of escher records (default is off) + */ + public void setSerializeEscher(boolean serialize) { + if (serialize) { + System.setProperty(ESCHER_SERIALIZE, "true"); + } else { + System.clearProperty(ESCHER_SERIALIZE); + } + } + + public void setZeroAlignRecord(boolean zeroAlignRecord) { + this.zeroAlignRecord = zeroAlignRecord; + } + + public void parse(File file) throws IOException { + parse(file, System.out); + } + + public void parse(File file, OutputStream os) throws IOException { + try (POIFSFileSystem fs = new POIFSFileSystem(file, true); + InputStream is = getPOIFSInputStream(fs); + PrintWriter pw = wrap(os) + ) { + if (rawHexOnly) { + byte[] data = IOUtils.toByteArray(is); + HexDump.dump(data, 0, System.out, 0); + } else { + IBiffRecordListener recListener = (globalOffset, recordCounter, sid, dataSize, data) -> { + String header = formatRecordDetails(globalOffset, sid, dataSize, recordCounter); + if (!noHeader) { + _headers.add(header); + } + if (biffHex) { + pw.write(header); + pw.write(NEW_LINE_CHARS); + hexDumpAligned(pw, data, dataSize+4, globalOffset); + pw.flush(); + } + }; + + try (InputStream is2 = new BiffDumpingStream(is, recListener)) { + createRecords(is2, pw); + } + } + } + } + + private static String formatRecordDetails(int globalOffset, int sid, int size, int recordCounter) { + return "Offset=" + HexDump.intToHex(globalOffset) + "(" + globalOffset + ")" + + " recno=" + recordCounter + + " sid=" + HexDump.shortToHex(sid) + + " size=" + HexDump.shortToHex(size) + "(" + size + ")"; + } + + /** + * Create an array of records from an input stream + * + * @param is the InputStream from which the records will be obtained + * @param ps the PrintWriter to output the record data + * + * @exception RecordFormatException on error processing the InputStream + */ + private void createRecords(InputStream is, PrintWriter ps) throws RecordFormatException { + RecordInputStream recStream = new RecordInputStream(is); + while (true) { + _headers.clear(); + boolean hasNext; + try { + hasNext = recStream.hasNextRecord(); + } catch (LeftoverDataException e) { + LOG.atError().withThrowable(e).log("Discarding {} bytes and continuing", box(recStream.remaining())); + recStream.readRemainder(); + hasNext = recStream.hasNextRecord(); + } + if (!hasNext) { + break; + } + recStream.nextRecord(); + if (recStream.getSid() == 0) { + continue; + } + Record record; + if (interpretRecords) { + record = HSSFRecordTypes.forSID(recStream.getSid()).getRecordConstructor().apply(recStream); + if (record.getSid() == ContinueRecord.sid) { + continue; + } + + _headers.forEach(ps::println); + ps.print(record); + } else { + recStream.readRemainder(); + } + ps.println(); + } + } + + private static PrintWriter wrap(OutputStream os) { + final OutputStream osOut; + final Charset cs; + + if (os == null) { + cs = Charset.defaultCharset(); + osOut = NullOutputStream.NULL_OUTPUT_STREAM; + } else if (os == System.out) { + // Use the system default encoding when sending to System Out + cs = Charset.defaultCharset(); + osOut = CloseShieldOutputStream.wrap(System.out); + } else { + cs = StringUtil.UTF8; + osOut = os; + } + return new PrintWriter(new OutputStreamWriter(osOut, cs)); + } + + + static InputStream getPOIFSInputStream(POIFSFileSystem fs) throws IOException { + String workbookName = HSSFWorkbook.getWorkbookDirEntryName(fs.getRoot()); + return fs.createDocumentInputStream(workbookName); + } + + + /** + * Hex-dumps a portion of a byte array in typical format, also preserving dump-line alignment + * @param globalOffset (somewhat arbitrary) used to calculate the addresses printed at the + * start of each line + */ + private void hexDumpAligned(Writer w, byte[] data, int dumpLen, int globalOffset) { + int baseDataOffset = 0; + + // perhaps this code should be moved to HexDump + int globalStart = globalOffset + baseDataOffset; + int globalEnd = globalOffset + baseDataOffset + dumpLen; + int startDelta = globalStart % DUMP_LINE_LEN; + int endDelta = globalEnd % DUMP_LINE_LEN; + if (zeroAlignRecord) { + endDelta -= startDelta; + if (endDelta < 0) { + endDelta += DUMP_LINE_LEN; + } + startDelta = 0; + } + int startLineAddr; + int endLineAddr; + if (zeroAlignRecord) { + endLineAddr = globalEnd - endDelta - (globalStart - startDelta); + startLineAddr = 0; + } else { + startLineAddr = globalStart - startDelta; + endLineAddr = globalEnd - endDelta; + } + + int lineDataOffset = baseDataOffset - startDelta; + int lineAddr = startLineAddr; + + // output (possibly incomplete) first line + if (startLineAddr == endLineAddr) { + hexDumpLine(w, data, lineAddr, lineDataOffset, startDelta, endDelta); + return; + } + hexDumpLine(w, data, lineAddr, lineDataOffset, startDelta, DUMP_LINE_LEN); + + // output all full lines in the middle + while (true) { + lineAddr += DUMP_LINE_LEN; + lineDataOffset += DUMP_LINE_LEN; + if (lineAddr >= endLineAddr) { + break; + } + hexDumpLine(w, data, lineAddr, lineDataOffset, 0, DUMP_LINE_LEN); + } + + + // output (possibly incomplete) last line + if (endDelta != 0) { + hexDumpLine(w, data, lineAddr, lineDataOffset, 0, endDelta); + } + } + + private static void hexDumpLine(Writer w, byte[] data, int lineStartAddress, int lineDataOffset, int startDelta, int endDelta) { + final char[] buf = new char[8+2*COLUMN_SEPARATOR.length+DUMP_LINE_LEN*3-1+DUMP_LINE_LEN+NEW_LINE_CHARS.length]; + + if (startDelta >= endDelta) { + throw new IllegalArgumentException("Bad start/end delta"); + } + int idx=0; + try { + writeHex(buf, idx, lineStartAddress, 8); + idx = arraycopy(COLUMN_SEPARATOR, buf, idx+8); + // raw hex data + for (int i=0; i< DUMP_LINE_LEN; i++) { + if (i>0) { + buf[idx++] = ' '; + } + if (i >= startDelta && i < endDelta) { + writeHex(buf, idx, data[lineDataOffset+i], 2); + } else { + buf[idx] = ' '; + buf[idx+1] = ' '; + } + idx += 2; + } + idx = arraycopy(COLUMN_SEPARATOR, buf, idx); + + // interpreted ascii + for (int i=0; i< DUMP_LINE_LEN; i++) { + char ch = ' '; + if (i >= startDelta && i < endDelta) { + ch = getPrintableChar(data[lineDataOffset+i]); + } + buf[idx++] = ch; + } + + idx = arraycopy(NEW_LINE_CHARS, buf, idx); + + w.write(buf, 0, idx); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static int arraycopy(char[] in, char[] out, int pos) { + int idx = pos; + for (char c : in) { + out[idx++] = c; + } + return idx; + } + + private static char getPrintableChar(byte b) { + char ib = (char) (b & 0x00FF); + if (ib < 32 || ib > 126) { + return '.'; + } + return ib; + } + + private static void writeHex(char[] buf, int startInBuf, int value, int nDigits) { + int acc = value; + for(int i=nDigits-1; i>=0; i--) { + int digit = acc & 0x0F; + buf[startInBuf+i] = (char) (digit < 10 ? ('0' + digit) : ('A' + digit - 10)); + acc >>>= 4; + } + } +} diff --git a/poi/src/test/java/org/apache/poi/hssf/dev/TestBiffDrawingToXml.java b/poi/src/test/java/org/apache/poi/hssf/dev/TestBiffDrawingToXml.java index fae6c5f766..3613d1b68c 100644 --- a/poi/src/test/java/org/apache/poi/hssf/dev/TestBiffDrawingToXml.java +++ b/poi/src/test/java/org/apache/poi/hssf/dev/TestBiffDrawingToXml.java @@ -20,11 +20,25 @@ import static org.apache.commons.io.output.NullOutputStream.NULL_OUTPUT_STREAM; import java.io.File; import java.io.FileInputStream; +import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.apache.poi.EncryptedDocumentException; +import org.apache.poi.ddf.EscherRecord; +import org.apache.poi.hssf.model.InternalWorkbook; +import org.apache.poi.hssf.record.DrawingGroupRecord; import org.apache.poi.hssf.record.RecordInputStream; +import org.apache.poi.hssf.usermodel.HSSFPatriarch; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.util.StringUtil; class TestBiffDrawingToXml extends BaseTestIteratingXLS { @@ -42,10 +56,69 @@ class TestBiffDrawingToXml extends BaseTestIteratingXLS { return excludes; } + // output sheets with specified name + private static final String[] SHEET_NAMES = {}; + + // output sheets with specified indexes + private static final int[] SHEET_IDX = {}; + + // exclude workbook-level records + private static final boolean EXCLUDE_WORKBOOK = false; + + @Override void runOneFile(File pFile) throws Exception { - try (InputStream wb = new FileInputStream(pFile)) { - BiffDrawingToXml.writeToFile(NULL_OUTPUT_STREAM, wb, false, new String[0]); + try (InputStream inp = new FileInputStream(pFile); + OutputStream outputStream = NULL_OUTPUT_STREAM) { + writeToFile(outputStream, inp); + } + } + + public static void writeToFile(OutputStream fos, InputStream xlsWorkbook) throws IOException { + try (HSSFWorkbook workbook = new HSSFWorkbook(xlsWorkbook)) { + InternalWorkbook internalWorkbook = workbook.getInternalWorkbook(); + DrawingGroupRecord r = (DrawingGroupRecord) internalWorkbook.findFirstRecordBySid(DrawingGroupRecord.sid); + + StringBuilder builder = new StringBuilder(); + builder.append("\n"); + String tab = "\t"; + if (!EXCLUDE_WORKBOOK && r != null) { + r.decode(); + List escherRecords = r.getEscherRecords(); + for (EscherRecord record : escherRecords) { + builder.append(record.toXml(tab)); + } + } + int i = 0; + for (HSSFSheet sheet : getSheets(workbook)) { + HSSFPatriarch p = sheet.getDrawingPatriarch(); + if (p != null) { + builder.append(tab).append("\n"); + builder.append(p.getBoundAggregate().toXml(tab + "\t")); + builder.append(tab).append("\n"); + i++; + } + } + builder.append("\n"); + fos.write(builder.toString().getBytes(StringUtil.UTF8)); } } + + private static List getSheets(HSSFWorkbook workbook) { + List sheetIdx = Arrays.stream(SHEET_IDX).boxed().collect(Collectors.toList()); + List sheetNms = Arrays.stream(SHEET_NAMES).collect(Collectors.toList()); + + List list = new ArrayList<>(); + + for (Sheet sheet : workbook) { + if ((sheetIdx.isEmpty() && sheetNms.isEmpty()) || + sheetIdx.contains(workbook.getSheetIndex(sheet)) || + sheetNms.contains(sheet.getSheetName()) + ) { + list.add((HSSFSheet)sheet); + } + } + + return list; + } } diff --git a/poi/src/test/java/org/apache/poi/hssf/dev/TestBiffViewer.java b/poi/src/test/java/org/apache/poi/hssf/dev/TestBiffViewer.java index 790ca14b58..1059213502 100644 --- a/poi/src/test/java/org/apache/poi/hssf/dev/TestBiffViewer.java +++ b/poi/src/test/java/org/apache/poi/hssf/dev/TestBiffViewer.java @@ -16,17 +16,10 @@ ==================================================================== */ package org.apache.poi.hssf.dev; -import static org.apache.commons.io.output.NullOutputStream.NULL_OUTPUT_STREAM; - import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; import java.util.Map; -import org.apache.poi.poifs.filesystem.POIFSFileSystem; -import org.apache.poi.util.LocaleUtil; import org.apache.poi.util.RecordFormatException; class TestBiffViewer extends BaseTestIteratingXLS { @@ -52,19 +45,10 @@ class TestBiffViewer extends BaseTestIteratingXLS { @Override void runOneFile(File fileIn) throws IOException { - try (POIFSFileSystem fs = new POIFSFileSystem(fileIn, true); - InputStream is = BiffViewer.getPOIFSInputStream(fs)) { - // use a NullOutputStream to not write the bytes anywhere for best runtime - PrintWriter dummy = new PrintWriter(new OutputStreamWriter(NULL_OUTPUT_STREAM, LocaleUtil.CHARSET_1252)); - BiffViewer.runBiffViewer(dummy, is, true, true, true, false); - } + BiffViewer bv = new BiffViewer(); + bv.setInterpretRecords(true); + bv.setDumpBiffHex(true); + bv.parse(fileIn, null); } -// @Test -// @Disabled("only used for manual tests") -// @SuppressWarnings("java:S2699") -// void testOneFile() throws Exception { -// POIDataSamples samples = POIDataSamples.getSpreadSheetInstance(); -// runOneFile(samples.getFile("43493.xls")); -// } } diff --git a/poi/src/test/java/org/apache/poi/hssf/dev/TestEFBiffViewer.java b/poi/src/test/java/org/apache/poi/hssf/dev/TestEFBiffViewer.java index 4924511648..bcd91e74d7 100644 --- a/poi/src/test/java/org/apache/poi/hssf/dev/TestEFBiffViewer.java +++ b/poi/src/test/java/org/apache/poi/hssf/dev/TestEFBiffViewer.java @@ -18,16 +18,16 @@ package org.apache.poi.hssf.dev; import java.io.File; import java.io.IOException; -import java.io.PrintStream; +import java.io.InputStream; import java.util.Map; import org.apache.poi.EncryptedDocumentException; +import org.apache.poi.hssf.eventusermodel.HSSFEventFactory; +import org.apache.poi.hssf.eventusermodel.HSSFRequest; import org.apache.poi.hssf.record.RecordInputStream; -import org.apache.commons.io.output.NullPrintStream; -import org.junit.jupiter.api.parallel.ResourceLock; -import org.junit.jupiter.api.parallel.Resources; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.junit.jupiter.api.Assertions; -@ResourceLock(Resources.SYSTEM_OUT) class TestEFBiffViewer extends BaseTestIteratingXLS { @Override protected Map> getExcludes() { @@ -41,27 +41,19 @@ class TestEFBiffViewer extends BaseTestIteratingXLS { excludes.put("43493.xls", RecordInputStream.LeftoverDataException.class); excludes.put("44958_1.xls", RecordInputStream.LeftoverDataException.class); // "Buffer overrun" - excludes.put("XRefCalc.xls", RuntimeException.class); + // excludes.put("XRefCalc.xls", RuntimeException.class); return excludes; } @Override void runOneFile(File fileIn) throws IOException { - PrintStream save = System.out; - try { - // redirect standard out during the test to avoid spamming the console with output - System.setOut(new NullPrintStream()); + HSSFRequest req = new HSSFRequest(); + req.addListenerForAllRecords(Assertions::assertNotNull); + HSSFEventFactory factory = new HSSFEventFactory(); - EFBiffViewer.main(new String[] { fileIn.getAbsolutePath() }); - } finally { - System.setOut(save); + try (POIFSFileSystem fs = new POIFSFileSystem(fileIn, true); + InputStream din = BiffViewer.getPOIFSInputStream(fs)) { + factory.processEvents(req, din); } } - - //@Test - void testFile() throws IOException { - EFBiffViewer viewer = new EFBiffViewer(); - viewer.setFile(new File("test-data/spreadsheet/59074.xls").getAbsolutePath()); - viewer.run(); - } } diff --git a/poi/src/test/java/org/apache/poi/hssf/dev/TestFormulaViewer.java b/poi/src/test/java/org/apache/poi/hssf/dev/TestFormulaViewer.java index d8e50f134b..177871e25b 100644 --- a/poi/src/test/java/org/apache/poi/hssf/dev/TestFormulaViewer.java +++ b/poi/src/test/java/org/apache/poi/hssf/dev/TestFormulaViewer.java @@ -16,19 +16,24 @@ ==================================================================== */ package org.apache.poi.hssf.dev; -import static org.junit.jupiter.api.Assumptions.assumeTrue; - import java.io.File; -import java.io.PrintStream; +import java.io.InputStream; +import java.io.PrintWriter; +import java.util.Locale; import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import org.apache.commons.io.output.NullWriter; import org.apache.poi.EncryptedDocumentException; +import org.apache.poi.hssf.model.HSSFFormulaParser; +import org.apache.poi.hssf.record.FormulaRecord; +import org.apache.poi.hssf.record.RecordFactory; import org.apache.poi.hssf.record.RecordInputStream; -import org.apache.commons.io.output.NullPrintStream; -import org.junit.jupiter.api.parallel.ResourceLock; -import org.junit.jupiter.api.parallel.Resources; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; +import org.apache.poi.ss.formula.ptg.FuncPtg; +import org.apache.poi.ss.formula.ptg.Ptg; -@ResourceLock(Resources.SYSTEM_OUT) class TestFormulaViewer extends BaseTestIteratingXLS { @Override protected Map> getExcludes() { @@ -42,27 +47,106 @@ class TestFormulaViewer extends BaseTestIteratingXLS { return excludes; } + private final boolean doListFormula = true; + @Override void runOneFile(File fileIn) throws Exception { - PrintStream save = System.out; + // replace with System.out for manual tests + PrintWriter out = new PrintWriter(new NullWriter()); + + final Function lister = (doListFormula) ? this::listFormula : this::parseFormulaRecord; + + try (POIFSFileSystem fs = new POIFSFileSystem(fileIn, true); + InputStream is = BiffViewer.getPOIFSInputStream(fs)) { + RecordFactory.createRecords(is).stream() + .filter(r -> r.getSid() == FormulaRecord.sid) + .map(FormulaRecord.class::cast) + .map(lister) + .map(Objects::nonNull) + .forEach(out::println); + } + } + + private String listFormula(FormulaRecord record) { + Ptg[] tokens = record.getParsedExpression(); + int numptgs = tokens.length; + final Ptg lastToken = tokens[numptgs - 1]; + + String fmlStr; try { - // redirect standard out during the test to avoid spamming the console with output - System.setOut(new NullPrintStream()); - - FormulaViewer viewer = new FormulaViewer(); - viewer.setFile(fileIn.getAbsolutePath()); - viewer.setList(true); - viewer.run(); - } catch (RuntimeException re) { - String m = re.getMessage(); - if (m.startsWith("toFormulaString") || m.startsWith("3D references")) { - // TODO: fix those cases, but ignore them for now ... - assumeTrue(true); - } else { - throw re; + fmlStr = lastToken.toFormulaString(); + } catch (Exception ignored) { + return null; + } + + return String.join("~", + fmlStr, + mapToken(lastToken), + (numptgs > 1 ? mapToken(tokens[numptgs - 2]) : "VALUE"), + String.valueOf(lastToken instanceof FuncPtg ? numptgs - 1 : -1) + ); + } + + private static String mapToken(Ptg token) { + switch (token.getPtgClass()) { + case Ptg.CLASS_REF: + return "REF"; + case Ptg.CLASS_VALUE: + return "VALUE"; + case Ptg.CLASS_ARRAY: + return "ARRAY"; + default: + throwInvalidRVAToken(token); + return ""; + } + } + + /** + * Method parseFormulaRecord + * + * @param record the record to be parsed + */ + public String parseFormulaRecord(FormulaRecord record) { + return String.format(Locale.ROOT, + "==============================\n" + + "row = %d, col = %d\n" + + "value = %f\n" + + "xf = %d, number of ptgs = %d, options = %d\n" + + "RPN List = %s\n" + + "Formula text = %s", + record.getRow(), record.getColumn(), record.getValue(), record.getXFIndex(), + record.getParsedExpression().length, record.getOptions(), + formulaString(record), composeFormula(record)); + } + + private String formulaString(FormulaRecord record) { + StringBuilder buf = new StringBuilder(); + Ptg[] tokens = record.getParsedExpression(); + for (Ptg token : tokens) { + buf.append(token.toFormulaString()); + switch (token.getPtgClass()) { + case Ptg.CLASS_REF: + buf.append("(R)"); + break; + case Ptg.CLASS_VALUE: + buf.append("(V)"); + break; + case Ptg.CLASS_ARRAY: + buf.append("(A)"); + break; + default: + throwInvalidRVAToken(token); } - } finally { - System.setOut(save); + buf.append(' '); } + return buf.toString(); + } + + private static void throwInvalidRVAToken(Ptg token) { + throw new IllegalStateException("Invalid RVA type (" + token.getPtgClass() + "). This should never happen."); + } + + private static String composeFormula(FormulaRecord record) { + return HSSFFormulaParser.toFormulaString(null, record.getParsedExpression()); } } diff --git a/poi/src/test/java/org/apache/poi/hssf/dev/TestReSave.java b/poi/src/test/java/org/apache/poi/hssf/dev/TestReSave.java deleted file mode 100644 index 475ff2a3d7..0000000000 --- a/poi/src/test/java/org/apache/poi/hssf/dev/TestReSave.java +++ /dev/null @@ -1,94 +0,0 @@ -/* ==================================================================== - Licensed to the Apache Software Foundation (ASF) under one or more - contributor license agreements. See the NOTICE file distributed with - this work for additional information regarding copyright ownership. - The ASF licenses this file to You under the Apache License, Version 2.0 - (the "License"); you may not use this file except in compliance with - the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -==================================================================== */ -package org.apache.poi.hssf.dev; - -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.io.File; -import java.io.PrintStream; -import java.util.Map; - -import org.apache.poi.EncryptedDocumentException; -import org.apache.poi.POIDataSamples; -import org.apache.poi.hssf.record.RecordInputStream; -import org.apache.commons.io.output.NullPrintStream; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.parallel.Isolated; -import org.junit.jupiter.api.parallel.ResourceLock; -import org.junit.jupiter.api.parallel.Resources; - -@Isolated("Modifies the test data directory") -@ResourceLock(Resources.SYSTEM_OUT) -class TestReSave extends BaseTestIteratingXLS { - @Override - protected Map> getExcludes() { - Map> excludes = super.getExcludes(); - // unsupported crypto api header - excludes.put("35897-type4.xls", EncryptedDocumentException.class); - excludes.put("51832.xls", EncryptedDocumentException.class); - excludes.put("xor-encryption-abc.xls", EncryptedDocumentException.class); - excludes.put("password.xls", EncryptedDocumentException.class); - // HSSFWorkbook cannot open it as well - excludes.put("43493.xls", RecordInputStream.LeftoverDataException.class); - excludes.put("44958_1.xls", RecordInputStream.LeftoverDataException.class); - // "Buffer overrun" - excludes.put("XRefCalc.xls", RuntimeException.class); - return excludes; - } - - @Override - void runOneFile(File fileIn) throws Exception { - // avoid running on files leftover from previous failed runs - if(fileIn.getName().endsWith("-saved.xls")) { - return; - } - - PrintStream save = System.out; - try { - // redirect standard out during the test to avoid spamming the console with output - System.setOut(new NullPrintStream()); - - File reSavedFile = new File(fileIn.getParentFile(), fileIn.getName().replace(".xls", "-saved.xls")); - try { - ReSave.main(new String[] { fileIn.getAbsolutePath() }); - - // also try BiffViewer on the saved file - new TestBiffViewer().runOneFile(reSavedFile); - - // had one case where the re-saved could not be re-saved! - ReSave.main(new String[] { "-bos", reSavedFile.getAbsolutePath() }); - } finally { - // clean up the re-saved file - assertTrue(!reSavedFile.exists() || reSavedFile.delete()); - } - } finally { - System.setOut(save); - } - } - - @Disabled("Only used for local testing") - @Test - void testOneFile() throws Exception { - String dataDirName = System.getProperty(POIDataSamples.TEST_PROPERTY); - if(dataDirName == null) { - dataDirName = "test-data"; - } - - runOneFile(new File(dataDirName + "/spreadsheet", "49931.xls")); - } -} diff --git a/poi/src/test/java/org/apache/poi/hssf/dev/TestRecordLister.java b/poi/src/test/java/org/apache/poi/hssf/dev/TestRecordLister.java index a0462b30c5..dbcb3c0618 100644 --- a/poi/src/test/java/org/apache/poi/hssf/dev/TestRecordLister.java +++ b/poi/src/test/java/org/apache/poi/hssf/dev/TestRecordLister.java @@ -18,33 +18,135 @@ package org.apache.poi.hssf.dev; import java.io.File; import java.io.IOException; -import java.io.PrintStream; +import java.io.InputStream; +import java.io.PrintWriter; +import java.util.Locale; -import org.apache.commons.io.output.NullPrintStream; -import org.junit.jupiter.api.parallel.ResourceLock; -import org.junit.jupiter.api.parallel.Resources; +import org.apache.commons.io.output.NullWriter; +import org.apache.poi.hssf.record.ContinueRecord; +import org.apache.poi.hssf.record.Record; +import org.apache.poi.hssf.record.RecordFactory; +import org.apache.poi.hssf.record.RecordInputStream; +import org.apache.poi.poifs.filesystem.POIFSFileSystem; + +/** + * This is a low-level debugging class, which simply prints out what records come in what order. + * Most people will want to use {@link BiffViewer} or {@link TestEFBiffViewer}, but this can be handy when + * trying to make sense of {@link ContinueRecord} special cases. + *

+ * Output is of the form: + * SID - Length - Type (if known) + * byte0 byte1 byte2 byte3 .... byte(n-4) byte(n-3) byte(n-2) byte(n-1) + */ -@ResourceLock(Resources.SYSTEM_OUT) class TestRecordLister extends BaseTestIteratingXLS { @Override void runOneFile(File fileIn) throws IOException { - PrintStream save = System.out; - try { - // redirect standard out during the test to avoid spamming the console with output - System.setOut(new NullPrintStream()); - - RecordLister viewer = new RecordLister(); - viewer.setFile(fileIn.getAbsolutePath()); - viewer.run(); - } finally { - System.setOut(save); + // replace it with System.out if you like it more verbatim + PrintWriter out = new PrintWriter(new NullWriter()); + + try (POIFSFileSystem fs = new POIFSFileSystem(fileIn, true); + InputStream din = BiffViewer.getPOIFSInputStream(fs)) { + RecordInputStream rinp = new RecordInputStream(din); + + while (rinp.hasNextRecord()) { + int sid = rinp.getNextSid(); + rinp.nextRecord(); + + int size = rinp.available(); + Class clz = RecordFactory.getRecordClass(sid); + + out.printf(Locale.ROOT, "%1$#06x (%1$04d) - %2$#05x (%2$03d) bytes", sid, size); + + if (clz != null) { + out.print(" \t"); + out.print(clz.getSimpleName()); + } + out.println(); + + byte[] data = rinp.readRemainder(); + if (data.length > 0) { + out.print(" "); + out.println(formatData(data)); + } + } } } - //@Test - void testFile() throws IOException { - RecordLister viewer = new RecordLister(); - viewer.setFile(new File("test-data/spreadsheet/testEXCEL_95.xls").getAbsolutePath()); - viewer.run(); + /* + private static String formatSID(int sid) { + String hex = Integer.toHexString(sid); + String dec = Integer.toString(sid); + + StringBuilder s = new StringBuilder(); + s.append("0x"); + for (int i = hex.length(); i < 4; i++) { + s.append('0'); + } + s.append(hex); + + s.append(" ("); + for (int i = dec.length(); i < 4; i++) { + s.append('0'); + } + s.append(dec); + s.append(")"); + + return s.toString(); + } + + private static String formatSize(int size) { + String hex = Integer.toHexString(size); + String dec = Integer.toString(size); + + final String MAX_DIGITS = "000"; + + StringBuilder s = new StringBuilder(); + s.append(MAX_DIGITS, 0, Math.max(MAX_DIGITS.length()-hex.length(),0)); + s.append(hex); + + s.append(" ("); + s.append(MAX_DIGITS, 0, Math.max(MAX_DIGITS.length()-dec.length(),0)); + s.append(dec); + s.append(')'); + + return s.toString(); + }*/ + + private static String formatData(byte[] data) { + if (data == null || data.length == 0) { + return ""; + } + + StringBuilder s = new StringBuilder(); + + // If possible, do first 4 and last 4 bytes + final int MAX_BYTES = 9; + int bLen = Math.min(data.length, MAX_BYTES); + for (int i=0; i0) { + s.append(' '); + } + int b; + if (i MAX_BYTES) { + s.append("..."); + continue; + } else { + b = data[data.length-(bLen-i)]; + } + + // byte to hex + if (b < 0) { + b += 256; + } + if (b < 16) { + s.append('0'); + } + s.append(Integer.toHexString(b)); + } + + return s.toString(); } } diff --git a/poi/src/main/java/org/apache/poi/hssf/dev/package-info.java b/poi/src/test/java/org/apache/poi/hssf/dev/package-info.java similarity index 100% rename from poi/src/main/java/org/apache/poi/hssf/dev/package-info.java rename to poi/src/test/java/org/apache/poi/hssf/dev/package-info.java -- 2.39.5