blob: 0b19d2c0104f49b707f590c44566c70f40dc6fed [file] [log] [blame]
/**************************************************************
*
* 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.openoffice.xmerge.converter.xml.sxc.minicalc;
import jmc.Workbook;
import jmc.Worksheet;
import jmc.CellAttributes;
import jmc.CellDescriptor;
import jmc.JMCconstants;
import jmc.JMCException;
import java.awt.Color;
import java.io.ByteArrayOutputStream;
import java.io.ByteArrayInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import org.openoffice.xmerge.converter.palm.Record;
import org.openoffice.xmerge.util.Debug;
import org.openoffice.xmerge.util.IntArrayList;
import org.openoffice.xmerge.converter.xml.sxc.SpreadsheetEncoder;
import org.openoffice.xmerge.converter.xml.sxc.Format;
import org.openoffice.xmerge.converter.xml.OfficeConstants;
/**
* This class is used by {@link
* org.openoffice.xmerge.converter.xml.sxc.SxcDocumentSerializerImpl
* SxcDocumentSerializerImpl} to encode the MiniCalc format.
*
* @author Paul Rank
*/
final class MinicalcEncoder extends SpreadsheetEncoder {
/** MiniCalc WorkBook to store sheets. */
private Workbook wb;
/** MiniCalc sheet - only one sheet can be open at a time. */
private Worksheet ws;
/**
* Estimate of the number of Palm pixels per character. Used for
* estimating the width of a cell on a Palm device.
*/
private final static int pixelsPerChar = 6;
/**
* The minimum width (in pixels) that we allow a column to be set to
* on a Palm device.
*/
private final static int minWidth = 10;
/**
* The maximum width (in pixels) that we allow a column to be set to
* on a Palm device.
*/
private final static int maxWidth = 80;
/**
* Constructor creates a MiniCalc WorkBook.
*
* @param log Log object for logging.
* @param name The name of the WorkBook.
* @param password The password for the WorkBook.
*
* @throws IOException If any I/O error occurs.
*/
MinicalcEncoder(String name, String password) throws IOException {
super(name, password);
try {
wb = new Workbook(name, password);
}
catch (JMCException e) {
Debug.log(Debug.ERROR, "new Workbook threw exception:" + e.getMessage());
throw new IOException(e.getMessage());
}
}
/**
* This method creates a WorkSheet belonging to the
* WorkBook.
*
* @param sheetName The name of the WorkSheet.
*
* @throws IOException If any I/O error occurs.
*/
public void createWorksheet(String sheetName) throws IOException {
try {
ws = wb.createWorksheet(sheetName);
}
catch (JMCException e) {
Debug.log(Debug.ERROR, "wb.createWorksheet threw exception:" + e.getMessage());
throw new IOException(e.getMessage());
}
}
/**
* This method gets the number of sheets in the WorkBook.
*
* @return The number of sheets in the WorkBook.
*/
public int getNumberOfSheets() {
int numSheets = wb.getNumberOfSheets();
return numSheets;
}
/**
* This method encodes the MiniCalc WorkBook information
* into an palm <code>Record</code> array in MiniCalc
* database format.
*
* @return Array of <code>Record</code> holding MiniCalc
* contents.
*
* @throws IOException If any I/O error occurs.
*/
public Record[] getRecords(int sheetID) throws IOException {
// Get the WorkSheet for the input sheetID
ws = wb.getWorksheet(sheetID);
// Need to call ws.initWrite() before we start querying the WorkSheet
try {
ws.initWrite();
}
catch (JMCException e) {
Debug.log(Debug.ERROR, "ws.initWrite in getRecords:" + e.getMessage());
throw new IOException(e.getMessage());
}
// Get the number of records in the WorkSheet
int numRecords = ws.getNumberOfRecords();
// Create the Record array
Record[] allRecords = new Record[numRecords];
// Get each record from the WorkSheet and store in allRecords[]
try {
for (int i = 0; i < allRecords.length; i++) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int length = ws.writeNextRecord(bos);
byte cBytes[] = bos.toByteArray();
allRecords[i] = new Record(cBytes);
}
}
catch (Exception e) {
Debug.log(Debug.ERROR, "ws.writeNextRecord in getRecords:" + e.getMessage());
throw new IOException(e.getMessage());
}
return allRecords;
}
/**
* A cell reference in a StarOffice formula looks like
* [.C2] (for cell C2). MiniCalc is expecting cell references
* to look like C2. This method strips out the braces and
* the period.
*
* @param formula A StarOffice formula <code>String</code>.
*
* @return A MiniCalc formula <code>String</code>.
*/
protected String parseFormula(String formula) {
StringBuffer inFormula = new StringBuffer(formula);
StringBuffer outFormula = new StringBuffer();
boolean inBrace = false;
boolean firstCharAfterBrace = false;
boolean firstCharAfterColon = false;
int len = inFormula.length();
for (int in = 0; in < len; in++) {
switch (inFormula.charAt(in)) {
case '[':
// We are now inside a StarOffice cell reference.
// We also need to strip out the '['
inBrace = true;
// If the next character is a '.', we want to strip it out
firstCharAfterBrace = true;
break;
case ']':
// We are exiting a StarOffice cell reference
// We are stripping out the ']'
inBrace = false;
break;
case ':':
// We have a cell range reference.
// May need to strip out the leading '.'
if (inBrace)
firstCharAfterColon = true;
outFormula.append(inFormula.charAt(in));
break;
case '.':
if (inBrace == true) {
if (firstCharAfterBrace == false &&
firstCharAfterColon == false) {
// Not the first character after the open brace.
// We have hit a separator between a sheet reference
// and a cell reference. MiniCalc uses a ! as
// this type of separator.
outFormula.append('!');
}
else {
firstCharAfterBrace = false;
firstCharAfterColon = false;
// Since we are in a StarOffice cell reference,
// and we are the first character, we need to
// strip out the '.'
}
break;
} else {
// We hit valid data, lets add it to the formula string
outFormula.append(inFormula.charAt(in));
break;
}
case ';':
// StarOffice XML format uses ';' as a separator. MiniCalc (and
// many spreadsheets) use ',' as a separator instead.
outFormula.append(',');
break;
default:
// We hit valid data, lets add it to the formula string
outFormula.append(inFormula.charAt(in));
// Need to make sure that firstCharAfterBrace is not true.
firstCharAfterBrace = false;
break;
}
}
return outFormula.toString();
}
/**
* Add a cell to the current WorkSheet.
*
* @param row The row number of the cell.
* @param column The column number of the cell.
* @param fmt The <code>Format</code> object describing
* the appearance of this cell.
* @param cellContents The text or formula of the cell's contents.
*
* @throws IOException If any I/O error occurs.
*/
public void addCell(int row, int column, Format fmt, String cellContents) throws IOException {
CellAttributes ca = new CellAttributes(getFormat(fmt),
fmt.getForeground(),
fmt.getBackground());
if (cellContents.startsWith("=")) {
cellContents = parseFormula(cellContents);
Debug.log(Debug.INFO, "YAHOO Found Formula" + cellContents);
}
CellDescriptor cellDes = new CellDescriptor(row, column, ca, cellContents);
try {
ws.putCell(cellDes);
}
catch (JMCException jmce) {
Debug.log(Debug.ERROR, "ws.putCell threw exception: " + jmce.getMessage());
throw new IOException(jmce.getMessage());
}
}
/**
* Set the width of the columns in the WorkBook.
*
* @param columnWidths An <code>IntArrayList</code> of column
* widths.
*/
public void setColumnWidths(IntArrayList columnWidths) throws IOException {
// Get the number of columns
int numColumns = columnWidths.size();
// Return if there are no columns in the listr
if (numColumns == 0) {
return;
}
// Need to set the FORM_FLAGS_NONDEFAULT flag for the column widths
// to be used in MiniCalc
long format = JMCconstants.FORM_FLAGS_NONDEFAULT;
CellAttributes ca = new CellAttributes(format);
try {
for (int i = 0; i < numColumns; i++) {
// Get the column width in Palm pixels
int width = columnWidths.get(i) * pixelsPerChar;
// Check limits on column width
if (width < minWidth) {
width = minWidth;
} else if (width > maxWidth) {
width = maxWidth;
}
// Add the column descriptor to the WorkSheet
ws.putColumn(i + 1, width, ca);
}
}
catch (JMCException jmce) {
Debug.log(Debug.ERROR, "ws.putColumn threw exception: " + jmce.getMessage());
throw new IOException(jmce.getMessage());
}
}
/**
* This method sets the format of a cell to <i>string</i>.
*
* @param format The cell format-may already contain display info,
* such as alignment or font type.
*
* @return The updated format of the cell.
*/
private long setFormatString(long format) {
format = clearCellFormatType(format);
// Set format to generic, since MiniCalc does not have a string type.
format = format | JMCconstants.FF_FORMAT_GENERIC;
return format;
}
/**
* This method sets the format of a cell to <i>floating point</i>.
*
* @param format The cell format. May already contain
* display info, such as alignment or
* font type.
* @param decimalPlaces The number of decimal places to
* set in the floating point number.
*
* @return The updated format of the cell.
*/
private long setFormatFloat(long format, int decimalPlaces) {
format = clearCellFormatType(format);
// Set format to floating point with correct number of decimal places
format = format | JMCconstants.FF_FORMAT_DECIMAL | decimalPlaces;
return format;
}
/**
* This method sets the format of a cell to <i>time</i>.
*
* @param format The cell format-may already contain display info,
* such as alignment or font type.
*
* @return The updated format of the cell.
*/
private long setFormatTime(long format) {
format = clearCellFormatType(format);
// Set format to time.
format = format | JMCconstants.FF_FORMAT_TIME;
return format;
}
/**
* This method sets the format of a cell to <i>date</i>.
*
* @param format The cell format-may already contain display info,
* such as alignment or font type.
*
* @return The updated format of the cell.
*/
private long setFormatDate(long format) {
format = clearCellFormatType(format);
// Set format to date.
format = format | JMCconstants.FF_FORMAT_DATE;
return format;
}
/**
* This method sets the format of a cell to <i>currency</i>.
*
* @param format The cell format-may already contain
* display info, such as alignment or
* font type.
* @param decimalPlaces The number of decimal places to set.
*
* @return The updated format of the cell.
*/
private long setFormatCurrency(long format, int decimalPlaces) {
format = clearCellFormatType(format);
// Set format to Currency with correct number of decimal places
format = format | JMCconstants.FF_FORMAT_CURRENCY | decimalPlaces;
return format;
}
/**
* This method sets the format of a cell to <i>boolean</i>.
*
* @param format The cell format-may already contain display info,
* such as alignment or font type.
*
* @return The updated format of the cell.
*/
private long setFormatBoolean(long format) {
format = clearCellFormatType(format);
// Set format to generic, since MiniCalc does not have a Boolean type.
format = format | JMCconstants.FF_FORMAT_GENERIC;
return format;
}
/**
* This method sets the format of a cell to <i>percent</i>.
*
* @param format The cell format-may already contain
* display info, such as alignment or
* font type.
* @param decimalPlaces The number of decimal places to set.
*
* @return The updated format of the cell.
*/
private long setFormatPercent(long format, int decimalPlaces) {
format = clearCellFormatType(format);
// Set format to Percent with correct number of decimal places
format = format | JMCconstants.FF_FORMAT_PERCENT | decimalPlaces;
return format;
}
/**
* This method clears out the format bits associated with
* the type of data (<i>float</i>, <i>time</i>, etc...) in
* a cell.
*
* @param format The original format for the cell.
*
* @return The updated cell format with the bits associated
* with the type of data (float, time, etc...)
* zeroed out.
*/
private long clearCellFormatType(long format) {
// First 4 bits are for the number of decimal places
// bits 5-8 are for the data format (float, time, etc...)
// Clear out first 8 bits
format = format & 0xFFFFFFFFFFFFFF00L;
return format;
}
/**
* Set a cell's formatting options via a separately create
* <code>Format</code> object.
*
* @param row The row number of the cell to be changed.
* @param column The column number of the cell to be changed.
* @param fmt Object containing formatting settings for
* this cell.
*/
public void setCellFormat(int row, int column, Format fmt) {
}
/**
* Get the names of the sheets in the WorkBook.
*
* @param sheet The required sheet.
*/
public String getSheetName(int sheet) {
return wb.getWorksheet(sheet).getName();
}
/*
* This method returns a MiniCalc style format from the
* <code>Format</code> object.
*/
private long getFormat(Format fmt)
{
String category = fmt.getCategory();
if (category.equalsIgnoreCase(OfficeConstants.CELLTYPE_BOOLEAN)) {
return setFormatBoolean(0);
}
else if (category.equalsIgnoreCase(OfficeConstants.CELLTYPE_CURRENCY)) {
return setFormatCurrency(0, fmt.getDecimalPlaces());
}
else if (category.equalsIgnoreCase(OfficeConstants.CELLTYPE_DATE)) {
return setFormatDate(0);
}
else if (category.equalsIgnoreCase(OfficeConstants.CELLTYPE_FLOAT)) {
return setFormatFloat(0, fmt.getDecimalPlaces());
}
else if (category.equalsIgnoreCase(OfficeConstants.CELLTYPE_PERCENT)) {
return setFormatPercent(0, fmt.getDecimalPlaces());
}
else if (category.equalsIgnoreCase(OfficeConstants.CELLTYPE_STRING)) {
return setFormatString(0);
}
else if (category.equalsIgnoreCase(OfficeConstants.CELLTYPE_TIME)) {
return setFormatTime(0);
}
else {
// Should never get here, but just in case
System.out.println("XXXXX Formatting information not found");
return 0;
}
}
}