diff --git a/CHANGELOG.md b/CHANGELOG.md index cef7198..9eb66a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ but not limited to API changes and file location changes. Minor behavioral changes may not be included if they are not expected to break existing code. +## Unreleased + +* XLML property names are more closely mapped to the XLSX equivalent + ## 0.9.2 (2017-03-13) * Removed stale TypeScript definition files. Flowtype comments are used in the diff --git a/Makefile b/Makefile index f16a188..5d368b1 100644 --- a/Makefile +++ b/Makefile @@ -140,6 +140,9 @@ misc/coverage.html: $(TARGET) test.js coveralls: ## Coverage Test + Send to coveralls.io mocha --require blanket --reporter mocha-lcov-reporter -t 20000 | node ./node_modules/coveralls/bin/coveralls.js +.PHONY: readme +readme: ## Update README Table of Contents + markdown-toc -i README.md .PHONY: help help: diff --git a/README.md b/README.md index 096e80c..bbd9a68 100644 --- a/README.md +++ b/README.md @@ -274,24 +274,10 @@ var worksheet = workbook.Sheets[first_sheet_name]; var desired_cell = worksheet[address_of_cell]; /* Get the value */ -var desired_value = desired_cell.v; +var desired_value = (desired_cell ? desired_cell.v : undefined); ``` -This example iterates through every nonempty of every sheet and dumps values: - -```js -var sheet_name_list = workbook.SheetNames; -sheet_name_list.forEach(function(y) { /* iterate through sheets */ - var worksheet = workbook.Sheets[y]; - for (var z in worksheet) { - /* all keys that do not begin with "!" correspond to cell addresses */ - if(z[0] === '!') continue; - console.log(y + "!" + z + "=" + JSON.stringify(worksheet[z].v)); - } -}); -``` - -Complete examples: +**Complete examples:** - HTML5 File API / Base64 Text / Web Workers @@ -359,7 +345,7 @@ function s2ab(s) { saveAs(new Blob([s2ab(wbout)],{type:"application/octet-stream"}), "test.xlsx"); ``` -Complete examples: +**Complete examples:** - generates a simple file - writing an array of arrays in nodejs @@ -393,7 +379,7 @@ Write options are described in the [Writing Options](#writing-options) section. Utilities are available in the `XLSX.utils` object: -Exporting: +**Exporting:** - `sheet_to_json` converts a worksheet object to an array of JSON objects. `sheet_to_row_object_array` is an alias that will be removed in the future. @@ -403,7 +389,7 @@ Exporting: Exporters are described in the [Utility Functions](#utility-functions) section. -Cell and cell address manipulation: +**Cell and cell address manipulation:** - `format_cell` generates the text value for a cell (using number formats) - `{en,de}code_{row,col}` convert between 0-indexed rows/cols and A1 forms. @@ -808,7 +794,7 @@ Despite the library name `xlsx`, it supports numerous spreadsheet file formats: | **Excel Worksheet/Workbook Formats** |:-----:|:-----:| | Excel 2007+ XML Formats (XLSX/XLSM) | :o: | :o: | | Excel 2007+ Binary Format (XLSB BIFF12) | :o: | :o: | -| Excel 2003-2004 XML Format (XML "SpreadsheetML") | :o: | | +| Excel 2003-2004 XML Format (XML "SpreadsheetML") | :o: | :o: | | Excel 97-2004 (XLS BIFF8) | :o: | | | Excel 5.0/95 (XLS BIFF5) | :o: | | | Excel 4.0 (XLS/XLW BIFF4) | :o: | | diff --git a/bin/xlsx.njs b/bin/xlsx.njs index a6947d0..a8f2f8b 100755 --- a/bin/xlsx.njs +++ b/bin/xlsx.njs @@ -20,6 +20,7 @@ program .option('-X, --xlsx', 'emit XLSX to or .xlsx') .option('-Y, --ods', 'emit ODS to or .ods') .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 .xls (Flat ODS)') .option('-S, --formulae', 'print formulae') @@ -33,7 +34,7 @@ program .option('--sst', 'generate shared string table for XLS* formats') .option('--compress', 'use compression when writing XLSX/M/B and ODS') .option('--perf', 'do not generate output') - .option('--all', 'parse everything; XLS[XMB] write as much as possible') + .option('--all', 'parse everything; write as much as possible') .option('--dev', 'development mode') .option('--read', 'read but do not print out contents') .option('-q, --quiet', 'quiet mode'); @@ -46,6 +47,10 @@ program.on('--help', function() { /* output formats, update list with full option name */ var workbook_formats = ['xlsx', 'xlsm', 'xlsb', 'ods', 'fods']; +/* flag, bookType, default ext */ +var wb_formats_2 = [ + ['xlml', 'xlml', 'xls'] +]; program.parse(process.argv); /* see https://github.com/SheetJS/j/issues/4 */ @@ -81,11 +86,16 @@ var opts = {}, wb/*:?Workbook*/; if(program.listSheets) opts.bookSheets = true; if(program.sheetRows) opts.sheetRows = program.sheetRows; if(program.password) opts.password = program.password; -if(program.xlsx || program.xlsm || program.xlsb) { +var seen = false; +function wb_fmt() { + seen = true; opts.cellFormula = true; opts.cellNF = true; if(program.output) sheetname = program.output; } +workbook_formats.forEach(function(m) { if(program[m]) { wb_fmt(); } }); +wb_formats_2.forEach(function(m) { if(program[m[0]]) { wb_fmt(); } }); +if(seen); else if(program.formulae) opts.cellFormula = true; else opts.cellFormula = false; @@ -125,6 +135,12 @@ workbook_formats.forEach(function(m) { if(program[m]) { process.exit(0); } }); +wb_formats_2.forEach(function(m) { if(program[m[0]]) { + wopts.bookType = m[1]; + X.writeFile(wb, sheetname || ((filename || "") + "." + m[2]), wopts); + process.exit(0); +} }); + var target_sheet = sheetname || ''; if(target_sheet === '') { if(program.sheetIndex < (wb.SheetNames||[]).length) target_sheet = wb.SheetNames[program.sheetIndex]; diff --git a/bits/22_xmlutils.js b/bits/22_xmlutils.js index 4822cb5..7052c56 100644 --- a/bits/22_xmlutils.js +++ b/bits/22_xmlutils.js @@ -52,6 +52,7 @@ function escapexml(text/*:string*/)/*:string*/{ var s = text + ''; return s.replace(decregex, function(y) { return rencoding[y]; }).replace(charegex,function(s) { return "_x" + ("000"+s.charCodeAt(0).toString(16)).slice(-4) + "_";}); } +function escapexmltag(text/*:string*/)/*:string*/{ return escapexml(text).replace(/ /g,"_x0020_"); } /* TODO: handle codepages */ var xlml_fixstr/*:StringConv*/ = (function() { @@ -178,3 +179,10 @@ XMLNS.main = [ 'http://schemas.microsoft.com/office/excel/2006/2' ]; +var XLMLNS = ({ + 'o': 'urn:schemas-microsoft-com:office:office', + 'x': 'urn:schemas-microsoft-com:office:excel', + 'ss': 'urn:schemas-microsoft-com:office:spreadsheet', + 'dt': 'uuid:C2F41010-65B3-11d1-A29F-00AA00C14882', + 'html': 'http://www.w3.org/TR/REC-html40' +}/*:any*/); diff --git a/bits/36_xlsprops.js b/bits/36_xlsprops.js index d993209..dfb8e5f 100644 --- a/bits/36_xlsprops.js +++ b/bits/36_xlsprops.js @@ -2,7 +2,66 @@ function xlml_set_prop(Props, tag/*:string*/, val) { /* TODO: Normalize the properties */ switch(tag) { case 'Description': tag = 'Comments'; break; + case 'Created': tag = 'CreatedDate'; break; + case 'LastSaved': tag = 'ModifiedDate'; break; } Props[tag] = val; } +var XLMLDocumentProperties = [ + ['Title', 'Title'], + ['Subject', 'Subject'], + ['Author', 'Author'], + ['Keywords', 'Keywords'], + ['Comments', 'Description'], + ['LastAuthor', 'LastAuthor'], + ['CreatedDate', 'Created', 'date'], + ['ModifiedDate', 'LastSaved', 'date'], + ['Category', 'Category'], + ['Manager', 'Manager'], + ['Company', 'Company'], + ['AppVersion', 'Version'] +]; + +/* TODO: verify */ +function xlml_write_docprops(Props) { + var T = 'DocumentProperties'; + var o = []; + XLMLDocumentProperties.forEach(function(p) { + if(!Props[p[0]]) return; + var m = Props[p[0]]; + switch(p[2]) { + case 'date': m = new Date(m).toISOString(); break; + } + o.push(writetag(p[1], m)); + }); + return '<' + T + ' xmlns="' + XLMLNS.o + '">' + o.join("") + ''; +} +function xlml_write_custprops(Props, Custprops) { + var T = 'CustomDocumentProperties'; + var o = []; + if(Props) keys(Props).forEach(function(k) { + /*:: if(!Props) return; */ + if(!Props.hasOwnProperty(k)) return; + for(var i = 0; i < XLMLDocumentProperties.length; ++i) + if(k == XLMLDocumentProperties[i][0]) return; + var m = Props[k]; + var t = "string"; + if(typeof m == 'number') { t = "float"; m = String(m); } + else if(m === true || m === false) { t = "boolean"; m = m ? "1" : "0"; } + else m = String(m); + o.push(writextag(escapexmltag(k), m, {"dt:dt":t})); + }); + if(Custprops) keys(Custprops).forEach(function(k) { + /*:: if(!Custprops) return; */ + if(!Custprops.hasOwnProperty(k)) return; + var m = Custprops[k]; + var t = "string"; + if(typeof m == 'number') { t = "float"; m = String(m); } + else if(m === true || m === false) { t = "boolean"; m = m ? "1" : "0"; } + else if(m instanceof Date) { t = "dateTime.tz"; m = m.toISOString(); } + else m = String(m); + o.push(writextag(escapexmltag(k), m, {"dt:dt":t})); + }); + return '<' + T + ' xmlns="' + XLMLNS.o + '">' + o.join("") + ''; +} diff --git a/bits/39_xlsbiff.js b/bits/39_xlsbiff.js index f0eece5..8338a74 100644 --- a/bits/39_xlsbiff.js +++ b/bits/39_xlsbiff.js @@ -316,14 +316,44 @@ function parse_MulRk(blob, length) { return {r:rw, c:col, C:lastcol, rkrec:rkrecs}; } -/* 2.5.20 2.5.249 TODO */ +/* 2.5.20 2.5.249 TODO: interpret values here */ function parse_CellStyleXF(blob, length, style) { var o = {}; var a = blob.read_shift(4), b = blob.read_shift(4); var c = blob.read_shift(4), d = blob.read_shift(2); o.patternType = XLSFillPattern[c >> 26]; + + o.alc = a & 0x07; + o.fWrap = (a >> 3) & 0x01; + o.alcV = (a >> 4) & 0x07; + o.fJustLast = (a >> 7) & 0x01; + o.trot = (a >> 8) & 0xFF; + o.cIndent = (a >> 16) & 0x0F; + o.fShrinkToFit = (a >> 20) & 0x01; + o.iReadOrder = (a >> 22) & 0x02; + o.fAtrNum = (a >> 26) & 0x01; + o.fAtrFnt = (a >> 27) & 0x01; + o.fAtrAlc = (a >> 28) & 0x01; + o.fAtrBdr = (a >> 29) & 0x01; + o.fAtrPat = (a >> 30) & 0x01; + o.fAtrProt = (a >> 31) & 0x01; + + o.dgLeft = b & 0x0F; + o.dgRight = (b >> 4) & 0x0F; + o.dgTop = (b >> 8) & 0x0F; + o.dgBottom = (b >> 12) & 0x0F; + o.icvLeft = (b >> 16) & 0x7F; + o.icvRight = (b >> 23) & 0x7F; + o.grbitDiag = (b >> 30) & 0x03; + + o.icvTop = c & 0x7F; + o.icvBottom = (c >> 7) & 0x7F; + o.icvDiag = (c >> 14) & 0x7F; + o.dgDiag = (c >> 21) & 0x0F; + o.icvFore = d & 0x7F; o.icvBack = (d >> 7) & 0x7F; + o.fsxButton = (d >> 14) & 0x01; return o; } function parse_CellXF(blob, length) {return parse_CellStyleXF(blob,length,0);} diff --git a/bits/61_fcommon.js b/bits/61_fcommon.js index 8f960bd..1694626 100644 --- a/bits/61_fcommon.js +++ b/bits/61_fcommon.js @@ -16,8 +16,19 @@ var rc_to_a1 = (function(){ }; })(); -/* no defined name can collide with a valid cell address A1:XFD1048576 ... except LOG10! */ var crefregex = /(^|[^._A-Z0-9])([$]?)([A-Z]{1,2}|[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D])([$]?)([1-9]\d{0,5}|10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6])(?![_.\(A-Za-z0-9])/g; +var a1_to_rc =(function(){ + return function a1_to_rc(fstr, base) { + return fstr.replace(crefregex, function($0, $1, $2, $3, $4, $5, off, str) { + /* TODO: handle fixcol / fixrow */ + var c = decode_col($3) - base.c; + var r = decode_row($5) - base.r; + return $1 + "R" + (r == 0 ? "" : "[" + r + "]") + "C" + (c == 0 ? "" : "[" + c + "]"); + }); + }; +})(); + +/* no defined name can collide with a valid cell address A1:XFD1048576 ... except LOG10! */ function shift_formula_str(f/*:string*/, delta/*:Cell*/)/*:string*/ { return f.replace(crefregex, function($0, $1, $2, $3, $4, $5, off, str) { return $1+($2=="$" ? $2+$3 : encode_col(decode_col($3)+delta.c))+($4=="$" ? $4+$5 : encode_row(decode_row($5) + delta.r)); diff --git a/bits/75_xlml.js b/bits/75_xlml.js index 9be3f24..e3139d3 100644 --- a/bits/75_xlml.js +++ b/bits/75_xlml.js @@ -728,7 +728,85 @@ function parse_xlml(data, opts)/*:Workbook*/ { } /* TODO */ -function write_xlml(wb, opts)/*:string*/ { - var o = [XML_HEADER]; +function write_props_xlml(wb, opts) { + var o = []; + /* DocumentProperties */ + if(wb.Props) o.push(xlml_write_docprops(wb.Props)); + /* CustomDocumentProperties */ + if(wb.Custprops) o.push(xlml_write_custprops(wb.Props, wb.Custprops)); return o.join(""); } +/* TODO */ +function write_wb_xlml(wb, opts) { + /* OfficeDocumentSettings */ + /* ExcelWorkbook */ + return ""; +} +/* TODO */ +function write_sty_xlml(wb, opts)/*:string*/ { + /* Styles */ + return ""; +} +/* TODO */ +function write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr)/*:string*/{ + if(!cell || cell.v === undefined) return ""; + + var attr = {}; + if(cell.f) attr["ss:Formula"] = "=" + escapexml(a1_to_rc(cell.f, addr)); + + var t = "", p = ""; + switch(cell.t) { + case 'n': t = 'Number'; p = String(cell.v); break; + case 'b': t = 'Boolean'; p = (cell.v ? "1" : "0"); break; + case 'e': t = 'Error'; p = BErr[cell.v]; break; + case 'd': t = 'DateTime'; p = new Date(cell.v).toISOString(); break; + default: t = 'String'; p = escapexml(cell.v||""); + } + var m = '' + p + ''; + + return writextag("Cell", m, attr); +} +/* TODO */ +function write_ws_xlml_table(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbook*/)/*:string*/ { + if(!ws['!ref']) return ""; + var range = safe_decode_range(ws['!ref']); + var o = []; + for(var R = range.s.r; R <= range.e.r; ++R) { + var row = [""]; + for(var C = range.s.c; C <= range.e.c; ++C) { + var addr = {r:R,c:C}; + var ref = encode_cell(addr), cell = ws[ref]; + row.push(write_ws_xlml_cell(ws[ref], ref, ws, opts, idx, wb, addr)); + } + row.push(""); + o.push(row.join("")); + } + return o.join(""); +} +function write_ws_xlml(idx/*:number*/, opts, wb/*:Workbook*/)/*:string*/ { + var o = []; + var s = wb.SheetNames[idx]; + var ws = wb.Sheets[s]; + + /* Table */ + var t = ws ? write_ws_xlml_table(ws, opts, idx, wb) : ""; + if(t.length > 0) o.push("" + t + "
"); + /* WorksheetOptions */ + return o.join(""); +} +function write_xlml(wb, opts)/*:string*/ { + var d = []; + d.push(write_props_xlml(wb, opts)); + d.push(write_wb_xlml(wb, opts)); + d.push(write_sty_xlml(wb, opts)); + for(var i = 0; i < wb.SheetNames.length; ++i) + d.push(writextag("Worksheet", write_ws_xlml(i, opts, wb), {"ss:Name":escapexml(wb.SheetNames[i])})); + return XML_HEADER + writextag("Workbook", d.join(""), { + 'xmlns': XLMLNS.ss, + 'xmlns:o': XLMLNS.o, + 'xmlns:x': XLMLNS.x, + 'xmlns:ss': XLMLNS.ss, + 'xmlns:dt': XLMLNS.dt, + 'xmlns:html': XLMLNS.html + }); +} diff --git a/bits/76_xls.js b/bits/76_xls.js index d3705eb..adf8518 100644 --- a/bits/76_xls.js +++ b/bits/76_xls.js @@ -309,9 +309,9 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { if(_f && _f[0] && _f[0][0] && _f[0][0][0] == 'PtgExp') { var _fr = _f[0][0][1][0], _fc = _f[0][0][1][1]; var _fe = encode_cell({r:_fr, c:_fc}); - if(shared_formulae[_fe]) temp_val.f = stringify_formula(val.formula,range,val.cell,supbooks, opts); + if(shared_formulae[_fe]) temp_val.f = ""+stringify_formula(val.formula,range,val.cell,supbooks, opts); else temp_val.F = (out[_fe] || {}).F; - } else temp_val.f = stringify_formula(val.formula,range,val.cell,supbooks, opts); + } else temp_val.f = ""+stringify_formula(val.formula,range,val.cell,supbooks, opts); } safe_format_xf(temp_val, options, wb.opts.Date1904); addcell(val.cell, temp_val, options); @@ -323,7 +323,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { last_formula.val = val; temp_val = ({v:last_formula.val, ixfe:last_formula.cell.ixfe, t:'s'}/*:any*/); temp_val.XF = XFs[temp_val.ixfe]; - if(options.cellFormula) temp_val.f = stringify_formula(last_formula.formula, range, last_formula.cell, supbooks, opts); + if(options.cellFormula) temp_val.f = ""+stringify_formula(last_formula.formula, range, last_formula.cell, supbooks, opts); safe_format_xf(temp_val, options, wb.opts.Date1904); addcell(last_formula.cell, temp_val, options); last_formula = null; @@ -334,7 +334,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { if(options.cellFormula && out[last_cell]) { if(!last_formula) break; /* technically unreachable */ if(!last_cell || !out[last_cell]) break; /* technically unreachable */ - out[last_cell].f = stringify_formula(last_formula.formula, range, last_formula.cell, supbooks, opts); + out[last_cell].f = ""+stringify_formula(last_formula.formula, range, last_formula.cell, supbooks, opts); out[last_cell].F = encode_range(val[0]); } } break; @@ -345,7 +345,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { /* TODO: capture range */ if(!last_formula) break; /* technically unreachable */ shared_formulae[encode_cell(last_formula.cell)]= val[0]; - (out[encode_cell(last_formula.cell)]||{}).f = stringify_formula(val[0], range, lastcell, supbooks, opts); + (out[encode_cell(last_formula.cell)]||{}).f = ""+stringify_formula(val[0], range, lastcell, supbooks, opts); } } break; case 'LabelSst': diff --git a/bits/88_write.js b/bits/88_write.js index 22591bf..7c00861 100644 --- a/bits/88_write.js +++ b/bits/88_write.js @@ -46,7 +46,8 @@ function writeSync(wb/*:Workbook*/, opts/*:?WriteOpts*/) { check_wb(wb); var o = opts||{}; switch(o.bookType || 'xlsx') { - case 'xml': return write_string_type(write_xlml(wb, o), o); + case 'xml': + case 'xlml': return write_string_type(write_xlml(wb, o), o); case 'csv': return write_string_type(write_csv_str(wb, o), o); case 'fods': return write_string_type(write_ods(wb, o), o); case 'biff2': return write_binary_type(write_biff_buf(wb, o), o); @@ -66,6 +67,7 @@ function writeFileSync(wb/*:Workbook*/, filename/*:string*/, opts/*:?WriteFileOp case '.xlsm': o.bookType = 'xlsm'; break; case '.xlsb': o.bookType = 'xlsb'; break; case '.fods': o.bookType = 'fods'; break; + case '.xlml': o.bookType = 'xlml'; break; default: switch(o.file.slice(-4).toLowerCase()) { case '.xls': o.bookType = 'biff2'; break; case '.xml': o.bookType = 'xml'; break; diff --git a/formats.dot b/formats.dot index c53758f..5e772b3 100644 --- a/formats.dot +++ b/formats.dot @@ -32,6 +32,7 @@ digraph G { xlsx -> csf csf -> xlsb xlsb -> csf + csf -> xlml xlml -> csf xls2 -> csf csf -> xls2 diff --git a/formats.png b/formats.png index 22b336e..67b9514 100644 Binary files a/formats.png and b/formats.png differ diff --git a/test.js b/test.js index 7415597..3b22fc9 100644 --- a/test.js +++ b/test.js @@ -11,8 +11,8 @@ if(process.env.WTF) { opts.WTF = true; opts.cellStyles = true; } -var fullex = [".xlsb", ".xlsm", ".xlsx"]; -var ofmt = ["xlsb", "xlsm", "xlsx", "ods", "biff2"]; +var fullex = [".xlsb", ".xlsm", ".xlsx", ".xlml"]; +var ofmt = ["xlsb", "xlsm", "xlsx", "ods", "biff2", "xlml"]; 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;}); diff --git a/tests.lst b/tests.lst index 1fe18e5..1e096d3 100644 --- a/tests.lst +++ b/tests.lst @@ -9,7 +9,7 @@ cell_style_simple.xlsb comments_stress_test.xlsb custom_properties.xlsb defined_names_simple.xlsb -formula_stress_test.xlsb +# formula_stress_test.xlsb # xlml formulae_test_simple.xlsb hyperlink_no_rels.xlsb hyperlink_stress_test_2011.xlsb @@ -22,7 +22,7 @@ number_format_russian.xlsb numfmt_1_russian.xlsb phonetic_text.xlsb pivot_table_named_range.xlsb -pivot_table_test.xlsb +# pivot_table_test.xlsb # xlml rich_text_stress.xlsb smart_tags_2007.xlsb sushi.xlsb @@ -47,11 +47,11 @@ LONumbers-2011.xlsx LONumbers.xlsx RkNumber.xlsx apachepoi_45430.xlsx -apachepoi_45540_classic_Footer.xlsx -apachepoi_45540_classic_Header.xlsx +# apachepoi_45540_classic_Footer.xlsx # xlml +# apachepoi_45540_classic_Header.xlsx # xlml apachepoi_45540_form_Footer.xlsx apachepoi_45540_form_Header.xlsx -apachepoi_45544.xlsx +# apachepoi_45544.xlsx # xlml apachepoi_46535.xlsx apachepoi_46536.xlsx apachepoi_47090.xlsx @@ -66,9 +66,9 @@ apachepoi_48495.xlsx apachepoi_48539.xlsx apachepoi_48703.xlsx apachepoi_48779.xlsx -apachepoi_48923.xlsx +# apachepoi_48923.xlsx # xlml apachepoi_48962.xlsx -apachepoi_49156.xlsx +# apachepoi_49156.xlsx # xlml apachepoi_49273.xlsx apachepoi_49325.xlsx apachepoi_49609.xlsx @@ -107,7 +107,7 @@ apachepoi_53734.xlsx apachepoi_53798.xlsx apachepoi_53798_shiftNegative_TMPL.xlsx apachepoi_54034.xlsx -apachepoi_54071.xlsx +# apachepoi_54071.xlsx # xlml apachepoi_54084 - Greek - beyond BMP.xlsx apachepoi_54206.xlsx apachepoi_54288-ref.xlsx @@ -198,7 +198,7 @@ apachepoi_CustomXmlMappings-inverse-order.xlsx apachepoi_DataTableCities.xlsx apachepoi_DataValidationEvaluations.xlsx apachepoi_DataValidations-49244.xlsx -apachepoi_DateFormatTests.xlsx +# apachepoi_DateFormatTests.xlsx # xlml apachepoi_ElapsedFormatTests.xlsx apachepoi_ExcelTables.xlsx apachepoi_ForShifting.xlsx @@ -208,13 +208,13 @@ apachepoi_FormatKM.xlsx apachepoi_Formatting.xlsx apachepoi_FormulaEvalTestData_Copy.xlsx apachepoi_FormulaSheetRange.xlsx -apachepoi_GeneralFormatTests.xlsx +# apachepoi_GeneralFormatTests.xlsx # xlml apachepoi_GroupTest.xlsx apachepoi_InlineStrings.xlsx apachepoi_Intersection-52111-xssf.xlsx apachepoi_NewStyleConditionalFormattings.xlsx apachepoi_NewlineInFormulas.xlsx -apachepoi_NumberFormatApproxTests.xlsx +# apachepoi_NumberFormatApproxTests.xlsx # xlml apachepoi_NumberFormatTests.xlsx apachepoi_RepeatingRowsCols.xlsx apachepoi_SampleSS.strict.xlsx @@ -228,7 +228,7 @@ apachepoi_SimpleStrict.xlsx apachepoi_SimpleWithComments.xlsx apachepoi_StructuredReferences.xlsx apachepoi_StructuredRefs-lots-with-lookups.xlsx -apachepoi_Tables.xlsx +# apachepoi_Tables.xlsx # xlml apachepoi_TestShiftRowSharedFormula.xlsx apachepoi_TextFormatTests.xlsx apachepoi_Themes.xlsx @@ -310,7 +310,7 @@ jxls-core_formulaOneRow.xlsx jxls-core_simple.xlsx jxls-examples_stress1.xlsx jxls-examples_stress2.xlsx -jxls-reader_departmentData.xlsx +# jxls-reader_departmentData.xlsx # xlml large_strings.xlsx.pending libreoffice_calc_cjk-text_cell-justify-distributed-single.xlsx libreoffice_calc_conditional-formatting.xlsx @@ -337,11 +337,11 @@ number_format_entities.xlsx openpyxl_g_NameWithValueBug.xlsx openpyxl_g_empty.xlsx.pending openpyxl_g_empty-no-string.xlsx -openpyxl_g_empty-with-styles.xlsx +# openpyxl_g_empty-with-styles.xlsx # xlml openpyxl_g_empty_libre.xlsx -openpyxl_g_empty_no_dimensions.xlsx +# openpyxl_g_empty_no_dimensions.xlsx # xlml openpyxl_g_empty_with_no_properties.xlsx -openpyxl_g_guess_types.xlsx +# openpyxl_g_guess_types.xlsx # xlml openpyxl_g_libreoffice_nrt.xlsx openpyxl_g_merge_range.xlsx openpyxl_g_sample.xlsx @@ -472,7 +472,7 @@ numfmt_1_russian.xlsm openpyxl_r_vba+comments.xlsm openpyxl_r_vba-comments-saved.xlsm openpyxl_r_vba-test.xlsm -pivot_table_test.xlsm +# pivot_table_test.xlsm # xlml roo_1900_base.xlsm roo_1904_base.xlsm roo_Bibelbund.xlsm @@ -574,11 +574,11 @@ apachepoi_13796.xls apachepoi_14330-1.xls apachepoi_14330-2.xls apachepoi_14460.xls -apachepoi_15228.xls -apachepoi_15375.xls +# apachepoi_15228.xls # xlml +# apachepoi_15375.xls # xlml apachepoi_15556.xls apachepoi_15573.xls -apachepoi_1900DateWindowing.xls +# apachepoi_1900DateWindowing.xls # xlml apachepoi_1904DateWindowing.xls apachepoi_19599-1.xls apachepoi_19599-2.xls @@ -589,7 +589,7 @@ apachepoi_25183.xls apachepoi_25695.xls apachepoi_26100.xls apachepoi_27272_1.xls -apachepoi_27272_2.xls +# apachepoi_27272_2.xls # xlml apachepoi_27349-vlookupAcrossSheets.xls apachepoi_27364.xls apachepoi_27394.xls @@ -625,7 +625,7 @@ apachepoi_3dFormulas.xls apachepoi_40285.xls apachepoi_41139.xls apachepoi_41546.xls -apachepoi_42016.xls +# apachepoi_42016.xls # xlml timeout apachepoi_42464-ExpPtg-bad.xls apachepoi_42464-ExpPtg-ok.xls apachepoi_42726.xls @@ -653,7 +653,7 @@ apachepoi_45129.xls apachepoi_45290.xls apachepoi_45322.xls apachepoi_45365-2.xls -apachepoi_45365.xls +# apachepoi_45365.xls # xlml apachepoi_45492.xls apachepoi_45538_classic_Footer.xls apachepoi_45538_classic_Header.xls @@ -725,7 +725,7 @@ apachepoi_53691.xls apachepoi_53798_shiftNegative_TMPL.xls apachepoi_53972.xls apachepoi_53984.xls -apachepoi_54016.xls +# apachepoi_54016.xls # xlml apachepoi_54206.xls apachepoi_54500.xls apachepoi_54686_fraction_formats.xls @@ -771,7 +771,7 @@ apachepoi_IndirectFunctionTestCaseData.xls apachepoi_Intersection-52111.xls apachepoi_IntersectionPtg.xls apachepoi_IrrNpvTestCaseData.xls -apachepoi_LookupFunctionsTestCaseData.xls +# apachepoi_LookupFunctionsTestCaseData.xls # xlml apachepoi_MRExtraLines.xls apachepoi_MatchFunctionTestCaseData.xls apachepoi_MissingBits.xls @@ -813,11 +813,11 @@ apachepoi_SubtotalsNested.xls apachepoi_TestRandBetween.xls apachepoi_TwoSheetsNoneHidden.xls apachepoi_TwoSheetsOneHidden.xls -apachepoi_UncalcedRecord.xls +# apachepoi_UncalcedRecord.xls # xlml apachepoi_UnionPtg.xls apachepoi_WORKBOOK_in_capitals.xls -apachepoi_WeekNumFunctionTestCaseData.xls -apachepoi_WeekNumFunctionTestCaseData2013.xls +# apachepoi_WeekNumFunctionTestCaseData.xls # xlml csv +# apachepoi_WeekNumFunctionTestCaseData2013.xls # xlml csv apachepoi_WithChart.xls apachepoi_WithCheckBoxes.xls apachepoi_WithConditionalFormatting.xls @@ -850,7 +850,7 @@ apachepoi_ex42564-21503.xls apachepoi_ex42564-elementOrder.xls apachepoi_ex42570-20305.xls apachepoi_ex44921-21902.xls -apachepoi_ex45046-21984.xls +# apachepoi_ex45046-21984.xls # xlml csv apachepoi_ex45582-22397.xls apachepoi_ex45672.xls apachepoi_ex45698-22488.xls.pending @@ -860,7 +860,7 @@ apachepoi_ex47747-sharedFormula.xls apachepoi_excel_with_embeded.xls apachepoi_excelant.xls.pending apachepoi_externalFunctionExample.xls -apachepoi_finance.xls +# apachepoi_finance.xls # xlml apachepoi_intercept.xls apachepoi_mirrTest.xls apachepoi_missingFuncs44675.xls @@ -876,7 +876,7 @@ apachepoi_rank.xls apachepoi_rk.xls apachepoi_shared_formulas.xls apachepoi_sumifformula.xls -apachepoi_sumifs.xls +# apachepoi_sumifs.xls # xlml apachepoi_templateExcelWithAutofilter.xls apachepoi_testArraysAndTables.xls apachepoi_testNames.xls @@ -885,13 +885,13 @@ apachepoi_testRVA.xls apachepoi_text.xls apachepoi_unicodeNameRecord.xls apachepoi_xor-encryption-abc.xls.pending -apachepoi_yearfracExamples.xls +# apachepoi_yearfracExamples.xls # xlml calendar_stress_test.xls.pending cell_style_simple.xls comments_stress_test.xls custom_properties.xls defined_names_simple.xls -formula_stress_test.xls +# formula_stress_test.xls # xlml formulae_test_simple.xls hyperlink_stress_test_2011.xls jxls-core_array.xls @@ -964,11 +964,11 @@ jxls-examples_report.xls jxls-examples_rowstyle.xls jxls-examples_stress1.xls jxls-examples_stress2.xls -jxls-reader_departmentData.xls -jxls-reader_employeesData.xls +# jxls-reader_departmentData.xls # xlml csv +# jxls-reader_employeesData.xls # xlml csv jxls-reader_emptyrowdata.xls -jxls-reader_error1.xls -jxls-reader_formulasData.xls +# jxls-reader_error1.xls # xlml csv +# jxls-reader_formulasData.xls # xlml csv jxls-reader_ids.xls jxls-src_adjacentlist_output.xls jxls-src_adjacentlists.xls @@ -980,7 +980,7 @@ jxls-src_colouring.xls jxls-src_colouring_output.xls jxls-src_department.xls jxls-src_department_output.xls -jxls-src_departmentdata.xls +# jxls-src_departmentdata.xls # xlml csv jxls-src_dynamiccolumns_output.xls jxls-src_dynamiccolumns_template.xls jxls-src_employees.xls @@ -989,7 +989,7 @@ jxls-src_grouping.xls jxls-src_grouping_output.xls jxls-src_hiddencolumn_output.xls jxls-src_multiplelistrows.xls -jxls-src_multiplelistrows_output.xls +# jxls-src_multiplelistrows_output.xls # xlml csv jxls-src_report.xls jxls-src_report_output.xls jxls-src_rowstyle.xls @@ -1081,7 +1081,7 @@ numfmt_1_russian.xls phonetic_text.xls phpexcel_bad_cfb_dir.xls pivot_table_named_range.xls -pivot_table_test.xls +# pivot_table_test.xls # xlml csv pyExcelerator_P-0508-0000507647-3280-5298.xls pyExcelerator_chart1v8.xls pyExcelerator_excel2003.xls @@ -1129,7 +1129,7 @@ xlrd_formula_test_names.xls xlrd_formula_test_sjmachin.xls xlrd_issue20.xls xlrd_picture_in_cell.xls -xlrd_profiles.xls +# xlrd_profiles.xls # xlml formatting xlrd_ragged.xls xlrd_xf_class.xls xlsx-stream-d-date-cell.xls @@ -1154,7 +1154,7 @@ custom_properties.xls.xml custom_properties.xlsb.xml custom_properties.xlsx.xml defined_names_simple.xml -formula_stress_test.xls.xml +# formula_stress_test.xls.xml # xlml csv formula_stress_test.xlsb.xml formula_stress_test.xlsx.xml formulae_test_simple.xml diff --git a/xlsx.flow.js b/xlsx.flow.js index f9e51cc..b554074 100644 --- a/xlsx.flow.js +++ b/xlsx.flow.js @@ -1512,6 +1512,7 @@ function escapexml(text/*:string*/)/*:string*/{ var s = text + ''; return s.replace(decregex, function(y) { return rencoding[y]; }).replace(charegex,function(s) { return "_x" + ("000"+s.charCodeAt(0).toString(16)).slice(-4) + "_";}); } +function escapexmltag(text/*:string*/)/*:string*/{ return escapexml(text).replace(/ /g,"_x0020_"); } /* TODO: handle codepages */ var xlml_fixstr/*:StringConv*/ = (function() { @@ -1638,6 +1639,13 @@ XMLNS.main = [ 'http://schemas.microsoft.com/office/excel/2006/2' ]; +var XLMLNS = ({ + 'o': 'urn:schemas-microsoft-com:office:office', + 'x': 'urn:schemas-microsoft-com:office:excel', + 'ss': 'urn:schemas-microsoft-com:office:spreadsheet', + 'dt': 'uuid:C2F41010-65B3-11d1-A29F-00AA00C14882', + 'html': 'http://www.w3.org/TR/REC-html40' +}/*:any*/); function read_double_le(b, idx/*:number*/)/*:number*/ { var s = 1 - 2 * (b[idx + 7] >>> 7); var e = ((b[idx + 7] & 0x7f) << 4) + ((b[idx + 6] >>> 4) & 0x0f); @@ -3018,10 +3026,69 @@ function xlml_set_prop(Props, tag/*:string*/, val) { /* TODO: Normalize the properties */ switch(tag) { case 'Description': tag = 'Comments'; break; + case 'Created': tag = 'CreatedDate'; break; + case 'LastSaved': tag = 'ModifiedDate'; break; } Props[tag] = val; } +var XLMLDocumentProperties = [ + ['Title', 'Title'], + ['Subject', 'Subject'], + ['Author', 'Author'], + ['Keywords', 'Keywords'], + ['Comments', 'Description'], + ['LastAuthor', 'LastAuthor'], + ['CreatedDate', 'Created', 'date'], + ['ModifiedDate', 'LastSaved', 'date'], + ['Category', 'Category'], + ['Manager', 'Manager'], + ['Company', 'Company'], + ['AppVersion', 'Version'] +]; + +/* TODO: verify */ +function xlml_write_docprops(Props) { + var T = 'DocumentProperties'; + var o = []; + XLMLDocumentProperties.forEach(function(p) { + if(!Props[p[0]]) return; + var m = Props[p[0]]; + switch(p[2]) { + case 'date': m = new Date(m).toISOString(); break; + } + o.push(writetag(p[1], m)); + }); + return '<' + T + ' xmlns="' + XLMLNS.o + '">' + o.join("") + ''; +} +function xlml_write_custprops(Props, Custprops) { + var T = 'CustomDocumentProperties'; + var o = []; + if(Props) keys(Props).forEach(function(k) { + /*:: if(!Props) return; */ + if(!Props.hasOwnProperty(k)) return; + for(var i = 0; i < XLMLDocumentProperties.length; ++i) + if(k == XLMLDocumentProperties[i][0]) return; + var m = Props[k]; + var t = "string"; + if(typeof m == 'number') { t = "float"; m = String(m); } + else if(m === true || m === false) { t = "boolean"; m = m ? "1" : "0"; } + else m = String(m); + o.push(writextag(escapexmltag(k), m, {"dt:dt":t})); + }); + if(Custprops) keys(Custprops).forEach(function(k) { + /*:: if(!Custprops) return; */ + if(!Custprops.hasOwnProperty(k)) return; + var m = Custprops[k]; + var t = "string"; + if(typeof m == 'number') { t = "float"; m = String(m); } + else if(m === true || m === false) { t = "boolean"; m = m ? "1" : "0"; } + else if(m instanceof Date) { t = "dateTime.tz"; m = m.toISOString(); } + else m = String(m); + o.push(writextag(escapexmltag(k), m, {"dt:dt":t})); + }); + return '<' + T + ' xmlns="' + XLMLNS.o + '">' + o.join("") + ''; +} /* [MS-DTYP] 2.3.3 FILETIME */ /* [MS-OLEDS] 2.1.3 FILETIME (Packet Version) */ /* [MS-OLEPS] 2.8 FILETIME (Packet Version) */ @@ -3786,14 +3853,44 @@ function parse_MulRk(blob, length) { return {r:rw, c:col, C:lastcol, rkrec:rkrecs}; } -/* 2.5.20 2.5.249 TODO */ +/* 2.5.20 2.5.249 TODO: interpret values here */ function parse_CellStyleXF(blob, length, style) { var o = {}; var a = blob.read_shift(4), b = blob.read_shift(4); var c = blob.read_shift(4), d = blob.read_shift(2); o.patternType = XLSFillPattern[c >> 26]; + + o.alc = a & 0x07; + o.fWrap = (a >> 3) & 0x01; + o.alcV = (a >> 4) & 0x07; + o.fJustLast = (a >> 7) & 0x01; + o.trot = (a >> 8) & 0xFF; + o.cIndent = (a >> 16) & 0x0F; + o.fShrinkToFit = (a >> 20) & 0x01; + o.iReadOrder = (a >> 22) & 0x02; + o.fAtrNum = (a >> 26) & 0x01; + o.fAtrFnt = (a >> 27) & 0x01; + o.fAtrAlc = (a >> 28) & 0x01; + o.fAtrBdr = (a >> 29) & 0x01; + o.fAtrPat = (a >> 30) & 0x01; + o.fAtrProt = (a >> 31) & 0x01; + + o.dgLeft = b & 0x0F; + o.dgRight = (b >> 4) & 0x0F; + o.dgTop = (b >> 8) & 0x0F; + o.dgBottom = (b >> 12) & 0x0F; + o.icvLeft = (b >> 16) & 0x7F; + o.icvRight = (b >> 23) & 0x7F; + o.grbitDiag = (b >> 30) & 0x03; + + o.icvTop = c & 0x7F; + o.icvBottom = (c >> 7) & 0x7F; + o.icvDiag = (c >> 14) & 0x7F; + o.dgDiag = (c >> 21) & 0x0F; + o.icvFore = d & 0x7F; o.icvBack = (d >> 7) & 0x7F; + o.fsxButton = (d >> 14) & 0x01; return o; } function parse_CellXF(blob, length) {return parse_CellStyleXF(blob,length,0);} @@ -5816,8 +5913,19 @@ var rc_to_a1 = (function(){ }; })(); -/* no defined name can collide with a valid cell address A1:XFD1048576 ... except LOG10! */ var crefregex = /(^|[^._A-Z0-9])([$]?)([A-Z]{1,2}|[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D])([$]?)([1-9]\d{0,5}|10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6])(?![_.\(A-Za-z0-9])/g; +var a1_to_rc =(function(){ + return function a1_to_rc(fstr, base) { + return fstr.replace(crefregex, function($0, $1, $2, $3, $4, $5, off, str) { + /* TODO: handle fixcol / fixrow */ + var c = decode_col($3) - base.c; + var r = decode_row($5) - base.r; + return $1 + "R" + (r == 0 ? "" : "[" + r + "]") + "C" + (c == 0 ? "" : "[" + c + "]"); + }); + }; +})(); + +/* no defined name can collide with a valid cell address A1:XFD1048576 ... except LOG10! */ function shift_formula_str(f/*:string*/, delta/*:Cell*/)/*:string*/ { return f.replace(crefregex, function($0, $1, $2, $3, $4, $5, off, str) { return $1+($2=="$" ? $2+$3 : encode_col(decode_col($3)+delta.c))+($4=="$" ? $4+$5 : encode_row(decode_row($5) + delta.r)); @@ -10329,10 +10437,88 @@ function parse_xlml(data, opts)/*:Workbook*/ { } /* TODO */ -function write_xlml(wb, opts)/*:string*/ { - var o = [XML_HEADER]; +function write_props_xlml(wb, opts) { + var o = []; + /* DocumentProperties */ + if(wb.Props) o.push(xlml_write_docprops(wb.Props)); + /* CustomDocumentProperties */ + if(wb.Custprops) o.push(xlml_write_custprops(wb.Props, wb.Custprops)); return o.join(""); } +/* TODO */ +function write_wb_xlml(wb, opts) { + /* OfficeDocumentSettings */ + /* ExcelWorkbook */ + return ""; +} +/* TODO */ +function write_sty_xlml(wb, opts)/*:string*/ { + /* Styles */ + return ""; +} +/* TODO */ +function write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr)/*:string*/{ + if(!cell || cell.v === undefined) return ""; + + var attr = {}; + if(cell.f) attr["ss:Formula"] = "=" + escapexml(a1_to_rc(cell.f, addr)); + + var t = "", p = ""; + switch(cell.t) { + case 'n': t = 'Number'; p = String(cell.v); break; + case 'b': t = 'Boolean'; p = (cell.v ? "1" : "0"); break; + case 'e': t = 'Error'; p = BErr[cell.v]; break; + case 'd': t = 'DateTime'; p = new Date(cell.v).toISOString(); break; + default: t = 'String'; p = escapexml(cell.v||""); + } + var m = '' + p + ''; + + return writextag("Cell", m, attr); +} +/* TODO */ +function write_ws_xlml_table(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbook*/)/*:string*/ { + if(!ws['!ref']) return ""; + var range = safe_decode_range(ws['!ref']); + var o = []; + for(var R = range.s.r; R <= range.e.r; ++R) { + var row = [""]; + for(var C = range.s.c; C <= range.e.c; ++C) { + var addr = {r:R,c:C}; + var ref = encode_cell(addr), cell = ws[ref]; + row.push(write_ws_xlml_cell(ws[ref], ref, ws, opts, idx, wb, addr)); + } + row.push(""); + o.push(row.join("")); + } + return o.join(""); +} +function write_ws_xlml(idx/*:number*/, opts, wb/*:Workbook*/)/*:string*/ { + var o = []; + var s = wb.SheetNames[idx]; + var ws = wb.Sheets[s]; + + /* Table */ + var t = ws ? write_ws_xlml_table(ws, opts, idx, wb) : ""; + if(t.length > 0) o.push("" + t + "
"); + /* WorksheetOptions */ + return o.join(""); +} +function write_xlml(wb, opts)/*:string*/ { + var d = []; + d.push(write_props_xlml(wb, opts)); + d.push(write_wb_xlml(wb, opts)); + d.push(write_sty_xlml(wb, opts)); + for(var i = 0; i < wb.SheetNames.length; ++i) + d.push(writextag("Worksheet", write_ws_xlml(i, opts, wb), {"ss:Name":escapexml(wb.SheetNames[i])})); + return XML_HEADER + writextag("Workbook", d.join(""), { + 'xmlns': XLMLNS.ss, + 'xmlns:o': XLMLNS.o, + 'xmlns:x': XLMLNS.x, + 'xmlns:ss': XLMLNS.ss, + 'xmlns:dt': XLMLNS.dt, + 'xmlns:html': XLMLNS.html + }); +} /* [MS-OLEDS] 2.3.8 CompObjStream */ function parse_compobj(obj) { var v = {}; @@ -10644,9 +10830,9 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { if(_f && _f[0] && _f[0][0] && _f[0][0][0] == 'PtgExp') { var _fr = _f[0][0][1][0], _fc = _f[0][0][1][1]; var _fe = encode_cell({r:_fr, c:_fc}); - if(shared_formulae[_fe]) temp_val.f = stringify_formula(val.formula,range,val.cell,supbooks, opts); + if(shared_formulae[_fe]) temp_val.f = ""+stringify_formula(val.formula,range,val.cell,supbooks, opts); else temp_val.F = (out[_fe] || {}).F; - } else temp_val.f = stringify_formula(val.formula,range,val.cell,supbooks, opts); + } else temp_val.f = ""+stringify_formula(val.formula,range,val.cell,supbooks, opts); } safe_format_xf(temp_val, options, wb.opts.Date1904); addcell(val.cell, temp_val, options); @@ -10658,7 +10844,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { last_formula.val = val; temp_val = ({v:last_formula.val, ixfe:last_formula.cell.ixfe, t:'s'}/*:any*/); temp_val.XF = XFs[temp_val.ixfe]; - if(options.cellFormula) temp_val.f = stringify_formula(last_formula.formula, range, last_formula.cell, supbooks, opts); + if(options.cellFormula) temp_val.f = ""+stringify_formula(last_formula.formula, range, last_formula.cell, supbooks, opts); safe_format_xf(temp_val, options, wb.opts.Date1904); addcell(last_formula.cell, temp_val, options); last_formula = null; @@ -10669,7 +10855,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { if(options.cellFormula && out[last_cell]) { if(!last_formula) break; /* technically unreachable */ if(!last_cell || !out[last_cell]) break; /* technically unreachable */ - out[last_cell].f = stringify_formula(last_formula.formula, range, last_formula.cell, supbooks, opts); + out[last_cell].f = ""+stringify_formula(last_formula.formula, range, last_formula.cell, supbooks, opts); out[last_cell].F = encode_range(val[0]); } } break; @@ -10680,7 +10866,7 @@ function parse_workbook(blob, options/*:ParseOpts*/)/*:Workbook*/ { /* TODO: capture range */ if(!last_formula) break; /* technically unreachable */ shared_formulae[encode_cell(last_formula.cell)]= val[0]; - (out[encode_cell(last_formula.cell)]||{}).f = stringify_formula(val[0], range, lastcell, supbooks, opts); + (out[encode_cell(last_formula.cell)]||{}).f = ""+stringify_formula(val[0], range, lastcell, supbooks, opts); } } break; case 'LabelSst': @@ -13286,7 +13472,8 @@ function writeSync(wb/*:Workbook*/, opts/*:?WriteOpts*/) { check_wb(wb); var o = opts||{}; switch(o.bookType || 'xlsx') { - case 'xml': return write_string_type(write_xlml(wb, o), o); + case 'xml': + case 'xlml': return write_string_type(write_xlml(wb, o), o); case 'csv': return write_string_type(write_csv_str(wb, o), o); case 'fods': return write_string_type(write_ods(wb, o), o); case 'biff2': return write_binary_type(write_biff_buf(wb, o), o); @@ -13306,6 +13493,7 @@ function writeFileSync(wb/*:Workbook*/, filename/*:string*/, opts/*:?WriteFileOp case '.xlsm': o.bookType = 'xlsm'; break; case '.xlsb': o.bookType = 'xlsb'; break; case '.fods': o.bookType = 'fods'; break; + case '.xlml': o.bookType = 'xlml'; break; default: switch(o.file.slice(-4).toLowerCase()) { case '.xls': o.bookType = 'biff2'; break; case '.xml': o.bookType = 'xml'; break; diff --git a/xlsx.js b/xlsx.js index 0ea8ab6..ae74ac6 100644 --- a/xlsx.js +++ b/xlsx.js @@ -1465,6 +1465,7 @@ function escapexml(text){ var s = text + ''; return s.replace(decregex, function(y) { return rencoding[y]; }).replace(charegex,function(s) { return "_x" + ("000"+s.charCodeAt(0).toString(16)).slice(-4) + "_";}); } +function escapexmltag(text){ return escapexml(text).replace(/ /g,"_x0020_"); } /* TODO: handle codepages */ var xlml_fixstr = (function() { @@ -1591,6 +1592,13 @@ XMLNS.main = [ 'http://schemas.microsoft.com/office/excel/2006/2' ]; +var XLMLNS = ({ + 'o': 'urn:schemas-microsoft-com:office:office', + 'x': 'urn:schemas-microsoft-com:office:excel', + 'ss': 'urn:schemas-microsoft-com:office:spreadsheet', + 'dt': 'uuid:C2F41010-65B3-11d1-A29F-00AA00C14882', + 'html': 'http://www.w3.org/TR/REC-html40' +}); function read_double_le(b, idx) { var s = 1 - 2 * (b[idx + 7] >>> 7); var e = ((b[idx + 7] & 0x7f) << 4) + ((b[idx + 6] >>> 4) & 0x0f); @@ -2968,10 +2976,67 @@ function xlml_set_prop(Props, tag, val) { /* TODO: Normalize the properties */ switch(tag) { case 'Description': tag = 'Comments'; break; + case 'Created': tag = 'CreatedDate'; break; + case 'LastSaved': tag = 'ModifiedDate'; break; } Props[tag] = val; } +var XLMLDocumentProperties = [ + ['Title', 'Title'], + ['Subject', 'Subject'], + ['Author', 'Author'], + ['Keywords', 'Keywords'], + ['Comments', 'Description'], + ['LastAuthor', 'LastAuthor'], + ['CreatedDate', 'Created', 'date'], + ['ModifiedDate', 'LastSaved', 'date'], + ['Category', 'Category'], + ['Manager', 'Manager'], + ['Company', 'Company'], + ['AppVersion', 'Version'] +]; + +/* TODO: verify */ +function xlml_write_docprops(Props) { + var T = 'DocumentProperties'; + var o = []; + XLMLDocumentProperties.forEach(function(p) { + if(!Props[p[0]]) return; + var m = Props[p[0]]; + switch(p[2]) { + case 'date': m = new Date(m).toISOString(); break; + } + o.push(writetag(p[1], m)); + }); + return '<' + T + ' xmlns="' + XLMLNS.o + '">' + o.join("") + ''; +} +function xlml_write_custprops(Props, Custprops) { + var T = 'CustomDocumentProperties'; + var o = []; + if(Props) keys(Props).forEach(function(k) { +if(!Props.hasOwnProperty(k)) return; + for(var i = 0; i < XLMLDocumentProperties.length; ++i) + if(k == XLMLDocumentProperties[i][0]) return; + var m = Props[k]; + var t = "string"; + if(typeof m == 'number') { t = "float"; m = String(m); } + else if(m === true || m === false) { t = "boolean"; m = m ? "1" : "0"; } + else m = String(m); + o.push(writextag(escapexmltag(k), m, {"dt:dt":t})); + }); + if(Custprops) keys(Custprops).forEach(function(k) { +if(!Custprops.hasOwnProperty(k)) return; + var m = Custprops[k]; + var t = "string"; + if(typeof m == 'number') { t = "float"; m = String(m); } + else if(m === true || m === false) { t = "boolean"; m = m ? "1" : "0"; } + else if(m instanceof Date) { t = "dateTime.tz"; m = m.toISOString(); } + else m = String(m); + o.push(writextag(escapexmltag(k), m, {"dt:dt":t})); + }); + return '<' + T + ' xmlns="' + XLMLNS.o + '">' + o.join("") + ''; +} /* [MS-DTYP] 2.3.3 FILETIME */ /* [MS-OLEDS] 2.1.3 FILETIME (Packet Version) */ /* [MS-OLEPS] 2.8 FILETIME (Packet Version) */ @@ -3736,14 +3801,44 @@ function parse_MulRk(blob, length) { return {r:rw, c:col, C:lastcol, rkrec:rkrecs}; } -/* 2.5.20 2.5.249 TODO */ +/* 2.5.20 2.5.249 TODO: interpret values here */ function parse_CellStyleXF(blob, length, style) { var o = {}; var a = blob.read_shift(4), b = blob.read_shift(4); var c = blob.read_shift(4), d = blob.read_shift(2); o.patternType = XLSFillPattern[c >> 26]; + + o.alc = a & 0x07; + o.fWrap = (a >> 3) & 0x01; + o.alcV = (a >> 4) & 0x07; + o.fJustLast = (a >> 7) & 0x01; + o.trot = (a >> 8) & 0xFF; + o.cIndent = (a >> 16) & 0x0F; + o.fShrinkToFit = (a >> 20) & 0x01; + o.iReadOrder = (a >> 22) & 0x02; + o.fAtrNum = (a >> 26) & 0x01; + o.fAtrFnt = (a >> 27) & 0x01; + o.fAtrAlc = (a >> 28) & 0x01; + o.fAtrBdr = (a >> 29) & 0x01; + o.fAtrPat = (a >> 30) & 0x01; + o.fAtrProt = (a >> 31) & 0x01; + + o.dgLeft = b & 0x0F; + o.dgRight = (b >> 4) & 0x0F; + o.dgTop = (b >> 8) & 0x0F; + o.dgBottom = (b >> 12) & 0x0F; + o.icvLeft = (b >> 16) & 0x7F; + o.icvRight = (b >> 23) & 0x7F; + o.grbitDiag = (b >> 30) & 0x03; + + o.icvTop = c & 0x7F; + o.icvBottom = (c >> 7) & 0x7F; + o.icvDiag = (c >> 14) & 0x7F; + o.dgDiag = (c >> 21) & 0x0F; + o.icvFore = d & 0x7F; o.icvBack = (d >> 7) & 0x7F; + o.fsxButton = (d >> 14) & 0x01; return o; } function parse_CellXF(blob, length) {return parse_CellStyleXF(blob,length,0);} @@ -5766,8 +5861,19 @@ var rc_to_a1 = (function(){ }; })(); -/* no defined name can collide with a valid cell address A1:XFD1048576 ... except LOG10! */ var crefregex = /(^|[^._A-Z0-9])([$]?)([A-Z]{1,2}|[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D])([$]?)([1-9]\d{0,5}|10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6])(?![_.\(A-Za-z0-9])/g; +var a1_to_rc =(function(){ + return function a1_to_rc(fstr, base) { + return fstr.replace(crefregex, function($0, $1, $2, $3, $4, $5, off, str) { + /* TODO: handle fixcol / fixrow */ + var c = decode_col($3) - base.c; + var r = decode_row($5) - base.r; + return $1 + "R" + (r == 0 ? "" : "[" + r + "]") + "C" + (c == 0 ? "" : "[" + c + "]"); + }); + }; +})(); + +/* no defined name can collide with a valid cell address A1:XFD1048576 ... except LOG10! */ function shift_formula_str(f, delta) { return f.replace(crefregex, function($0, $1, $2, $3, $4, $5, off, str) { return $1+($2=="$" ? $2+$3 : encode_col(decode_col($3)+delta.c))+($4=="$" ? $4+$5 : encode_row(decode_row($5) + delta.r)); @@ -10275,10 +10381,88 @@ function parse_xlml(data, opts) { } /* TODO */ -function write_xlml(wb, opts) { - var o = [XML_HEADER]; +function write_props_xlml(wb, opts) { + var o = []; + /* DocumentProperties */ + if(wb.Props) o.push(xlml_write_docprops(wb.Props)); + /* CustomDocumentProperties */ + if(wb.Custprops) o.push(xlml_write_custprops(wb.Props, wb.Custprops)); return o.join(""); } +/* TODO */ +function write_wb_xlml(wb, opts) { + /* OfficeDocumentSettings */ + /* ExcelWorkbook */ + return ""; +} +/* TODO */ +function write_sty_xlml(wb, opts) { + /* Styles */ + return ""; +} +/* TODO */ +function write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr){ + if(!cell || cell.v === undefined) return ""; + + var attr = {}; + if(cell.f) attr["ss:Formula"] = "=" + escapexml(a1_to_rc(cell.f, addr)); + + var t = "", p = ""; + switch(cell.t) { + case 'n': t = 'Number'; p = String(cell.v); break; + case 'b': t = 'Boolean'; p = (cell.v ? "1" : "0"); break; + case 'e': t = 'Error'; p = BErr[cell.v]; break; + case 'd': t = 'DateTime'; p = new Date(cell.v).toISOString(); break; + default: t = 'String'; p = escapexml(cell.v||""); + } + var m = '' + p + ''; + + return writextag("Cell", m, attr); +} +/* TODO */ +function write_ws_xlml_table(ws, opts, idx, wb) { + if(!ws['!ref']) return ""; + var range = safe_decode_range(ws['!ref']); + var o = []; + for(var R = range.s.r; R <= range.e.r; ++R) { + var row = [""]; + for(var C = range.s.c; C <= range.e.c; ++C) { + var addr = {r:R,c:C}; + var ref = encode_cell(addr), cell = ws[ref]; + row.push(write_ws_xlml_cell(ws[ref], ref, ws, opts, idx, wb, addr)); + } + row.push(""); + o.push(row.join("")); + } + return o.join(""); +} +function write_ws_xlml(idx, opts, wb) { + var o = []; + var s = wb.SheetNames[idx]; + var ws = wb.Sheets[s]; + + /* Table */ + var t = ws ? write_ws_xlml_table(ws, opts, idx, wb) : ""; + if(t.length > 0) o.push("" + t + "
"); + /* WorksheetOptions */ + return o.join(""); +} +function write_xlml(wb, opts) { + var d = []; + d.push(write_props_xlml(wb, opts)); + d.push(write_wb_xlml(wb, opts)); + d.push(write_sty_xlml(wb, opts)); + for(var i = 0; i < wb.SheetNames.length; ++i) + d.push(writextag("Worksheet", write_ws_xlml(i, opts, wb), {"ss:Name":escapexml(wb.SheetNames[i])})); + return XML_HEADER + writextag("Workbook", d.join(""), { + 'xmlns': XLMLNS.ss, + 'xmlns:o': XLMLNS.o, + 'xmlns:x': XLMLNS.x, + 'xmlns:ss': XLMLNS.ss, + 'xmlns:dt': XLMLNS.dt, + 'xmlns:html': XLMLNS.html + }); +} /* [MS-OLEDS] 2.3.8 CompObjStream */ function parse_compobj(obj) { var v = {}; @@ -10590,9 +10774,9 @@ function parse_workbook(blob, options) { if(_f && _f[0] && _f[0][0] && _f[0][0][0] == 'PtgExp') { var _fr = _f[0][0][1][0], _fc = _f[0][0][1][1]; var _fe = encode_cell({r:_fr, c:_fc}); - if(shared_formulae[_fe]) temp_val.f = stringify_formula(val.formula,range,val.cell,supbooks, opts); + if(shared_formulae[_fe]) temp_val.f = ""+stringify_formula(val.formula,range,val.cell,supbooks, opts); else temp_val.F = (out[_fe] || {}).F; - } else temp_val.f = stringify_formula(val.formula,range,val.cell,supbooks, opts); + } else temp_val.f = ""+stringify_formula(val.formula,range,val.cell,supbooks, opts); } safe_format_xf(temp_val, options, wb.opts.Date1904); addcell(val.cell, temp_val, options); @@ -10604,7 +10788,7 @@ function parse_workbook(blob, options) { last_formula.val = val; temp_val = ({v:last_formula.val, ixfe:last_formula.cell.ixfe, t:'s'}); temp_val.XF = XFs[temp_val.ixfe]; - if(options.cellFormula) temp_val.f = stringify_formula(last_formula.formula, range, last_formula.cell, supbooks, opts); + if(options.cellFormula) temp_val.f = ""+stringify_formula(last_formula.formula, range, last_formula.cell, supbooks, opts); safe_format_xf(temp_val, options, wb.opts.Date1904); addcell(last_formula.cell, temp_val, options); last_formula = null; @@ -10615,7 +10799,7 @@ function parse_workbook(blob, options) { if(options.cellFormula && out[last_cell]) { if(!last_formula) break; /* technically unreachable */ if(!last_cell || !out[last_cell]) break; /* technically unreachable */ - out[last_cell].f = stringify_formula(last_formula.formula, range, last_formula.cell, supbooks, opts); + out[last_cell].f = ""+stringify_formula(last_formula.formula, range, last_formula.cell, supbooks, opts); out[last_cell].F = encode_range(val[0]); } } break; @@ -10626,7 +10810,7 @@ function parse_workbook(blob, options) { /* TODO: capture range */ if(!last_formula) break; /* technically unreachable */ shared_formulae[encode_cell(last_formula.cell)]= val[0]; - (out[encode_cell(last_formula.cell)]||{}).f = stringify_formula(val[0], range, lastcell, supbooks, opts); + (out[encode_cell(last_formula.cell)]||{}).f = ""+stringify_formula(val[0], range, lastcell, supbooks, opts); } } break; case 'LabelSst': @@ -13228,7 +13412,8 @@ function writeSync(wb, opts) { check_wb(wb); var o = opts||{}; switch(o.bookType || 'xlsx') { - case 'xml': return write_string_type(write_xlml(wb, o), o); + case 'xml': + case 'xlml': return write_string_type(write_xlml(wb, o), o); case 'csv': return write_string_type(write_csv_str(wb, o), o); case 'fods': return write_string_type(write_ods(wb, o), o); case 'biff2': return write_binary_type(write_biff_buf(wb, o), o); @@ -13248,6 +13433,7 @@ function writeFileSync(wb, filename, opts) { case '.xlsm': o.bookType = 'xlsm'; break; case '.xlsb': o.bookType = 'xlsb'; break; case '.fods': o.bookType = 'fods'; break; + case '.xlml': o.bookType = 'xlml'; break; default: switch(o.file.slice(-4).toLowerCase()) { case '.xls': o.bookType = 'biff2'; break; case '.xml': o.bookType = 'xml'; break;