]> source.dussan.org Git - poi.git/commitdiff
Move org.apache.poi.hssf.dev classes from poi:main to poi:test
authorAndreas Beeker <kiwiwings@apache.org>
Mon, 15 Nov 2021 00:03:44 +0000 (00:03 +0000)
committerAndreas Beeker <kiwiwings@apache.org>
Mon, 15 Nov 2021 00:03:44 +0000 (00:03 +0000)
Use CloseShieldOutputStream instead of CloseIgnoringInputStream

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1895041 13f79535-47bb-0310-9956-ffa450edef68

22 files changed:
poi-integration/src/test/java/org/apache/poi/stress/HSSFFileHandler.java
poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/temp/AesZipFileZipEntrySource.java
poi/src/main/java/org/apache/poi/hssf/dev/BiffDrawingToXml.java [deleted file]
poi/src/main/java/org/apache/poi/hssf/dev/BiffViewer.java [deleted file]
poi/src/main/java/org/apache/poi/hssf/dev/EFBiffViewer.java [deleted file]
poi/src/main/java/org/apache/poi/hssf/dev/FormulaViewer.java [deleted file]
poi/src/main/java/org/apache/poi/hssf/dev/ReSave.java [deleted file]
poi/src/main/java/org/apache/poi/hssf/dev/RecordLister.java [deleted file]
poi/src/main/java/org/apache/poi/hssf/dev/package-info.java [deleted file]
poi/src/main/java/org/apache/poi/hssf/record/RecordInputStream.java
poi/src/main/java/org/apache/poi/util/CloseIgnoringInputStream.java [deleted file]
poi/src/main/java9/module-info.class
poi/src/main/java9/module-info.java
poi/src/test/java/org/apache/poi/hssf/dev/BiffDumpingStream.java [new file with mode: 0644]
poi/src/test/java/org/apache/poi/hssf/dev/BiffViewer.java [new file with mode: 0644]
poi/src/test/java/org/apache/poi/hssf/dev/TestBiffDrawingToXml.java
poi/src/test/java/org/apache/poi/hssf/dev/TestBiffViewer.java
poi/src/test/java/org/apache/poi/hssf/dev/TestEFBiffViewer.java
poi/src/test/java/org/apache/poi/hssf/dev/TestFormulaViewer.java
poi/src/test/java/org/apache/poi/hssf/dev/TestReSave.java [deleted file]
poi/src/test/java/org/apache/poi/hssf/dev/TestRecordLister.java
poi/src/test/java/org/apache/poi/hssf/dev/package-info.java [new file with mode: 0644]

