mirror of
https://github.com/AppleCommander/AppleCommander.git
synced 2025-01-18 04:34:25 +00:00
This FileFilter exports AppleWorks Spread Sheet (ASP) files.
This commit is contained in:
parent
b5296d8f15
commit
dcb6dd79db
@ -0,0 +1,275 @@
|
|||||||
|
/*
|
||||||
|
* AppleCommander - An Apple ][ image utility.
|
||||||
|
* Copyright (C) 2002-2003 by Robert Greene
|
||||||
|
* robgreene at users.sourceforge.net
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful, but
|
||||||
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
||||||
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||||
|
* for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||||
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
*/
|
||||||
|
package com.webcodepro.applecommander.storage;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.text.NumberFormat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export an AppleWorks SpreadSheet file.
|
||||||
|
* The spread-sheet file starts with a 300 byte header, followed by
|
||||||
|
* multiple cells.
|
||||||
|
* <p>
|
||||||
|
* See: http://www.gno.org/pub/apple2/doc/apple/filetypes/ftn.1b.xxxx
|
||||||
|
* <p>
|
||||||
|
* Date Created: Feb 23, 2003
|
||||||
|
* @author Rob Greene
|
||||||
|
*/
|
||||||
|
public class AppleWorksSpreadSheetFileFilter implements FileFilter {
|
||||||
|
/**
|
||||||
|
* SSMinVers. Ths minimum version of AppleWorks needed
|
||||||
|
* to read this file. If this file contains version 3.0-
|
||||||
|
* specific functions (such as calculated labels or new
|
||||||
|
* functions), this byte will contain the version number
|
||||||
|
* 30 ($1E). Otherwise it will be zero ($00).
|
||||||
|
*/
|
||||||
|
private static final int HEADER_SSMINVERS_BYTE = 242;
|
||||||
|
/**
|
||||||
|
* Value Constant mask bytes +000 and +001.
|
||||||
|
*/
|
||||||
|
private static final int CELL_VALUE_CONSTANT_MASK = 0xa0e0;
|
||||||
|
/**
|
||||||
|
* Value Constant id byte +000 and +001.
|
||||||
|
*/
|
||||||
|
private static final int CELL_VALUE_CONSTANT_ID = 0xa000;
|
||||||
|
/**
|
||||||
|
* Value Label mask bytes +000 and +001.
|
||||||
|
*/
|
||||||
|
private static final int CELL_VALUE_LABEL_MASK = 0xa088;
|
||||||
|
/**
|
||||||
|
* Value Label id bytes +000 and +001.
|
||||||
|
*/
|
||||||
|
private static final int CELL_VALUE_LABEL_ID = 0x8088;
|
||||||
|
/**
|
||||||
|
* Value Formula mask bytes +000 and +001.
|
||||||
|
*/
|
||||||
|
private static final int CELL_VALUE_FORMULA_MASK = 0xa080;
|
||||||
|
/**
|
||||||
|
* Value Formula id bytes +000 and +001.
|
||||||
|
*/
|
||||||
|
private static final int CELL_VALUE_FORMULA_ID = 0x8080;
|
||||||
|
/**
|
||||||
|
* Propagated Value Cells mask byte +000.
|
||||||
|
*/
|
||||||
|
private static final int CELL_PROPAGATED_VALUE_MASK = 0xa0;
|
||||||
|
/**
|
||||||
|
* Propagated Value Cells id byte +000.
|
||||||
|
*/
|
||||||
|
private static final int CELL_PROPAGATED_VALUE_ID = 0x20;
|
||||||
|
/**
|
||||||
|
* "Regular" Label Cell mask byte +000.
|
||||||
|
*/
|
||||||
|
private static final int CELL_LABEL_VALUE_MASK = 0xe0;
|
||||||
|
/**
|
||||||
|
* "Regular" Label Cell id byte +000.
|
||||||
|
*/
|
||||||
|
private static final int CELL_LABEL_VALUE_ID = 0x00;
|
||||||
|
/**
|
||||||
|
* This is the value that the formulas begin at.
|
||||||
|
*/
|
||||||
|
private static final int FORMULA_OFFSET = 0xc0;
|
||||||
|
/**
|
||||||
|
* These are the formulas starting at FORMULA_OFFSET.
|
||||||
|
*/
|
||||||
|
private static final String[] formulaText = {
|
||||||
|
"@Deg", "@Rad", "@Pi", "@True", "@False", "@Not", "@IsBlank",
|
||||||
|
"@IsNA", "@IsError", "@Exp", "@Ln", "@Log", "@Cos", "@Sin",
|
||||||
|
"@Tan", "@ACos", "@ASin", "@ATan2", "@ATan", "@Mod", "@FV",
|
||||||
|
"@PV", "@PMT", "@Term", "@Rate", "@Round", "@Or", "@And",
|
||||||
|
"@Sum", "@Avg", "@Choose", "@Count", "@Error", "@IRR", "@If",
|
||||||
|
"@Int", "@Lookup", "@Max", "@Min", "@NA", "@NPV", "@Sqrt",
|
||||||
|
"@Abs", null, "<>", ">=", "<=", "=", ">", "<", ",", "^",
|
||||||
|
")", "-", "+", "/", "*", "(", "-", "+", "..", null, null,
|
||||||
|
null
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* The @Error formula requires 3 trailing zero bytes.
|
||||||
|
*/
|
||||||
|
private static final int FORMULA_ERROR_CODE = 0xe0;
|
||||||
|
/**
|
||||||
|
* The @NA formula requires 3 trailing zero bytes.
|
||||||
|
*/
|
||||||
|
private static final int FORMULA_NA_CODE = 0xe7;
|
||||||
|
/**
|
||||||
|
* The $FD formula control byte indicates the next 8 bytes
|
||||||
|
* are a SANE number.
|
||||||
|
*/
|
||||||
|
private static final int FORMULA_SANE_CODE = 0xfd;
|
||||||
|
/**
|
||||||
|
* The $FE formula control byte indicates that the next
|
||||||
|
* 3 bytes indicate row, column reference.
|
||||||
|
*/
|
||||||
|
private static final int FORMULA_ROW_COLUMN_CODE = 0xfe;
|
||||||
|
/**
|
||||||
|
* The $FF formula control byte indicates that a Pascal
|
||||||
|
* string follows. The next byte is the length of the string.
|
||||||
|
*/
|
||||||
|
private static final int FORMULA_STRING_CODE = 0xff;
|
||||||
|
/**
|
||||||
|
* Process the given FileEntry and return a byte array with filtered data.
|
||||||
|
* @see com.webcodepro.applecommander.storage.FileFilter#filter(FileEntry)
|
||||||
|
*/
|
||||||
|
public byte[] filter(FileEntry fileEntry) {
|
||||||
|
byte[] fileData = fileEntry.getFileData();
|
||||||
|
ByteArrayOutputStream byteArray = new ByteArrayOutputStream();
|
||||||
|
PrintWriter printWriter = new PrintWriter(byteArray, true);
|
||||||
|
boolean isVersion30 = (fileData[HEADER_SSMINVERS_BYTE] == 30);
|
||||||
|
int offset = 300 + (isVersion30 ? 2 : 0);
|
||||||
|
int rowLength = AppleUtil.getWordValue(fileData, offset);
|
||||||
|
while (rowLength != 0xffff) {
|
||||||
|
int rowNumber = AppleUtil.getWordValue(fileData, offset+2);
|
||||||
|
offset+= 4;
|
||||||
|
processRow(printWriter, fileData, offset, rowNumber);
|
||||||
|
offset+= rowLength - 2;
|
||||||
|
rowLength = AppleUtil.getWordValue(fileData, offset);
|
||||||
|
}
|
||||||
|
// return CSV file:
|
||||||
|
return byteArray.toByteArray();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Give suggested file name.
|
||||||
|
* @see com.webcodepro.applecommander.storage.FileFilter#getSuggestedFileName(FileEntry)
|
||||||
|
*/
|
||||||
|
public String getSuggestedFileName(FileEntry fileEntry) {
|
||||||
|
return fileEntry.getFilename() + ".csv";
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Process an entire row.
|
||||||
|
*/
|
||||||
|
public void processRow(PrintWriter printWriter, byte[] fileData, int offset, int rowNumber) {
|
||||||
|
int column = 0;
|
||||||
|
while (true) {
|
||||||
|
int rowControl = AppleUtil.getUnsignedByte(fileData[offset]);
|
||||||
|
if (rowControl <= 0x7f) { // process row
|
||||||
|
if (column > 0) printWriter.print(",");
|
||||||
|
processCell(printWriter, fileData, offset+1, rowControl,
|
||||||
|
rowNumber, column);
|
||||||
|
offset+= rowControl;
|
||||||
|
} else if (rowControl < 0xff) { // skip rows
|
||||||
|
if (column > 0) printWriter.print(",");
|
||||||
|
int columns = rowControl - 0x80;
|
||||||
|
skipColumns(column, printWriter, columns);
|
||||||
|
column+= columns;
|
||||||
|
} else { // end of row ($FF)
|
||||||
|
printWriter.println();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
offset++;
|
||||||
|
column++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Skip the given number of columns.
|
||||||
|
*/
|
||||||
|
protected void skipColumns(int column, PrintWriter printWriter, int columns) {
|
||||||
|
while (columns > 0) {
|
||||||
|
if (column > 0) printWriter.print(",");
|
||||||
|
printWriter.print("\",\"");
|
||||||
|
columns--;
|
||||||
|
column++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Process an individual cell.
|
||||||
|
*/
|
||||||
|
protected void processCell(PrintWriter printWriter, byte[] fileData,
|
||||||
|
int offset, int length, int currentRow, int currentColumn) {
|
||||||
|
int byte0 = AppleUtil.getUnsignedByte(fileData[offset]);
|
||||||
|
int byte1 = AppleUtil.getUnsignedByte(fileData[offset+1]);
|
||||||
|
int cellFlag = (byte0 << 8) + byte1;
|
||||||
|
if ((cellFlag & CELL_VALUE_CONSTANT_MASK) == CELL_VALUE_CONSTANT_ID) {
|
||||||
|
double value = AppleUtil.getSaneNumber(fileData, offset+2);
|
||||||
|
printWriter.print(value);
|
||||||
|
} else if ((cellFlag & CELL_VALUE_LABEL_MASK) == CELL_VALUE_LABEL_ID) {
|
||||||
|
// This is AW 3.0 or later, skipping until an example is found.
|
||||||
|
} else if ((cellFlag & CELL_VALUE_FORMULA_MASK) == CELL_VALUE_FORMULA_ID) {
|
||||||
|
int i = 10;
|
||||||
|
printWriter.print('"');
|
||||||
|
while (i < length) {
|
||||||
|
int controlByte = AppleUtil.getUnsignedByte(fileData[offset+i]);
|
||||||
|
i++;
|
||||||
|
switch (controlByte) {
|
||||||
|
case FORMULA_ERROR_CODE:
|
||||||
|
i+= 3; // skip 3 zero bytes
|
||||||
|
break;
|
||||||
|
case FORMULA_NA_CODE:
|
||||||
|
i+= 3; // skip 3 zero bytes
|
||||||
|
break;
|
||||||
|
case FORMULA_SANE_CODE:
|
||||||
|
double value = AppleUtil.getSaneNumber(fileData, offset+i);
|
||||||
|
printWriter.print(value);
|
||||||
|
i+= 8; // skip past SANE number
|
||||||
|
break;
|
||||||
|
case FORMULA_ROW_COLUMN_CODE:
|
||||||
|
int column = fileData[offset+i];
|
||||||
|
int row = AppleUtil.getSignedWordValue(fileData, offset+i+1);
|
||||||
|
printWriter.print(getColumnReference(currentColumn + column));
|
||||||
|
printWriter.print(getRowReference(currentRow + row));
|
||||||
|
i+= 3; // skip past row/column reference
|
||||||
|
break;
|
||||||
|
case FORMULA_STRING_CODE:
|
||||||
|
String string = AppleUtil.getPascalString(fileData, offset+i);
|
||||||
|
printWriter.print(string);
|
||||||
|
i+= string.length() + 1; // skip past string
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
printWriter.print(formulaText[controlByte - FORMULA_OFFSET]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printWriter.print('"');
|
||||||
|
} else if ((byte0 & CELL_PROPAGATED_VALUE_MASK) == CELL_PROPAGATED_VALUE_ID) {
|
||||||
|
char ch = (char) AppleUtil.getUnsignedByte(fileData[offset+1]);
|
||||||
|
printWriter.print('"');
|
||||||
|
for (int i=0; i<8; i++) { // 8 is an arbitrary cell width
|
||||||
|
printWriter.print(ch);
|
||||||
|
}
|
||||||
|
printWriter.print('"');
|
||||||
|
} else if ((byte0 & CELL_LABEL_VALUE_MASK) == CELL_LABEL_VALUE_ID) {
|
||||||
|
String string = AppleUtil.getString(fileData, offset+1, length-1);
|
||||||
|
printWriter.print('"');
|
||||||
|
printWriter.print(string);
|
||||||
|
printWriter.print('"');
|
||||||
|
} else {
|
||||||
|
printWriter.print("\"Unknown Cell Contents!\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Build a column reference (convert to A or whatever it should be).
|
||||||
|
*/
|
||||||
|
protected String getColumnReference(int column) {
|
||||||
|
int pos1 = column / 26;
|
||||||
|
int pos2 = column % 26;
|
||||||
|
StringBuffer buf = new StringBuffer();
|
||||||
|
if (pos1 > 0) {
|
||||||
|
buf.append((char) '@' + pos1);
|
||||||
|
}
|
||||||
|
buf.append((char)('@' + pos2));
|
||||||
|
return buf.toString();
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Build a row reference.
|
||||||
|
*/
|
||||||
|
protected String getRowReference(int row) {
|
||||||
|
NumberFormat formatter = NumberFormat.getIntegerInstance();
|
||||||
|
return formatter.format(row);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user