diff --git a/.gitignore b/.gitignore
index 8f481d8ba3121c447e240c73fee7a628dd98a843..1ac4705d9265eca7295772ddf7b397f800f2ccc6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,7 @@ tmp
*.[qQ][pP][wW]
*.[bB][iI][fF][fF][23458]
*.[rR][tT][fF]
+*.[eE][tT][hH]
*.123
*.htm
*.html
diff --git a/.npmignore b/.npmignore
index dfb5f49b40d57c5affc20e601d96a4fc43af0be1..8ff5b6cd80f5747b0e8ef33e3b51fe076978da22 100644
--- a/.npmignore
+++ b/.npmignore
@@ -24,6 +24,7 @@ tmp
*.[qQ][pP][wW]
*.[bB][iI][fF][fF][23458]
*.[rR][tT][fF]
+*.[eE][tT][hH]
*.123
*.htm
*.html
diff --git a/.spelling b/.spelling
index 979e8adb0b7a7135cd43e6919a391dfac5403c3e..ef7dbdb16de424e256a9165cfe9bc5d51ee863c5 100644
--- a/.spelling
+++ b/.spelling
@@ -34,6 +34,7 @@ tooltips
Browserify
CDNjs
CommonJS
+Ethercalc
ExtendScript
FileSaver
JavaScriptCore
diff --git a/Makefile b/Makefile
index 603241a22be8b50933c994b8d4bc4ae48a996d44..588af6bdb428125ba270c405c15500eb3406e5cd 100644
--- a/Makefile
+++ b/Makefile
@@ -56,6 +56,7 @@ dist: dist-deps $(TARGET) bower.json ## Prepare JS files for distribution
mkdir -p dist
<$(TARGET) sed "s/require('stream')/{}/g;s/require('....*')/undefined/g" > dist/$(TARGET)
cp LICENSE dist/
+ uglifyjs shim.js $(UGLIFYOPTS) -o dist/shim.min.js --preamble "$$(head -n 1 bits/00_header.js)"
uglifyjs $(DISTHDR) dist/$(TARGET) $(UGLIFYOPTS) -o dist/$(LIB).min.js --source-map dist/$(LIB).min.map --preamble "$$(head -n 1 bits/00_header.js)"
misc/strip_sourcemap.sh dist/$(LIB).min.js
uglifyjs $(DISTHDR) $(REQS) dist/$(TARGET) $(UGLIFYOPTS) -o dist/$(LIB).core.min.js --source-map dist/$(LIB).core.min.map --preamble "$$(head -n 1 bits/00_header.js)"
diff --git a/README.md b/README.md
index 897a225c0e929403e82ee6cf89d65651b19f8d74..d45d42bbcc4f35c34cb1b0d3442308ad37a162e9 100644
--- a/README.md
+++ b/README.md
@@ -128,6 +128,7 @@ enhancements, additional features by request, and dedicated support.
+ [Data Interchange Format (DIF)](#data-interchange-format-dif)
+ [HTML](#html)
+ [Rich Text Format (RTF)](#rich-text-format-rtf)
+ + [Ethercalc Record Format (ETH)](#ethercalc-record-format-eth)
- [Testing](#testing)
* [Node](#node)
* [Browser](#browser)
@@ -1586,6 +1587,7 @@ Plain text format guessing follows the priority order:
| DSV | more unquoted `";"` chars than `"\t"` or `","` in the first 1024 |
| TSV | more unquoted `"\t"` chars than `","` chars in the first 1024 |
| CSV | one of the first 1024 characters is a comma `","` |
+| ETH | starts with `socialcalc:version:` |
| PRN | (default) |
- HTML tags include: `html`, `table`, `head`, `meta`, `script`, `style`, `div`
@@ -1661,6 +1663,7 @@ output formats. The specific file type is controlled with `bookType` option:
| `dbf` | `.dbf` | none | single | dBASE II + VFP Extensions (DBF) |
| `rtf` | `.rtf` | none | single | Rich Text Format (RTF) |
| `prn` | `.prn` | none | single | Lotus Formatted Text |
+| `eth` | `.eth` | none | single | Ethercalc Record Format (ETH) |
- `compression` only applies to formats with ZIP containers.
- Formats that only support a single sheet require a `sheet` option specifying
@@ -2000,6 +2003,7 @@ Despite the library name `xlsx`, it supports numerous spreadsheet file formats:
| **Other Common Spreadsheet Output Formats** |:-----:|:-----:|
| HTML Tables | :o: | :o: |
| Rich Text Format tables (RTF) | | :o: |
+| Ethercalc Record Format (ETH) | :o: | :o: |
### Excel 2007+ XML (XLSX/XLSM)
@@ -2235,6 +2239,16 @@ worksheet. The supported codes are a subset of the Word RTF support.
+#### Ethercalc Record Format (ETH)
+
+
+ (click to show)
+
+[Ethercalc](https://ethercalc.net/) is an open source web spreadsheet powered by
+a record format reminiscent of SYLK wrapped in a MIME multi-part message.
+
+
+
## Testing
diff --git a/bin/xlsx.njs b/bin/xlsx.njs
index f3d41405bf887ed028f15b3ff800e85f9cf4aee9..764aa540e7b5d23d1eeb83a1cc47d450fdd9dfe0 100755
--- a/bin/xlsx.njs
+++ b/bin/xlsx.njs
@@ -37,6 +37,7 @@ program
.option('-U, --dbf', 'emit DBF to or .dbf (MSVFP DBF)')
.option('-K, --sylk', 'emit SYLK to or .slk (Excel SYLK)')
.option('-P, --prn', 'emit PRN to or .prn (Lotus PRN)')
+ .option('-E, --eth', 'emit ETH to or .eth (Ethercalc)')
.option('-t, --txt', 'emit TXT to or .txt (UTF-8 TSV)')
.option('-r, --rtf', 'emit RTF to or .txt (Table RTF)')
@@ -188,6 +189,7 @@ if(!program.quiet && !program.book) console.error(target_sheet);
['sylk', '.slk'],
['html', '.html'],
['prn', '.prn'],
+ ['eth', '.eth'],
['rtf', '.rtf'],
['txt', '.txt'],
['dbf', '.dbf'],
diff --git a/bits/01_version.js b/bits/01_version.js
index b33c9186769980ec0666b1361742bb6325eb52fe..0039da2f6a9501316be38208d0db836854eb7980 100644
--- a/bits/01_version.js
+++ b/bits/01_version.js
@@ -1 +1 @@
-XLSX.version = '0.11.11';
+XLSX.version = '0.11.12';
diff --git a/bits/23_binutils.js b/bits/23_binutils.js
index 543f3e3f4c49a7052c1805658958b57ad7a22191..8959554841a5d391e16fe0b565f5a727a6310266 100644
--- a/bits/23_binutils.js
+++ b/bits/23_binutils.js
@@ -126,6 +126,12 @@ function ReadShift(size/*:number*/, t/*:?string*/)/*:number|string*/ {
loc+=2;
} o = oo.join(""); size *= 2; break;
+ case 'cpstr':
+ if(typeof cptable !== 'undefined') {
+ o = cptable.utils.decode(current_codepage, this.slice(this.l, this.l + size));
+ break;
+ }
+ /* falls through */
case 'sbcs-cont': o = ""; loc = this.l;
for(i = 0; i != size; ++i) {
if(this.lens && this.lens.indexOf(loc) !== -1) {
diff --git a/bits/38_xlstypes.js b/bits/38_xlstypes.js
index 8abc056ffaf9727c8241eb8d5242d8fe1a070c09..b5bea788ba77747119932eefe2a63ca483745362 100644
--- a/bits/38_xlstypes.js
+++ b/bits/38_xlstypes.js
@@ -360,7 +360,7 @@ function parse_XLUnicodeString2(blob, length, opts) {
if(opts.biff > 5) return parse_XLUnicodeString(blob, length, opts);
var cch = blob.read_shift(1);
if(cch === 0) { blob.l++; return ""; }
- return blob.read_shift(cch, 'sbcs-cont');
+ return blob.read_shift(cch, opts.biff == 4 ? 'cpstr' : 'sbcs-cont');
}
/* TODO: BIFF5 and lower, codepage awareness */
function write_XLUnicodeString(str, opts, o) {
diff --git a/bits/40_harb.js b/bits/40_harb.js
index 55eadd8ab40cc2412864f390096d48648b00234e..cac5c890ba741a7349caf3446d0e0bd27f3abe60 100644
--- a/bits/40_harb.js
+++ b/bits/40_harb.js
@@ -580,6 +580,105 @@ var DIF = (function() {
};
})();
+var ETH = (function() {
+ function decode(s/*:string*/)/*:string*/ { return s.replace(/\\b/g,"\\").replace(/\\c/g,":").replace(/\\n/g,"\n"); }
+ function encode(s/*:string*/)/*:string*/ { return s.replace(/\\/g, "\\b").replace(/:/g, "\\c").replace(/\n/g,"\\n"); }
+
+ function eth_to_aoa(str/*:string*/, opts)/*:AOA*/ {
+ var records = str.split('\n'), R = -1, C = -1, ri = 0, arr = [];
+ for (; ri !== records.length; ++ri) {
+ var record = records[ri].trim().split(":");
+ if(record[0] !== 'cell') continue;
+ var addr = decode_cell(record[1]);
+ if(arr.length <= addr.r) for(R = arr.length; R <= addr.r; ++R) if(!arr[R]) arr[R] = [];
+ R = addr.r; C = addr.c;
+ switch(record[2]) {
+ case 't': arr[R][C] = decode(record[3]); break;
+ case 'v': arr[R][C] = +record[3]; break;
+ case 'vtf': var _f = record[record.length - 1];
+ /* falls through */
+ case 'vtc':
+ switch(record[3]) {
+ case 'nl': arr[R][C] = +record[4] ? true : false; break;
+ default: arr[R][C] = +record[4]; break;
+ }
+ if(record[2] == 'vtf') arr[R][C] = [arr[R][C], _f];
+ }
+ }
+ return arr;
+ }
+
+ function eth_to_sheet(d/*:string*/, opts)/*:Worksheet*/ { return aoa_to_sheet(eth_to_aoa(d, opts), opts); }
+ function eth_to_workbook(d/*:string*/, opts)/*:Workbook*/ { return sheet_to_workbook(eth_to_sheet(d, opts), opts); }
+
+ var header = [
+ "socialcalc:version:1.5",
+ "MIME-Version: 1.0",
+ "Content-Type: multipart/mixed; boundary=SocialCalcSpreadsheetControlSave"
+ ].join("\n");
+
+ var sep = [
+ "--SocialCalcSpreadsheetControlSave",
+ "Content-type: text/plain; charset=UTF-8"
+ ].join("\n") + "\n";
+
+ /* TODO: the other parts */
+ var meta = [
+ "# SocialCalc Spreadsheet Control Save",
+ "part:sheet"
+ ].join("\n");
+
+ var end = "--SocialCalcSpreadsheetControlSave--";
+
+ function sheet_to_eth_data(ws/*:Worksheet*/)/*:string*/ {
+ if(!ws || !ws['!ref']) return "";
+ var o = [], oo = [], cell, coord;
+ var r = decode_range(ws['!ref']);
+ var dense = Array.isArray(ws);
+ for(var R = r.s.r; R <= r.e.r; ++R) {
+ for(var C = r.s.c; C <= r.e.c; ++C) {
+ coord = encode_cell({r:R,c:C});
+ cell = dense ? (ws[R]||[])[C] : ws[coord];
+ if(!cell || cell.v == null || cell.t === 'z') continue;
+ oo = ["cell", coord, 't'];
+ switch(cell.t) {
+ case 's': case 'str': oo.push(encode(cell.v)); break;
+ case 'n':
+ if(!cell.f) { oo[2]='v'; oo[3]=cell.v; }
+ else { oo[2]='vtf'; oo[3]='n'; oo[4]=cell.v; oo[5]=encode(cell.f); }
+ break;
+ case 'b':
+ oo[2] = 'vt'+(cell.f?'f':'c'); oo[3]='nl'; oo[4]=+!!cell.v;
+ oo[5] = encode(cell.f||(cell.v?'TRUE':'FALSE'));
+ break;
+ case 'd':
+ var t = datenum(parseDate(cell.v));
+ oo[2] = 'vtc'; oo[3] = 'nd'; oo[4] = t;
+ oo[5] = cell.w || SSF.format(cell.z || SSF._table[14], t);
+ break;
+ case 'e': continue;
+ }
+ o.push(oo.join(":"));
+ }
+ }
+ o.push("sheet:c:" + (r.e.c-r.s.c+1) + ":r:" + (r.e.r-r.s.r+1) + ":tvf:1");
+ o.push("valueformat:1:text-wiki");
+ //o.push("copiedfrom:" + ws['!ref']); // clipboard only
+ return o.join("\n");
+ }
+
+ function sheet_to_eth(ws/*:Worksheet*/, opts/*:?any*/)/*:string*/ {
+ return [header, sep, meta, sep, sheet_to_eth_data(ws), end].join("\n");
+ // return ["version:1.5", sheet_to_eth_data(ws)].join("\n"); // clipboard form
+ }
+
+ return {
+ to_workbook: eth_to_workbook,
+ to_sheet: eth_to_sheet,
+ from_sheet: sheet_to_eth
+ };
+})();
+
var PRN = (function() {
function set_text_arr(data/*:string*/, arr/*:AOA*/, R/*:number*/, C/*:number*/, o/*:any*/) {
if(o.raw) arr[R][C] = data;
@@ -713,7 +812,7 @@ var PRN = (function() {
}
function prn_to_sheet_str(str/*:string*/, opts)/*:Worksheet*/ {
- if(str.substr(0,4) == "sep=") return dsv_to_sheet_str(str, opts);
+ if(str.slice(0,4) == "sep=") return dsv_to_sheet_str(str, opts);
if(str.indexOf("\t") >= 0 || str.indexOf(",") >= 0 || str.indexOf(";") >= 0) return dsv_to_sheet_str(str, opts);
return aoa_to_sheet(prn_to_aoa_str(str, opts), opts);
}
@@ -729,6 +828,7 @@ var PRN = (function() {
default: throw new Error("Unrecognized type " + opts.type);
}
if(bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) str = utf8read(str.slice(3));
+ if(str.slice(0,19) == "socialcalc:version:") return ETH.to_sheet(opts.type == 'string' ? str : utf8read(str), opts);
return prn_to_sheet_str(str, opts);
}
diff --git a/bits/82_sheeter.js b/bits/82_sheeter.js
index 3111dbabad082de184d22b89978480401b5e6d91..e050ab80755050a204e57e6dff76e157c83c14d7 100644
--- a/bits/82_sheeter.js
+++ b/bits/82_sheeter.js
@@ -21,4 +21,5 @@ var write_rtf_str = write_obj_str(RTF);
var write_txt_str = write_obj_str({from_sheet:sheet_to_txt});
// $FlowIgnore
var write_dbf_buf = write_obj_str(DBF);
+var write_eth_str = write_obj_str(ETH);
diff --git a/bits/88_write.js b/bits/88_write.js
index 7622350b4b74c99c7f52063de955719f6adcb5b9..a973acd3292c3ad8a1eec6880d2e9db3ae97eabc 100644
--- a/bits/88_write.js
+++ b/bits/88_write.js
@@ -92,6 +92,7 @@ function writeSync(wb/*:Workbook*/, opts/*:?WriteOpts*/) {
case 'dbf': return write_binary_type(write_dbf_buf(wb, o), o);
case 'prn': return write_string_type(write_prn_str(wb, o), o);
case 'rtf': return write_string_type(write_rtf_str(wb, o), o);
+ case 'eth': return write_string_type(write_eth_str(wb, o), o);
case 'fods': return write_string_type(write_ods(wb, o), o);
case 'biff2': if(!o.biff) o.biff = 2; /* falls through */
case 'biff3': if(!o.biff) o.biff = 3; /* falls through */
@@ -113,6 +114,7 @@ function resolve_book_type(o/*:WriteFileOpts*/) {
"xls": "biff8",
"htm": "html",
"slk": "sylk",
+ "socialcalc": "eth",
"Sh33tJS": "WTF"
};
var ext = o.file.slice(o.file.lastIndexOf(".")).toLowerCase();
diff --git a/bits/90_utils.js b/bits/90_utils.js
index 7d39734415d41efbc95bbb5ffbacdad483682453..7628e4a6022aada731d47bf5cedbc2af2bede9c9 100644
--- a/bits/90_utils.js
+++ b/bits/90_utils.js
@@ -222,6 +222,9 @@ var utils/*:any*/ = {
sheet_to_txt: sheet_to_txt,
sheet_to_json: sheet_to_json,
sheet_to_html: HTML_.from_sheet,
+ sheet_to_dif: DIF.from_sheet,
+ sheet_to_slk: SYLK.from_sheet,
+ sheet_to_eth: ETH.from_sheet,
sheet_to_formulae: sheet_to_formulae,
sheet_to_row_object_array: sheet_to_json
};
diff --git a/dist/shim.min.js b/dist/shim.min.js
new file mode 100644
index 0000000000000000000000000000000000000000..ef31ade37d5c6ad660c6299685fcd6918b707231
Binary files /dev/null and b/dist/shim.min.js differ
diff --git a/dist/xlsx.core.min.js b/dist/xlsx.core.min.js
index 65fda89ffb8a5698453dbc16d3426fa93294e7b3..2198045cbfa0facb6319ab3697f6c02d6c412802 100644
Binary files a/dist/xlsx.core.min.js and b/dist/xlsx.core.min.js differ
diff --git a/dist/xlsx.core.min.map b/dist/xlsx.core.min.map
index d2a09e32d528013d040b0ed94b50f24cadc6dcfb..80932e3bf5b263d4da01da11d8786391358f2b94 100644
Binary files a/dist/xlsx.core.min.map and b/dist/xlsx.core.min.map differ
diff --git a/dist/xlsx.full.min.js b/dist/xlsx.full.min.js
index 06250a373029ce216939eca960adc194ff08882c..63e14c225607803a14961aeb8a10d6da2d46a4c6 100644
Binary files a/dist/xlsx.full.min.js and b/dist/xlsx.full.min.js differ
diff --git a/dist/xlsx.full.min.map b/dist/xlsx.full.min.map
index 5f0848a71c57435169c82026d974f5917ece8b4f..24ac9283cce09aebf2b9ee6d5c736036bfa85929 100644
Binary files a/dist/xlsx.full.min.map and b/dist/xlsx.full.min.map differ
diff --git a/dist/xlsx.js b/dist/xlsx.js
index ba839bd4fcc3511efac1e05117cae5cef107db31..5109969235454021c7a326bf4321a4026224339f 100644
Binary files a/dist/xlsx.js and b/dist/xlsx.js differ
diff --git a/dist/xlsx.min.js b/dist/xlsx.min.js
index e77b84e1b940f79874978dc853522690474f6ccc..8e92054a8e7e9a1b6bc0977b692a1269d9913fb5 100644
Binary files a/dist/xlsx.min.js and b/dist/xlsx.min.js differ
diff --git a/dist/xlsx.min.map b/dist/xlsx.min.map
index f19d7a7e15dbcff3e75a53b3fe0944a5f9b827e5..72e9ba76e727bcad9de91cc267ab36e8c6b4d6a2 100644
Binary files a/dist/xlsx.min.map and b/dist/xlsx.min.map differ
diff --git a/docbits/80_parseopts.md b/docbits/80_parseopts.md
index 625ecc752d5d89b21aeb6e0d03b9069ecdd27e6d..8e52c0f2c56ad5fc78004590fd047c1c85267731 100644
--- a/docbits/80_parseopts.md
+++ b/docbits/80_parseopts.md
@@ -100,6 +100,7 @@ Plain text format guessing follows the priority order:
| DSV | more unquoted `";"` chars than `"\t"` or `","` in the first 1024 |
| TSV | more unquoted `"\t"` chars than `","` chars in the first 1024 |
| CSV | one of the first 1024 characters is a comma `","` |
+| ETH | starts with `socialcalc:version:` |
| PRN | (default) |
- HTML tags include: `html`, `table`, `head`, `meta`, `script`, `style`, `div`
diff --git a/docbits/81_writeopts.md b/docbits/81_writeopts.md
index 95e5339415b0508adb8de544f5b1897113fedd02..ec4e0e2dfe88fbcf9b988eccd6db5a121b032234 100644
--- a/docbits/81_writeopts.md
+++ b/docbits/81_writeopts.md
@@ -49,6 +49,7 @@ output formats. The specific file type is controlled with `bookType` option:
| `dbf` | `.dbf` | none | single | dBASE II + VFP Extensions (DBF) |
| `rtf` | `.rtf` | none | single | Rich Text Format (RTF) |
| `prn` | `.prn` | none | single | Lotus Formatted Text |
+| `eth` | `.eth` | none | single | Ethercalc Record Format (ETH) |
- `compression` only applies to formats with ZIP containers.
- Formats that only support a single sheet require a `sheet` option specifying
diff --git a/docbits/85_filetype.md b/docbits/85_filetype.md
index 087c4f689735d8ba34182a0f7bd43b2b71f7d681..e75f205c71d1383257f5ac48cb49a12f55fd60ac 100644
--- a/docbits/85_filetype.md
+++ b/docbits/85_filetype.md
@@ -29,6 +29,7 @@ Despite the library name `xlsx`, it supports numerous spreadsheet file formats:
| **Other Common Spreadsheet Output Formats** |:-----:|:-----:|
| HTML Tables | :o: | :o: |
| Rich Text Format tables (RTF) | | :o: |
+| Ethercalc Record Format (ETH) | :o: | :o: |
### Excel 2007+ XML (XLSX/XLSM)
@@ -264,4 +265,14 @@ worksheet. The supported codes are a subset of the Word RTF support.
+#### Ethercalc Record Format (ETH)
+
+
+ (click to show)
+
+[Ethercalc](https://ethercalc.net/) is an open source web spreadsheet powered by
+a record format reminiscent of SYLK wrapped in a MIME multi-part message.
+
+
+
diff --git a/misc/docs/README.md b/misc/docs/README.md
index 8161dce514beb2aed11c4d5c6c72cb2e5427aa3c..927fb373d877031795e5d545367016b3a6958d0f 100644
--- a/misc/docs/README.md
+++ b/misc/docs/README.md
@@ -123,6 +123,7 @@ enhancements, additional features by request, and dedicated support.
+ [Data Interchange Format (DIF)](#data-interchange-format-dif)
+ [HTML](#html)
+ [Rich Text Format (RTF)](#rich-text-format-rtf)
+ + [Ethercalc Record Format (ETH)](#ethercalc-record-format-eth)
- [Testing](#testing)
* [Node](#node)
* [Browser](#browser)
@@ -1455,6 +1456,7 @@ Plain text format guessing follows the priority order:
| DSV | more unquoted `";"` chars than `"\t"` or `","` in the first 1024 |
| TSV | more unquoted `"\t"` chars than `","` chars in the first 1024 |
| CSV | one of the first 1024 characters is a comma `","` |
+| ETH | starts with `socialcalc:version:` |
| PRN | (default) |
- HTML tags include: `html`, `table`, `head`, `meta`, `script`, `style`, `div`
@@ -1526,6 +1528,7 @@ output formats. The specific file type is controlled with `bookType` option:
| `dbf` | `.dbf` | none | single | dBASE II + VFP Extensions (DBF) |
| `rtf` | `.rtf` | none | single | Rich Text Format (RTF) |
| `prn` | `.prn` | none | single | Lotus Formatted Text |
+| `eth` | `.eth` | none | single | Ethercalc Record Format (ETH) |
- `compression` only applies to formats with ZIP containers.
- Formats that only support a single sheet require a `sheet` option specifying
@@ -1844,6 +1847,7 @@ Despite the library name `xlsx`, it supports numerous spreadsheet file formats:
| **Other Common Spreadsheet Output Formats** |:-----:|:-----:|
| HTML Tables | :o: | :o: |
| Rich Text Format tables (RTF) | | :o: |
+| Ethercalc Record Format (ETH) | :o: | :o: |
### Excel 2007+ XML (XLSX/XLSM)
@@ -2028,6 +2032,13 @@ Excel RTF worksheets are stored in clipboard when copying cells or ranges from a
worksheet. The supported codes are a subset of the Word RTF support.
+#### Ethercalc Record Format (ETH)
+
+
+[Ethercalc](https://ethercalc.net/) is an open source web spreadsheet powered by
+a record format reminiscent of SYLK wrapped in a MIME multi-part message.
+
+
## Testing
diff --git a/misc/docs/SUMMARY.md b/misc/docs/SUMMARY.md
index c43105e2a17851db161cd1b02a30c1ed825548da..6f3b2363bf8bde63c0b80c441528d1e5b5e03929 100644
--- a/misc/docs/SUMMARY.md
+++ b/misc/docs/SUMMARY.md
@@ -76,6 +76,7 @@
+ [Data Interchange Format (DIF)](README.md#data-interchange-format-dif)
+ [HTML](README.md#html)
+ [Rich Text Format (RTF)](README.md#rich-text-format-rtf)
+ + [Ethercalc Record Format (ETH)](README.md#ethercalc-record-format-eth)
- [Testing](README.md#testing)
* [Node](README.md#node)
* [Browser](README.md#browser)
diff --git a/package.json b/package.json
index 10850fadb7d595d1112aa1f2f1f186721d2b377b..35c1746b82bcff6d4483f685258cdab9a301c550 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "xlsx",
- "version": "0.11.11",
+ "version": "0.11.12",
"author": "sheetjs",
"description": "SheetJS Spreadsheet data parser and writer",
"keywords": [
diff --git a/test.js b/test.js
index d0e167a678a584f235383ddf7d32805bb7c97015..1359c099c8a43a9d872fbbbb5fc5df8bba685138 100644
--- a/test.js
+++ b/test.js
@@ -22,7 +22,7 @@ var opts = ({cellNF: true}/*:any*/);
var TYPE = browser ? "binary" : "buffer";
opts.type = TYPE;
var fullex = [".xlsb", /*".xlsm",*/ ".xlsx"/*, ".xlml", ".xls"*/];
-var ofmt = ["xlsb", "xlsm", "xlsx", "ods", "biff2", "biff5", "biff8", "xlml", "sylk", "dif", "dbf"];
+var ofmt = ["xlsb", "xlsm", "xlsx", "ods", "biff2", "biff5", "biff8", "xlml", "sylk", "dif", "dbf", "eth"];
var ex = fullex.slice(); ex = ex.concat([".ods", ".xls", ".xml", ".fods"]);
if(typeof process != 'undefined' && ((process||{}).env)) {
opts.WTF = true;
@@ -321,7 +321,7 @@ var wbtable = {};
fileA.forEach(function(x) {
if(x.slice(-8) == ".pending" || !fs.existsSync(dir + x)) return;
it(x, function() {
- var wb = X.readFile(dir + x, {WTF:opts.wtf, sheetRows:10});
+ var wb = X.readFile(dir + x, {WTF:opts.WTF, sheetRows:10});
parsetest(x, wb, false);
});
});
@@ -623,6 +623,7 @@ describe('output formats', function() {
["csv", true, true],
["txt", true, true],
["sylk", false, true],
+ ["eth", false, true],
["html", true, true],
["dif", false, true],
["dbf", false, false],
diff --git a/test_files b/test_files
index e16abc3908a12be7c7293518e5fed1e31e374605..8b3f773774567a164c0654a0b8056db680c3e5a5 160000
--- a/test_files
+++ b/test_files
@@ -1 +1 @@
-Subproject commit e16abc3908a12be7c7293518e5fed1e31e374605
+Subproject commit 8b3f773774567a164c0654a0b8056db680c3e5a5
diff --git a/tests/core.js b/tests/core.js
index d0e167a678a584f235383ddf7d32805bb7c97015..d185339f6fb1597d850201b735a89a79f657278d 100644
Binary files a/tests/core.js and b/tests/core.js differ
diff --git a/tests/write.js b/tests/write.js
index 74df66e829b8de2a73abfd04facd3fed96ee8982..66fc7370e0b9769416c4e319954e3b25f1fdecde 100644
--- a/tests/write.js
+++ b/tests/write.js
@@ -171,6 +171,7 @@ var filenames = [
['sheetjs.csv'],
['sheetjs.txt'],
['sheetjs.slk'],
+ ['sheetjs.eth'],
['sheetjs.htm'],
['sheetjs.dif'],
['sheetjs.dbf', {sheet:"Hidden"}],
diff --git a/types/bin_xlsx.ts b/types/bin_xlsx.ts
index 4fab33faa704db08ce15ec7697a3ec318e6f1816..e223d97a2e3e5b44516a2b892755651888befe60 100755
--- a/types/bin_xlsx.ts
+++ b/types/bin_xlsx.ts
@@ -21,6 +21,7 @@ program
.option('-X, --xlsx', 'emit XLSX to or .xlsx')
.option('-Y, --ods', 'emit ODS to or .ods')
.option('-8, --xls', 'emit XLS to or .xls (BIFF8)')
+ .option('-5, --biff5','emit XLS to or .xls (BIFF5)')
.option('-2, --biff2','emit XLS to or .xls (BIFF2)')
.option('-6, --xlml', 'emit SSML to or .xls (2003 XML)')
.option('-T, --fods', 'emit FODS to or .fods (Flat ODS)')
@@ -31,8 +32,10 @@ program
.option('-A, --arrays', 'emit rows as JS objects (raw numbers)')
.option('-H, --html', 'emit HTML to or .html')
.option('-D, --dif', 'emit DIF to or .dif (Lotus DIF)')
+ .option('-U, --dbf', 'emit DBF to or .dbf (MSVFP DBF)')
.option('-K, --sylk', 'emit SYLK to or .slk (Excel SYLK)')
.option('-P, --prn', 'emit PRN to or .prn (Lotus PRN)')
+ .option('-E, --eth', 'emit ETH to or .eth (Ethercalc)')
.option('-t, --txt', 'emit TXT to or .txt (UTF-8 TSV)')
.option('-r, --rtf', 'emit RTF to or .txt (Table RTF)')
@@ -41,11 +44,11 @@ program
.option('-n, --sheet-rows ', 'Number of rows to process (0=all rows)')
.option('--sst', 'generate shared string table for XLS* formats')
.option('--compress', 'use compression when writing XLSX/M/B and ODS')
- .option('--read-only', 'do not generate output')
+ .option('--read', 'read but do not generate output')
+ .option('--book', 'for single-sheet formats, emit a file per worksheet')
.option('--all', 'parse everything; write as much as possible')
.option('--dev', 'development mode')
.option('--sparse', 'sparse mode')
- .option('--read', 'read but do not print out contents')
.option('-q, --quiet', 'quiet mode');
program.on('--help', function() {
@@ -81,7 +84,6 @@ if(!filename) {
console.error(n + ": must specify a filename");
process.exit(1);
}
-/*:: if(filename) { */
if(!fs.existsSync(filename)) {
console.error(n + ": " + filename + ": No such file or directory");
process.exit(2);
@@ -109,6 +111,9 @@ if(seen) {
} else if(program.formulae) opts.cellFormula = true;
else opts.cellFormula = false;
+let wopts: X.WritingOptions = ({WTF:opts.WTF, bookSST:program.sst}/*:any*/);
+if(program.compress) wopts.compression = true;
+
if(program.all) {
opts.cellFormula = true;
opts.bookVBA = true;
@@ -117,6 +122,8 @@ if(program.all) {
opts.cellStyles = true;
opts.sheetStubs = true;
opts.cellDates = true;
+ wopts.cellStyles = true;
+ wopts.bookVBA = true;
}
if(program.sparse) opts.dense = false; else opts.dense = true;
@@ -132,16 +139,13 @@ if(program.dev) {
process.exit(3);
}
if(program.read) process.exit(0);
-
-/*:: if(wb) { */
+if(!wb) { console.error(n + ": error parsing " + filename + ": empty workbook"); process.exit(0); }
+/*:: if(!wb) throw new Error("unreachable"); */
if(program.listSheets) {
console.log((wb.SheetNames||[]).join("\n"));
process.exit(0);
}
-let wopts: X.WritingOptions = ({WTF:opts.WTF, bookSST:program.sst}/*:any*/);
-if(program.compress) wopts.compression = true;
-
/* full workbook formats */
workbook_formats.forEach(function(m) { if(program[m[0]] || isfmt(m[0])) {
wopts.bookType = (m[1]);
@@ -173,9 +177,9 @@ try {
process.exit(4);
}
-if(program.readOnly) process.exit(0);
+if(!program.quiet && !program.book) console.error(target_sheet);
-/* single worksheet formats */
+/* single worksheet file formats */
[
['biff2', '.xls'],
['biff3', '.xls'],
@@ -183,8 +187,10 @@ if(program.readOnly) process.exit(0);
['sylk', '.slk'],
['html', '.html'],
['prn', '.prn'],
+ ['eth', '.eth'],
['rtf', '.rtf'],
['txt', '.txt'],
+ ['dbf', '.dbf'],
['dif', '.dif']
].forEach(function(m) { if(program[m[0]] || isfmt(m[1])) {
wopts.bookType = (m[0]);
diff --git a/types/index.d.ts b/types/index.d.ts
index adb59d629a6319c950525d162bf4aba7e44bb510..25102abd568e3ec54141b31ab3297b3f6053fd05 100644
--- a/types/index.d.ts
+++ b/types/index.d.ts
@@ -7,6 +7,9 @@ export const version: string;
/** SSF Formatter Library */
export const SSF: any;
+/** CFB Library */
+export const CFB: any;
+
/** Attempts to read filename and parse */
export function readFile(filename: string, opts?: ParsingOptions): WorkBook;
/** Attempts to parse data */
@@ -75,12 +78,26 @@ export interface CommonOptions {
*/
WTF?: boolean;
+ /**
+ * When reading a file with VBA macros, expose CFB blob to `vbaraw` field
+ * When writing BIFF8/XLSB/XLSM, reseat `vbaraw` and export to file
+ * @default false
+ */
+ bookVBA?: boolean;
+
/**
* When reading a file, store dates as type d (default is n)
* When writing XLSX/XLSM file, use native date (default uses date codes)
* @default false
*/
cellDates?: boolean;
+
+ /**
+ * When reading a file, save style/theme info to the .s field
+ * When writing a file, export style/theme info
+ * @default false
+ */
+ cellStyles?: boolean;
}
export interface DateNFOption {
@@ -111,12 +128,6 @@ export interface ParsingOptions extends CommonOptions {
*/
cellNF?: boolean;
- /**
- * Save style/theme info to the .s field
- * @default false
- */
- cellStyles?: boolean;
-
/**
* Generate formatted text to the .w field
* @default true
@@ -162,12 +173,6 @@ export interface ParsingOptions extends CommonOptions {
*/
bookSheets?: boolean;
- /**
- * If true, expose vbaProject.bin to vbaraw field
- * @default false
- */
- bookVBA?: boolean;
-
/**
* If defined and file is encrypted, use password
* @default ''
@@ -231,6 +236,8 @@ export interface WorkBook {
Props?: FullProperties;
Workbook?: WBProps;
+
+ vbaraw?: any;
}
export interface SheetProps {
@@ -483,7 +490,7 @@ export type ExcelDataType = 'b' | 'n' | 'e' | 's' | 'd' | 'z';
* Type of generated workbook
* @default 'xlsx'
*/
-export type BookType = 'xlsx' | 'xlsm' | 'xlsb' | 'xls' | 'biff8' | 'biff5' | 'biff2' | 'xlml' | 'ods' | 'fods' | 'csv' | 'txt' | 'sylk' | 'html' | 'dif' | 'rtf' | 'prn';
+export type BookType = 'xlsx' | 'xlsm' | 'xlsb' | 'xls' | 'biff8' | 'biff5' | 'biff2' | 'xlml' | 'ods' | 'fods' | 'csv' | 'txt' | 'sylk' | 'html' | 'dif' | 'rtf' | 'prn' | 'eth';
/** Comment element */
export interface Comment {
@@ -658,6 +665,15 @@ export interface XLSX$Utils {
/** Generates a list of the formulae (with value fallbacks) */
sheet_to_formulae(worksheet: WorkSheet): string[];
+ /** Generates DIF */
+ sheet_to_dif(worksheet: WorkSheet, options?: Sheet2HTMLOpts): string;
+
+ /** Generates SYLK (Symbolic Link) */
+ sheet_to_slk(worksheet: WorkSheet, options?: Sheet2HTMLOpts): string;
+
+ /** Generates ETH */
+ sheet_to_eth(worksheet: WorkSheet, options?: Sheet2HTMLOpts): string;
+
/* --- Cell Address Utilities --- */
/** Converts 0-indexed cell address to A1 form */
diff --git a/types/write.ts b/types/write.ts
index 7b34c42dd08a6639dfb5d0d5e0f2a114a7d572f4..9101d5f6a993d4b89c864f50be60de6067c1426d 100644
--- a/types/write.ts
+++ b/types/write.ts
@@ -159,6 +159,7 @@ const filenames: Array<[string]|[string, XLSX.WritingOptions]> = [
['sheetjs.csv'],
['sheetjs.txt'],
['sheetjs.slk'],
+ ['sheetjs.eth'],
['sheetjs.htm'],
['sheetjs.dif'],
['sheetjs.dbf', {sheet:"Hidden"}],
diff --git a/types/xlsx-tests.ts b/types/xlsx-tests.ts
index 9fe130ddd2a29fa57a76b8e4d2463d7010cff16b..f2a431a17aae4bc5545da70896df8d8b1fc3f8d3 100644
--- a/types/xlsx-tests.ts
+++ b/types/xlsx-tests.ts
@@ -21,6 +21,9 @@ interface Tester {
const jsonvalues: Tester[] = XLSX.utils.sheet_to_json(firstworksheet);
const csv: string = XLSX.utils.sheet_to_csv(firstworksheet);
const txt: string = XLSX.utils.sheet_to_txt(firstworksheet);
+const dif: string = XLSX.utils.sheet_to_dif(firstworksheet);
+const slk: string = XLSX.utils.sheet_to_slk(firstworksheet);
+const eth: string = XLSX.utils.sheet_to_eth(firstworksheet);
const formulae: string[] = XLSX.utils.sheet_to_formulae(firstworksheet);
const aoa: any[][] = XLSX.utils.sheet_to_json(firstworksheet, {raw:true, header:1});
diff --git a/xlsx.flow.js b/xlsx.flow.js
index e12999a950e02e6c5cce2156b7353b96614e7667..bae92759b77cfb00fc351ed8e51c50952d8dec07 100644
--- a/xlsx.flow.js
+++ b/xlsx.flow.js
@@ -4,7 +4,7 @@
/*global global, exports, module, require:false, process:false, Buffer:false */
var XLSX = {};
(function make_xlsx(XLSX){
-XLSX.version = '0.11.11';
+XLSX.version = '0.11.12';
var current_codepage = 1200;
/*:: declare var cptable:any; */
/*global cptable:true */
@@ -2385,6 +2385,12 @@ function ReadShift(size/*:number*/, t/*:?string*/)/*:number|string*/ {
loc+=2;
} o = oo.join(""); size *= 2; break;
+ case 'cpstr':
+ if(typeof cptable !== 'undefined') {
+ o = cptable.utils.decode(current_codepage, this.slice(this.l, this.l + size));
+ break;
+ }
+ /* falls through */
case 'sbcs-cont': o = ""; loc = this.l;
for(i = 0; i != size; ++i) {
if(this.lens && this.lens.indexOf(loc) !== -1) {
@@ -4436,7 +4442,7 @@ function parse_XLUnicodeString2(blob, length, opts) {
if(opts.biff > 5) return parse_XLUnicodeString(blob, length, opts);
var cch = blob.read_shift(1);
if(cch === 0) { blob.l++; return ""; }
- return blob.read_shift(cch, 'sbcs-cont');
+ return blob.read_shift(cch, opts.biff == 4 ? 'cpstr' : 'sbcs-cont');
}
/* TODO: BIFF5 and lower, codepage awareness */
function write_XLUnicodeString(str, opts, o) {
@@ -6079,6 +6085,105 @@ var DIF = (function() {
};
})();
+var ETH = (function() {
+ function decode(s/*:string*/)/*:string*/ { return s.replace(/\\b/g,"\\").replace(/\\c/g,":").replace(/\\n/g,"\n"); }
+ function encode(s/*:string*/)/*:string*/ { return s.replace(/\\/g, "\\b").replace(/:/g, "\\c").replace(/\n/g,"\\n"); }
+
+ function eth_to_aoa(str/*:string*/, opts)/*:AOA*/ {
+ var records = str.split('\n'), R = -1, C = -1, ri = 0, arr = [];
+ for (; ri !== records.length; ++ri) {
+ var record = records[ri].trim().split(":");
+ if(record[0] !== 'cell') continue;
+ var addr = decode_cell(record[1]);
+ if(arr.length <= addr.r) for(R = arr.length; R <= addr.r; ++R) if(!arr[R]) arr[R] = [];
+ R = addr.r; C = addr.c;
+ switch(record[2]) {
+ case 't': arr[R][C] = decode(record[3]); break;
+ case 'v': arr[R][C] = +record[3]; break;
+ case 'vtf': var _f = record[record.length - 1];
+ /* falls through */
+ case 'vtc':
+ switch(record[3]) {
+ case 'nl': arr[R][C] = +record[4] ? true : false; break;
+ default: arr[R][C] = +record[4]; break;
+ }
+ if(record[2] == 'vtf') arr[R][C] = [arr[R][C], _f];
+ }
+ }
+ return arr;
+ }
+
+ function eth_to_sheet(d/*:string*/, opts)/*:Worksheet*/ { return aoa_to_sheet(eth_to_aoa(d, opts), opts); }
+ function eth_to_workbook(d/*:string*/, opts)/*:Workbook*/ { return sheet_to_workbook(eth_to_sheet(d, opts), opts); }
+
+ var header = [
+ "socialcalc:version:1.5",
+ "MIME-Version: 1.0",
+ "Content-Type: multipart/mixed; boundary=SocialCalcSpreadsheetControlSave"
+ ].join("\n");
+
+ var sep = [
+ "--SocialCalcSpreadsheetControlSave",
+ "Content-type: text/plain; charset=UTF-8"
+ ].join("\n") + "\n";
+
+ /* TODO: the other parts */
+ var meta = [
+ "# SocialCalc Spreadsheet Control Save",
+ "part:sheet"
+ ].join("\n");
+
+ var end = "--SocialCalcSpreadsheetControlSave--";
+
+ function sheet_to_eth_data(ws/*:Worksheet*/)/*:string*/ {
+ if(!ws || !ws['!ref']) return "";
+ var o = [], oo = [], cell, coord;
+ var r = decode_range(ws['!ref']);
+ var dense = Array.isArray(ws);
+ for(var R = r.s.r; R <= r.e.r; ++R) {
+ for(var C = r.s.c; C <= r.e.c; ++C) {
+ coord = encode_cell({r:R,c:C});
+ cell = dense ? (ws[R]||[])[C] : ws[coord];
+ if(!cell || cell.v == null || cell.t === 'z') continue;
+ oo = ["cell", coord, 't'];
+ switch(cell.t) {
+ case 's': case 'str': oo.push(encode(cell.v)); break;
+ case 'n':
+ if(!cell.f) { oo[2]='v'; oo[3]=cell.v; }
+ else { oo[2]='vtf'; oo[3]='n'; oo[4]=cell.v; oo[5]=encode(cell.f); }
+ break;
+ case 'b':
+ oo[2] = 'vt'+(cell.f?'f':'c'); oo[3]='nl'; oo[4]=+!!cell.v;
+ oo[5] = encode(cell.f||(cell.v?'TRUE':'FALSE'));
+ break;
+ case 'd':
+ var t = datenum(parseDate(cell.v));
+ oo[2] = 'vtc'; oo[3] = 'nd'; oo[4] = t;
+ oo[5] = cell.w || SSF.format(cell.z || SSF._table[14], t);
+ break;
+ case 'e': continue;
+ }
+ o.push(oo.join(":"));
+ }
+ }
+ o.push("sheet:c:" + (r.e.c-r.s.c+1) + ":r:" + (r.e.r-r.s.r+1) + ":tvf:1");
+ o.push("valueformat:1:text-wiki");
+ //o.push("copiedfrom:" + ws['!ref']); // clipboard only
+ return o.join("\n");
+ }
+
+ function sheet_to_eth(ws/*:Worksheet*/, opts/*:?any*/)/*:string*/ {
+ return [header, sep, meta, sep, sheet_to_eth_data(ws), end].join("\n");
+ // return ["version:1.5", sheet_to_eth_data(ws)].join("\n"); // clipboard form
+ }
+
+ return {
+ to_workbook: eth_to_workbook,
+ to_sheet: eth_to_sheet,
+ from_sheet: sheet_to_eth
+ };
+})();
+
var PRN = (function() {
function set_text_arr(data/*:string*/, arr/*:AOA*/, R/*:number*/, C/*:number*/, o/*:any*/) {
if(o.raw) arr[R][C] = data;
@@ -6212,7 +6317,7 @@ var PRN = (function() {
}
function prn_to_sheet_str(str/*:string*/, opts)/*:Worksheet*/ {
- if(str.substr(0,4) == "sep=") return dsv_to_sheet_str(str, opts);
+ if(str.slice(0,4) == "sep=") return dsv_to_sheet_str(str, opts);
if(str.indexOf("\t") >= 0 || str.indexOf(",") >= 0 || str.indexOf(";") >= 0) return dsv_to_sheet_str(str, opts);
return aoa_to_sheet(prn_to_aoa_str(str, opts), opts);
}
@@ -6228,6 +6333,7 @@ var PRN = (function() {
default: throw new Error("Unrecognized type " + opts.type);
}
if(bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) str = utf8read(str.slice(3));
+ if(str.slice(0,19) == "socialcalc:version:") return ETH.to_sheet(opts.type == 'string' ? str : utf8read(str), opts);
return prn_to_sheet_str(str, opts);
}
@@ -18148,6 +18254,7 @@ var write_rtf_str = write_obj_str(RTF);
var write_txt_str = write_obj_str({from_sheet:sheet_to_txt});
// $FlowIgnore
var write_dbf_buf = write_obj_str(DBF);
+var write_eth_str = write_obj_str(ETH);
function fix_opts_func(defaults/*:Array >*/)/*:{(o:any):void}*/ {
return function fix_opts(opts) {
@@ -18732,6 +18839,7 @@ function writeSync(wb/*:Workbook*/, opts/*:?WriteOpts*/) {
case 'dbf': return write_binary_type(write_dbf_buf(wb, o), o);
case 'prn': return write_string_type(write_prn_str(wb, o), o);
case 'rtf': return write_string_type(write_rtf_str(wb, o), o);
+ case 'eth': return write_string_type(write_eth_str(wb, o), o);
case 'fods': return write_string_type(write_ods(wb, o), o);
case 'biff2': if(!o.biff) o.biff = 2; /* falls through */
case 'biff3': if(!o.biff) o.biff = 3; /* falls through */
@@ -18753,6 +18861,7 @@ function resolve_book_type(o/*:WriteFileOpts*/) {
"xls": "biff8",
"htm": "html",
"slk": "sylk",
+ "socialcalc": "eth",
"Sh33tJS": "WTF"
};
var ext = o.file.slice(o.file.lastIndexOf(".")).toLowerCase();
@@ -18999,6 +19108,9 @@ var utils/*:any*/ = {
sheet_to_txt: sheet_to_txt,
sheet_to_json: sheet_to_json,
sheet_to_html: HTML_.from_sheet,
+ sheet_to_dif: DIF.from_sheet,
+ sheet_to_slk: SYLK.from_sheet,
+ sheet_to_eth: ETH.from_sheet,
sheet_to_formulae: sheet_to_formulae,
sheet_to_row_object_array: sheet_to_json
};
diff --git a/xlsx.js b/xlsx.js
index 55be88841d7eea6eaf3775130ba1c62a7a0102c5..87d594961bc15f6cc001cd9bbeb6bd26d4462d25 100644
Binary files a/xlsx.js and b/xlsx.js differ