more formats from js-harb

- clarify usage of Props and Custprops (fixes #274 h/t @michahell)
- SYLK from js-harb
- DIF from js-harb
- HTML empty string bug fix
This commit is contained in:
SheetJS 2017-04-01 03:32:12 -04:00
parent e42cf43c02
commit b93569badf
22 changed files with 802 additions and 59 deletions

@ -39,3 +39,5 @@ test.js
bits/
docbits/
tests/
_book
book.json

@ -38,9 +38,10 @@ with a unified JS representation, and ES3/ES5 browser compatibility back to IE6.
+ [Data Types](#data-types)
+ [Dates](#dates)
* [Sheet Objects](#sheet-objects)
* [Worksheet Object](#worksheet-object)
* [Chartsheet Object](#chartsheet-object)
+ [Worksheet Object](#worksheet-object)
+ [Chartsheet Object](#chartsheet-object)
* [Workbook Object](#workbook-object)
+ [Workbook File Properties](#workbook-file-properties)
* [Document Features](#document-features)
+ [Formulae](#formulae)
+ [Column Properties](#column-properties)
@ -65,9 +66,12 @@ with a unified JS representation, and ES3/ES5 browser compatibility back to IE6.
* [Excel 2003-2004 (SpreadsheetML)](#excel-2003-2004-spreadsheetml)
* [Excel 2007+ Binary (XLSB, BIFF12)](#excel-2007-binary-xlsb-biff12)
* [OpenDocument Spreadsheet (ODS/FODS) and Uniform Office Spreadsheet (UOS1/2)](#opendocument-spreadsheet-odsfods-and-uniform-office-spreadsheet-uos12)
* [dBASE and Visual FoxPro (DBF)](#dbase-and-visual-foxpro-dbf)
* [Comma-Separated Values](#comma-separated-values)
* [HTML](#html)
* [Comma-Separated Values (CSV)](#comma-separated-values-csv)
* [Other Single-Worksheet Formats](#other-single-worksheet-formats)
+ [dBASE and Visual FoxPro (DBF)](#dbase-and-visual-foxpro-dbf)
+ [Symbolic Link (SYLK)](#symbolic-link-sylk)
+ [Data Interchange Format (DIF)](#data-interchange-format-dif)
+ [HTML](#html)
- [Testing](#testing)
* [Tested Environments](#tested-environments)
* [Test Files](#test-files)
@ -545,7 +549,7 @@ Special sheet keys (accessible as `sheet[key]`, each starting with `!`):
When reading a worksheet with the `sheetRows` property set, the ref parameter
will use the restricted range. The original range is set at `ws['!fullref']`
### Worksheet Object
#### Worksheet Object
In addition to the base sheet keys, worksheets also add:
@ -560,7 +564,7 @@ In addition to the base sheet keys, worksheets also add:
will write all cells in the merge range if they exist, so be sure that only
the first cell (upper-left) in the range is set.
### Chartsheet Object
#### Chartsheet Object
Chartsheets are represented as standard sheets. They are distinguished with the
`!type` property set to `"chart"`.
@ -576,7 +580,7 @@ first row of the chartsheet is the underlying header.
`wb.Props` is an object storing the standard properties. `wb.Custprops` stores
custom properties. Since the XLS standard properties deviate from the XLSX
standard, XLS parsing stores core properties in both places. .
standard, XLS parsing stores core properties in both places.
`wb.WBProps` includes more workbook-level properties:
@ -585,6 +589,38 @@ standard, XLS parsing stores core properties in both places. .
The workbook's epoch can be determined by examining the workbook's
`wb.WBProps.date1904` property.
#### Workbook File Properties
The various file formats use different internal names for file properties. The
workbook `Props` object normalizes the names:
| JS Name | Excel Description |
|:------------|:-------------------------------|
| Title | Summary tab "Title" |
| Subject | Summary tab "Subject" |
| Author | Summary tab "Author" |
| Manager | Summary tab "Manager" |
| Company | Summary tab "Company" |
| Category | Summary tab "Category" |
| Keywords | Summary tab "Keywords" |
| Comments | Summary tab "Comments" |
| LastAuthor | Statistics tab "Last saved by" |
| CreatedDate | Statistics tab "Created" |
For example, to set the workbook title property:
```js
if(!wb.Props) wb.Props = {};
wb.Props.Title = "Insert Title Here";
```
Custom properties are added in the workbook `Custprops` object:
```js
if(!wb.Custprops) wb.Custprops = {};
wb.Custprops["Custom Property"] = "Custom Value";
```
### Document Features
Even for basic features like date storage, the official Excel formats store the
@ -818,6 +854,8 @@ file but Excel will know how to handle it. This library applies similar logic:
| `0x09` | BIFF Stream | BIFF 2/3/4/5 |
| `0x3C` | XML/HTML | SpreadsheetML / Flat ODS / UOS1 / HTML / plaintext |
| `0x50` | ZIP Archive | XLSB or XLSX/M or ODS or UOS2 or plaintext |
| `0x49` | Plain Text | SYLK or plaintext |
| `0x54` | Plain Text | DIF or plaintext |
| `0xFE` | UTF8 Text | SpreadsheetML or Flat ODS or UOS1 or plaintext |
DBF files are detected based on the first byte as well as the third and fourth
@ -858,6 +896,8 @@ output formats. The specific file type is controlled with `bookType` option:
| `biff2` | `.xls` | none | single | Excel 2.0 Worksheet format |
| `fods` | `.fods` | none | multi | Flat OpenDocument Spreadsheet |
| `csv` | `.csv` | none | single | Comma Separated Values |
| `sylk` | `.sylk` | none | single | Symbolic Link (SYLK) |
| `dif` | `.dif` | none | single | Data Interchange Format (DIF) |
- `compression` only applies to formats with ZIP containers.
- Formats that only support a single sheet require a `sheet` option specifying
@ -1088,6 +1128,8 @@ Despite the library name `xlsx`, it supports numerous spreadsheet file formats:
| Excel 2.0/2.1 (XLS BIFF2) | :o: | :o: |
| **Excel Supported Text Formats** |:-----:|:-----:|
| Delimiter-Separated Values (CSV/TSV/DSV) | | :o: |
| Data Interchange Format (DIF) | :o: | :o: |
| Symbolic Link (SYLK/SLK) | :o: | :o: |
| **Other Workbook/Worksheet Formats** |:-----:|:-----:|
| OpenDocument Spreadsheet (ODS) | :o: | :o: |
| Flat XML ODF Spreadsheet (FODS) | :o: | :o: |
@ -1153,7 +1195,17 @@ UOS is a very similar format, and it comes in 2 varieties corresponding to ODS
and FODS respectively. For the most part, the difference between the formats
lies in the names of tags and attributes.
### dBASE and Visual FoxPro (DBF)
### Comma-Separated Values (CSV)
Excel CSV deviates from RFC4180 in a number of important ways. The generated
CSV files should generally work in Excel although they may not work in RFC4180
compatible readers.
### Other Single-Worksheet Formats
Many older formats supported only one worksheet:
#### dBASE and Visual FoxPro (DBF)
DBF is really a typed table format: each column can only hold one data type and
each record omits type information. The parser generates a header row and
@ -1162,13 +1214,18 @@ inserts records starting at the second row of the worksheet.
Multi-file extensions like external memos and tables are currently unsupported,
limited by the general ability to read arbitrary files in the web browser.
### Comma-Separated Values
#### Symbolic Link (SYLK)
Excel CSV deviates from RFC4180 in a number of important ways. The generated
CSV files should generally work in Excel although they may not work in RFC4180
compatible readers.
There is no real documentation. All knowledge was gathered by saving files in
various versions of Excel to deduce the meaning of fields.
### HTML
#### Data Interchange Format (DIF)
There is no unified definition. Visicalc DIF differs from Lotus DIF, and both
differ from Excel DIF. Where ambiguous, the parser/writer follows the expected
behavior from Excel.
#### HTML
Excel HTML worksheets include special metadata encoded in styles. For example,
`mso-number-format` is a localized string containing the number format. Despite

@ -27,6 +27,8 @@ program
.option('-j, --json', 'emit formatted JSON (all fields text)')
.option('-J, --raw-js', 'emit raw JS object (raw numbers)')
.option('-A, --arrays', 'emit rows as JS objects (raw numbers)')
.option('-D, --dif', 'emit data interchange format (dif)')
.option('-K, --sylk', 'emit symbolic link (sylk)')
.option('-F, --field-sep <sep>', 'CSV field separator', ",")
.option('-R, --row-sep <sep>', 'CSV row separator', "\n")
@ -162,9 +164,13 @@ try {
if(program.perf) process.exit(0);
/* single worksheet XLS formats */
['biff2'].forEach(function(m) { if(program[m]) {
wopts.bookType = m;
X.writeFile(wb, sheetname || ((filename || "") + ".xls"), wopts);
[
['biff2', '.xls'],
['sylk', '.slk'],
['dif', '.dif']
].forEach(function(m) { if(program[m[0]]) {
wopts.bookType = m[0];
X.writeFile(wb, sheetname || ((filename || "") + m[1]), wopts);
process.exit(0);
} });

@ -182,3 +182,180 @@ function dbf_to_workbook(buf, opts)/*:Workbook*/ {
to_sheet: dbf_to_sheet
};
})();
var SYLK = (function() {
/* TODO: find an actual specification */
function sylk_to_aoa(d/*:RawData*/, opts)/*:AOA*/ {
switch(opts.type) {
case 'base64': return sylk_to_aoa_str(Base64.decode(d), opts);
case 'binary': return sylk_to_aoa_str(d, opts);
case 'buffer': return sylk_to_aoa_str(d.toString('binary'), opts);
case 'array': return sylk_to_aoa_str(cc2str(d), opts);
}
throw new Error("Unrecognized type " + opts.type);
}
function sylk_to_aoa_str(str/*:string*/, opts)/*:AOA*/ {
var records = str.split(/[\n\r]+/), R = -1, C = -1, ri = 0, rj = 0, arr = [];
var formats = [];
var next_cell_format = null;
for (; ri !== records.length; ++ri) {
var record = records[ri].trim().split(";");
var RT = record[0], val;
if(RT === 'P') for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) {
case 'P':
formats.push(record[rj].substr(1));
break;
}
else if(RT !== 'C' && RT !== 'F') continue;
else for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) {
case 'Y':
R = parseInt(record[rj].substr(1))-1; C = 0;
for(var j = arr.length; j <= R; ++j) arr[j] = [];
break;
case 'X': C = parseInt(record[rj].substr(1))-1; break;
case 'K':
val = record[rj].substr(1);
if(val.charAt(0) === '"') val = val.substr(1,val.length - 2);
else if(val === 'TRUE') val = true;
else if(val === 'FALSE') val = false;
else if(+val === +val) {
val = +val;
if(next_cell_format !== null && next_cell_format.match(/[ymdhmsYMDHMS]/)) val = numdate(val);
}
arr[R][C] = val;
next_cell_format = null;
break;
case 'P':
if(RT !== 'F') break;
next_cell_format = formats[parseInt(record[rj].substr(1))];
}
}
return arr;
}
function sylk_to_sheet(str/*:string*/, opts)/*:Worksheet*/ { return aoa_to_sheet(sylk_to_aoa(str, opts), opts); }
function sylk_to_workbook(str/*:string*/, opts)/*:Workbook*/ { return sheet_to_workbook(sylk_to_sheet(str, opts), opts); }
function write_ws_cell_sylk(cell/*:Cell*/, ws/*:Worksheet*/, R/*:number*/, C/*:number*/, opts)/*:string*/ {
var o = "C;Y" + (R+1) + ";X" + (C+1) + ";K";
switch(cell.t) {
case 'n': o += cell.v; break;
case 'b': o += cell.v ? "TRUE" : "FALSE"; break;
case 'e': o += cell.w || cell.v; break;
case 'd': o += '"' + (cell.w || cell.v) + '"'; break;
case 's': o += '"' + cell.v.replace(/"/g,"") + '"'; break;
}
return o;
}
function sheet_to_sylk(ws/*:Worksheet*/, opts/*:?any*/)/*:string*/ {
var preamble/*:Array<string>*/ = ["ID;PWXL;N;E"], o/*:Array<string>*/ = [];
preamble.push("P;PGeneral");
var r = decode_range(ws['!ref']), cell/*:Cell*/;
for(var R = r.s.r; R <= r.e.r; ++R) {
for(var C = r.s.c; C <= r.e.c; ++C) {
var coord = encode_cell({r:R,c:C});
if(!(cell = ws[coord]) || cell.v == null) continue;
o.push(write_ws_cell_sylk(cell, ws, R, C, opts));
}
}
preamble.push("F;P0;DG0G8;M255");
var RS = "\r\n";
return preamble.join(RS) + RS + o.join(RS) + RS + "E" + RS;
}
return {
to_workbook: sylk_to_workbook,
to_sheet: sylk_to_sheet,
from_sheet: sheet_to_sylk
};
})();
var DIF = (function() {
function dif_to_aoa(d/*:RawData*/, opts)/*:AOA*/ {
switch(opts.type) {
case 'base64': return dif_to_aoa_str(Base64.decode(d), opts);
case 'binary': return dif_to_aoa_str(d, opts);
case 'buffer': return dif_to_aoa_str(d.toString('binary'), opts);
case 'array': return dif_to_aoa_str(cc2str(d), opts);
}
throw new Error("Unrecognized type " + opts.type);
}
function dif_to_aoa_str(str/*:string*/, opts)/*:AOA*/ {
var records = str.split('\n'), R = -1, C = -1, ri = 0, arr = [];
for (; ri !== records.length; ++ri) {
if (records[ri].trim() === 'BOT') { arr[++R] = []; C = 0; continue; }
if (R < 0) continue;
var metadata = records[ri].trim().split(",");
var type = metadata[0], value = metadata[1];
++ri;
var data = records[ri].trim();
switch (+type) {
case -1:
if (data === 'BOT') { arr[++R] = []; C = 0; continue; }
else if (data !== 'EOD') throw new Error("Unrecognized DIF special command " + data);
break;
case 0:
if(data === 'TRUE') arr[R][C] = true;
else if(data === 'FALSE') arr[R][C] = false;
else if(+value == +value) arr[R][C] = +value;
else if(!isNaN(new Date(value).getDate())) arr[R][C] = new Date(value);
else arr[R][C] = value;
++C; break;
case 1:
data = data.substr(1,data.length-2);
arr[R][C++] = data !== '' ? data : null;
break;
}
if (data === 'EOD') break;
}
return arr;
}
function dif_to_sheet(str/*:string*/, opts)/*:Worksheet*/ { return aoa_to_sheet(dif_to_aoa(str, opts), opts); }
function dif_to_workbook(str/*:string*/, opts)/*:Workbook*/ { return sheet_to_workbook(dif_to_sheet(str, opts), opts); }
var sheet_to_dif = (function() {
var push_field = function pf(o/*:Array<string>*/, topic/*:string*/, v/*:number*/, n/*:number*/, s/*:string*/) {
o.push(topic);
o.push(v + "," + n);
o.push('"' + s.replace(/"/g,'""') + '"');
};
var push_value = function po(o/*:Array<string>*/, type/*:number*/, v/*:number*/, s/*:string*/) {
o.push(type + "," + v);
o.push(type == 1 ? '"' + s.replace(/"/g,'""') + '"' : s);
};
return function sheet_to_dif(ws/*:Worksheet*/, opts/*:?any*/)/*:string*/ {
var o/*:Array<string>*/ = [];
var r = decode_range(ws['!ref']), cell/*:Cell*/;
push_field(o, "TABLE", 0, 1, "sheetjs");
push_field(o, "VECTORS", 0, r.e.r - r.s.r + 1,"");
push_field(o, "TUPLES", 0, r.e.c - r.s.c + 1,"");
push_field(o, "DATA", 0, 0,"");
for(var R = r.s.r; R <= r.e.r; ++R) {
push_value(o, -1, 0, "BOT");
for(var C = r.s.c; C <= r.e.c; ++C) {
var coord = encode_cell({r:R,c:C});
if(!(cell = ws[coord]) || cell.v == null) { push_value(o, 1, 0, ""); continue;}
switch(cell.t) {
case 'n': push_value(o, 0, (/*cell.w ||*/ cell.v), "V"); break;
case 'b': push_value(o, 0, cell.v ? 1 : 0, cell.v ? "TRUE" : "FALSE"); break;
case 's': push_value(o, 1, 0, cell.v); break;
default: push_value(o, 1, 0, "");
}
}
}
push_value(o, -1, 0, "EOD");
var RS = "\r\n";
var oo = o.join(RS);
//while((oo.length & 0x7F) != 0) oo += "\0";
return oo;
};
})();
return {
to_workbook: dif_to_workbook,
to_sheet: dif_to_sheet,
from_sheet: sheet_to_dif
};
})();

@ -53,12 +53,13 @@ function parse_dom_table(table/*:HTMLElement*/, opts/*:?any*/)/*:Worksheet*/ {
CS = +elt.getAttribute("colspan") || 1;
if((RS = +elt.getAttribute("rowspan"))>0) merges.push({s:{r:R,c:C},e:{r:R + RS - 1, c:C + CS - 1}});
var o = {t:'s', v:v};
if(!isNaN(Number(v))) o = {t:'n', v:Number(v)};
if(v != null && v.length && !isNaN(Number(v))) o = {t:'n', v:Number(v)};
ws[encode_cell({c:C, r:R})] = o;
C += CS;
}
}
ws['!merges'] = merges;
ws['!ref'] = encode_range(range);
return ws;
}

@ -5,3 +5,15 @@ function write_csv_str(wb/*:Workbook*/, o/*:WriteOpts*/) {
if(idx == 0 && !!o.sheet && wb.SheetNames[0] != o.sheet) throw new Error("Sheet not found: " + o.sheet);
return sheet_to_csv(wb.Sheets[wb.SheetNames[idx]], o);
}
function write_obj_str(factory) {
return function write_str(wb/*:Workbook*/, o/*:WriteOpts*/) {
var idx = 0;
for(var i=0;i<wb.SheetNames.length;++i) if(wb.SheetNames[i] == o.sheet) idx=i;
if(idx == 0 && !!o.sheet && wb.SheetNames[0] != o.sheet) throw new Error("Sheet not found: " + o.sheet);
return factory.from_sheet(wb.Sheets[wb.SheetNames[idx]], o);
};
}
var write_slk_str = write_obj_str(SYLK);
var write_dif_str = write_obj_str(DIF);

@ -34,7 +34,7 @@ function write_zip(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ {
/*::if(!wb.Props) throw "unreachable"; */
f = "docProps/app.xml";
if(!wb.Workbook || !wb.Workbook.Sheets) wb.Props.SheetNames = wb.SheetNames;
/*:: else if(!wb.Props) throw "unreachable"; */
// $FlowIgnore
else wb.Props.SheetNames = wb.Workbook.Sheets.filter(function(x) { return x.Hidden != 2; }).map(function(x) { return x.name; });
wb.Props.Worksheets = wb.Props.SheetNames.length;
zip.file(f, write_ext_props(wb.Props, opts));

@ -33,6 +33,8 @@ function readSync(data/*:RawData*/, opts/*:?ParseOpts*/)/*:Workbook*/ {
case 0xD0: return parse_xlscfb(CFB.read(d, o), o);
case 0x09: return parse_xlscfb(s2a(o.type === 'base64' ? Base64.decode(d) : d), o);
case 0x3C: return parse_xlml(d, o);
case 0x49: if(n[1] == 0x44) return SYLK.to_workbook(d, o); break;
case 0x54: if(n[1] == 0x41 && n[2] == 0x42 && n[3] == 0x4C) return DIF.to_workbook(d, o); break;
case 0x50: if(n[1] == 0x4B && n[2] < 0x20 && n[3] < 0x20) return read_zip(d, o); break;
case 0xEF: return parse_xlml(d, o);
case 0x03: case 0x83: case 0x8B: return DBF.to_workbook(d, o);

@ -48,7 +48,10 @@ function writeSync(wb/*:Workbook*/, opts/*:?WriteOpts*/) {
switch(o.bookType || 'xlsx') {
case 'xml':
case 'xlml': return write_string_type(write_xlml(wb, o), o);
case 'slk':
case 'sylk': return write_string_type(write_slk_str(wb, o), o);
case 'csv': return write_string_type(write_csv_str(wb, o), o);
case 'dif': return write_string_type(write_dif_str(wb, o), o);
case 'fods': return write_string_type(write_ods(wb, o), o);
case 'biff2': return write_binary_type(write_biff_buf(wb, o), o);
case 'xlsx':
@ -66,11 +69,14 @@ function resolve_book_type(o/*?WriteFileOpts*/) {
case '.xlsb': o.bookType = 'xlsb'; break;
case '.fods': o.bookType = 'fods'; break;
case '.xlml': o.bookType = 'xlml'; break;
case '.sylk': o.bookType = 'sylk'; break;
default: switch(o.file.slice(-4).toLowerCase()) {
case '.xls': o.bookType = 'biff2'; break;
case '.xml': o.bookType = 'xml'; break;
case '.ods': o.bookType = 'ods'; break;
case '.csv': o.bookType = 'csv'; break;
case '.dif': o.bookType = 'dif'; break;
case '.slk': o.bookType = 'sylk'; break;
}}
}

@ -1,4 +1,4 @@
### Worksheet Object
#### Worksheet Object
In addition to the base sheet keys, worksheets also add:
@ -13,7 +13,7 @@ In addition to the base sheet keys, worksheets also add:
will write all cells in the merge range if they exist, so be sure that only
the first cell (upper-left) in the range is set.
### Chartsheet Object
#### Chartsheet Object
Chartsheets are represented as standard sheets. They are distinguished with the
`!type` property set to `"chart"`.

@ -6,7 +6,7 @@
`wb.Props` is an object storing the standard properties. `wb.Custprops` stores
custom properties. Since the XLS standard properties deviate from the XLSX
standard, XLS parsing stores core properties in both places. .
standard, XLS parsing stores core properties in both places.
`wb.WBProps` includes more workbook-level properties:

32
docbits/56_wbprops.md Normal file

@ -0,0 +1,32 @@
#### Workbook File Properties
The various file formats use different internal names for file properties. The
workbook `Props` object normalizes the names:
| JS Name | Excel Description |
|:------------|:-------------------------------|
| Title | Summary tab "Title" |
| Subject | Summary tab "Subject" |
| Author | Summary tab "Author" |
| Manager | Summary tab "Manager" |
| Company | Summary tab "Company" |
| Category | Summary tab "Category" |
| Keywords | Summary tab "Keywords" |
| Comments | Summary tab "Comments" |
| LastAuthor | Statistics tab "Last saved by" |
| CreatedDate | Statistics tab "Created" |
For example, to set the workbook title property:
```js
if(!wb.Props) wb.Props = {};
wb.Props.Title = "Insert Title Here";
```
Custom properties are added in the workbook `Custprops` object:
```js
if(!wb.Custprops) wb.Custprops = {};
wb.Custprops["Custom Property"] = "Custom Value";
```

@ -65,6 +65,8 @@ file but Excel will know how to handle it. This library applies similar logic:
| `0x09` | BIFF Stream | BIFF 2/3/4/5 |
| `0x3C` | XML/HTML | SpreadsheetML / Flat ODS / UOS1 / HTML / plaintext |
| `0x50` | ZIP Archive | XLSB or XLSX/M or ODS or UOS2 or plaintext |
| `0x49` | Plain Text | SYLK or plaintext |
| `0x54` | Plain Text | DIF or plaintext |
| `0xFE` | UTF8 Text | SpreadsheetML or Flat ODS or UOS1 or plaintext |
DBF files are detected based on the first byte as well as the third and fourth

@ -33,6 +33,8 @@ output formats. The specific file type is controlled with `bookType` option:
| `biff2` | `.xls` | none | single | Excel 2.0 Worksheet format |
| `fods` | `.fods` | none | multi | Flat OpenDocument Spreadsheet |
| `csv` | `.csv` | none | single | Comma Separated Values |
| `sylk` | `.sylk` | none | single | Symbolic Link (SYLK) |
| `dif` | `.dif` | none | single | Data Interchange Format (DIF) |
- `compression` only applies to formats with ZIP containers.
- Formats that only support a single sheet require a `sheet` option specifying

@ -15,6 +15,8 @@ Despite the library name `xlsx`, it supports numerous spreadsheet file formats:
| Excel 2.0/2.1 (XLS BIFF2) | :o: | :o: |
| **Excel Supported Text Formats** |:-----:|:-----:|
| Delimiter-Separated Values (CSV/TSV/DSV) | | :o: |
| Data Interchange Format (DIF) | :o: | :o: |
| Symbolic Link (SYLK/SLK) | :o: | :o: |
| **Other Workbook/Worksheet Formats** |:-----:|:-----:|
| OpenDocument Spreadsheet (ODS) | :o: | :o: |
| Flat XML ODF Spreadsheet (FODS) | :o: | :o: |
@ -80,7 +82,17 @@ UOS is a very similar format, and it comes in 2 varieties corresponding to ODS
and FODS respectively. For the most part, the difference between the formats
lies in the names of tags and attributes.
### dBASE and Visual FoxPro (DBF)
### Comma-Separated Values (CSV)
Excel CSV deviates from RFC4180 in a number of important ways. The generated
CSV files should generally work in Excel although they may not work in RFC4180
compatible readers.
### Other Single-Worksheet Formats
Many older formats supported only one worksheet:
#### dBASE and Visual FoxPro (DBF)
DBF is really a typed table format: each column can only hold one data type and
each record omits type information. The parser generates a header row and
@ -89,13 +101,18 @@ inserts records starting at the second row of the worksheet.
Multi-file extensions like external memos and tables are currently unsupported,
limited by the general ability to read arbitrary files in the web browser.
### Comma-Separated Values
#### Symbolic Link (SYLK)
Excel CSV deviates from RFC4180 in a number of important ways. The generated
CSV files should generally work in Excel although they may not work in RFC4180
compatible readers.
There is no real documentation. All knowledge was gathered by saving files in
various versions of Excel to deduce the meaning of fields.
### HTML
#### Data Interchange Format (DIF)
There is no unified definition. Visicalc DIF differs from Lotus DIF, and both
differ from Excel DIF. Where ambiguous, the parser/writer follows the expected
behavior from Excel.
#### HTML
Excel HTML worksheets include special metadata encoded in styles. For example,
`mso-number-format` is a localized string containing the number format. Despite

@ -1,5 +1,5 @@
digraph G {
graph [mindist=0.1];
graph [mindist=0.1];
csf [shape=doublecircle,label="Common\nSpreadsheet\nFormat\n(JS Object)"];
subgraph XL {
node [style=filled,color=green];
@ -17,7 +17,7 @@ digraph G {
node [style=filled,color=yellow];
ods [label="ODS"];
fods [label="Flat\nODS"];
uos [label="UOS\n标文通"];
uos [label="UOS"];
}
subgraph OLD {
@ -25,6 +25,8 @@ digraph G {
html [label="HTML\nTable"];
csv [label="CSV"];
dbf [label="DBF"];
dif [label="DIF"];
slk [label="SYLK"];
}
subgraph JSXLSX {
@ -41,6 +43,10 @@ digraph G {
xls4 -> csf
xls5 -> csf
xls8 -> csf
csf -> slk
slk -> csf
csf -> dif
dif -> csf
csf -> csv
ods -> csf
csf -> ods

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 94 KiB

@ -18,9 +18,10 @@
+ [Data Types](README.md#data-types)
+ [Dates](README.md#dates)
* [Sheet Objects](README.md#sheet-objects)
* [Worksheet Object](README.md#worksheet-object)
* [Chartsheet Object](README.md#chartsheet-object)
+ [Worksheet Object](README.md#worksheet-object)
+ [Chartsheet Object](README.md#chartsheet-object)
* [Workbook Object](README.md#workbook-object)
+ [Workbook File Properties](README.md#workbook-file-properties)
* [Document Features](README.md#document-features)
+ [Formulae](README.md#formulae)
+ [Column Properties](README.md#column-properties)
@ -45,9 +46,12 @@
* [Excel 2003-2004 (SpreadsheetML)](README.md#excel-2003-2004-spreadsheetml)
* [Excel 2007+ Binary (XLSB, BIFF12)](README.md#excel-2007-binary-xlsb-biff12)
* [OpenDocument Spreadsheet (ODS/FODS) and Uniform Office Spreadsheet (UOS1/2)](README.md#opendocument-spreadsheet-odsfods-and-uniform-office-spreadsheet-uos12)
* [dBASE and Visual FoxPro (DBF)](README.md#dbase-and-visual-foxpro-dbf)
* [Comma-Separated Values](README.md#comma-separated-values)
* [HTML](README.md#html)
* [Comma-Separated Values (CSV)](README.md#comma-separated-values-csv)
* [Other Single-Worksheet Formats](README.md#other-single-worksheet-formats)
+ [dBASE and Visual FoxPro (DBF)](README.md#dbase-and-visual-foxpro-dbf)
+ [Symbolic Link (SYLK)](README.md#symbolic-link-sylk)
+ [Data Interchange Format (DIF)](README.md#data-interchange-format-dif)
+ [HTML](README.md#html)
- [Testing](README.md#testing)
* [Tested Environments](README.md#tested-environments)
* [Test Files](README.md#test-files)

43
test.js

@ -12,7 +12,7 @@ if(process.env.WTF) {
opts.cellStyles = true;
}
var fullex = [".xlsb", ".xlsm", ".xlsx"/*, ".xlml"*/];
var ofmt = ["xlsb", "xlsm", "xlsx", "ods", "biff2", "xlml"];
var ofmt = ["xlsb", "xlsm", "xlsx", "ods", "biff2", "xlml", "sylk", "dif"];
var ex = fullex.slice(); ex = ex.concat([".ods", ".xls", ".xml", ".fods"]);
if(process.env.FMTS === "full") process.env.FMTS = ex.join(":");
if(process.env.FMTS) ex=process.env.FMTS.split(":").map(function(x){return x[0]==="."?x:"."+x;});
@ -951,26 +951,31 @@ describe('roundtrip features', function() {
var bef = (function() { X = require(modp); });
if(typeof before != 'undefined') before(bef);
else it('before', bef);
describe('should parse core properties and custom properties', function() {
var wb1, wb2, base = './tmp/cp';
var bef = (function() {
wb1 = X.readFile(paths.cpxlsx);
wb2 = X.readFile(paths.cpxlsb);
fullex.forEach(function(p) {
X.writeFile(wb1, base + '.xlsm' + p);
X.writeFile(wb2, base + '.xlsb' + p);
});
describe('should preserve core properties', function() { [
['xlml', paths.cpxml],
['xlsx', paths.cpxlsx],
['xlsb', paths.cpxlsb]
].forEach(function(w) {
it(w[0], function() {
var wb1 = X.readFile(w[1]);
var wb2 = X.read(X.write(wb1, {bookType:w[0], type:"buffer"}), {type:"buffer"});
coreprop(wb1);
coreprop(wb2);
});
if(typeof before != 'undefined') before(bef);
else it('before', bef);
fullex.forEach(function(p) { ['.xlsm','.xlsb'].forEach(function(q) {
it(q + p + ' should roundtrip core and custom properties', function() {
var wb = X.readFile(base + q + p);
coreprop(wb);
custprop(wb);
}); });
}); });
describe('should preserve custom properties', function() { [
['xlml', paths.cpxml],
['xlsx', paths.cpxlsx],
['xlsb', paths.cpxlsb]
].forEach(function(w) {
it(w[0], function() {
var wb1 = X.readFile(w[1]);
var wb2 = X.read(X.write(wb1, {bookType:w[0], type:"buffer"}), {type:"buffer"});
custprop(wb1);
custprop(wb2);
});
});
}); });
describe('should preserve features', function() {
it('merge cells', function() {

@ -81,12 +81,25 @@ console.log("Worksheet Model:")
console.log(ws);
/* TEST: hidden sheets */
wb.SheetNames.push("Hidden");
wb.Sheets["Hidden"] = XLSX.utils.aoa_to_sheet(["Hidden".split(""), [1,2,3]]);
wb.Workbook = {Sheets:[]};
wb.Workbook.Sheets[1] = {Hidden:1};
/* TEST: properties */
wb.Props = {
Title: "SheetJS Test",
Subject: "Tests",
Author: "Devs at SheetJS",
Manager: "Sheet Manager",
Company: "SheetJS",
Category: "Experimentation",
Keywords: "Test",
Comments: "Nothing to say here",
LastAuthor: "Not SheetJS",
CreatedDate: new Date(2017,1,19)
}
/* write file */
XLSX.writeFile(wb, 'sheetjs.xlsx', {bookSST:true});
XLSX.writeFile(wb, 'sheetjs.xlsm');
@ -95,6 +108,7 @@ XLSX.writeFile(wb, 'sheetjs.xls', {bookType:'biff2'}); // no formula
XLSX.writeFile(wb, 'sheetjs.xml.xls', {bookType:'xlml'});
XLSX.writeFile(wb, 'sheetjs.ods');
XLSX.writeFile(wb, 'sheetjs.fods');
XLSX.writeFile(wb, 'sheetjs.slk');
XLSX.writeFile(wb, 'sheetjs.csv');
/* test by reading back files */
@ -105,4 +119,5 @@ XLSX.readFile('sheetjs.xls');
XLSX.readFile('sheetjs.xml.xls');
XLSX.readFile('sheetjs.ods');
XLSX.readFile('sheetjs.fods');
XLSX.readFile('sheetjs.slk');
//XLSX.readFile('sheetjs.csv');

@ -4932,6 +4932,183 @@ function dbf_to_workbook(buf, opts)/*:Workbook*/ {
to_sheet: dbf_to_sheet
};
})();
var SYLK = (function() {
/* TODO: find an actual specification */
function sylk_to_aoa(d/*:RawData*/, opts)/*:AOA*/ {
switch(opts.type) {
case 'base64': return sylk_to_aoa_str(Base64.decode(d), opts);
case 'binary': return sylk_to_aoa_str(d, opts);
case 'buffer': return sylk_to_aoa_str(d.toString('binary'), opts);
case 'array': return sylk_to_aoa_str(cc2str(d), opts);
}
throw new Error("Unrecognized type " + opts.type);
}
function sylk_to_aoa_str(str/*:string*/, opts)/*:AOA*/ {
var records = str.split(/[\n\r]+/), R = -1, C = -1, ri = 0, rj = 0, arr = [];
var formats = [];
var next_cell_format = null;
for (; ri !== records.length; ++ri) {
var record = records[ri].trim().split(";");
var RT = record[0], val;
if(RT === 'P') for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) {
case 'P':
formats.push(record[rj].substr(1));
break;
}
else if(RT !== 'C' && RT !== 'F') continue;
else for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) {
case 'Y':
R = parseInt(record[rj].substr(1))-1; C = 0;
for(var j = arr.length; j <= R; ++j) arr[j] = [];
break;
case 'X': C = parseInt(record[rj].substr(1))-1; break;
case 'K':
val = record[rj].substr(1);
if(val.charAt(0) === '"') val = val.substr(1,val.length - 2);
else if(val === 'TRUE') val = true;
else if(val === 'FALSE') val = false;
else if(+val === +val) {
val = +val;
if(next_cell_format !== null && next_cell_format.match(/[ymdhmsYMDHMS]/)) val = numdate(val);
}
arr[R][C] = val;
next_cell_format = null;
break;
case 'P':
if(RT !== 'F') break;
next_cell_format = formats[parseInt(record[rj].substr(1))];
}
}
return arr;
}
function sylk_to_sheet(str/*:string*/, opts)/*:Worksheet*/ { return aoa_to_sheet(sylk_to_aoa(str, opts), opts); }
function sylk_to_workbook(str/*:string*/, opts)/*:Workbook*/ { return sheet_to_workbook(sylk_to_sheet(str, opts), opts); }
function write_ws_cell_sylk(cell/*:Cell*/, ws/*:Worksheet*/, R/*:number*/, C/*:number*/, opts)/*:string*/ {
var o = "C;Y" + (R+1) + ";X" + (C+1) + ";K";
switch(cell.t) {
case 'n': o += cell.v; break;
case 'b': o += cell.v ? "TRUE" : "FALSE"; break;
case 'e': o += cell.w || cell.v; break;
case 'd': o += '"' + (cell.w || cell.v) + '"'; break;
case 's': o += '"' + cell.v.replace(/"/g,"") + '"'; break;
}
return o;
}
function sheet_to_sylk(ws/*:Worksheet*/, opts/*:?any*/)/*:string*/ {
var preamble/*:Array<string>*/ = ["ID;PWXL;N;E"], o/*:Array<string>*/ = [];
preamble.push("P;PGeneral");
var r = decode_range(ws['!ref']), cell/*:Cell*/;
for(var R = r.s.r; R <= r.e.r; ++R) {
for(var C = r.s.c; C <= r.e.c; ++C) {
var coord = encode_cell({r:R,c:C});
if(!(cell = ws[coord]) || cell.v == null) continue;
o.push(write_ws_cell_sylk(cell, ws, R, C, opts));
}
}
preamble.push("F;P0;DG0G8;M255");
var RS = "\r\n";
return preamble.join(RS) + RS + o.join(RS) + RS + "E" + RS;
}
return {
to_workbook: sylk_to_workbook,
to_sheet: sylk_to_sheet,
from_sheet: sheet_to_sylk
};
})();
var DIF = (function() {
function dif_to_aoa(d/*:RawData*/, opts)/*:AOA*/ {
switch(opts.type) {
case 'base64': return dif_to_aoa_str(Base64.decode(d), opts);
case 'binary': return dif_to_aoa_str(d, opts);
case 'buffer': return dif_to_aoa_str(d.toString('binary'), opts);
case 'array': return dif_to_aoa_str(cc2str(d), opts);
}
throw new Error("Unrecognized type " + opts.type);
}
function dif_to_aoa_str(str/*:string*/, opts)/*:AOA*/ {
var records = str.split('\n'), R = -1, C = -1, ri = 0, arr = [];
for (; ri !== records.length; ++ri) {
if (records[ri].trim() === 'BOT') { arr[++R] = []; C = 0; continue; }
if (R < 0) continue;
var metadata = records[ri].trim().split(",");
var type = metadata[0], value = metadata[1];
++ri;
var data = records[ri].trim();
switch (+type) {
case -1:
if (data === 'BOT') { arr[++R] = []; C = 0; continue; }
else if (data !== 'EOD') throw new Error("Unrecognized DIF special command " + data);
break;
case 0:
if(data === 'TRUE') arr[R][C] = true;
else if(data === 'FALSE') arr[R][C] = false;
else if(+value == +value) arr[R][C] = +value;
else if(!isNaN(new Date(value).getDate())) arr[R][C] = new Date(value);
else arr[R][C] = value;
++C; break;
case 1:
data = data.substr(1,data.length-2);
arr[R][C++] = data !== '' ? data : null;
break;
}
if (data === 'EOD') break;
}
return arr;
}
function dif_to_sheet(str/*:string*/, opts)/*:Worksheet*/ { return aoa_to_sheet(dif_to_aoa(str, opts), opts); }
function dif_to_workbook(str/*:string*/, opts)/*:Workbook*/ { return sheet_to_workbook(dif_to_sheet(str, opts), opts); }
var sheet_to_dif = (function() {
var push_field = function pf(o/*:Array<string>*/, topic/*:string*/, v/*:number*/, n/*:number*/, s/*:string*/) {
o.push(topic);
o.push(v + "," + n);
o.push('"' + s.replace(/"/g,'""') + '"');
};
var push_value = function po(o/*:Array<string>*/, type/*:number*/, v/*:number*/, s/*:string*/) {
o.push(type + "," + v);
o.push(type == 1 ? '"' + s.replace(/"/g,'""') + '"' : s);
};
return function sheet_to_dif(ws/*:Worksheet*/, opts/*:?any*/)/*:string*/ {
var o/*:Array<string>*/ = [];
var r = decode_range(ws['!ref']), cell/*:Cell*/;
push_field(o, "TABLE", 0, 1, "sheetjs");
push_field(o, "VECTORS", 0, r.e.r - r.s.r + 1,"");
push_field(o, "TUPLES", 0, r.e.c - r.s.c + 1,"");
push_field(o, "DATA", 0, 0,"");
for(var R = r.s.r; R <= r.e.r; ++R) {
push_value(o, -1, 0, "BOT");
for(var C = r.s.c; C <= r.e.c; ++C) {
var coord = encode_cell({r:R,c:C});
if(!(cell = ws[coord]) || cell.v == null) { push_value(o, 1, 0, ""); continue;}
switch(cell.t) {
case 'n': push_value(o, 0, (/*cell.w ||*/ cell.v), "V"); break;
case 'b': push_value(o, 0, cell.v ? 1 : 0, cell.v ? "TRUE" : "FALSE"); break;
case 's': push_value(o, 1, 0, cell.v); break;
default: push_value(o, 1, 0, "");
}
}
}
push_value(o, -1, 0, "EOD");
var RS = "\r\n";
var oo = o.join(RS);
//while((oo.length & 0x7F) != 0) oo += "\0";
return oo;
};
})();
return {
to_workbook: dif_to_workbook,
to_sheet: dif_to_sheet,
from_sheet: sheet_to_dif
};
})();
/* 18.4.1 charset to codepage mapping */
var CS2CP = ({
/*::[*/0/*::]*/: 1252, /* ANSI */
@ -13729,12 +13906,13 @@ function parse_dom_table(table/*:HTMLElement*/, opts/*:?any*/)/*:Worksheet*/ {
CS = +elt.getAttribute("colspan") || 1;
if((RS = +elt.getAttribute("rowspan"))>0) merges.push({s:{r:R,c:C},e:{r:R + RS - 1, c:C + CS - 1}});
var o = {t:'s', v:v};
if(!isNaN(Number(v))) o = {t:'n', v:Number(v)};
if(v != null && v.length && !isNaN(Number(v))) o = {t:'n', v:Number(v)};
ws[encode_cell({c:C, r:R})] = o;
C += CS;
}
}
ws['!merges'] = merges;
ws['!ref'] = encode_range(range);
return ws;
}
@ -14218,6 +14396,18 @@ function write_csv_str(wb/*:Workbook*/, o/*:WriteOpts*/) {
if(idx == 0 && !!o.sheet && wb.SheetNames[0] != o.sheet) throw new Error("Sheet not found: " + o.sheet);
return sheet_to_csv(wb.Sheets[wb.SheetNames[idx]], o);
}
function write_obj_str(factory) {
return function write_str(wb/*:Workbook*/, o/*:WriteOpts*/) {
var idx = 0;
for(var i=0;i<wb.SheetNames.length;++i) if(wb.SheetNames[i] == o.sheet) idx=i;
if(idx == 0 && !!o.sheet && wb.SheetNames[0] != o.sheet) throw new Error("Sheet not found: " + o.sheet);
return factory.from_sheet(wb.Sheets[wb.SheetNames[idx]], o);
};
}
var write_slk_str = write_obj_str(SYLK);
var write_dif_str = write_obj_str(DIF);
/* Part 3: Packages */
function parse_ods(zip/*:ZIPFile*/, opts/*:?ParseOpts*/) {
opts = opts || ({}/*:any*/);
@ -14510,7 +14700,7 @@ function write_zip(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ {
/*::if(!wb.Props) throw "unreachable"; */
f = "docProps/app.xml";
if(!wb.Workbook || !wb.Workbook.Sheets) wb.Props.SheetNames = wb.SheetNames;
/*:: else if(!wb.Props) throw "unreachable"; */
// $FlowIgnore
else wb.Props.SheetNames = wb.Workbook.Sheets.filter(function(x) { return x.Hidden != 2; }).map(function(x) { return x.name; });
wb.Props.Worksheets = wb.Props.SheetNames.length;
zip.file(f, write_ext_props(wb.Props, opts));
@ -14606,6 +14796,8 @@ function readSync(data/*:RawData*/, opts/*:?ParseOpts*/)/*:Workbook*/ {
case 0xD0: return parse_xlscfb(CFB.read(d, o), o);
case 0x09: return parse_xlscfb(s2a(o.type === 'base64' ? Base64.decode(d) : d), o);
case 0x3C: return parse_xlml(d, o);
case 0x49: if(n[1] == 0x44) return SYLK.to_workbook(d, o); break;
case 0x54: if(n[1] == 0x41 && n[2] == 0x42 && n[3] == 0x4C) return DIF.to_workbook(d, o); break;
case 0x50: if(n[1] == 0x4B && n[2] < 0x20 && n[3] < 0x20) return read_zip(d, o); break;
case 0xEF: return parse_xlml(d, o);
case 0x03: case 0x83: case 0x8B: return DBF.to_workbook(d, o);
@ -14669,7 +14861,10 @@ function writeSync(wb/*:Workbook*/, opts/*:?WriteOpts*/) {
switch(o.bookType || 'xlsx') {
case 'xml':
case 'xlml': return write_string_type(write_xlml(wb, o), o);
case 'slk':
case 'sylk': return write_string_type(write_slk_str(wb, o), o);
case 'csv': return write_string_type(write_csv_str(wb, o), o);
case 'dif': return write_string_type(write_dif_str(wb, o), o);
case 'fods': return write_string_type(write_ods(wb, o), o);
case 'biff2': return write_binary_type(write_biff_buf(wb, o), o);
case 'xlsx':
@ -14687,11 +14882,14 @@ function resolve_book_type(o/*?WriteFileOpts*/) {
case '.xlsb': o.bookType = 'xlsb'; break;
case '.fods': o.bookType = 'fods'; break;
case '.xlml': o.bookType = 'xlml'; break;
case '.sylk': o.bookType = 'sylk'; break;
default: switch(o.file.slice(-4).toLowerCase()) {
case '.xls': o.bookType = 'biff2'; break;
case '.xml': o.bookType = 'xml'; break;
case '.ods': o.bookType = 'ods'; break;
case '.csv': o.bookType = 'csv'; break;
case '.dif': o.bookType = 'dif'; break;
case '.slk': o.bookType = 'sylk'; break;
}}
}

203
xlsx.js

@ -4878,6 +4878,183 @@ function dbf_to_workbook(buf, opts) {
to_sheet: dbf_to_sheet
};
})();
var SYLK = (function() {
/* TODO: find an actual specification */
function sylk_to_aoa(d, opts) {
switch(opts.type) {
case 'base64': return sylk_to_aoa_str(Base64.decode(d), opts);
case 'binary': return sylk_to_aoa_str(d, opts);
case 'buffer': return sylk_to_aoa_str(d.toString('binary'), opts);
case 'array': return sylk_to_aoa_str(cc2str(d), opts);
}
throw new Error("Unrecognized type " + opts.type);
}
function sylk_to_aoa_str(str, opts) {
var records = str.split(/[\n\r]+/), R = -1, C = -1, ri = 0, rj = 0, arr = [];
var formats = [];
var next_cell_format = null;
for (; ri !== records.length; ++ri) {
var record = records[ri].trim().split(";");
var RT = record[0], val;
if(RT === 'P') for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) {
case 'P':
formats.push(record[rj].substr(1));
break;
}
else if(RT !== 'C' && RT !== 'F') continue;
else for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) {
case 'Y':
R = parseInt(record[rj].substr(1))-1; C = 0;
for(var j = arr.length; j <= R; ++j) arr[j] = [];
break;
case 'X': C = parseInt(record[rj].substr(1))-1; break;
case 'K':
val = record[rj].substr(1);
if(val.charAt(0) === '"') val = val.substr(1,val.length - 2);
else if(val === 'TRUE') val = true;
else if(val === 'FALSE') val = false;
else if(+val === +val) {
val = +val;
if(next_cell_format !== null && next_cell_format.match(/[ymdhmsYMDHMS]/)) val = numdate(val);
}
arr[R][C] = val;
next_cell_format = null;
break;
case 'P':
if(RT !== 'F') break;
next_cell_format = formats[parseInt(record[rj].substr(1))];
}
}
return arr;
}
function sylk_to_sheet(str, opts) { return aoa_to_sheet(sylk_to_aoa(str, opts), opts); }
function sylk_to_workbook(str, opts) { return sheet_to_workbook(sylk_to_sheet(str, opts), opts); }
function write_ws_cell_sylk(cell, ws, R, C, opts) {
var o = "C;Y" + (R+1) + ";X" + (C+1) + ";K";
switch(cell.t) {
case 'n': o += cell.v; break;
case 'b': o += cell.v ? "TRUE" : "FALSE"; break;
case 'e': o += cell.w || cell.v; break;
case 'd': o += '"' + (cell.w || cell.v) + '"'; break;
case 's': o += '"' + cell.v.replace(/"/g,"") + '"'; break;
}
return o;
}
function sheet_to_sylk(ws, opts) {
var preamble = ["ID;PWXL;N;E"], o = [];
preamble.push("P;PGeneral");
var r = decode_range(ws['!ref']), cell;
for(var R = r.s.r; R <= r.e.r; ++R) {
for(var C = r.s.c; C <= r.e.c; ++C) {
var coord = encode_cell({r:R,c:C});
if(!(cell = ws[coord]) || cell.v == null) continue;
o.push(write_ws_cell_sylk(cell, ws, R, C, opts));
}
}
preamble.push("F;P0;DG0G8;M255");
var RS = "\r\n";
return preamble.join(RS) + RS + o.join(RS) + RS + "E" + RS;
}
return {
to_workbook: sylk_to_workbook,
to_sheet: sylk_to_sheet,
from_sheet: sheet_to_sylk
};
})();
var DIF = (function() {
function dif_to_aoa(d, opts) {
switch(opts.type) {
case 'base64': return dif_to_aoa_str(Base64.decode(d), opts);
case 'binary': return dif_to_aoa_str(d, opts);
case 'buffer': return dif_to_aoa_str(d.toString('binary'), opts);
case 'array': return dif_to_aoa_str(cc2str(d), opts);
}
throw new Error("Unrecognized type " + opts.type);
}
function dif_to_aoa_str(str, opts) {
var records = str.split('\n'), R = -1, C = -1, ri = 0, arr = [];
for (; ri !== records.length; ++ri) {
if (records[ri].trim() === 'BOT') { arr[++R] = []; C = 0; continue; }
if (R < 0) continue;
var metadata = records[ri].trim().split(",");
var type = metadata[0], value = metadata[1];
++ri;
var data = records[ri].trim();
switch (+type) {
case -1:
if (data === 'BOT') { arr[++R] = []; C = 0; continue; }
else if (data !== 'EOD') throw new Error("Unrecognized DIF special command " + data);
break;
case 0:
if(data === 'TRUE') arr[R][C] = true;
else if(data === 'FALSE') arr[R][C] = false;
else if(+value == +value) arr[R][C] = +value;
else if(!isNaN(new Date(value).getDate())) arr[R][C] = new Date(value);
else arr[R][C] = value;
++C; break;
case 1:
data = data.substr(1,data.length-2);
arr[R][C++] = data !== '' ? data : null;
break;
}
if (data === 'EOD') break;
}
return arr;
}
function dif_to_sheet(str, opts) { return aoa_to_sheet(dif_to_aoa(str, opts), opts); }
function dif_to_workbook(str, opts) { return sheet_to_workbook(dif_to_sheet(str, opts), opts); }
var sheet_to_dif = (function() {
var push_field = function pf(o, topic, v, n, s) {
o.push(topic);
o.push(v + "," + n);
o.push('"' + s.replace(/"/g,'""') + '"');
};
var push_value = function po(o, type, v, s) {
o.push(type + "," + v);
o.push(type == 1 ? '"' + s.replace(/"/g,'""') + '"' : s);
};
return function sheet_to_dif(ws, opts) {
var o = [];
var r = decode_range(ws['!ref']), cell;
push_field(o, "TABLE", 0, 1, "sheetjs");
push_field(o, "VECTORS", 0, r.e.r - r.s.r + 1,"");
push_field(o, "TUPLES", 0, r.e.c - r.s.c + 1,"");
push_field(o, "DATA", 0, 0,"");
for(var R = r.s.r; R <= r.e.r; ++R) {
push_value(o, -1, 0, "BOT");
for(var C = r.s.c; C <= r.e.c; ++C) {