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