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