Browse Source

Move org.apache.poi.hssf.dev classes from poi:main to poi:test

Use CloseShieldOutputStream instead of CloseIgnoringInputStream

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1895041 13f79535-47bb-0310-9956-ffa450edef68
tags/REL_5_2_0
Andreas Beeker 2 years ago
parent
commit
27b4bdc3f7
21 changed files with 799 additions and 1665 deletions
  1. 2
    5
      poi-integration/src/test/java/org/apache/poi/stress/HSSFFileHandler.java
  2. 12
    17
      poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/temp/AesZipFileZipEntrySource.java
  3. 0
    162
      poi/src/main/java/org/apache/poi/hssf/dev/BiffDrawingToXml.java
  4. 0
    713
      poi/src/main/java/org/apache/poi/hssf/dev/BiffViewer.java
  5. 0
    74
      poi/src/main/java/org/apache/poi/hssf/dev/EFBiffViewer.java
  6. 0
    200
      poi/src/main/java/org/apache/poi/hssf/dev/FormulaViewer.java
  7. 0
    71
      poi/src/main/java/org/apache/poi/hssf/dev/ReSave.java
  8. 0
    193
      poi/src/main/java/org/apache/poi/hssf/dev/RecordLister.java
  9. 1
    11
      poi/src/main/java/org/apache/poi/hssf/record/RecordInputStream.java
  10. 0
    38
      poi/src/main/java/org/apache/poi/util/CloseIgnoringInputStream.java
  11. BIN
      poi/src/main/java9/module-info.class
  12. 0
    1
      poi/src/main/java9/module-info.java
  13. 121
    0
      poi/src/test/java/org/apache/poi/hssf/dev/BiffDumpingStream.java
  14. 342
    0
      poi/src/test/java/org/apache/poi/hssf/dev/BiffViewer.java
  15. 75
    2
      poi/src/test/java/org/apache/poi/hssf/dev/TestBiffDrawingToXml.java
  16. 4
    20
      poi/src/test/java/org/apache/poi/hssf/dev/TestBiffViewer.java
  17. 12
    20
      poi/src/test/java/org/apache/poi/hssf/dev/TestEFBiffViewer.java
  18. 108
    24
      poi/src/test/java/org/apache/poi/hssf/dev/TestFormulaViewer.java
  19. 0
    94
      poi/src/test/java/org/apache/poi/hssf/dev/TestReSave.java
  20. 122
    20
      poi/src/test/java/org/apache/poi/hssf/dev/TestRecordLister.java
  21. 0
    0
      poi/src/test/java/org/apache/poi/hssf/dev/package-info.java

+ 2
- 5
poi-integration/src/test/java/org/apache/poi/stress/HSSFFileHandler.java View 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

+ 12
- 17
poi-ooxml/src/main/java/org/apache/poi/poifs/crypt/temp/AesZipFileZipEntrySource.java View 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);
}
}

+ 0
- 162
poi/src/main/java/org/apache/poi/hssf/dev/BiffDrawingToXml.java View File

@@ -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));
}
}

}

+ 0
- 713
poi/src/main/java/org/apache/poi/hssf/dev/BiffViewer.java View File

@@ -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;
}
}
}

+ 0
- 74
poi/src/main/java/org/apache/poi/hssf/dev/EFBiffViewer.java View File

@@ -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");
}
}
}

+ 0
- 200
poi/src/main/java/org/apache/poi/hssf/dev/FormulaViewer.java View File

@@ -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();
}
}
}

+ 0
- 71
poi/src/main/java/org/apache/poi/hssf/dev/ReSave.java View File

@@ -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");
}
}
}
}
}

+ 0
- 193
poi/src/main/java/org/apache/poi/hssf/dev/RecordLister.java View File

@@ -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");
}
}
}

+ 1
- 11
poi/src/main/java/org/apache/poi/hssf/record/RecordInputStream.java View 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()

+ 0
- 38
poi/src/main/java/org/apache/poi/util/CloseIgnoringInputStream.java View File

