dmolony-DiskBrowser/src/com/bytezone/diskbrowser/visicalc/Sheet.java

514 lines
15 KiB
Java
Raw Normal View History

2016-03-04 03:56:28 +00:00
package com.bytezone.diskbrowser.visicalc;
2015-06-01 09:35:51 +00:00
import java.text.DecimalFormat;
2016-03-01 22:02:52 +00:00
import java.util.*;
2015-06-01 09:35:51 +00:00
import java.util.regex.Matcher;
import java.util.regex.Pattern;
2016-02-24 21:11:14 +00:00
import com.bytezone.diskbrowser.utilities.HexFormatter;
2015-06-01 09:35:51 +00:00
2016-03-06 08:05:32 +00:00
public class Sheet implements Iterable<Cell>
2015-06-01 09:35:51 +00:00
{
2016-02-04 00:31:44 +00:00
private static final Pattern addressPattern =
Pattern.compile ("([A-B]?[A-Z])([0-9]{1,3}):");
2016-03-07 12:16:11 +00:00
// private static final Pattern functionPattern = Pattern
// .compile ("\\(([A-B]?[A-Z])([0-9]{1,3})\\.\\.\\.([A-B]?[A-Z])([0-9]{1,3})\\)?");
// private static final Pattern addressList = Pattern.compile ("\\(([^,]+(,[^,]+)*)\\)");
2015-06-01 09:35:51 +00:00
2016-03-06 08:05:32 +00:00
private final Map<Integer, Cell> sheet = new TreeMap<Integer, Cell> ();
2015-06-01 09:35:51 +00:00
private final Map<String, Double> functions = new HashMap<String, Double> ();
2016-03-06 08:05:32 +00:00
Cell currentCell = null;
2015-06-01 09:35:51 +00:00
char defaultFormat;
2016-03-04 05:27:14 +00:00
private final Map<Integer, Integer> columnWidths = new TreeMap<Integer, Integer> ();
int columnWidth = 12;
2015-06-01 09:35:51 +00:00
// Maximum cell = BK254
//Commands:
// /G<address> goto
// /B blank the cell
// /C clear and home
// A-Z label (" to force a label)
// 0-9.+-()*/@# value (+ to force a value)
// /S Storage (ISLDWR)
// /SI Storage init
// /SS Storage Save
// /SL Storage Load
// /SD Storage Delete
// /SW Storage Write (to cassette)
// /SR Storage Read (from cassette)
// /R Replicate
// /G Global (CORF)
// /GF Global Format (DGILR$*)
// /GFI Global Format Integer
// /GF$ Global Format Currency
// /GC Global Column <width>
// /GR Global
2016-03-06 04:41:15 +00:00
// /GRA
2015-06-01 09:35:51 +00:00
// /GO Global
2016-03-06 04:41:15 +00:00
// /GOC
2015-06-01 09:35:51 +00:00
// /T Titles (HVBN)
// /TH fix Horizontal Titles
// /TV fix Vertical Titles
// /TB fix Both Titles
// /TN fix Neither
// /W Window (HV1SU)
// /WV Window Vertical (split on cursor column)
// /WH Window Horizontal (split on cursor row)
2016-02-04 00:31:44 +00:00
/*
from: Apple II TextFiles
http://www.textfiles.com/apple/
*----------------------*
VISICALC COMMAND CHART
BY: NIGHT HAWK
*----------------------*
/B SET AN ENTRY TO BLANK
/C CLEARS THE SHEET, SETTING ALL ENTRIES TO BLANK
/D DELETES THE ROW(/DR) OR COLUMN(/DC) ON WHICH THE CURSOR
LIES.
/E ALLOWS EDITING OF THE ENTRY CONTENTS OF ANY ENTRY POSITION
BY REDISPLAYING IT ON THE EDIT LINE. USE <- -> KEYS & ESC.
/F SETS THE DISPLAY FORMAT OF AN ENTRY TO ONE OF THE FOLLOWING
FORMATS:
/FG GENERAL
/FI INTEGER
/F$ DOLLAR AND CENTS
/FL LEFT JUSTIFIED
/FR RIGHT JUSTIFIED
2016-03-01 22:51:56 +00:00
/F* GRAPH
/FD DEFAULT
2016-02-04 00:31:44 +00:00
/G GLOBAL COMMANDS. THESE APPLY TO THE ENTIRE SHEET OR WINDOW.
/GC SETS COLUMN WIDTH
/GF SETS THE GLOBAL DEFAULT FORMAT
/GO SETS THE ORDER OF RECALCULATION TO BE DOWN THE
COLUMNS OR ACROSS THE ROWS
/GR SETS RECALCULATION TO BE AUTOMATIC(/GRA) OR MANUAL(/GRM).
/I INSERTS A ROW(/IR) OR A COLUMN(/IC)
/M MOVES AN ENTIRE ROW OR COLUMN TO A NEW POSITION.
/P PRINT COMMAND
/R REPLICATE COMMAND
/S STORAGE COMMANDS ARE AS FOLLOWS:
2016-03-01 22:51:56 +00:00
/SS SAVE
/SL LOAD
/SD DELETES SPECIFIED FILE ON DISK
2016-02-04 00:31:44 +00:00
/SI INITIALIZE A DISK ON SPECIFIED DRIVE
/SQ QUITS VISICALC
/T SETS A HORIZONTAL TITLE AREA(/TH), A VERTICAL TITLE AREA
(/TV), SET BOTH A HORIZONTAL & VERTICAL TITLE AREA(/TB)
OR RESETS THE WINDOWS TO HAVE NO TITLE AREAS(/TN)
/V DISPLAYS VISICALC'S VERSION NUMBER ON THE PROMPT LINE
/W WINDOW CONTROL
/WH HORIZONTAL WINDOW
/WV VERTICAL WINDOW
/W1 RETURNS SCREEN TO ONE WINDOW
/WS SYNCHRONIZED WINDOWS
/WU UNSYNCHRONIZED
/- REPEATING LABEL
*/
2016-03-06 08:05:32 +00:00
public Sheet (byte[] buffer)
2015-06-01 09:35:51 +00:00
{
2016-03-04 05:27:14 +00:00
int last = buffer.length;
while (buffer[--last] == 0)
;
2015-06-01 09:35:51 +00:00
2016-03-04 05:27:14 +00:00
int ptr = 0;
2016-03-06 23:52:46 +00:00
final List<String> lines = new ArrayList<String> ();
while (ptr < last)
2015-06-01 09:35:51 +00:00
{
2016-03-06 23:52:46 +00:00
int length = getLineLength (buffer, ptr);
String line = HexFormatter.getString (buffer, ptr, length);
lines.add (line);
processLine (line);
ptr += length + 1; // +1 for end-of-line token
2015-06-01 09:35:51 +00:00
}
2016-03-05 21:53:29 +00:00
if (true)
2016-03-04 05:27:14 +00:00
{
2016-03-06 04:41:15 +00:00
System.out.println ();
System.out.println ("Lines:");
2016-03-05 21:53:29 +00:00
for (String line : lines)
System.out.println (line);
System.out.println ();
2016-03-06 04:41:15 +00:00
System.out.println ("Cells:");
2016-03-06 08:05:32 +00:00
for (Cell cell : sheet.values ())
2015-06-01 09:35:51 +00:00
System.out.println (cell);
2016-02-04 00:31:44 +00:00
2016-03-05 21:53:29 +00:00
System.out.println ();
2016-03-06 04:41:15 +00:00
System.out.println ("Column widths:");
2016-03-05 21:53:29 +00:00
System.out.printf ("Default width : %3d%n", columnWidth);
2016-03-04 05:27:14 +00:00
for (Map.Entry<Integer, Integer> entry : columnWidths.entrySet ())
2016-03-05 21:53:29 +00:00
System.out.printf (" column %3d: %3d%n", entry.getKey (), entry.getValue ());
2016-03-04 05:27:14 +00:00
}
}
2016-03-06 23:52:46 +00:00
private int getLineLength (byte[] buffer, int offset)
2016-03-04 05:27:14 +00:00
{
2016-03-06 23:52:46 +00:00
int ptr = offset;
while (buffer[ptr] != (byte) 0x8D) // end-of-line token
2016-03-04 05:27:14 +00:00
ptr++;
2016-03-06 23:52:46 +00:00
return ptr - offset;
2015-06-01 09:35:51 +00:00
}
2016-03-06 23:52:46 +00:00
private void processLine (String command)
2015-06-01 09:35:51 +00:00
{
2016-03-01 22:51:56 +00:00
// NB no closing bracket: [>K11:@SUM(J11...F11]
2016-03-06 04:41:15 +00:00
if (command.isEmpty ())
{
System.out.println ("empty command");
return;
}
2016-03-01 22:02:52 +00:00
if (command.startsWith (">")) // GOTO cell
2015-06-01 09:35:51 +00:00
{
Matcher m = addressPattern.matcher (command);
if (m.find ())
{
Address address = new Address (m.group (1), m.group (2));
2016-03-06 04:41:15 +00:00
currentCell = sheet.get (address.sortValue);
2016-03-04 05:27:14 +00:00
int pos = command.indexOf (':'); // end of cell address
2015-06-01 09:35:51 +00:00
command = command.substring (pos + 1);
2016-03-06 04:41:15 +00:00
if (currentCell == null)
2015-06-01 09:35:51 +00:00
{
2016-03-06 08:05:32 +00:00
currentCell = new Cell (this, address);
2016-03-05 21:53:29 +00:00
if (!command.startsWith ("/GCC"))
2016-03-06 04:41:15 +00:00
sheet.put (currentCell.address.sortValue, currentCell);
2015-06-01 09:35:51 +00:00
}
}
else
System.out.printf ("Invalid cell address: %s%n", command);
}
2016-03-01 22:02:52 +00:00
if (command.startsWith ("/")) // command
2015-06-01 09:35:51 +00:00
{
2016-03-04 05:27:14 +00:00
String data = command.substring (1);
2015-06-01 09:35:51 +00:00
char subCommand = command.charAt (1);
switch (subCommand)
{
2016-03-05 02:25:15 +00:00
case 'W': // Window control
2016-03-01 22:51:56 +00:00
// System.out.println (" Window command: " + data);
2015-06-01 09:35:51 +00:00
break;
2016-03-05 02:25:15 +00:00
case 'G': // Global command
2016-03-01 22:51:56 +00:00
// System.out.println (" Global command: " + data);
2016-02-04 00:31:44 +00:00
try
{
if (data.charAt (1) == 'C')
{
if (data.charAt (2) == 'C')
{
int width = Integer.parseInt (data.substring (3));
int column = currentCell.address.column;
columnWidths.put (column, width);
}
else
columnWidth = Integer.parseInt (data.substring (2));
}
else if (data.charAt (1) == 'F')
defaultFormat = data.charAt (2);
}
catch (NumberFormatException e)
{
System.out.printf ("NFE: %s%n", data.substring (2));
}
2015-06-01 09:35:51 +00:00
break;
2016-03-05 02:25:15 +00:00
case 'T': // Set title area
2016-03-01 22:51:56 +00:00
// System.out.println (" Title command: " + data);
2015-06-01 09:35:51 +00:00
break;
2016-03-06 04:41:15 +00:00
case 'X': // Position cursor?
break;
2015-06-01 09:35:51 +00:00
default:
currentCell.doCommand (command);
}
}
2016-03-07 04:37:01 +00:00
else if (command.startsWith ("@")) // function
2015-06-01 09:35:51 +00:00
{
currentCell.doCommand (command);
}
else if (command.startsWith ("\""))
{
currentCell.doCommand (command);
}
else if (command.startsWith ("+"))
{
currentCell.doCommand (command);
}
else if (command.matches ("^[0-9.]+$")) // value
{
currentCell.doCommand (command);
}
else if (command.matches ("^[-A-Z]+$")) // label
{
currentCell.doCommand (command);
}
else
currentCell.doCommand (command); // formula
}
2016-03-07 12:16:11 +00:00
// private double evaluateFunction (String function)
// {
// if (functions.containsKey (function))
// return functions.get (function);
//
// // System.out.println (function);
// double result = 0;
//
// if (function.startsWith ("@IF("))
// {
// return result;
// }
//
// if (function.startsWith ("@LOOKUP("))
// {
// return result;
// }
//
// // Range range = getRange (function);
// // if (range == null)
// // return result;
//
// if (function.startsWith ("@SUM("))
// {
// // for (Address address : range)
// // result += getValue (address);
// String text = function.substring (4, function.length () - 1);
// Sum sum = new Sum (this, text);
// result = sum.getValue ();
// }
// else if (function.startsWith ("@COUNT("))
// {
// // int count = 0;
// // for (Address address : range)
// // {
// // VisicalcCell cell = getCell (address);
// // if (cell != null && cell.hasValue () && cell.getValue () != 0.0)
// // ++count;
// // }
// // result = count;
// String text = function.substring (7, function.length () - 1);
// Count count = new Count (this, text);
// result = count.getValue ();
// }
// else if (function.startsWith ("@MIN("))
// {
// // double min = Double.MAX_VALUE;
// // for (Address address : range)
// // if (min > getValue (address))
// // min = getValue (address);
// String text = function.substring (5, function.length () - 1);
// Min min = new Min (this, text);
// result = min.getValue ();
// }
// else if (function.startsWith ("@MAX("))
// {
// // double max = Double.MIN_VALUE;
// // for (Address address : range)
// // if (max < getValue (address))
// // max = getValue (address);
// // result = max;
// String text = function.substring (5, function.length () - 1);
// Max max = new Max (this, text);
// result = max.getValue ();
// }
// else
// System.out.println ("Unimplemented function: " + function);
//
// functions.put (function, result);
// return result;
// }
// Range getRange (String text)
// {
// Range range = null;
// Matcher m = functionPattern.matcher (text);
// while (m.find ())
// {
// Address fromAddress = new Address (m.group (1), m.group (2));
// Address toAddress = new Address (m.group (3), m.group (4));
// range = new Range (fromAddress, toAddress);
// }
//
// if (range != null)
// return range;
//
// m = addressList.matcher (text);
// while (m.find ())
// {
// String[] cells = m.group (1).split (",");
// range = new Range (cells);
// }
//
// if (range != null)
// return range;
//
// int pos = text.indexOf ("...");
// if (pos > 0)
// {
// String from = text.substring (0, pos);
// String to = text.substring (pos + 3);
// Address fromAddress = new Address (from);
// Address toAddress = new Address (to);
// range = new Range (fromAddress, toAddress);
// }
//
// if (range != null)
// return range;
// System.out.println ("null range : " + text);
//
// return range;
// }
// private double getValue (Address address)
// {
// Cell cell = sheet.get (address.sortValue);
// return cell == null ? 0.0 : cell.getValue ();
// }
// private double getValue (String cellName)
// {
// Address address = new Address (cellName);
// return getValue (address);
// }
2015-06-01 09:35:51 +00:00
2016-03-07 04:37:01 +00:00
Cell getCell (Address address)
2015-06-01 09:35:51 +00:00
{
return sheet.get (address.sortValue);
}
public int size ()
{
return sheet.size ();
}
@Override
2016-03-06 08:05:32 +00:00
public Iterator<Cell> iterator ()
2015-06-01 09:35:51 +00:00
{
return sheet.values ().iterator ();
}
public String getCells ()
{
StringBuilder text = new StringBuilder ();
2016-03-06 04:41:15 +00:00
String longLine;
if (false)
longLine = "%+++++++++++++++++++++++++++++++++++++++++++++++++++++++"
+ "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
else
longLine = " "
+ " ";
2015-06-01 09:35:51 +00:00
DecimalFormat nf = new DecimalFormat ("$#####0.00");
// NumberFormat nf = NumberFormat.getCurrencyInstance ();
int lastRow = 0;
2016-03-05 21:53:29 +00:00
int lastColumn = 0;
2015-06-01 09:35:51 +00:00
2016-03-06 08:05:32 +00:00
for (Cell cell : sheet.values ())
2015-06-01 09:35:51 +00:00
{
while (lastRow < cell.address.row)
{
text.append ("\n");
++lastRow;
2016-03-05 21:53:29 +00:00
lastColumn = 0;
2015-06-01 09:35:51 +00:00
}
2016-03-05 21:53:29 +00:00
while (lastColumn < cell.address.column)
2015-06-01 09:35:51 +00:00
{
2016-02-04 00:31:44 +00:00
int width = columnWidth;
2016-03-05 21:53:29 +00:00
if (columnWidths.containsKey (lastColumn))
width = columnWidths.get (lastColumn);
2016-02-04 00:31:44 +00:00
text.append (longLine.substring (0, width));
2015-06-01 09:35:51 +00:00
++lastColumn;
}
2016-02-04 00:31:44 +00:00
int colWidth = columnWidth;
if (columnWidths.containsKey (cell.address.column))
colWidth = columnWidths.get (cell.address.column);
2016-03-05 21:53:29 +00:00
++lastColumn;
char format = cell.getFormat ();
if (format == ' ')
format = defaultFormat;
2016-02-04 00:31:44 +00:00
2015-06-01 09:35:51 +00:00
if (cell.hasValue ())
{
2016-03-05 21:53:29 +00:00
if (format == 'I')
2016-02-04 00:31:44 +00:00
{
2016-03-06 04:41:15 +00:00
String integerFormat = String.format ("%%%d.0f", colWidth);
2016-03-05 21:53:29 +00:00
// System.out.printf ("Integer format:%s%n", integerFormat);
2015-06-01 09:35:51 +00:00
text.append (String.format (integerFormat, cell.getValue ()));
2016-02-04 00:31:44 +00:00
}
2016-03-05 21:53:29 +00:00
else if (format == '$')
2016-02-04 00:31:44 +00:00
{
String currencyFormat = String.format ("%%%d.%ds", colWidth, colWidth);
2016-03-05 21:53:29 +00:00
// System.out.printf ("Currency format:%s%n", currencyFormat);
2015-06-01 09:35:51 +00:00
text.append (String.format (currencyFormat, nf.format (cell.getValue ())));
2016-02-04 00:31:44 +00:00
}
2016-03-06 04:41:15 +00:00
else if (format == '*')
{
String graphFormat = String.format ("%%-%d.%ds", colWidth, colWidth);
text.append (String.format (graphFormat, "********************"));
}
2015-06-01 09:35:51 +00:00
else
2016-02-04 00:31:44 +00:00
{
2016-03-06 04:41:15 +00:00
// this could be improved
String numberFormat = String.format ("%%%d.3f", colWidth + 4);
2016-03-05 21:53:29 +00:00
// System.out.printf ("Number format:%s%n", numberFormat);
2016-03-06 04:41:15 +00:00
String val = String.format (numberFormat, cell.getValue ());
while (val.endsWith ("0"))
val = ' ' + val.substring (0, val.length () - 1);
if (val.endsWith ("."))
val = ' ' + val.substring (0, val.length () - 1);
if (val.length () > colWidth)
val = val.substring (val.length () - colWidth);
text.append (val);
2016-02-04 00:31:44 +00:00
}
2015-06-01 09:35:51 +00:00
}
else
2016-02-04 00:31:44 +00:00
{
2016-03-05 21:53:29 +00:00
if (format == 'R')
{
String labelFormat = String.format ("%%%d.%ds", colWidth, colWidth);
// System.out.printf ("Label format:%s%n", labelFormat);
text.append (String.format (labelFormat, cell.value ()));
}
else
{
String labelFormat = String.format ("%%-%d.%ds", colWidth, colWidth);
// System.out.printf ("Label format:%s%n", labelFormat);
text.append (String.format (labelFormat, cell.value ()));
}
2016-02-04 00:31:44 +00:00
}
2015-06-01 09:35:51 +00:00
}
return text.toString ();
}
}