diff --git a/.gitignore b/.gitignore index 9a1a6a2..517a41b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ test_files/ +node_modules/ diff --git a/dist/ods.js b/dist/ods.js index 30e9f56..ea1abc5 100644 --- a/dist/ods.js +++ b/dist/ods.js @@ -21,6 +21,14 @@ function cc2str(arr) { for(var i = 0; i != arr.length; ++i) o += String.fromCharCode(arr[i]); return o; } + +function dup(o) { + if(typeof JSON != 'undefined') return JSON.parse(JSON.stringify(o)); + if(typeof o != 'object' || !o) return o; + var out = {}; + for(var k in o) if(o.hasOwnProperty(k)) out[k] = dup(o[k]); + return out; +} function getdata(data) { if(!data) return null; if(data.data) return data.data; @@ -67,7 +75,7 @@ function parsexmltag(tag, skip_root) { for(; eq !== tag.length; ++eq) if((c = tag.charCodeAt(eq)) === 32 || c === 10 || c === 13) break; if(!skip_root) z[0] = tag.substr(0, eq); if(eq === tag.length) return z; - var m = tag.match(attregexg), j=0, w="", v="", i=0, q="", cc=""; + var m = tag.match(attregexg), j=0, v="", i=0, q="", cc=""; if(m) for(i = 0; i != m.length; ++i) { cc = m[i]; for(c=0; c != cc.length; ++c) if(cc.charCodeAt(c) === 61) break; @@ -108,7 +116,7 @@ function escapexml(text){ return s.replace(decregex, function(y) { return rencoding[y]; }).replace(charegex,function(s) { return "_x" + ("000"+s.charCodeAt(0).toString(16)).substr(-4) + "_";}); } -function parsexmlbool(value, tag) { +function parsexmlbool(value) { switch(value) { case '1': case 'true': case 'TRUE': return true; /* case '0': case 'false': case 'FALSE':*/ @@ -147,6 +155,8 @@ function parse_isodur(s) { } return sec; } + +var XML_HEADER = '\r\n'; /* copied from js-xls (C) SheetJS Apache2 license */ function xlml_normalize(d) { if(has_buf && Buffer.isBuffer(d)) return d.toString('utf8'); @@ -157,14 +167,14 @@ function xlml_normalize(d) { var xlmlregex = /<(\/?)([a-z0-9]*:|)([\w-]+)[^>]*>/mg; /* Part 3 Section 4 Manifest File */ var CT_ODS = "application/vnd.oasis.opendocument.spreadsheet"; -var parse_manifest = function(d, opts) { +function parse_manifest(d, opts) { var str = xlml_normalize(d); var Rn; var FEtag; while((Rn = xlmlregex.exec(str))) switch(Rn[3]) { case 'manifest': break; // 4.2 case 'file-entry': // 4.3 - FEtag = parsexmltag(Rn[0]); + FEtag = parsexmltag(Rn[0], false); if(FEtag.path == '/' && FEtag.type !== CT_ODS) throw new Error("This OpenDocument is not a spreadsheet"); break; case 'encryption-data': // 4.4 @@ -172,11 +182,46 @@ var parse_manifest = function(d, opts) { case 'start-key-generation': // 4.6 case 'key-derivation': // 4.7 throw new Error("Unsupported ODS Encryption"); - default: throw Rn; + default: if(opts && opts.WTF) throw Rn; } -}; +} + +function write_manifest(manifest, opts) { + var o = [XML_HEADER]; + o.push('\n'); + o.push(' \n'); + for(var i = 0; i < manifest.length; ++i) o.push(' \n'); + o.push(''); + return o.join(""); +} +/* Part 3 Section 6 Metadata Manifest File */ +function write_rdf_type(file, res, tag) { + return [ + ' \n', + ' \n', + ' \n' + ].join(""); +} +function write_rdf_has(base, file) { + return [ + ' \n', + ' \n', + ' \n' + ].join(""); +} +function write_rdf(rdf, opts) { + var o = [XML_HEADER]; + o.push('\n'); + for(var i = 0; i != rdf.length; ++i) { + o.push(write_rdf_type(rdf[i][0], rdf[i][1])); + o.push(write_rdf_has("",rdf[i][0])); + } + o.push(write_rdf_type("","Document", "pkg")); + o.push(''); + return o.join(""); +} var parse_text_p = function(text, tag) { - return utf8read(text.replace(//g," ").replace(/<[^>]*>/g,"")); + return unescapexml(utf8read(text.replace(//g," ").replace(/<[^>]*>/g,""))); }; var utf8read = function utf8reada(orig) { @@ -213,27 +258,29 @@ var parse_content_xml = (function() { var str = xlml_normalize(d); var state = [], tmp; var tag; - var NFtag, NF, pidx; + var NFtag = {name:""}, NF = "", pidx = 0; var sheetag; var Sheets = {}, SheetNames = [], ws = {}; var Rn, q; - var ctag; - var textp, textpidx, textptag; - var R, C, range = {s: {r:1000000,c:10000000}, e: {r:0, c:0}}; + var ctag = {value:""}; + var textp = "", textpidx = 0, textptag; + var R = -1, C = -1, range = {s: {r:1000000,c:10000000}, e: {r:0, c:0}}; var number_format_map = {}; var merges = [], mrange = {}, mR = 0, mC = 0; - + var rept = 1; + xlmlregex.lastIndex = 0; while((Rn = xlmlregex.exec(str))) switch(Rn[3]) { case 'table': // 9.1.2 if(Rn[1]==='/') { if(range.e.c >= range.s.c && range.e.r >= range.s.r) ws['!ref'] = get_utils().encode_range(range); if(merges.length) ws['!merges'] = merges; + sheetag.name = utf8read(sheetag.name); SheetNames.push(sheetag.name); Sheets[sheetag.name] = ws; } else if(Rn[0].charAt(Rn[0].length-2) !== '/') { - sheetag = parsexmltag(Rn[0]); + sheetag = parsexmltag(Rn[0], false); R = C = -1; range.s.r = range.s.c = 10000000; range.e.r = range.e.c = 0; ws = {}; merges = []; @@ -247,17 +294,18 @@ var parse_content_xml = (function() { ++C; break; /* stub */ case 'table-cell': if(Rn[0].charAt(Rn[0].length-2) === '/') { - ctag = parsexmltag(Rn[0]); + ctag = parsexmltag(Rn[0], false); if(ctag['number-columns-repeated']) C+= parseInt(ctag['number-columns-repeated'], 10); else ++C; } else if(Rn[1]!=='/') { ++C; + rept = 1; if(C > range.e.c) range.e.c = C; if(R > range.e.r) range.e.r = R; if(C < range.s.c) range.s.c = C; if(R < range.s.r) range.s.r = R; - ctag = parsexmltag(Rn[0]); + ctag = parsexmltag(Rn[0], false); q = {t:ctag['value-type'], v:null}; if(ctag['number-columns-spanned'] || ctag['number-rows-spanned']) { mR = parseInt(ctag['number-rows-spanned'],10) || 0; @@ -265,6 +313,10 @@ var parse_content_xml = (function() { mrange = {s: {r:R,c:C}, e:{r:R + mR-1,c:C + mC-1}}; merges.push(mrange); } + + /* 19.675.2 table:number-columns-repeated */ + if(ctag['number-columns-repeated']) rept = parseInt(ctag['number-columns-repeated'], 10); + /* 19.385 office:value-type */ switch(q.t) { case 'boolean': q.t = 'b'; q.v = parsexmlbool(ctag['boolean-value']); break; @@ -273,14 +325,22 @@ var parse_content_xml = (function() { case 'currency': q.t = 'n'; q.v = parseFloat(ctag.value); break; case 'date': q.t = 'n'; q.v = datenum(ctag['date-value']); q.z = 'm/d/yy'; break; case 'time': q.t = 'n'; q.v = parse_isodur(ctag['time-value'])/86400; break; - case 'string': q.t = 's'; break; - default: throw new Error('Unsupported value type ' + q.t); + default: + if(q.t === 'string' || !q.t) { + q.t = 's'; + if(ctag['string-value'] != null) textp = ctag['string-value']; + } else throw new Error('Unsupported value type ' + q.t); } } else { - if(q.t === 's') q.v = textp; + if(q.t === 's') q.v = textp || ''; if(textp) q.w = textp; - if(!(opts.sheetRows && opts.sheetRows < R)) ws[get_utils().encode_cell({r:R,c:C})] = q; - q = null; + if(!(opts.sheetRows && opts.sheetRows < R)) { + ws[get_utils().encode_cell({r:R,c:C})] = q; + while(--rept > 0) ws[get_utils().encode_cell({r:R,c:++C})] = dup(q); + if(range.e.c <= C) range.e.c = C; + } + q = {}; + textp = ""; } break; // 9.1.4 @@ -296,6 +356,13 @@ var parse_content_xml = (function() { /* ignore state */ case 'shapes': // 9.2.8 case 'frame': // 10.4.2 + case 'text-box': // 10.4.3 + case 'image': // 10.4.4 + case 'data-pilot-tables': // 9.6.2 + case 'list-style': // 16.30 + case 'form': // 13.13 + case 'dde-links': // 9.8 + case 'annotation': // 14.1 if(Rn[1]==='/'){if((tmp=state.pop())[0]!==Rn[3]) throw "Bad state: "+tmp;} else if(Rn[0].charAt(Rn[0].length-2) !== '/') state.push([Rn[3], false]); break; @@ -309,7 +376,7 @@ var parse_content_xml = (function() { if((tmp=state.pop())[0]!==Rn[3]) throw "Bad state: "+tmp; } else if(Rn[0].charAt(Rn[0].length-2) !== '/') { NF = ""; - NFtag = parsexmltag(Rn[0]); + NFtag = parsexmltag(Rn[0], false); state.push([Rn[3], true]); } break; @@ -317,6 +384,7 @@ var parse_content_xml = (function() { case 'automatic-styles': break; // 3.15.3 case 'style': break; // 16.2 + case 'map': break; // 16.3 case 'font-face': break; // 16.21 case 'paragraph-properties': break; // 17.6 @@ -329,10 +397,12 @@ var parse_content_xml = (function() { switch(state[state.length-1][0]) { case 'time-style': case 'date-style': - tag = parsexmltag(Rn[0]); + tag = parsexmltag(Rn[0], false); NF += number_formats[Rn[3]][tag.style==='long'?1:0]; break; } break; + case 'fraction': break; // TODO 16.27.6 + case 'day': // 16.27.11 case 'month': // 16.27.12 case 'year': // 16.27.13 @@ -347,7 +417,7 @@ var parse_content_xml = (function() { switch(state[state.length-1][0]) { case 'time-style': case 'date-style': - tag = parsexmltag(Rn[0]); + tag = parsexmltag(Rn[0], false); NF += number_formats[Rn[3]][tag.style==='long'?1:0]; break; } break; @@ -373,30 +443,114 @@ var parse_content_xml = (function() { case 'forms': break; // 12.25.2 13.2 case 'table-column': break; // 9.1.6 - case 'graphic-properties': break; + case 'null-date': break; // 9.4.2 TODO: date1904 + + case 'graphic-properties': break; // 17.21 case 'calculation-settings': break; // 9.4.1 case 'named-expressions': break; // 9.4.11 - case 'named-range': break; // 9.4.11 + case 'named-range': break; // 9.4.12 + case 'named-expression': break; // 9.4.13 + case 'sort': break; // 9.4.19 + case 'sort-by': break; // 9.4.20 + case 'sort-groups': break; // 9.4.22 + case 'span': break; // + case 'line-break': break; // 6.1.5 case 'p': if(Rn[1]==='/') textp = parse_text_p(str.slice(textpidx,Rn.index), textptag); - else { textptag = parsexmltag(Rn[0]); textpidx = Rn.index + Rn[0].length; } + else { textptag = parsexmltag(Rn[0], false); textpidx = Rn.index + Rn[0].length; } break; // case 's': break; // case 'date': break; // <*:date> - case 'annotation': break; case 'object': break; // 10.4.6.2 case 'title': break; // <*:title> case 'desc': break; // <*:desc> + case 'table-source': break; // 9.2.6 + + case 'iteration': break; // 9.4.3 + case 'content-validations': break; // 9.4.4 case 'database-range': break; // 9.4.15 case 'filter': break; // 9.5.2 case 'filter-and': break; // 9.5.3 case 'filter-or': break; // 9.5.4 case 'filter-condition': break; // 9.5.5 - default: if(opts.WTF) throw Rn; + + case 'list-level-style-bullet': break; // 16.31 + case 'property': break; // 13.8 + + case 'a': break; // 6.1.8 hyperlink + + /* non-standard */ + case 'table-protection': break; + case 'data-pilot-grand-total': break; // \n'); + var R=0,C=0, range = get_utils().decode_range(ws['!ref']); + for(R = 0; R < range.s.r; ++R) o.push(' \n'); + for(; R <= range.e.r; ++R) { + o.push(' \n'); + for(C=0; C < range.s.c; ++C) o.push(null_cell_xml); + for(; C <= range.e.c; ++C) { + var ref = get_utils().encode_cell({r:R, c:C}), cell = ws[ref]; + if(cell) switch(cell.t) { + case 'b': o.push(' ' + (cell.v ? 'TRUE' : 'FALSE') + '\n'); break; + case 'n': o.push(' ' + (cell.w||cell.v) + '\n'); break; + case 's': case 'str': o.push(' ' + escapexml(cell.v) + '\n'); break; + //case 'd': // TODO + //case 'e': + default: o.push(null_cell_xml); + } else o.push(null_cell_xml); + } + o.push(' \n'); + } + o.push(' \n'); + return o.join(""); + }; + + return function wcx(wb, opts) { + var o = [XML_HEADER]; + /* 3.1.3.2 */ + o.push('\n'); // TODO + o.push(' \n'); + o.push(' \n'); + for(var i = 0; i != wb.SheetNames.length; ++i) o.push(write_ws(wb.Sheets[wb.SheetNames[i]], wb, i, opts)); + o.push(' \n'); + o.push(' \n'); + o.push(''); + return o.join(""); + }; +})(); /* Part 3: Packages */ -var parse_ods = function(zip, opts) { - //var manifest = parse_manifest(getzipdata(zip, 'META-INF/manifest.xml')); +function parse_ods(zip, opts) { + opts = opts || ({}); + var manifest = parse_manifest(getzipdata(zip, 'META-INF/manifest.xml'), opts); return parse_content_xml(getzipdata(zip, 'content.xml'), opts); -}; +} +function write_ods(wb, opts) { +var zip = new jszip(); + var f = ""; + + var manifest = []; + var rdf = []; + + /* 3:3.3 and 2:2.2.4 */ + f = "mimetype"; + zip.file(f, "application/vnd.oasis.opendocument.spreadsheet"); + + /* Part 2 Section 2.2 Documents */ + f = "content.xml"; + zip.file(f, write_content_xml(wb, opts)); + manifest.push([f, "text/xml"]); + rdf.push([f, "ContentFile"]); + + /* Part 3 Section 6 Metadata Manifest File */ + f = "manifest.rdf"; + zip.file(f, write_rdf(rdf, opts)); + manifest.push([f, "application/rdf+xml"]); + + /* Part 3 Section 4 Manifest File */ + f = "META-INF/manifest.xml"; + zip.file(f, write_manifest(manifest, opts)); + + return zip; +} ODS.parse_ods = parse_ods; +ODS.write_ods = write_ods; })(typeof exports !== 'undefined' ? exports : ODS); diff --git a/ods.js b/ods.js index 30e9f56..ea1abc5 100644 --- a/ods.js +++ b/ods.js @@ -21,6 +21,14 @@ function cc2str(arr) { for(var i = 0; i != arr.length; ++i) o += String.fromCharCode(arr[i]); return o; } + +function dup(o) { + if(typeof JSON != 'undefined') return JSON.parse(JSON.stringify(o)); + if(typeof o != 'object' || !o) return o; + var out = {}; + for(var k in o) if(o.hasOwnProperty(k)) out[k] = dup(o[k]); + return out; +} function getdata(data) { if(!data) return null; if(data.data) return data.data; @@ -67,7 +75,7 @@ function parsexmltag(tag, skip_root) { for(; eq !== tag.length; ++eq) if((c = tag.charCodeAt(eq)) === 32 || c === 10 || c === 13) break; if(!skip_root) z[0] = tag.substr(0, eq); if(eq === tag.length) return z; - var m = tag.match(attregexg), j=0, w="", v="", i=0, q="", cc=""; + var m = tag.match(attregexg), j=0, v="", i=0, q="", cc=""; if(m) for(i = 0; i != m.length; ++i) { cc = m[i]; for(c=0; c != cc.length; ++c) if(cc.charCodeAt(c) === 61) break; @@ -108,7 +116,7 @@ function escapexml(text){ return s.replace(decregex, function(y) { return rencoding[y]; }).replace(charegex,function(s) { return "_x" + ("000"+s.charCodeAt(0).toString(16)).substr(-4) + "_";}); } -function parsexmlbool(value, tag) { +function parsexmlbool(value) { switch(value) { case '1': case 'true': case 'TRUE': return true; /* case '0': case 'false': case 'FALSE':*/ @@ -147,6 +155,8 @@ function parse_isodur(s) { } return sec; } + +var XML_HEADER = '\r\n'; /* copied from js-xls (C) SheetJS Apache2 license */ function xlml_normalize(d) { if(has_buf && Buffer.isBuffer(d)) return d.toString('utf8'); @@ -157,14 +167,14 @@ function xlml_normalize(d) { var xlmlregex = /<(\/?)([a-z0-9]*:|)([\w-]+)[^>]*>/mg; /* Part 3 Section 4 Manifest File */ var CT_ODS = "application/vnd.oasis.opendocument.spreadsheet"; -var parse_manifest = function(d, opts) { +function parse_manifest(d, opts) { var str = xlml_normalize(d); var Rn; var FEtag; while((Rn = xlmlregex.exec(str))) switch(Rn[3]) { case 'manifest': break; // 4.2 case 'file-entry': // 4.3 - FEtag = parsexmltag(Rn[0]); + FEtag = parsexmltag(Rn[0], false); if(FEtag.path == '/' && FEtag.type !== CT_ODS) throw new Error("This OpenDocument is not a spreadsheet"); break; case 'encryption-data': // 4.4 @@ -172,11 +182,46 @@ var parse_manifest = function(d, opts) { case 'start-key-generation': // 4.6 case 'key-derivation': // 4.7 throw new Error("Unsupported ODS Encryption"); - default: throw Rn; + default: if(opts && opts.WTF) throw Rn; } -}; +} + +function write_manifest(manifest, opts) { + var o = [XML_HEADER]; + o.push('\n'); + o.push(' \n'); + for(var i = 0; i < manifest.length; ++i) o.push(' \n'); + o.push(''); + return o.join(""); +} +/* Part 3 Section 6 Metadata Manifest File */ +function write_rdf_type(file, res, tag) { + return [ + ' \n', + ' \n', + ' \n' + ].join(""); +} +function write_rdf_has(base, file) { + return [ + ' \n', + ' \n', + ' \n' + ].join(""); +} +function write_rdf(rdf, opts) { + var o = [XML_HEADER]; + o.push('\n'); + for(var i = 0; i != rdf.length; ++i) { + o.push(write_rdf_type(rdf[i][0], rdf[i][1])); + o.push(write_rdf_has("",rdf[i][0])); + } + o.push(write_rdf_type("","Document", "pkg")); + o.push(''); + return o.join(""); +} var parse_text_p = function(text, tag) { - return utf8read(text.replace(//g," ").replace(/<[^>]*>/g,"")); + return unescapexml(utf8read(text.replace(//g," ").replace(/<[^>]*>/g,""))); }; var utf8read = function utf8reada(orig) { @@ -213,27 +258,29 @@ var parse_content_xml = (function() { var str = xlml_normalize(d); var state = [], tmp; var tag; - var NFtag, NF, pidx; + var NFtag = {name:""}, NF = "", pidx = 0; var sheetag; var Sheets = {}, SheetNames = [], ws = {}; var Rn, q; - var ctag; - var textp, textpidx, textptag; - var R, C, range = {s: {r:1000000,c:10000000}, e: {r:0, c:0}}; + var ctag = {value:""}; + var textp = "", textpidx = 0, textptag; + var R = -1, C = -1, range = {s: {r:1000000,c:10000000}, e: {r:0, c:0}}; var number_format_map = {}; var merges = [], mrange = {}, mR = 0, mC = 0; - + var rept = 1; + xlmlregex.lastIndex = 0; while((Rn = xlmlregex.exec(str))) switch(Rn[3]) { case 'table': // 9.1.2 if(Rn[1]==='/') { if(range.e.c >= range.s.c && range.e.r >= range.s.r) ws['!ref'] = get_utils().encode_range(range); if(merges.length) ws['!merges'] = merges; + sheetag.name = utf8read(sheetag.name); SheetNames.push(sheetag.name); Sheets[sheetag.name] = ws; } else if(Rn[0].charAt(Rn[0].length-2) !== '/') { - sheetag = parsexmltag(Rn[0]); + sheetag = parsexmltag(Rn[0], false); R = C = -1; range.s.r = range.s.c = 10000000; range.e.r = range.e.c = 0; ws = {}; merges = []; @@ -247,17 +294,18 @@ var parse_content_xml = (function() { ++C; break; /* stub */ case 'table-cell': if(Rn[0].charAt(Rn[0].length-2) === '/') { - ctag = parsexmltag(Rn[0]); + ctag = parsexmltag(Rn[0], false); if(ctag['number-columns-repeated']) C+= parseInt(ctag['number-columns-repeated'], 10); else ++C; } else if(Rn[1]!=='/') { ++C; + rept = 1; if(C > range.e.c) range.e.c = C; if(R > range.e.r) range.e.r = R; if(C < range.s.c) range.s.c = C; if(R < range.s.r) range.s.r = R; - ctag = parsexmltag(Rn[0]); + ctag = parsexmltag(Rn[0], false); q = {t:ctag['value-type'], v:null}; if(ctag['number-columns-spanned'] || ctag['number-rows-spanned']) { mR = parseInt(ctag['number-rows-spanned'],10) || 0; @@ -265,6 +313,10 @@ var parse_content_xml = (function() { mrange = {s: {r:R,c:C}, e:{r:R + mR-1,c:C + mC-1}}; merges.push(mrange); } + + /* 19.675.2 table:number-columns-repeated */ + if(ctag['number-columns-repeated']) rept = parseInt(ctag['number-columns-repeated'], 10); + /* 19.385 office:value-type */ switch(q.t) { case 'boolean': q.t = 'b'; q.v = parsexmlbool(ctag['boolean-value']); break; @@ -273,14 +325,22 @@ var parse_content_xml = (function() { case 'currency': q.t = 'n'; q.v = parseFloat(ctag.value); break; case 'date': q.t = 'n'; q.v = datenum(ctag['date-value']); q.z = 'm/d/yy'; break; case 'time': q.t = 'n'; q.v = parse_isodur(ctag['time-value'])/86400; break; - case 'string': q.t = 's'; break; - default: throw new Error('Unsupported value type ' + q.t); + default: + if(q.t === 'string' || !q.t) { + q.t = 's'; + if(ctag['string-value'] != null) textp = ctag['string-value']; + } else throw new Error('Unsupported value type ' + q.t); } } else { - if(q.t === 's') q.v = textp; + if(q.t === 's') q.v = textp || ''; if(textp) q.w = textp; - if(!(opts.sheetRows && opts.sheetRows < R)) ws[get_utils().encode_cell({r:R,c:C})] = q; - q = null; + if(!(opts.sheetRows && opts.sheetRows < R)) { + ws[get_utils().encode_cell({r:R,c:C})] = q; + while(--rept > 0) ws[get_utils().encode_cell({r:R,c:++C})] = dup(q); + if(range.e.c <= C) range.e.c = C; + } + q = {}; + textp = ""; } break; // 9.1.4 @@ -296,6 +356,13 @@ var parse_content_xml = (function() { /* ignore state */ case 'shapes': // 9.2.8 case 'frame': // 10.4.2 + case 'text-box': // 10.4.3 + case 'image': // 10.4.4 + case 'data-pilot-tables': // 9.6.2 + case 'list-style': // 16.30 + case 'form': // 13.13 + case 'dde-links': // 9.8 + case 'annotation': // 14.1 if(Rn[1]==='/'){if((tmp=state.pop())[0]!==Rn[3]) throw "Bad state: "+tmp;} else if(Rn[0].charAt(Rn[0].length-2) !== '/') state.push([Rn[3], false]); break; @@ -309,7 +376,7 @@ var parse_content_xml = (function() { if((tmp=state.pop())[0]!==Rn[3]) throw "Bad state: "+tmp; } else if(Rn[0].charAt(Rn[0].length-2) !== '/') { NF = ""; - NFtag = parsexmltag(Rn[0]); + NFtag = parsexmltag(Rn[0], false); state.push([Rn[3], true]); } break; @@ -317,6 +384,7 @@ var parse_content_xml = (function() { case 'automatic-styles': break; // 3.15.3 case 'style': break; // 16.2 + case 'map': break; // 16.3 case 'font-face': break; // 16.21 case 'paragraph-properties': break; // 17.6 @@ -329,10 +397,12 @@ var parse_content_xml = (function() { switch(state[state.length-1][0]) { case 'time-style': case 'date-style': - tag = parsexmltag(Rn[0]); + tag = parsexmltag(Rn[0], false); NF += number_formats[Rn[3]][tag.style==='long'?1:0]; break; } break; + case 'fraction': break; // TODO 16.27.6 + case 'day': // 16.27.11 case 'month': // 16.27.12 case 'year': // 16.27.13 @@ -347,7 +417,7 @@ var parse_content_xml = (function() { switch(state[state.length-1][0]) { case 'time-style': case 'date-style': - tag = parsexmltag(Rn[0]); + tag = parsexmltag(Rn[0], false); NF += number_formats[Rn[3]][tag.style==='long'?1:0]; break; } break; @@ -373,30 +443,114 @@ var parse_content_xml = (function() { case 'forms': break; // 12.25.2 13.2 case 'table-column': break; // 9.1.6 - case 'graphic-properties': break; + case 'null-date': break; // 9.4.2 TODO: date1904 + + case 'graphic-properties': break; // 17.21 case 'calculation-settings': break; // 9.4.1 case 'named-expressions': break; // 9.4.11 - case 'named-range': break; // 9.4.11 + case 'named-range': break; // 9.4.12 + case 'named-expression': break; // 9.4.13 + case 'sort': break; // 9.4.19 + case 'sort-by': break; // 9.4.20 + case 'sort-groups': break; // 9.4.22 + case 'span': break; // + case 'line-break': break; // 6.1.5 case 'p': if(Rn[1]==='/') textp = parse_text_p(str.slice(textpidx,Rn.index), textptag); - else { textptag = parsexmltag(Rn[0]); textpidx = Rn.index + Rn[0].length; } + else { textptag = parsexmltag(Rn[0], false); textpidx = Rn.index + Rn[0].length; } break; // case 's': break; // case 'date': break; // <*:date> - case 'annotation': break; case 'object': break; // 10.4.6.2 case 'title': break; // <*:title> case 'desc': break; // <*:desc> + case 'table-source': break; // 9.2.6 + + case 'iteration': break; // 9.4.3 + case 'content-validations': break; // 9.4.4 case 'database-range': break; // 9.4.15 case 'filter': break; // 9.5.2 case 'filter-and': break; // 9.5.3 case 'filter-or': break; // 9.5.4 case 'filter-condition': break; // 9.5.5 - default: if(opts.WTF) throw Rn; + + case 'list-level-style-bullet': break; // 16.31 + case 'property': break; // 13.8 + + case 'a': break; // 6.1.8 hyperlink + + /* non-standard */ + case 'table-protection': break; + case 'data-pilot-grand-total': break; // \n'); + var R=0,C=0, range = get_utils().decode_range(ws['!ref']); + for(R = 0; R < range.s.r; ++R) o.push(' \n'); + for(; R <= range.e.r; ++R) { + o.push(' \n'); + for(C=0; C < range.s.c; ++C) o.push(null_cell_xml); + for(; C <= range.e.c; ++C) { + var ref = get_utils().encode_cell({r:R, c:C}), cell = ws[ref]; + if(cell) switch(cell.t) { + case 'b': o.push(' ' + (cell.v ? 'TRUE' : 'FALSE') + '\n'); break; + case 'n': o.push(' ' + (cell.w||cell.v) + '\n'); break; + case 's': case 'str': o.push(' ' + escapexml(cell.v) + '\n'); break; + //case 'd': // TODO + //case 'e': + default: o.push(null_cell_xml); + } else o.push(null_cell_xml); + } + o.push(' \n'); + } + o.push(' \n'); + return o.join(""); + }; + + return function wcx(wb, opts) { + var o = [XML_HEADER]; + /* 3.1.3.2 */ + o.push('\n'); // TODO + o.push(' \n'); + o.push(' \n'); + for(var i = 0; i != wb.SheetNames.length; ++i) o.push(write_ws(wb.Sheets[wb.SheetNames[i]], wb, i, opts)); + o.push(' \n'); + o.push(' \n'); + o.push(''); + return o.join(""); + }; +})(); /* Part 3: Packages */ -var parse_ods = function(zip, opts) { - //var manifest = parse_manifest(getzipdata(zip, 'META-INF/manifest.xml')); +function parse_ods(zip, opts) { + opts = opts || ({}); + var manifest = parse_manifest(getzipdata(zip, 'META-INF/manifest.xml'), opts); return parse_content_xml(getzipdata(zip, 'content.xml'), opts); -}; +} +function write_ods(wb, opts) { +var zip = new jszip(); + var f = ""; + + var manifest = []; + var rdf = []; + + /* 3:3.3 and 2:2.2.4 */ + f = "mimetype"; + zip.file(f, "application/vnd.oasis.opendocument.spreadsheet"); + + /* Part 2 Section 2.2 Documents */ + f = "content.xml"; + zip.file(f, write_content_xml(wb, opts)); + manifest.push([f, "text/xml"]); + rdf.push([f, "ContentFile"]); + + /* Part 3 Section 6 Metadata Manifest File */ + f = "manifest.rdf"; + zip.file(f, write_rdf(rdf, opts)); + manifest.push([f, "application/rdf+xml"]); + + /* Part 3 Section 4 Manifest File */ + f = "META-INF/manifest.xml"; + zip.file(f, write_manifest(manifest, opts)); + + return zip; +} ODS.parse_ods = parse_ods; +ODS.write_ods = write_ods; })(typeof exports !== 'undefined' ? exports : ODS); diff --git a/xlsx.js b/xlsx.js index 04b0bc9..03ddef9 100644 --- a/xlsx.js +++ b/xlsx.js @@ -4,7 +4,7 @@ /*jshint funcscope:true, eqnull:true */ var XLSX = {}; (function make_xlsx(XLSX){ -XLSX.version = '0.8.1'; +XLSX.version = '0.8.2'; var current_codepage = 1200, current_cptable; if(typeof module !== "undefined" && typeof require !== 'undefined') { if(typeof cptable === 'undefined') cptable = require('./dist/cpexcel'); @@ -1645,7 +1645,8 @@ function WriteShift(t, val, f) { size = 2 * val.length; } else switch(t) { case 1: size = 1; this[this.l] = val&255; break; - case 3: size = 3; this[this.l+2] = val & 255; val >>>= 8; this[this.l+1] = val&255; val >>>= 8; this[this.l] = val&255; break; + case 2: size = 2; this[this.l] = val&255; val >>>= 8; this[this.l+1] = val&255; break; + case 3: size = 3; this[this.l] = val&255; val >>>= 8; this[this.l+1] = val&255; val >>>= 8; this[this.l+2] = val&255; break; case 4: size = 4; this.writeUInt32LE(val, this.l); break; case 8: size = 8; if(f === 'f') { this.writeDoubleLE(val, this.l); break; } /* falls through */ @@ -1706,7 +1707,7 @@ function buf_array() { var curbuf = newblk(blksz); var endbuf = function ba_endbuf() { - curbuf.length = curbuf.l; + if(curbuf.length > curbuf.l) curbuf = curbuf.slice(0, curbuf.l); if(curbuf.length > 0) bufs.push(curbuf); curbuf = null; }; @@ -1893,6 +1894,15 @@ function parse_RkNumber(data) { var RK = fInt === 0 ? __double([0,0,0,0,b[0],b[1],b[2],b[3]],0) : __readInt32LE(b,0)>>2; return fX100 ? RK/100 : RK; } +function write_RkNumber(data, o) { + if(o == null) o = new_buf(4); + var fX100 = 0, fInt = 0, d100 = data * 100; + if(data == (data | 0) && data >= -(1<<29) && data < (1 << 29)) { fInt = 1; } + else if(d100 == (d100 | 0) && d100 >= -(1<<29) && d100 < (1 << 29)) { fInt = 1; fX100 = 1; } + if(fInt) o.write_shift(-4, ((fX100 ? d100 : data) << 2) + (fX100 + 2)); + else throw new Error("unsupported RkNumber " + data); // TODO +} + /* [MS-XLSB] 2.5.153 */ function parse_UncheckedRfX(data) { @@ -1915,8 +1925,9 @@ function write_UncheckedRfX(r, o) { /* [MS-XLSB] 2.5.171 */ /* [MS-XLS] 2.5.342 */ +/* TODO: error checking, NaN and Infinity values are not valid Xnum */ function parse_Xnum(data, length) { return data.read_shift(8, 'f'); } -function write_Xnum(data, o) { return (o || new_buf(8)).write_shift(8, 'f', data); } +function write_Xnum(data, o) { return (o || new_buf(8)).write_shift(8, data, 'f'); } /* [MS-XLSB] 2.5.198.2 */ var BErr = { @@ -2730,7 +2741,7 @@ function parse_cust_props(data, opts) { p[name] = unescapexml(text); break; default: - if(typeof console !== 'undefined') console.warn('Unexpected', x, type, toks); + if(opts.WTF && typeof console !== 'undefined') console.warn('Unexpected', x, type, toks); } } else if(x.substr(0,2) === "")!==-1) { var merges = data.match(mergecregex); - for(ridx = 0; ridx != merges.length; ++ridx) + if(merges) for(ridx = 0; ridx != merges.length; ++ridx) mergecells[ridx] = safe_decode_range(merges[ridx].substr(merges[ridx].indexOf("\"")+1)); } @@ -7298,7 +7309,7 @@ function parse_ws_xml(data, opts, rels) { parse_ws_xml_cols(columns, cols); } - var refguess = {s: {r:1000000, c:1000000}, e: {r:0, c:0} }; + var refguess = {s: {r:2000000, c:2000000}, e: {r:0, c:0} }; /* 18.3.1.80 sheetData CT_SheetData ? */ var mtch=data.match(sheetdataregex); @@ -7573,6 +7584,45 @@ function parse_BrtRowHdr(data, length) { data.l += length-4; return z; } +function write_BrtRowHdr(R, range, ws) { + var o = new_buf(17+8*16); + o.write_shift(4, R); + + /* TODO: flags styles */ + o.write_shift(4, 0); + o.write_shift(2, 0x0140); + o.write_shift(2, 0); + o.write_shift(1, 0); + + /* [MS-XLSB] 2.5.8 BrtColSpan explains the mechanism */ + var ncolspan = 0, lcs = o.l; + o.l += 4; + + var caddr = {r:R, c:0}; + for(var i = 0; i < 16; ++i) { + if(range.s.c > ((i+1) << 10) || range.e.c < (i << 10)) continue; + var first = -1, last = -1; + for(var j = (i<<10); j < ((i+1)<<10); ++j) { + caddr.c = j; + if(ws[encode_cell(caddr)]) { if(first < 0) first = j; last = j; } + } + if(first < 0) continue; + ++ncolspan; + o.write_shift(4, first); + o.write_shift(4, last); + } + + var l = o.l; + o.l = lcs; + o.write_shift(4, ncolspan); + o.l = l; + + return o.length > o.l ? o.slice(0, o.l) : o; +} +function write_row_header(ba, ws, range, R) { + var o = write_BrtRowHdr(R, range, ws); + if(o.length > 17) write_record(ba, 'BrtRowHdr', o); +} /* [MS-XLSB] 2.4.812 BrtWsDim */ var parse_BrtWsDim = parse_UncheckedRfX; @@ -7592,9 +7642,9 @@ function parse_BrtCellBlank(data, length) { var cell = parse_XLSBCell(data); return [cell]; } -function write_BrtCellBlank(cell, val, o) { +function write_BrtCellBlank(cell, ncell, o) { if(o == null) o = new_buf(8); - return write_XLSBCell(val, o); + return write_XLSBCell(ncell, o); } @@ -7604,12 +7654,18 @@ function parse_BrtCellBool(data, length) { var fBool = data.read_shift(1); return [cell, fBool, 'b']; } +function write_BrtCellBool(cell, ncell, o) { + if(o == null) o = new_buf(9); + write_XLSBCell(ncell, o); + o.write_shift(1, cell.v ? 1 : 0); + return o; +} /* [MS-XLSB] 2.4.305 BrtCellError */ function parse_BrtCellError(data, length) { var cell = parse_XLSBCell(data); - var fBool = data.read_shift(1); - return [cell, fBool, 'e']; + var bError = data.read_shift(1); + return [cell, bError, 'e']; } /* [MS-XLSB] 2.4.308 BrtCellIsst */ @@ -7618,6 +7674,12 @@ function parse_BrtCellIsst(data, length) { var isst = data.read_shift(4); return [cell, isst, 's']; } +function write_BrtCellIsst(cell, ncell, o) { + if(o == null) o = new_buf(12); + write_XLSBCell(ncell, o); + o.write_shift(4, ncell.v); + return o; +} /* [MS-XLSB] 2.4.310 BrtCellReal */ function parse_BrtCellReal(data, length) { @@ -7625,6 +7687,12 @@ function parse_BrtCellReal(data, length) { var value = parse_Xnum(data); return [cell, value, 'n']; } +function write_BrtCellReal(cell, ncell, o) { + if(o == null) o = new_buf(16); + write_XLSBCell(ncell, o); + write_Xnum(cell.v, o); + return o; +} /* [MS-XLSB] 2.4.311 BrtCellRk */ function parse_BrtCellRk(data, length) { @@ -7632,6 +7700,13 @@ function parse_BrtCellRk(data, length) { var value = parse_RkNumber(data); return [cell, value, 'n']; } +function write_BrtCellRk(cell, ncell, o) { + if(o == null) o = new_buf(12); + write_XLSBCell(ncell, o); + write_RkNumber(cell.v, o); + return o; +} + /* [MS-XLSB] 2.4.314 BrtCellSt */ function parse_BrtCellSt(data, length) { @@ -7639,6 +7714,12 @@ function parse_BrtCellSt(data, length) { var value = parse_XLWideString(data); return [cell, value, 'str']; } +function write_BrtCellSt(cell, ncell, o) { + if(o == null) o = new_buf(12 + 4 * cell.v.length); + write_XLSBCell(ncell, o); + write_XLWideString(cell.v, o); + return o.length > o.l ? o.slice(0, o.l) : o; +} /* [MS-XLSB] 2.4.647 BrtFmlaBool */ function parse_BrtFmlaBool(data, length, opts) { @@ -7714,12 +7795,13 @@ function parse_ws_bin(data, opts, rels) { var s = {}; var ref; - var refguess = {s: {r:1000000, c:1000000}, e: {r:0, c:0} }; + var refguess = {s: {r:2000000, c:2000000}, e: {r:0, c:0} }; var pass = false, end = false; var row, p, cf, R, C, addr, sstr, rr; var mergecells = []; recordhopper(data, function ws_parse(val, R) { + //console.log(R); if(end) return; switch(R.n) { case 'BrtWsDim': ref = val; break; @@ -7896,7 +7978,7 @@ function parse_ws_bin(data, opts, rels) { default: if(!pass || opts.WTF) throw new Error("Unexpected record " + R.n); } }, opts); - if(!s["!ref"] && (refguess.s.r < 1000000 || ref.e.r > 0 || ref.e.c > 0 || ref.s.r > 0 || ref.s.c > 0)) s["!ref"] = encode_range(ref); + if(!s["!ref"] && (refguess.s.r < 2000000 || ref.e.r > 0 || ref.e.c > 0 || ref.s.r > 0 || ref.s.c > 0)) s["!ref"] = encode_range(ref); if(opts.sheetRows && s["!ref"]) { var tmpref = safe_decode_range(s["!ref"]); if(opts.sheetRows < +tmpref.e.r) { @@ -7929,12 +8011,23 @@ function write_ws_bin_cell(ba, cell, R, C, opts) { case 's': case 'str': if(opts.bookSST) { vv = get_sst_id(opts.Strings, cell.v); - o.t = "s"; break; + o.t = "s"; o.v = vv; + write_record(ba, "BrtCellIsst", write_BrtCellIsst(cell, o)); + } else { + o.t = "str"; + write_record(ba, "BrtCellSt", write_BrtCellSt(cell, o)); } - o.t = "str"; break; - case 'n': break; - case 'b': o.t = "b"; break; - case 'e': o.t = "e"; break; + return; + case 'n': + /* TODO: determine threshold for Real vs RK */ + if(cell.v == (cell.v | 0) && cell.v > -1000 && cell.v < 1000) write_record(ba, "BrtCellRk", write_BrtCellRk(cell, o)); + else write_record(ba, "BrtCellReal", write_BrtCellReal(cell, o)); + return; + case 'b': + o.t = "b"; + write_record(ba, "BrtCellBool", write_BrtCellBool(cell, o)); + return; + case 'e': /* TODO: error */ o.t = "e"; break; } write_record(ba, "BrtCellBlank", write_BrtCellBlank(cell, o)); } @@ -7946,6 +8039,7 @@ function write_CELLTABLE(ba, ws, idx, opts, wb) { rr = encode_row(R); /* [ACCELLTABLE] */ /* BrtRowHdr */ + write_row_header(ba, ws, range, R); for(var C = range.s.c; C <= range.e.c; ++C) { /* *16384CELL */ if(R === range.s.r) cols[C] = encode_col(C); @@ -8258,7 +8352,7 @@ function write_BrtBundleSh(data, o) { o.write_shift(4, data.iTabID); write_RelID(data.strRelID, o); write_XLWideString(data.name.substr(0,31), o); - return o; + return o.length > o.l ? o.slice(0, o.l) : o; } /* [MS-XLSB] 2.4.807 BrtWbProp */ @@ -8366,7 +8460,7 @@ function write_BrtFileVersion(data, o) { write_XLWideString(XLSX.version, o); write_XLWideString("7262", o); o.length = o.l; - return o; + return o.length > o.l ? o.slice(0, o.l) : o; } /* [MS-XLSB] 2.1.7.60 Workbook */ @@ -8389,6 +8483,7 @@ function write_BrtCalcProp(data, o) { return o; } +/* [MS-XLSB] 2.4.640 BrtFileRecover */ function write_BrtFileRecover(data, o) { if(!o) o = new_buf(1); o.write_shift(1,0); @@ -8401,22 +8496,22 @@ function write_wb_bin(wb, opts) { write_record(ba, "BrtBeginBook"); write_record(ba, "BrtFileVersion", write_BrtFileVersion()); /* [[BrtFileSharingIso] BrtFileSharing] */ - write_record(ba, "BrtWbProp", write_BrtWbProp()); + if(0) write_record(ba, "BrtWbProp", write_BrtWbProp()); /* [ACABSPATH] */ /* [[BrtBookProtectionIso] BrtBookProtection] */ - write_BOOKVIEWS(ba, wb, opts); + if(0) write_BOOKVIEWS(ba, wb, opts); write_BUNDLESHS(ba, wb, opts); /* [FNGROUP] */ /* [EXTERNALS] */ /* *BrtName */ - write_record(ba, "BrtCalcProp", write_BrtCalcProp()); + if(0) write_record(ba, "BrtCalcProp", write_BrtCalcProp()); /* [BrtOleSize] */ /* *(BrtUserBookView *FRT) */ /* [PIVOTCACHEIDS] */ /* [BrtWbFactoid] */ /* [SMARTTAGTYPES] */ /* [BrtWebOpt] */ - write_record(ba, "BrtFileRecover", write_BrtFileRecover()); + if(0) write_record(ba, "BrtFileRecover", write_BrtFileRecover()); /* [WEBPUBITEMS] */ /* [CRERRS] */ /* FRTWORKBOOK */ @@ -8625,7 +8720,7 @@ function parse_xlml_xml(d, opts) { var sheets = {}, sheetnames = [], cursheet = {}, sheetname = ""; var table = {}, cell = {}, row = {}, dtag, didx; var c = 0, r = 0; - var refguess = {s: {r:1000000, c:1000000}, e: {r:0, c:0} }; + var refguess = {s: {r:2000000, c:2000000}, e: {r:0, c:0} }; var styles = {}, stag = {}; var ss = "", fidx = 0; var mergecells = []; @@ -8685,7 +8780,7 @@ function parse_xlml_xml(d, opts) { if(mergecells.length) cursheet["!merges"] = mergecells; sheets[sheetname] = cursheet; } else { - refguess = {s: {r:1000000, c:1000000}, e: {r:0, c:0} }; + refguess = {s: {r:2000000, c:2000000}, e: {r:0, c:0} }; r = c = 0; state.push([Rn[3], false]); tmp = xlml_parsexmltag(Rn[0]); @@ -11080,12 +11175,17 @@ var XLSRecordEnum = { }; -/* Helper function to call out to ODS parser */ +/* Helper functions to call out to ODS */ function parse_ods(zip, opts) { if(typeof module !== "undefined" && typeof require !== 'undefined' && typeof ODS === 'undefined') ODS = require('./od' + 's'); if(typeof ODS === 'undefined' || !ODS.parse_ods) throw new Error("Unsupported ODS"); return ODS.parse_ods(zip, opts); } +function write_ods(wb, opts) { + if(typeof module !== "undefined" && typeof require !== 'undefined' && typeof ODS === 'undefined') ODS = require('./od' + 's'); + if(typeof ODS === 'undefined' || !ODS.write_ods) throw new Error("Unsupported ODS"); + return ODS.write_ods(wb, opts); +} function fix_opts_func(defaults) { return function fix_opts(opts) { for(var i = 0; i != defaults.length; ++i) { @@ -11124,6 +11224,8 @@ var fix_write_opts = fix_opts_func([ ['bookType', 'xlsx'], /* Type of workbook (xlsx/m/b) */ + ['compression', false], /* Use file compression */ + ['WTF', false] /* WTF mode (throws errors) */ ]); function safe_parse_wbrels(wbrels, sheets) { @@ -11278,6 +11380,7 @@ function add_rels(rels, rId, f, type, relobj) { } function write_zip(wb, opts) { + if(opts.bookType == "ods") return write_ods(wb, opts); if(wb && !wb.SSF) { wb.SSF = SSF.get_table(); } @@ -11405,13 +11508,17 @@ function readFileSync(data, opts) { function write_zip_type(wb, opts) { var o = opts||{}; var z = write_zip(wb, o); + var oopts = {}; + if(opts.compression) oopts.compression = 'DEFLATE'; switch(o.type) { - case "base64": return z.generate({type:"base64"}); - case "binary": return z.generate({type:"string"}); - case "buffer": return z.generate({type:"nodebuffer"}); - case "file": return _fs.writeFileSync(o.file, z.generate({type:"nodebuffer"})); + case "base64": oopts.type = "base64"; break; + case "binary": oopts.type = "string"; break; + case "buffer": + case "file": oopts.type = "nodebuffer"; break; default: throw new Error("Unrecognized type " + o.type); } + if(o.type === "file") return _fs.writeFileSync(o.file, z.generate(oopts)); + return z.generate(oopts); } function writeSync(wb, opts) { @@ -11425,13 +11532,14 @@ function writeSync(wb, opts) { function writeFileSync(wb, filename, opts) { var o = opts||{}; o.type = 'file'; o.file = filename; - switch(o.file.substr(-5).toLowerCase()) { + if(!o.bookType) switch(o.file.substr(-5).toLowerCase()) { case '.xlsx': o.bookType = 'xlsx'; break; case '.xlsm': o.bookType = 'xlsm'; break; case '.xlsb': o.bookType = 'xlsb'; break; default: switch(o.file.substr(-4).toLowerCase()) { case '.xls': o.bookType = 'xls'; break; case '.xml': o.bookType = 'xml'; break; + case '.ods': o.bookType = 'ods'; break; }} return writeSync(wb, o); }