ethercalc
Version:
Multi-User Spreadsheet Server
1,421 lines (1,191 loc) • 227 kB
JavaScript
//
// The main SocialCalc code module of the SocialCalc package
//
/*
// (c) Copyright 2010 Socialtext, Inc.
// All Rights Reserved.
//
// The contents of this file are subject to the Artistic License 2.0; you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at http://socialcalc.org/licenses/al-20/.
//
// Some of the other files in the SocialCalc package are licensed under
// different licenses. Please note the licenses of the modules you use.
//
// Code History:
//
// Initially coded by Dan Bricklin of Software Garden, Inc., for Socialtext, Inc.
// Based in part on the SocialCalc 1.1.0 code written in Perl.
// The SocialCalc 1.1.0 code was:
// Portions (c) Copyright 2005, 2006, 2007 Software Garden, Inc.
// All Rights Reserved.
// Portions (c) Copyright 2007 Socialtext, Inc.
// All Rights Reserved.
// The Perl SocialCalc started as modifications to the wikiCalc(R) program, version 1.0.
// wikiCalc 1.0 was written by Software Garden, Inc.
// Unless otherwise specified, referring to "SocialCalc" in comments refers to this
// JavaScript version of the code, not the SocialCalc Perl code.
//
*/
/*
**** Overview ****
This is the beginning of a library of routines for displaying and editing spreadsheet
data in a browser. The HTML that includes this does not need to have anything
specific to the spreadsheet or editor already present -- everything is dynamically
added to the DOM by this code, including the rendered sheet and any editing controls.
The library has a few parts. This is the main SocialCalc code module.
Other parts are the Table Editor module, the Formula module, and the Format Number module.
Note: The Table Editor module is licensed under a different license than this module.
The class/object style is derived from O'Reilly's JavaScript by Flanagan, 5th Edition,
section 9.3, page 157.
All of the data, object definitions, functions, etc., are stored as properties of the SocialCalc
object so as not to clutter up the global variables nor conflict with other names.
A design goal (not tested yet for success) is to make it possible to have more than one
spreadsheet active on a page, perhaps even open for editing. It is assumed, though, that
there is only one mouse and one keyboard (a good assumption on most PCs today but not in the
new "touch and surface world" Apple and Microsoft are working towards).
The testing has been on Windows Firefox (2 and 3),
Internet Explorer (6 and 7), Opera (9.23 and mainly later), Mac Safari (3.1), and Mac Firefox (2.0.0.6).
There are small issues with Firefox before 2.0 (cosmetic with drag handles) and larger ones
with Opera before 9.5 (the Delete key isn't recognized in some cases -- the 9.5 version was still
in beta and this bug affects other products like GMail, I believe).
The data is stored in a SocialCalc.Sheet object. The data is organized in a form similar to that
used by SocialCalc 1.1.0. There is a function for converting a normal SocialCalc spreadsheet
save data string (the spreadsheet part of a SocialCalc data file) into this internal form.
The SocialCalc.RenderContext class provides methods for rendering a table into the DOM representing
part of the spreadsheet. It is assumed that the spreadsheet could possibly be very large
and that rendering the whole thing at once could be too time consuming. It is also set up so
that it might be possible to have some of the sheet data only be loaded on demand (such as by Ajax).
The rendering can render cells to the right and below the already active area of the spreadsheet
so that you can scroll to that "clean" area without explicitly doing "add row/column". The class also
does simple operations such as "scrolling" within that table. The table may optionally include
row and column headers and may be split into panes. Most of the code assumes any number of panes,
but only the rightmost pane has scrolling code. In normal operation there would be one or two
panes horizontally and vertically. The panes may start on any row/column, though a given row/column
should only appear in one pane at a time (not all code enforces this, yet).
The RenderContext is designed to be rendered as part of a SocialCalc.TableEditor. The TableEditor
includes the spreadsheet grid as well as scrollbars, pane sliders, and (eventually) editing controls.
The layout is dynamic and may be recomputed on the fly, such as in response to resizing the browser
window.
The scrollbars and pane sliders are created using SocialCalc.TableControl objects. These in turn
make use of Dragging, ToolTip, Button, and MouseWheel functions.
The keyboard input is handled by keyboard code.
There are also some helper routines.
More comments yet to come...
*/
var SocialCalc;
if (!SocialCalc) SocialCalc = {};
// *************************************
//
// Shared values
//
// These are "global" values shared by the classes, including default settings
//
// *************************************
// Callbacks
SocialCalc.Callbacks = {
// The next two are used by SocialCalc.format_text_for_display
// The function to expand wiki text - should be set if you want wikitext expansion
// The form is: expand_wiki(displayvalue, sheetobj, linkstyle, valueformat)
// valueformat is text-wiki followed by optional sub-formats, e.g., text-wikipagelink
expand_wiki: null,
expand_markup: function(displayvalue, sheetobj, linkstyle) // the old function to expand wiki text - may be replaced
{return SocialCalc.default_expand_markup(displayvalue, sheetobj, linkstyle);},
// MakePageLink is used to create the href for a link to another "page"
// The form is: MakePageLink(pagename, workspacename, linktyle, valueformat), returns string
MakePageLink: null,
// NormalizeSheetName is used to make different variations of sheetnames use the same cache slot
NormalizeSheetName: null // use default - lowercase
};
// Shared flags
// none at present
// *************************************
//
// Cell class:
//
// *************************************
//
// Class SocialCalc.Cell
//
// Usage: var s = new SocialCalc.Cell(coord);
//
// Cell attributes include:
//
// coord: the column/row as a string, e.g., "A1"
// datavalue: the value to be used for computation and formatting for display,
// string or numeric (tolerant of numbers stored as strings)
// datatype: if present, v=numeric value, t=text value, f=formula,
// or c=constant that is not a simple number (like "$1.20")
// formula: if present, the formula (without leading "=") for computation or the constant
// valuetype: first char is main type, the following are sub-types.
// Main types are b=blank cell, n=numeric, t=text, e=error
// Examples of using sub-types would be "nt" for a numeric time value, "n$" for currency, "nl" for logical
// readonly: if present, whether the current cell is read-only of writable
// displayvalue: if present, rendered version of datavalue with formatting attributes applied
// parseinfo: if present, cached parsed version of formula
//
// The following optional values, if present, are mainly used in rendering, overriding defaults:
//
// bt, br, bb, bl: number of border's definition
// layout: layout (vertical alignment, padding) definition number
// font: font definition number
// color: text color definition number
// bgcolor: background color definition number
// cellformat: cell format (horizontal alignment) definition number
// nontextvalueformat: custom format definition number for non-text values, e.g., numbers
// textvalueformat: custom format definition number for text values
// colspan, rowspan: number of cells to span for merged cells (only on main cell)
// cssc: custom css classname for cell, as text (no special chars)
// csss: custom css style definition
// mod: modification allowed flag "y" if present
// comment: cell comment string
//
SocialCalc.Cell = function(coord) {
this.coord = coord;
this.datavalue = "";
this.datatype = null;
this.formula = "";
this.valuetype = "b";
this.readonly = false;
}
// The types of cell properties
//
// Type 1: Base, Type 2: Attribute, Type 3: Special (e.g., displaystring, parseinfo)
SocialCalc.CellProperties = {
coord: 1, datavalue: 1, datatype: 1, formula: 1, valuetype: 1, errors: 1, comment: 1, readonly: 1,
bt: 2, br: 2, bb: 2, bl: 2, layout: 2, font: 2, color: 2, bgcolor: 2,
cellformat: 2, nontextvalueformat: 2, textvalueformat: 2, colspan: 2, rowspan: 2,
cssc: 2, csss: 2, mod: 2,
displaystring: 3, // used to cache rendered HTML of cell contents
parseinfo: 3, // used to cache parsed formulas
hcolspan: 3, hrowspan: 3 // spans taking hidden cols/rows into account (!!! NOT YET !!!)
};
SocialCalc.CellPropertiesTable = {
bt: "borderstyle", br: "borderstyle", bb: "borderstyle", bl: "borderstyle",
layout: "layout", font: "font", color: "color", bgcolor: "color",
cellformat: "cellformat", nontextvalueformat: "valueformat", textvalueformat: "valueformat"
};
// *************************************
//
// Sheet class:
//
// *************************************
//
// Class SocialCalc.Sheet
//
// Usage: var s = new SocialCalc.Sheet();
//
SocialCalc.Sheet = function() {
SocialCalc.ResetSheet(this);
// Set other values:
//
// sheet.statuscallback(data, status, arg, this.statuscallbackparams) is called
// during recalc and commands.
//
// During recalc, data is the current recalcdata.
// The values for status and the corresponding arg are:
//
// calcorder, {coord: coord, total: celllist length, count: count} [0 or more times per recalc]
// calccheckdone, calclist length [once per recalc]
// calcstep, {coord: coord, total: calclist length, count: count} [0 or more times per recalc]
// calcloading, {sheetname: name-of-sheet}
// calcserverfunc, {funcname: name-of-function, coord: coord, total: calclist length, count: count}
// calcfinished, time in milliseconds [once per recalc]
//
// During commands, data is SocialCalc.SheetCommandInfo.
// These values for status and arg are:
//
// cmdstart, cmdstr
// cmdend
//
this.statuscallback = null; // routine called with cmdstart, calcstart, etc., status and args:
// sheet.statuscallback(data, status, arg, params)
this.statuscallbackparams = null; // parameters passed to that routine
}
//
// SocialCalc.ResetSheet(sheet)
//
// Resets (and/or initializes) sheet data values.
//
SocialCalc.ResetSheet = function(sheet, reload) {
// properties:
sheet.cells = {}; // at least one for each non-blank cell: coord: cell-object
sheet.attribs = // sheet attributes
{
lastcol: 1,
lastrow: 1,
defaultlayout: 0,
usermaxcol: 0,
usermaxrow: 0
};
sheet.rowattribs =
{
hide: {}, // access by row number
height: {}
};
sheet.colattribs =
{
width: {}, // access by col name
hide: {}
};
sheet.names={}; // Each is: {desc: "optional description", definition: "B5, A1:B7, or =formula"}
sheet.layouts=[];
sheet.layouthash={};
sheet.fonts=[];
sheet.fonthash={};
sheet.colors=[];
sheet.colorhash={};
sheet.borderstyles=[];
sheet.borderstylehash={};
sheet.cellformats=[];
sheet.cellformathash={};
sheet.valueformats=[];
sheet.valueformathash={};
sheet.matched_cells=[];
sheet.selected_search_cell=undefined;
sheet.copiedfrom = ""; // if a range, then this was loaded from a saved range as clipboard content
sheet.changes = new SocialCalc.UndoStack();
sheet.renderneeded = false;
sheet.changedrendervalues = true; // if true, spans and/or fonts have changed (set by ExecuteSheetCommand & GetStyle)
sheet.recalcchangedavalue = false; // true if a recalc resulted in a change to a cell's calculated value
sheet.hiddencolrow = ""; // "col" or "row" if it was hidden
sheet.sci = new SocialCalc.SheetCommandInfo(sheet);
}
// Methods:
SocialCalc.Sheet.prototype.ResetSheet = function() {SocialCalc.ResetSheet(this);};
SocialCalc.Sheet.prototype.AddCell = function(newcell) {return this.cells[newcell.coord]=newcell;};
SocialCalc.Sheet.prototype.GetAssuredCell = function(coord) {
return this.cells[coord] || this.AddCell(new SocialCalc.Cell(coord));
};
SocialCalc.Sheet.prototype.ParseSheetSave = function(savedsheet) {SocialCalc.ParseSheetSave(savedsheet,this);};
SocialCalc.Sheet.prototype.CellFromStringParts = function(cell, parts, j) {return SocialCalc.CellFromStringParts(this, cell, parts, j);};
SocialCalc.Sheet.prototype.CreateSheetSave = function(range, canonicalize) {return SocialCalc.CreateSheetSave(this, range, canonicalize);};
SocialCalc.Sheet.prototype.CellToString = function(cell) {return SocialCalc.CellToString(this, cell);};
SocialCalc.Sheet.prototype.CanonicalizeSheet = function(full) {return SocialCalc.CanonicalizeSheet(this, full);};
SocialCalc.Sheet.prototype.EncodeCellAttributes = function(coord) {return SocialCalc.EncodeCellAttributes(this, coord);};
SocialCalc.Sheet.prototype.EncodeSheetAttributes = function() {return SocialCalc.EncodeSheetAttributes(this);};
SocialCalc.Sheet.prototype.DecodeCellAttributes = function(coord, attribs, range) {return SocialCalc.DecodeCellAttributes(this, coord, attribs, range);};
SocialCalc.Sheet.prototype.DecodeSheetAttributes = function(attribs) {return SocialCalc.DecodeSheetAttributes(this, attribs);};
SocialCalc.Sheet.prototype.ScheduleSheetCommands = function(cmd, saveundo) {return SocialCalc.ScheduleSheetCommands(this, cmd, saveundo);};
SocialCalc.Sheet.prototype.SheetUndo = function() {return SocialCalc.SheetUndo(this);};
SocialCalc.Sheet.prototype.SheetRedo = function() {return SocialCalc.SheetRedo(this);};
SocialCalc.Sheet.prototype.CreateAuditString = function() {return SocialCalc.CreateAuditString(this);};
SocialCalc.Sheet.prototype.GetStyleNum = function(atype, style) {return SocialCalc.GetStyleNum(this, atype, style);};
SocialCalc.Sheet.prototype.GetStyleString = function(atype, num) {return SocialCalc.GetStyleString(this, atype, num);};
SocialCalc.Sheet.prototype.RecalcSheet = function() {return SocialCalc.RecalcSheet(this);};
//
// Sheet save format:
//
// linetype:param1:param2:...
//
// Linetypes are:
//
// version:versionname - version of this format. Currently 1.4.
//
// cell:coord:type:value...:type:value... - Types are as follows:
//
// v:value - straight numeric value
// t:value - straight text/wiki-text in cell, encoded to handle \, :, newlines
// vt:fulltype:value - value with value type/subtype
// vtf:fulltype:value:formulatext - formula resulting in value with value type/subtype, value and text encoded
// vtc:fulltype:value:valuetext - formatted text constant resulting in value with value type/subtype, value and text encoded
// vf:fvalue:formulatext - formula resulting in value, value and text encoded (obsolete: only pre format version 1.1)
// fvalue - first char is "N" for numeric value, "T" for text value, "H" for HTML value, rest is the value
// e:errortext - Error text. Non-blank means formula parsing/calculation results in error.
// b:topborder#:rightborder#:bottomborder#:leftborder# - border# in sheet border list or blank if none
// l:layout# - number in cell layout list
// f:font# - number in sheet fonts list
// c:color# - sheet color list index for text
// bg:color# - sheet color list index for background color
// cf:format# - sheet cell format number for explicit format (align:left, etc.)
// cvf:valueformat# - sheet cell value format number (obsolete: only pre format v1.2)
// tvf:valueformat# - sheet cell text value format number
// ntvf:valueformat# - sheet cell non-text value format number
// colspan:numcols - number of columns spanned in merged cell
// rowspan:numrows - number of rows spanned in merged cell
// cssc:classname - name of CSS class to be used for cell when published instead of one calculated here
// csss:styletext - explicit CSS style information, encoded to handle :, etc.
// mod:allow - if "y" allow modification of cell for live "view" recalc
// comment:value - encoded text of comment for this cell (added in v1.5)
//
// col:
// w:widthval - number, "auto" (no width in <col> tag), number%, or blank (use default)
// hide: - yes/no, no is assumed if missing
// row:
// hide - yes/no, no is assumed if missing
//
// sheet:
// c:lastcol - number
// r:lastrow - number
// w:defaultcolwidth - number, "auto", number%, or blank (default->80)
// h:defaultrowheight - not used
// tf:format# - cell format number for sheet default for text values
// ntf:format# - cell format number for sheet default for non-text values (i.e., numbers)
// layout:layout# - default cell layout number in cell layout list
// font:font# - default font number in sheet font list
// vf:valueformat# - default number value format number in sheet valueformat list (obsolete: only pre format version 1.2)
// ntvf:valueformat# - default non-text (number) value format number in sheet valueformat list
// tvf:valueformat# - default text value format number in sheet valueformat list
// color:color# - default number for text color in sheet color list
// bgcolor:color# - default number for background color in sheet color list
// circularreferencecell:coord - cell coord with a circular reference
// recalc:value - on/off (on is default). If not "off", appropriate changes to the sheet cause a recalc
// needsrecalc:value - yes/no (no is default). If "yes", formula values are not up to date
// usermaxcol:value - maximum column to display, 0 for unlimited (default=0)
// usermaxrow:value - maximum row to display, 0 for unlimited (default=0)
//
// name:name:description:value - name definition, name in uppercase, with value being "B5", "A1:B7", or "=formula";
// description and value are encoded.
// font:fontnum:value - text of font definition (style weight size family) for font fontnum
// "*" for "style weight", size, or family, means use default (first look to sheet, then builtin)
// color:colornum:rgbvalue - text of color definition (e.g., rgb(255,255,255)) for color colornum
// border:bordernum:value - text of border definition (thickness style color) for border bordernum
// layout:layoutnum:value - text of vertical alignment and padding style for cell layout layoutnum (* for default):
// vertical-alignment:vavalue;padding:topval rightval bottomval leftval;
// cellformat:cformatnum:value - text of cell alignment (left/center/right) for cellformat cformatnum
// valueformat:vformatnum:value - text of number format (see FormatValueForDisplay) for valueformat vformatnum (changed in v1.2)
// clipboardrange:upperleftcoord:bottomrightcoord - ignored -- from wikiCalc
// clipboard:coord:type:value:... - ignored -- from wikiCalc
//
// If this is clipboard contents, then there is also information to facilitate pasting:
//
// copiedfrom:upperleftcoord:bottomrightcoord - range from which this was copied
//
// Functions:
SocialCalc.ParseSheetSave = function(savedsheet,sheetobj) {
var lines=savedsheet.split(/\r\n|\n/);
var parts=[];
var line;
var i, j, t, v, coord, cell, attribs, name;
var scc = SocialCalc.Constants;
for (i=0;i<lines.length;i++) {
line=lines[i];
parts = line.split(":");
switch (parts[0]) {
case "cell":
cell=sheetobj.GetAssuredCell(parts[1]);
j=2;
sheetobj.CellFromStringParts(cell, parts, j);
break;
case "col":
coord=parts[1];
j=2;
while (t=parts[j++]) {
switch (t) {
case "w":
sheetobj.colattribs.width[coord]=parts[j++]; // must be text - could be auto or %, etc.
break;
case "hide":
sheetobj.colattribs.hide[coord]=parts[j++];
break;
default:
throw scc.s_pssUnknownColType+" '"+t+"'";
break;
}
}
break;
case "row":
coord=parts[1]-0;
j=2;
while (t=parts[j++]) {
switch (t) {
case "h":
sheetobj.rowattribs.height[coord]=parts[j++]-0;
break;
case "hide":
sheetobj.rowattribs.hide[coord]=parts[j++];
break;
default:
throw scc.s_pssUnknownRowType+" '"+t+"'";
break;
}
}
break;
case "sheet":
attribs=sheetobj.attribs;
j=1;
while (t=parts[j++]) {
switch (t) {
case "c":
attribs.lastcol=parts[j++]-0;
break;
case "r":
attribs.lastrow=parts[j++]-0;
break;
case "w":
attribs.defaultcolwidth=parts[j++]+"";
break;
case "h":
attribs.defaultrowheight=parts[j++]-0;
break;
case "tf":
attribs.defaulttextformat=parts[j++]-0;
break;
case "ntf":
attribs.defaultnontextformat=parts[j++]-0;
break;
case "layout":
attribs.defaultlayout=parts[j++]-0;
break;
case "font":
attribs.defaultfont=parts[j++]-0;
break;
case "tvf":
attribs.defaulttextvalueformat=parts[j++]-0;
break;
case "ntvf":
attribs.defaultnontextvalueformat=parts[j++]-0;
break;
case "color":
attribs.defaultcolor=parts[j++]-0;
break;
case "bgcolor":
attribs.defaultbgcolor=parts[j++]-0;
break;
case "circularreferencecell":
attribs.circularreferencecell=parts[j++];
break;
case "recalc":
attribs.recalc=parts[j++];
break;
case "needsrecalc":
attribs.needsrecalc=parts[j++];
break;
case "usermaxcol":
attribs.usermaxcol=parts[j++]-0;
break;
case "usermaxrow":
attribs.usermaxrow=parts[j++]-0;
break;
default:
j+=1;
break;
}
}
break;
case "name":
name = SocialCalc.decodeFromSave(parts[1]).toUpperCase();
sheetobj.names[name] = {desc: SocialCalc.decodeFromSave(parts[2])};
sheetobj.names[name].definition = SocialCalc.decodeFromSave(parts[3]);
break;
case "layout":
parts=lines[i].match(/^layout\:(\d+)\:(.+)$/); // layouts can have ":" in them
sheetobj.layouts[parts[1]-0]=parts[2];
sheetobj.layouthash[parts[2]]=parts[1]-0;
break;
case "font":
sheetobj.fonts[parts[1]-0]=parts[2];
sheetobj.fonthash[parts[2]]=parts[1]-0;
break;
case "color":
sheetobj.colors[parts[1]-0]=parts[2];
sheetobj.colorhash[parts[2]]=parts[1]-0;
break;
case "border":
sheetobj.borderstyles[parts[1]-0]=parts[2];
sheetobj.borderstylehash[parts[2]]=parts[1]-0;
break;
case "cellformat":
v=SocialCalc.decodeFromSave(parts[2]);
sheetobj.cellformats[parts[1]-0]=v;
sheetobj.cellformathash[v]=parts[1]-0;
break;
case "valueformat":
v=SocialCalc.decodeFromSave(parts[2]);
sheetobj.valueformats[parts[1]-0]=v;
sheetobj.valueformathash[v]=parts[1]-0;
break;
case "version":
break;
case "copiedfrom":
sheetobj.copiedfrom = parts[1]+":"+parts[2];
break;
case "clipboardrange": // in save versions up to 1.3. Ignored.
case "clipboard":
break;
case "":
break;
default:
alert(scc.s_pssUnknownLineType+" '"+parts[0]+"'");
throw scc.s_pssUnknownLineType+" '"+parts[0]+"'";
break;
}
parts = null;
}
}
//
// SocialCalc.CellFromStringParts(sheet, cell, parts, j)
//
// Takes string that has been split by ":" in parts, starting at item j,
// and fills in cell assuming save format.
//
SocialCalc.CellFromStringParts = function(sheet, cell, parts, j) {
var cell, t, v;
while (t=parts[j++]) {
switch (t) {
case "v":
cell.datavalue=SocialCalc.decodeFromSave(parts[j++])-0;
cell.datatype="v";
cell.valuetype="n";
break;
case "t":
cell.datavalue=SocialCalc.decodeFromSave(parts[j++]);
cell.datatype="t";
cell.valuetype=SocialCalc.Constants.textdatadefaulttype;
break;
case "vt":
v=parts[j++];
cell.valuetype=v;
if (v.charAt(0)=="n") {
cell.datatype="v";
cell.datavalue=SocialCalc.decodeFromSave(parts[j++])-0;
}
else {
cell.datatype="t";
cell.datavalue=SocialCalc.decodeFromSave(parts[j++]);
}
break;
case "vtf":
v=parts[j++];
cell.valuetype=v;
if (v.charAt(0)=="n") {
cell.datavalue=SocialCalc.decodeFromSave(parts[j++])-0;
}
else {
cell.datavalue=SocialCalc.decodeFromSave(parts[j++]);
}
cell.formula=SocialCalc.decodeFromSave(parts[j++]);
cell.datatype="f";
break;
case "vtc":
v=parts[j++];
cell.valuetype=v;
if (v.charAt(0)=="n") {
cell.datavalue=SocialCalc.decodeFromSave(parts[j++])-0;
}
else {
cell.datavalue=SocialCalc.decodeFromSave(parts[j++]);
}
cell.formula=SocialCalc.decodeFromSave(parts[j++]);
cell.datatype="c";
break;
case "ro":
ro=SocialCalc.decodeFromSave(parts[j++]);
cell.readonly=ro.toLowerCase()=="yes";
break;
case "e":
cell.errors=SocialCalc.decodeFromSave(parts[j++]);
break;
case "b":
cell.bt=parts[j++]-0;
cell.br=parts[j++]-0;
cell.bb=parts[j++]-0;
cell.bl=parts[j++]-0;
break;
case "l":
cell.layout=parts[j++]-0;
break;
case "f":
cell.font=parts[j++]-0;
break;
case "c":
cell.color=parts[j++]-0;
break;
case "bg":
cell.bgcolor=parts[j++]-0;
break;
case "cf":
cell.cellformat=parts[j++]-0;
break;
case "ntvf":
cell.nontextvalueformat=parts[j++]-0;
break;
case "tvf":
cell.textvalueformat=parts[j++]-0;
break;
case "colspan":
cell.colspan=parts[j++]-0;
break;
case "rowspan":
cell.rowspan=parts[j++]-0;
break;
case "cssc":
cell.cssc=parts[j++];
break;
case "csss":
cell.csss=SocialCalc.decodeFromSave(parts[j++]);
break;
case "mod":
j+=1;
break;
case "comment":
cell.comment=SocialCalc.decodeFromSave(parts[j++]);
break;
default:
throw SocialCalc.Constants.s_cfspUnknownCellType+" '"+t+"'";
break;
}
}
}
SocialCalc.sheetfields = ["defaultrowheight", "defaultcolwidth", "circularreferencecell", "recalc", "needsrecalc", "usermaxcol", "usermaxrow"];
SocialCalc.sheetfieldsshort = ["h", "w", "circularreferencecell", "recalc", "needsrecalc", "usermaxcol", "usermaxrow"];
SocialCalc.sheetfieldsxlat = ["defaulttextformat", "defaultnontextformat",
"defaulttextvalueformat", "defaultnontextvalueformat",
"defaultcolor", "defaultbgcolor", "defaultfont", "defaultlayout"];
SocialCalc.sheetfieldsxlatshort = ["tf", "ntf", "tvf", "ntvf", "color", "bgcolor", "font", "layout"];
SocialCalc.sheetfieldsxlatxlt = ["cellformat", "cellformat", "valueformat", "valueformat",
"color", "color", "font", "layout"];
//
// sheetstr = SocialCalc.CreateSheetSave(sheetobj, range, canonicalize)
//
// Creates a text representation of the sheetobj data.
// If the range is present then only those cells are saved
// (as clipboard data with "copiedfrom" set).
//
SocialCalc.CreateSheetSave = function(sheetobj, range, canonicalize) {
var cell, cr1, cr2, row, col, coord, attrib, line, value, formula, i, t, r, b, l, name, blanklen;
var result=[];
var prange;
sheetobj.CanonicalizeSheet(canonicalize || SocialCalc.Constants.doCanonicalizeSheet);
var xlt = sheetobj.xlt;
if (range) {
prange = SocialCalc.ParseRange(range);
}
else {
prange = {cr1: {row: 1, col:1},
cr2: {row: xlt.maxrow, col: xlt.maxcol}};
}
cr1 = prange.cr1;
cr2 = prange.cr2;
result.push("version:1.5");
for (row=cr1.row; row <= cr2.row; row++) {
for (col=cr1.col; col <= cr2.col; col++) {
coord = SocialCalc.crToCoord(col, row);
cell=sheetobj.cells[coord];
if (!cell) continue;
line=sheetobj.CellToString(cell);
if (line.length==0) continue; // ignore completely empty cells
line="cell:"+coord+line;
result.push(line);
}
}
for (col=1; col <= xlt.maxcol; col++) {
coord = SocialCalc.rcColname(col);
if (sheetobj.colattribs.width[coord])
result.push("col:"+coord+":w:"+sheetobj.colattribs.width[coord]);
if (sheetobj.colattribs.hide[coord])
result.push("col:"+coord+":hide:"+sheetobj.colattribs.hide[coord]);
}
for (row=1; row <= xlt.maxrow; row++) {
if (sheetobj.rowattribs.height[row])
result.push("row:"+row+":h:"+sheetobj.rowattribs.height[row]);
if (sheetobj.rowattribs.hide[row])
result.push("row:"+row+":hide:"+sheetobj.rowattribs.hide[row]);
}
line="sheet:c:"+xlt.maxcol+":r:"+xlt.maxrow;
for (i=0; i<SocialCalc.sheetfields.length; i++) { // non-xlated values
value = SocialCalc.encodeForSave(sheetobj.attribs[SocialCalc.sheetfields[i]]);
if (value) line+=":"+SocialCalc.sheetfieldsshort[i]+":"+value;
}
for (i=0; i<SocialCalc.sheetfieldsxlat.length; i++) { // xlated values
value = sheetobj.attribs[SocialCalc.sheetfieldsxlat[i]];
if (value) line+=":"+SocialCalc.sheetfieldsxlatshort[i]+":"+xlt[SocialCalc.sheetfieldsxlatxlt[i]+"sxlat"][value];
}
result.push(line);
for (i=1;i<xlt.newborderstyles.length;i++) {
result.push("border:"+i+":"+xlt.newborderstyles[i]);
}
for (i=1;i<xlt.newcellformats.length;i++) {
result.push("cellformat:"+i+":"+SocialCalc.encodeForSave(xlt.newcellformats[i]));
}
for (i=1;i<xlt.newcolors.length;i++) {
result.push("color:"+i+":"+xlt.newcolors[i]);
}
for (i=1;i<xlt.newfonts.length;i++) {
result.push("font:"+i+":"+xlt.newfonts[i]);
}
for (i=1;i<xlt.newlayouts.length;i++) {
result.push("layout:"+i+":"+xlt.newlayouts[i]);
}
for (i=1;i<xlt.newvalueformats.length;i++) {
result.push("valueformat:"+i+":"+SocialCalc.encodeForSave(xlt.newvalueformats[i]));
}
for (i=0; i<xlt.namesorder.length; i++) {
name = xlt.namesorder[i];
result.push("name:"+SocialCalc.encodeForSave(name).toUpperCase()+":"+
SocialCalc.encodeForSave(sheetobj.names[name].desc)+":"+
SocialCalc.encodeForSave(sheetobj.names[name].definition));
}
if (range) {
result.push("copiedfrom:"+SocialCalc.crToCoord(cr1.col, cr1.row)+":"+
SocialCalc.crToCoord(cr2.col, cr2.row));
}
result.push(""); // one extra to get extra \n
delete sheetobj.xlt; // clean up
return result.join("\n");
}
//
// line = SocialCalc.CellToString(sheet, cell)
//
SocialCalc.CellToString = function(sheet, cell) {
var cell, line, value, formula, t, r, b, l, xlt;
line = "";
if (!cell) return line;
value = SocialCalc.encodeForSave(cell.datavalue);
if (cell.datatype=="v") {
if (cell.valuetype=="n") line += ":v:"+value;
else line += ":vt:"+cell.valuetype+":"+value;
}
else if (cell.datatype=="t") {
if (cell.valuetype==SocialCalc.Constants.textdatadefaulttype)
line += ":t:"+value;
else line += ":vt:"+cell.valuetype+":"+value;
}
else {
formula = SocialCalc.encodeForSave(cell.formula);
if (cell.datatype=="f") {
line += ":vtf:"+cell.valuetype+":"+value+":"+formula;
}
else if (cell.datatype=="c") {
line += ":vtc:"+cell.valuetype+":"+value+":"+formula;
}
}
if (cell.readonly) {
line += ":ro:yes";
}
if (cell.errors) {
line += ":e:"+SocialCalc.encodeForSave(cell.errors);
}
t = cell.bt || "";
r = cell.br || "";
b = cell.bb || "";
l = cell.bl || "";
if (sheet.xlt) { // if have canonical save info
xlt = sheet.xlt;
if (t || r || b || l)
line += ":b:"+xlt.borderstylesxlat[t||0]+":"+xlt.borderstylesxlat[r||0]+":"+xlt.borderstylesxlat[b||0]+":"+xlt.borderstylesxlat[l||0];
if (cell.layout) line += ":l:"+xlt.layoutsxlat[cell.layout];
if (cell.font) line += ":f:"+xlt.fontsxlat[cell.font];
if (cell.color) line += ":c:"+xlt.colorsxlat[cell.color];
if (cell.bgcolor) line += ":bg:"+xlt.colorsxlat[cell.bgcolor];
if (cell.cellformat) line += ":cf:"+xlt.cellformatsxlat[cell.cellformat];
if (cell.textvalueformat) line += ":tvf:"+xlt.valueformatsxlat[cell.textvalueformat];
if (cell.nontextvalueformat) line += ":ntvf:"+xlt.valueformatsxlat[cell.nontextvalueformat];
}
else {
if (t || r || b || l)
line += ":b:"+t+":"+r+":"+b+":"+l;
if (cell.layout) line += ":l:"+cell.layout;
if (cell.font) line += ":f:"+cell.font;
if (cell.color) line += ":c:"+cell.color;
if (cell.bgcolor) line += ":bg:"+cell.bgcolor;
if (cell.cellformat) line += ":cf:"+cell.cellformat;
if (cell.textvalueformat) line += ":tvf:"+cell.textvalueformat;
if (cell.nontextvalueformat) line += ":ntvf:"+cell.nontextvalueformat;
}
if (cell.colspan) line += ":colspan:"+cell.colspan;
if (cell.rowspan) line += ":rowspan:"+cell.rowspan;
if (cell.cssc) line += ":cssc:"+cell.cssc;
if (cell.csss) line += ":csss:"+SocialCalc.encodeForSave(cell.csss);
if (cell.mod) line += ":mod:"+cell.mod;
if (cell.comment) line += ":comment:"+SocialCalc.encodeForSave(cell.comment);
return line;
}
//
// SocialCalc.CanonicalizeSheet(sheetobj, full)
//
// Goes through the sheet and fills in sheetobj.xlt with the following:
//
// .maxrow, .maxcol - lastrow and lastcol are as small as possible
// .newlayouts - new version of sheetobj.layouts without unused ones and all in ascending order
// .layoutsxlat - maps old layouts index to new one
// same ".new" and ".xlat" for fonts, colors, borderstyles, cell and value formats
// .namesorder - array with names sorted
//
// If full or SocialCalc.Constants.doCanonicalizeSheet are not true, then the values will leave things unchanged (to save time, etc.)
//
// sheetobj.xlt should be deleted when you are finished using it
//
SocialCalc.CanonicalizeSheet = function(sheetobj, full) {
var l, coord, cr, cell, filled, an, a, newa, newxlat, used, ahash, i, v;
var maxrow = 0;
var maxcol = 0;
var alist = ["borderstyle", "cellformat", "color", "font", "layout", "valueformat"];
var xlt = {};
xlt.namesorder = []; // always return a sorted list
for (a in sheetobj.names) {
xlt.namesorder.push(a);
}
xlt.namesorder.sort();
if (!SocialCalc.Constants.doCanonicalizeSheet || !full) { // return make-no-changes values if not wanted
for (an=0; an<alist.length; an++) {
a = alist[an];
xlt["new"+a+"s"] = sheetobj[a+"s"];
l = sheetobj[a+"s"].length;
newxlat = new Array(l);
newxlat[0] = "";
for (i=1; i<l; i++) {
newxlat[i] = i;
}
xlt[a+"sxlat"] = newxlat;
}
xlt.maxrow = sheetobj.attribs.lastrow;
xlt.maxcol = sheetobj.attribs.lastcol;
sheetobj.xlt = xlt;
return;
}
for (an=0; an<alist.length; an++) {
a = alist[an];
xlt[a+"sUsed"] = {};
}
var colorsUsed = xlt.colorsUsed;
var borderstylesUsed = xlt.borderstylesUsed;
var fontsUsed = xlt.fontsUsed;
var layoutsUsed = xlt.layoutsUsed;
var cellformatsUsed = xlt.cellformatsUsed;
var valueformatsUsed = xlt.valueformatsUsed;
for (coord in sheetobj.cells) { // check all cells to see which values are used
cr = SocialCalc.coordToCr(coord);
cell = sheetobj.cells[coord];
filled = false;
if (cell.valuetype && cell.valuetype!="b") filled = true;
if (cell.color) {
colorsUsed[cell.color] = 1;
filled = true;
}
if (cell.bgcolor) {
colorsUsed[cell.bgcolor] = 1;
filled = true;
}
if (cell.bt) {
borderstylesUsed[cell.bt] = 1;
filled = true;
}
if (cell.br) {
borderstylesUsed[cell.br] = 1;
filled = true;
}
if (cell.bb) {
borderstylesUsed[cell.bb] = 1;
filled = true;
}
if (cell.bl) {
borderstylesUsed[cell.bl] = 1;
filled = true;
}
if (cell.layout) {
layoutsUsed[cell.layout] = 1;
filled = true;
}
if (cell.font) {
fontsUsed[cell.font] = 1;
filled = true;
}
if (cell.cellformat) {
cellformatsUsed[cell.cellformat] = 1;
filled = true;
}
if (cell.textvalueformat) {
valueformatsUsed[cell.textvalueformat] = 1;
filled = true;
}
if (cell.nontextvalueformat) {
valueformatsUsed[cell.nontextvalueformat] = 1;
filled = true;
}
if (filled) {
if (cr.row > maxrow) maxrow = cr.row;
if (cr.col > maxcol) maxcol = cr.col;
}
}
for (i=0; i<SocialCalc.sheetfieldsxlat.length; i++) { // do sheet values, too
v = sheetobj.attribs[SocialCalc.sheetfieldsxlat[i]];
if (v) {
xlt[SocialCalc.sheetfieldsxlatxlt[i]+"sUsed"][v] = 1;
}
}
a = {"height": 1, "hide": 1}; // look at explicit row settings
for (v in a) {
for (cr in sheetobj.rowattribs[v]) {
if (cr > maxrow) maxrow = cr;
}
}
a = {"hide": 1, "width": 1}; // look at explicit col settings
for (v in a) {
for (coord in sheetobj.colattribs[v]) {
cr = SocialCalc.coordToCr(coord+"1");
if (cr.col > maxcol) maxcol = cr.col;
}
}
for (an=0; an<alist.length; an++) { // go through the attribs we want
a = alist[an];
newa = [];
used = xlt[a+"sUsed"];
for (v in used) {
newa.push(sheetobj[a+"s"][v]);
}
newa.sort();
newa.unshift("");
newxlat = [""];
ahash = sheetobj[a+"hash"];
for (i=1; i<newa.length; i++) {
newxlat[ahash[newa[i]]] = i;
}
xlt[a+"sxlat"] = newxlat;
xlt["new"+a+"s"] = newa;
}
xlt.maxrow = maxrow || 1;
xlt.maxcol = maxcol || 1;
sheetobj.xlt = xlt; // leave for use by caller
}
//
// result = SocialCalc.EncodeCellAttributes(sheet, coord)
//
// Returns the cell's attributes in an object, each in the following form:
//
// attribname: {def: true/false, val: full-value}
//
SocialCalc.EncodeCellAttributes = function(sheet, coord) {
var value, i, b, bb;
var result = {};
var InitAttrib = function(name) {
result[name] = {def: true, val: ""};
}
var InitAttribs = function(namelist) {
for (var i=0; i<namelist.length; i++) {
InitAttrib(namelist[i]);
}
}
var SetAttrib = function(name, v) {
result[name].def = false;
result[name].val = v || "";
}
var SetAttribStar = function(name, v) {
if (v=="*") return;
result[name].def = false;
result[name].val = v;
}
var cell = sheet.GetAssuredCell(coord);
// cellformat: alignhoriz
InitAttrib("alignhoriz");
if (cell.cellformat) {
SetAttrib("alignhoriz", sheet.cellformats[cell.cellformat]);
}
// layout: alignvert, padtop, padright, padbottom, padleft
InitAttribs(["alignvert", "padtop", "padright", "padbottom", "padleft"]);
if (cell.layout) {
parts = sheet.layouts[cell.layout].match(/^padding:\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+);vertical-align:\s*(\S+);/);
SetAttribStar("padtop", parts[1]);
SetAttribStar("padright", parts[2]);
SetAttribStar("padbottom", parts[3]);
SetAttribStar("padleft", parts[4]);
SetAttribStar("alignvert", parts[5]);
}
// font: fontfamily, fontlook, fontsize
InitAttribs(["fontfamily", "fontlook", "fontsize"]);
if (cell.font) {
parts = sheet.fonts[cell.font].match(/^(\*|\S+? \S+?) (\S+?) (\S.*)$/);
SetAttribStar("fontfamily", parts[3]);
SetAttribStar("fontsize", parts[2]);
SetAttribStar("fontlook", parts[1]);
}
// color: textcolor
InitAttrib("textcolor");
if (cell.color) {
SetAttrib("textcolor", sheet.colors[cell.color]);
}
// bgcolor: bgcolor
InitAttrib("bgcolor");
if (cell.bgcolor) {
SetAttrib("bgcolor", sheet.colors[cell.bgcolor]);
}
// formatting: numberformat, textformat
InitAttribs(["numberformat", "textformat"]);
if (cell.nontextvalueformat) {
SetAttrib("numberformat", sheet.valueformats[cell.nontextvalueformat]);
}
if (cell.textvalueformat) {
SetAttrib("textformat", sheet.valueformats[cell.textvalueformat]);
}
// merges: colspan, rowspan
InitAttribs(["colspan", "rowspan"]);
SetAttrib("colspan", cell.colspan || 1);
SetAttrib("rowspan", cell.rowspan || 1);
// borders: bXthickness, bXstyle, bXcolor for X = t, r, b, and l
for (i=0; i<4; i++) {
b = "trbl".charAt(i);
bb = "b"+b;
InitAttrib(bb);
SetAttrib(bb, cell[bb] ? sheet.borderstyles[cell[bb]] : "");
InitAttrib(bb+"thickness");
InitAttrib(bb+"style");
InitAttrib(bb+"color");
if (cell[bb]) {
parts = sheet.borderstyles[cell[bb]].match(/(\S+)\s+(\S+)\s+(\S.+)/);
SetAttrib(bb+"thickness", parts[1]);
SetAttrib(bb+"style", parts[2]);
SetAttrib(bb+"color", parts[3]);
}
}
// misc: cssc, csss, mod
InitAttribs(["cssc", "csss", "mod"]);
SetAttrib("cssc", cell.cssc || "");
SetAttrib("csss", cell.csss || "");
SetAttrib("mod", cell.mod || "n");
return result;
}
//
// result = SocialCalc.EncodeSheetAttributes(sheet)
//
// Returns the sheet's attributes in an object, each in the following form:
//
// attribname: {def: true/false, val: full-value}
//
SocialCalc.EncodeSheetAttributes = function(sheet) {
var value;
var attribs = sheet.attribs;
var result = {};
var InitAttrib = function(name) {
result[name] = {def: true, val: ""};
}
var InitAttribs = function(namelist) {
for (var i=0; i<namelist.length; i++) {
InitAttrib(namelist[i]);
}
}
var SetAttrib = function(name, v) {
result[name].def = false;
result[name].val = v || value;
}
var SetAttribStar = function(name, v) {
if (v=="*") return;
result[name].def = false;
result[name].val = v;
}
// sizes: colwidth, rowheight
InitAttrib("colwidth");
if (attribs.defaultcolwidth) {
SetAttrib("colwidth", attribs.defaultcolwidth);
}
InitAttrib("rowheight");
if (attribs.rowheight) {
SetAttrib("rowheight", attribs.defaultrowheight);
}
// cellformat: textalignhoriz, numberalignhoriz
InitAttrib("textalignhoriz");
if (attribs.defaulttextformat) {
SetAttrib("textalignhoriz", sheet.cellformats[attribs.defaulttextformat]);
}
InitAttrib("numberalignhoriz");
if (attribs.defaultnontextformat) {
SetAttrib("numberalignhoriz", sheet.cellformats[attribs.defaultnontextformat]);
}
// layout: alignvert, padtop, padright, padbottom, padleft
InitAttribs(["alignvert", "padtop", "padright", "padbottom", "padleft"]);
if (attribs.defaultlayout) {
parts = sheet.layouts[attribs.defaultlayout].match(/^padding:\s*(\S+)\s+(\S+)\s+(\S+)\s+(\S+);vertical-align:\s*(\S+);/);
SetAttribStar("padtop", parts[1]);
SetAttribStar("padright", parts[2]);
SetAttribStar("padbottom", parts[3]);
SetAttribStar("padleft", parts[4]);
SetAttribStar("alignvert", parts[5]);
}
// font: fontfamily, fontlook, fontsize
InitAttribs(["fontfamily", "fontlook", "fontsize"]);
if (attribs.defaultfont) {
parts = sheet.fonts[attribs.defaultfont].match(/^(\*|\S+? \S+?) (\S+?) (\S.*)$/);
SetAttribStar("fontfamily", parts[3]);
SetAttribStar("fontsize", parts[2]);
SetAttribStar("fontlook", parts[1]);
}
// color: textcolor
InitAttrib("textcolor");
if (attribs.defaultcolor) {
SetAttrib("textcolor", sheet.colors[attribs.defaultcolor]);
}
// bgcolor: bgcolor
InitAttrib("bgcolor");
if (attribs.defaultbgcolor) {
SetAttrib("bgcolor", sheet.colors[attribs.defaultbgcolor]);
}
// formatting: numberformat, textformat
InitAttribs(["numberformat", "textformat"]);
if (attribs.defaultnontextvalueformat) {
SetAttrib("numberformat", sheet.valueformats[attribs.defaultnontextvalueformat]);
}
if (attribs.defaulttextvalueformat) {
SetAttrib("textformat", sheet.valueformats[attribs.defaulttextvalueformat]);
}
// recalc: recalc
InitAttrib("recalc");
if (attribs.recalc) {
SetAttrib("recalc", attribs.recalc);
}
// usermaxcol, usermaxrow
InitAttrib("usermaxcol");
if (attribs.usermaxcol) {
SetAttrib("usermaxcol", attribs.usermaxcol);
}
InitAttrib("usermaxrow");
if (attribs.usermaxrow) {
SetAttrib("usermaxrow", attribs.usermaxrow);
}
return result;
}
//
// cmdstr = SocialCalc.DecodeCellAttributes(sheet, coord, attribs, range)
//
// Takes cell attributes in an object, each in the following form:
//
// attribname: {def: true/false, val: full-value}
//
// and returns the sheet commands to make the actual attributes correspond.
// Returns a non-null string if any commands are to be executed, null otherwise.
//
// If range is provided, the commands are executed on the whole range.
//
SocialCalc.DecodeCellAttributes = function(sheet, coord, newattribs, range) {
var value, b, bb;
var cell = sheet.GetAssuredCell(coord);
var changed = false;
var CheckChanges = function(attribname, oldval, cmdname) {
var val;
if (newattribs[attribname]) {
if (newattribs[attribname].def) {
val = "";
}
else {
val = newattribs[attribname].val;
}
if (val != (oldval || "")) {
DoCmd(cmdname+" "+val);
}
}
}
var cmdstr = "";
var DoCmd = function(str) {
if (cmdstr) cmdstr += "\n";
cmdstr += "set "+(range || coord)+" "+str;
changed = true;
}
// cellformat: alignhoriz
CheckChanges("alignhoriz", sheet.cellformats[cell.cellformat], "cellformat");
// layout: alignvert, padtop, padright, padbottom, padleft
if (!newattribs.alignvert.def || !newattribs.padtop.def || !newattribs.padright.def ||
!newattribs.padbottom.def || !newattribs.padleft.def) {
value = "padding:" +
(newattribs.padtop.def ? "* " : newattribs.padtop.val + " ") +
(newattribs.padright.def ? "* " : newattribs.padright.val + " ") +
(newattribs.padbottom.def ? "* " : newattribs.padbottom.val + " ") +
(newattribs.padleft.def ? "*" : newattribs.padleft.val) +
";vertical-align:" +
(newattribs.alignvert.def ? "*;" : newattribs.alignvert.val+";");
}
else {
value = "";
}
if (value != (sheet.layouts[cell.layout] || "")) {
DoCmd("layout "+value);
}
// font: fontfamily, fontlook, fontsize
if (!newattribs.fontlook.def || !newattribs.fontsize.def |