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:
parent
e42cf43c02
commit
b93569badf
@ -39,3 +39,5 @@ test.js
|
||||
bits/
|
||||
docbits/
|
||||
tests/
|
||||
_book
|
||||
book.json
|
||||
|
85
README.md
85
README.md
@ -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
|
||||
|
12
bin/xlsx.njs
12
bin/xlsx.njs
@ -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);
|
||||
} });
|
||||
|
||||
|
177
bits/40_harb.js
177
bits/40_harb.js
@ -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
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
|
||||
|
10
formats.dot
10
formats.dot
@ -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
|
||||
|
BIN
formats.png
BIN
formats.png
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
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');
|
||||
|
202
xlsx.flow.js
202
xlsx.flow.js
@ -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
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) {
|
||||