From dcb6dd79db92cec64f9dc5594afaad283f0e6f2e Mon Sep 17 00:00:00 2001 From: Robert Greene Date: Wed, 26 Feb 2003 01:30:04 +0000 Subject: [PATCH] This FileFilter exports AppleWorks Spread Sheet (ASP) files. --- .../AppleWorksSpreadSheetFileFilter.java | 275 ++++++++++++++++++ 1 file changed, 275 insertions(+) create mode 100644 src/com/webcodepro/applecommander/storage/AppleWorksSpreadSheetFileFilter.java diff --git a/src/com/webcodepro/applecommander/storage/AppleWorksSpreadSheetFileFilter.java b/src/com/webcodepro/applecommander/storage/AppleWorksSpreadSheetFileFilter.java new file mode 100644 index 0000000..7887257 --- /dev/null +++ b/src/com/webcodepro/applecommander/storage/AppleWorksSpreadSheetFileFilter.java @@ -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. + *

+ * See: http://www.gno.org/pub/apple2/doc/apple/filetypes/ftn.1b.xxxx + *

+ * 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); + } +}