@@ -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
}
}

BIN
poi/src/main/java9/module-info.class View File


+ 0
- 1
poi/src/main/java9/module-info.java View 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;

+ 121
- 0
poi/src/test/java/org/apache/poi/hssf/dev/BiffDumpingStream.java View File

@@ -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();
}
}

+ 342
- 0
poi/src/test/java/org/apache/poi/hssf/dev/BiffViewer.java View File

@@ -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;
}
}
}

+ 75
- 2
poi/src/test/java/org/apache/poi/hssf/dev/TestBiffDrawingToXml.java View 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;
}
}

+ 4
- 20
poi/src/test/java/org/apache/poi/hssf/dev/TestBiffViewer.java View File

@@ -16,17 +16,10 @@
==================================================================== */
package org.apache.poi.hssf.dev;

import static org.apache.commons.io.output.NullOutputStream.NULL_OUTPUT_STREAM;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.Map;

import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.util.LocaleUtil;
import org.apache.poi.util.RecordFormatException;

class TestBiffViewer extends BaseTestIteratingXLS {
@@ -52,19 +45,10 @@ class TestBiffViewer extends BaseTestIteratingXLS {

@Override
void runOneFile(File fileIn) throws IOException {
try (POIFSFileSystem fs = new POIFSFileSystem(fileIn, true);
InputStream is = BiffViewer.getPOIFSInputStream(fs)) {
// use a NullOutputStream to not write the bytes anywhere for best runtime
PrintWriter dummy = new PrintWriter(new OutputStreamWriter(NULL_OUTPUT_STREAM, LocaleUtil.CHARSET_1252));
BiffViewer.runBiffViewer(dummy, is, true, true, true, false);
}
BiffViewer bv = new BiffViewer();
bv.setInterpretRecords(true);
bv.setDumpBiffHex(true);
bv.parse(fileIn, null);
}

// @Test
// @Disabled("only used for manual tests")
// @SuppressWarnings("java:S2699")
// void testOneFile() throws Exception {
// POIDataSamples samples = POIDataSamples.getSpreadSheetInstance();
// runOneFile(samples.getFile("43493.xls"));
// }
}

+ 12
- 20
poi/src/test/java/org/apache/poi/hssf/dev/TestEFBiffViewer.java View 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();
}
}

+ 108
- 24
poi/src/test/java/org/apache/poi/hssf/dev/TestFormulaViewer.java View File

@@ -16,19 +16,24 @@
==================================================================== */
package org.apache.poi.hssf.dev;

import static org.junit.jupiter.api.Assumptions.assumeTrue;

import java.io.File;
import java.io.PrintStream;
import java.io.InputStream;
import java.io.PrintWriter;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;

import org.apache.commons.io.output.NullWriter;
import org.apache.poi.EncryptedDocumentException;
import org.apache.poi.hssf.model.HSSFFormulaParser;
import org.apache.poi.hssf.record.FormulaRecord;
import org.apache.poi.hssf.record.RecordFactory;
import org.apache.poi.hssf.record.RecordInputStream;
import org.apache.commons.io.output.NullPrintStream;
import org.junit.jupiter.api.parallel.ResourceLock;
import org.junit.jupiter.api.parallel.Resources;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
import org.apache.poi.ss.formula.ptg.FuncPtg;
import org.apache.poi.ss.formula.ptg.Ptg;

@ResourceLock(Resources.SYSTEM_OUT)
class TestFormulaViewer extends BaseTestIteratingXLS {
@Override
protected Map<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());
}
}

+ 0
- 94
poi/src/test/java/org/apache/poi/hssf/dev/TestReSave.java View File

@@ -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"));
}
}

+ 122
- 20
poi/src/test/java/org/apache/poi/hssf/dev/TestRecordLister.java View 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();
}
}

poi/src/main/java/org/apache/poi/hssf/dev/package-info.java → poi/src/test/java/org/apache/poi/hssf/dev/package-info.java View File


Loading…
Cancel
Save