diff --git a/.npmignore b/.npmignore index 7aa78ea..050a796 100644 --- a/.npmignore +++ b/.npmignore @@ -39,3 +39,5 @@ test.js bits/ docbits/ tests/ +_book +book.json diff --git a/README.md b/README.md index d1a8e5b..dd8e2a1 100644 --- a/README.md +++ b/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 diff --git a/bin/xlsx.njs b/bin/xlsx.njs index e6ce726..cb93e9d 100755 --- a/bin/xlsx.njs +++ b/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 ', 'CSV field separator', ",") .option('-R, --row-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); } }); diff --git a/bits/40_harb.js b/bits/40_harb.js index 59fd729..bb1dd9a 100644 --- a/bits/40_harb.js +++ b/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*/ = ["ID;PWXL;N;E"], o/*:Array*/ = []; + 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*/, 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*/, 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*/ = []; + 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 + }; +})(); diff --git a/bits/79_html.js b/bits/79_html.js index 97a43c7..c878d96 100644 --- a/bits/79_html.js +++ b/bits/79_html.js @@ -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; } diff --git a/bits/82_sheeter.js b/bits/82_sheeter.js index fdc32b8..aae3357 100644 --- a/bits/82_sheeter.js +++ b/bits/82_sheeter.js @@ -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 csf xls5 -> csf xls8 -> csf + csf -> slk + slk -> csf + csf -> dif + dif -> csf csf -> csv ods -> csf csf -> ods diff --git a/formats.png b/formats.png index fb756af..4734962 100644 Binary files a/formats.png and b/formats.png differ diff --git a/misc/docs/SUMMARY.md b/misc/docs/SUMMARY.md index 5f648b2..f319ff4 100644 --- a/misc/docs/SUMMARY.md +++ b/misc/docs/SUMMARY.md @@ -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) diff --git a/test.js b/test.js index 17acb0e..fcc043c 100644 --- a/test.js +++ b/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() { diff --git a/tests/write.js b/tests/write.js index a97914a..2ebf6fd 100644 --- a/tests/write.js +++ b/tests/write.js @@ -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'); diff --git a/xlsx.flow.js b/xlsx.flow.js index 0bd6221..09622b5 100644 --- a/xlsx.flow.js +++ b/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*/ = ["ID;PWXL;N;E"], o/*:Array*/ = []; + 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*/, 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*/, 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*/ = []; + 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;i0) 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; } @@ -14159,6 +14337,18 @@ function write_csv_str(wb, o) { 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, o) { + var idx = 0; + for(var i=0;i