index 62e0d7cbc30ec67c1e49d33cc1f5432f81ab974a..b009d2ea0b96a7b897b0a3207de977d018a35593 100644 (file)
@@ -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
index f66808354afdc3c020230c56a00c891299bfcefb..0e0655781221f85773fd5219dd1d24d5d6dda33d 100644 (file)
@@ -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 <code>ZipEntrySource</code> 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 (file)
index 3fc3f5d..0000000
+++ /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<Integer> getIndexesByName(String[] params, HSSFWorkbook workbook) {
-        List<Integer> 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<Integer> getIndexesByIdArray(String[] params) {
-        List<Integer> 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<Integer> getSheetsIndexes(String[] params, HSSFWorkbook workbook) {
-        List<Integer> 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   <indexes>   output sheets with specified indexes");
-            System.out.println("  -sheet-namek  <names>        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("<workbook>\n");
-            String tab = "\t";
-            if (!excludeWorkbookRecords && r != null) {
-                r.decode();
-                List<EscherRecord> escherRecords = r.getEscherRecords();
-                for (EscherRecord record : escherRecords) {
-                    builder.append(record.toXml(tab));
-                }
-            }
-            List<Integer> sheets = getSheetsIndexes(params, workbook);
-            for (Integer i : sheets) {
-                HSSFPatriarch p = workbook.getSheetAt(i).getDrawingPatriarch();
-                if (p != null) {
-                    builder.append(tab).append("<sheet").append(i).append(">\n");
-                    builder.append(p.getBoundAggregate().toXml(tab + "\t"));
-                    builder.append(tab).append("</sheet").append(i).append(">\n");
-                }
-            }
-            builder.append("</workbook>\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 (file)
index 006194d..0000000
+++ /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<nArgs; i++) {
-                String arg = args[i];
-                if (arg.startsWith("--")) {
-                    if ("--biffhex".equals(arg)) {
-                        biffhex = true;
-                    } else if ("--noint".equals(arg)) {
-                        noint = true;
-                    } else if ("--out".equals(arg)) {
-                        out = true;
-                    } else if ("--escher".equals(arg)) {
-                        System.setProperty("poi.deserialize.escher", "true");
-                    } else if ("--rawhex".equals(arg)) {
-                        rawhex = true;
-                    } else if ("--noheader".equals(arg)) {
-                        noheader = true;
-                    } else {
-                        throw new CommandParseException("Unexpected option '" + arg + "'");
-                    }
-                    continue;
-                }
-                file = new File(arg);
-                if (!file.exists()) {
-                    throw new CommandParseException("Specified file '" + arg + "' does not exist");
-                }
-                if (i+1<nArgs) {
-                    throw new CommandParseException("File name must be the last arg");
-                }
-            }
-            if (file == null) {
-                throw new CommandParseException("Biff viewer needs a filename");
-            }
-            return new CommandArgs(biffhex, noint, out, rawhex, noheader, file);
-        }
-        boolean shouldDumpBiffHex() {
-            return _biffhex;
-        }
-        boolean shouldDumpRecordInterpretations() {
-            return !_noint;
-        }
-        boolean shouldOutputToFile() {
-            return _out;
-        }
-        boolean shouldOutputRawHexOnly() {
-            return _rawhex;
-        }
-        boolean suppressHeader() {
-            return _noHeader;
-        }
-        public File getFile() {
-            return _file;
-        }
-    }
-    private static final class CommandParseException extends Exception {
-        CommandParseException(String msg) {
-            super(msg);
-        }
-    }
-
-    /**
-     * Method main with 1 argument just run straight biffview against given
-     * file<p>
-     *
-     * <b>Usage</b>:<p>
-     *
-     * BiffViewer [--biffhex] [--noint] [--noescher] [--out] &lt;fileName&gt;<p>
-     * BiffViewer --rawhex  [--out] &lt;fileName&gt;
-     *
-     * <table>
-     * <caption>BiffViewer options</caption>
-     * <tr><td>--biffhex</td><td>show hex dump of each BIFF record</td></tr>
-     * <tr><td>--noint</td><td>do not output interpretation of BIFF records</td></tr>
-     * <tr><td>--out</td><td>send output to &lt;fileName&gt;.out</td></tr>
-     * <tr><td>--rawhex</td><td>output raw hex dump of whole workbook stream</td></tr>
-     * <tr><td>--escher</td><td>turn on deserialization of escher records (default is off)</td></tr>
-     * <tr><td>--noheader</td><td>do not print record header (default is on)</td></tr>
-     * </table>
-     *
-     * @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<String> _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<String> getRecentHeaders() {
-            List<String> 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 (file)
index 8fe58a1..0000000
+++ /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 (file)
index 20aa00f..0000000
+++ /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<org.apache.poi.hssf.record.Record> 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 (file)
index 2254ed5..0000000
+++ /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 (file)
index fd6c8ef..0000000
+++ /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<? extends Record> 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/dev/package-info.java b/poi/src/main/java/org/apache/poi/hssf/dev/package-info.java
deleted file mode 100644 (file)
index 74d4c09..0000000
+++ /dev/null
@@ -1,25 +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.
-==================================================================== */
-
-/**
- * DEV package serves two purposes:
- * <ol>
- * <li>Examples for how to use HSSF
- * <li>tools for developing and validating HSSF
- * </ol>
- */
-package org.apache.poi.hssf.dev;
index 6cb3aacb5e322fcd72bc71304470c57676bfa0b0..62535e50b8e083dc1696a68747c1fa20b43595fb 100644 (file)
@@ -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 (file)
index 82247b1..0000000
+++ /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
-   }
-}
index 7ca4ecd882a04d09234b79b8f0809aa79580adb5..a27868cdf4ab031ce77f7eff01d32dadd450c52a 100644 (file)
Binary files a/poi/src/main/java9/module-info.class and b/poi/src/main/java9/module-info.class differ
index 6e7fe11df8b8eaa13e947af34b7e1f726fe2c79f..389b4b5a85d8cfd1cdb5a25ac5422af1ef31be2d 100644 (file)
@@ -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 (file)
index 0000000..0591b35
--- /dev/null
@@ -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 (file)
index 0000000..274c140
--- /dev/null
@@ -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<String> _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;
+        }
+    }
+}
index fae6c5f766dc6a9be840943a2007e93b058f5883..3613d1b68cd85f3c2bce8dc01fe427f34503c74e 100644 (file)
@@ -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("<workbook>\n");
+            String tab = "\t";
+            if (!EXCLUDE_WORKBOOK && r != null) {
+                r.decode();
+                List<EscherRecord> 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("<sheet").append(i).append(">\n");
+                    builder.append(p.getBoundAggregate().toXml(tab + "\t"));
+                    builder.append(tab).append("</sheet").append(i).append(">\n");
+                    i++;
+                }
+            }
+            builder.append("</workbook>\n");
+            fos.write(builder.toString().getBytes(StringUtil.UTF8));
         }
     }
+
+    private static List<HSSFSheet> getSheets(HSSFWorkbook workbook) {
+        List<Integer> sheetIdx = Arrays.stream(SHEET_IDX).boxed().collect(Collectors.toList());
+        List<String> sheetNms = Arrays.stream(SHEET_NAMES).collect(Collectors.toList());
+
+        List<HSSFSheet> 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;
+    }
 }
index 790ca14b5832d505e7439f8a24aaaf19d4555203..1059213502c72bea507f8f5c99a4ff7eb4a57d1e 100644 (file)
 ==================================================================== */
 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"));
-//    }
 }
index 49245116489b40f284ea2a78cd5de4f1040b4916..bcd91e74d7a12d7adc023acef41153511d29d093 100644 (file)
@@ -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<String, Class<? extends Throwable>> 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();
-    }
 }
index d8e50f134b0299664eb3cd2475a8a74640c883c6..177871e25b29ce65d505343096a442ee413eac33 100644 (file)
 ==================================================================== */
 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<String, Class<? extends Throwable>> 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<FormulaRecord, String> 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 (file)
index 475ff2a..0000000
+++ /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<String, Class<? extends Throwable>> getExcludes() {
-        Map<String, Class<? extends Throwable>> 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"));
-    }
-}
index a0462b30c52aa74678990220e6f8a866c68bbb0e..dbcb3c06180706fd46d41a3ba67013fe6698d769 100644 (file)
@@ -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.
+ * <p>
+ * 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<? extends Record> 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; i<bLen; i++) {
+            if (i>0) {
+                s.append(' ');
+            }
+            int b;
+            if (i<MAX_BYTES/2) {
+                b = data[i];
+            } else if (i == MAX_BYTES/2 && data.length > 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/test/java/org/apache/poi/hssf/dev/package-info.java b/poi/src/test/java/org/apache/poi/hssf/dev/package-info.java
new file mode 100644 (file)
index 0000000..74d4c09
--- /dev/null
@@ -0,0 +1,25 @@
+/* ====================================================================
+   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.
+==================================================================== */
+
+/**
+ * DEV package serves two purposes:
+ * <ol>
+ * <li>Examples for how to use HSSF
+ * <li>tools for developing and validating HSSF
+ * </ol>
+ */
+package org.apache.poi.hssf.dev;