/* ods.js (C) 2014-present SheetJS -- http://sheetjs.com */ /* vim: set ts=2: */ /*jshint -W041 */ var ODS = {}; (function make_ods(ODS) { /* Open Document Format for Office Applications (OpenDocument) Version 1.2 */ var get_utils = function() { if(typeof XLSX !== 'undefined') return XLSX.utils; if(typeof module !== "undefined" && typeof require !== 'undefined') try { return require('../xlsx.js').utils; } catch(e) { return require('./xlsx.js').utils; } throw new Error("Cannot find XLSX utils"); }; var has_buf = (typeof Buffer !== 'undefined'); function cc2str(arr) { var o = ""; 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 getdatastr(data) { if(!data) return null; if(data.data) return data.data; if(data.asNodeBuffer && has_buf) return data.asNodeBuffer().toString('binary'); if(data.asBinary) return data.asBinary(); if(data._data && data._data.getContent) return cc2str(Array.prototype.slice.call(data._data.getContent(),0)); return null; } /* ODS and friends only use text files in container */ function getdata(data) { return getdatastr(data); } /* NOTE: unlike ECMA-376, OASIS does not comment on filename case sensitivity */ function safegetzipfile(zip, file) { var f = file; if(zip.files[f]) return zip.files[f]; f = file.toLowerCase(); if(zip.files[f]) return zip.files[f]; f = f.replace(/\//g,'\\'); if(zip.files[f]) return zip.files[f]; return null; } function getzipfile(zip, file) { var o = safegetzipfile(zip, file); if(o == null) throw new Error("Cannot find file " + file + " in zip"); return o; } function getzipdata(zip, file, safe) { if(!safe) return getdata(getzipfile(zip, file)); if(!file) return null; try { return getzipdata(zip, file); } catch(e) { return null; } } var _fs, jszip; if(typeof JSZip !== 'undefined') jszip = JSZip; if (typeof exports !== 'undefined') { if (typeof module !== 'undefined' && module.exports) { if(typeof jszip === 'undefined') jszip = require('./jszip.js'); _fs = require('fs'); } } var attregexg=/[^\s?>\/]+=["'][^"]*['"]/g; var tagregex=/<[^>]*>/g; var nsregex=/<\w*:/, nsregex2 = /<(\/?)\w+:/; function parsexmltag(tag, skip_root) { var z = []; var eq = 0, c = 0; 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, 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; q = cc.substr(0,c); v = cc.substring(c+2, cc.length-1); for(j=0;j!=q.length;++j) if(q.charCodeAt(j) === 58) break; if(j===q.length) { if(q.indexOf("_") > 0) q = q.substr(0, q.indexOf("_")); z[q] = v; } else { var k = (j===5 && q.substr(0,5)==="xmlns"?"xmlns":"")+q.substr(j+1); if(z[k] && q.substr(j-3,3) == "ext") continue; z[k] = v; } } return z; } function strip_ns(x) { return x.replace(nsregex2, "<$1"); } var encodings = { '"': '"', ''': "'", '>': '>', '<': '<', '&': '&' }; var rencoding = { '"': '"', "'": ''', '>': '>', '<': '<', '&': '&' }; var rencstr = "&<>'\"".split(""); // TODO: CP remap (need to read file version to determine OS) /* 22.4.2.4 bstr (Basic String) */ var encregex = /&[a-z]*;/g, coderegex = /_x([\da-fA-F]{4})_/g; function unescapexml(text){ var s = text + ''; return s.replace(encregex, function($$) { return encodings[$$]; }).replace(coderegex,function(m,c) {return String.fromCharCode(parseInt(c,16));}); } var decregex=/[&<>'"]/g, charegex = /[\u0000-\u0008\u000b-\u001f]/g; 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)).substr(-4) + "_";}); } function parsexmlbool(value) { switch(value) { case '1': case 'true': case 'TRUE': return true; /* case '0': case 'false': case 'FALSE':*/ default: return false; } } function datenum(v) { var epoch = Date.parse(v); return (epoch + 2209161600000) / (24 * 60 * 60 * 1000); } /* ISO 8601 Duration */ function parse_isodur(s) { var sec = 0, mt = 0, time = false; var m = s.match(/P([0-9\.]+Y)?([0-9\.]+M)?([0-9\.]+D)?T([0-9\.]+H)?([0-9\.]+M)?([0-9\.]+S)?/); if(!m) throw new Error("|" + s + "| is not an ISO8601 Duration"); for(var i = 1; i != m.length; ++i) { if(!m[i]) continue; mt = 1; if(i > 3) time = true; switch(m[i].substr(m[i].length-1)) { case 'Y': throw new Error("Unsupported ISO Duration Field: " + m[i].substr(m[i].length-1)); case 'D': mt *= 24; /* falls through */ case 'H': mt *= 60; /* falls through */ case 'M': if(!time) throw new Error("Unsupported ISO Duration Field: M"); else mt *= 60; /* falls through */ case 'S': break; } sec += mt * parseInt(m[i], 10); } 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'); if(typeof d === 'string') return d; throw "badf"; } /* UOS uses CJK in tags, original regex /<(\/?)([a-z0-9]*:|)([\w-]+)[^>]*>/ */ var xlmlregex = /<(\/?)([^\s?>\/:]*:|)([^\s?>]*[^\s?>\/])[^>]*>/mg; /* Part 3 Section 4 Manifest File */ var CT_ODS = "application/vnd.oasis.opendocument.spreadsheet"; 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], false); if(FEtag.path == '/' && FEtag.type !== CT_ODS) throw new Error("This OpenDocument is not a spreadsheet"); break; case 'encryption-data': // 4.4 case 'algorithm': // 4.5 case 'start-key-generation': // 4.6 case 'key-derivation': // 4.7 throw new Error("Unsupported ODS Encryption"); 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 unescapexml(text.replace(//g," ").replace(/<[^>]*>/g,"")); }; var utf8read = function utf8reada(orig) { var out = "", i = 0, c = 0, d = 0, e = 0, f = 0, w = 0; while (i < orig.length) { c = orig.charCodeAt(i++); if (c < 128) { out += String.fromCharCode(c); continue; } d = orig.charCodeAt(i++); if (c>191 && c<224) { out += String.fromCharCode(((c & 31) << 6) | (d & 63)); continue; } e = orig.charCodeAt(i++); if (c < 240) { out += String.fromCharCode(((c & 15) << 12) | ((d & 63) << 6) | (e & 63)); continue; } f = orig.charCodeAt(i++); w = (((c & 7) << 18) | ((d & 63) << 12) | ((e & 63) << 6) | (f & 63))-65536; out += String.fromCharCode(0xD800 + ((w>>>10)&1023)); out += String.fromCharCode(0xDC00 + (w&1023)); } return out; }; /* Part 3 TODO: actually parse formulae */ function ods_to_csf_formula(f) { if(f.substr(0,3) == "of:") f = f.substr(3); /* 5.2 Basic Expressions */ if(f.charCodeAt(0) == 61) { f = f.substr(1); if(f.charCodeAt(0) == 61) f = f.substr(1); } /* Part 3 Section 5.8 References */ return f.replace(/\[((?:\.[A-Z]+[0-9]+)(?::\.[A-Z]+[0-9]+)?)\]/g, "$1").replace(/\./g, ""); } var parse_content_xml = (function() { var number_formats = { /* ods name: [short ssf fmt, long ssf fmt] */ day: ["d", "dd"], month: ["m", "mm"], year: ["y", "yy"], hours: ["h", "hh"], minutes: ["m", "mm"], seconds: ["s", "ss"], "am-pm": ["A/P", "AM/PM"], "day-of-week": ["ddd", "dddd"] }; return function pcx(d, opts) { var str = xlml_normalize(d); var state = [], tmp; var tag; var NFtag = {name:""}, NF = "", pidx = 0; var sheetag; var rowtag; var Sheets = {}, SheetNames = [], ws = {}; var Rn, q; 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 arrayf = []; var rept = 1, isstub = false; var i = 0; xlmlregex.lastIndex = 0; while((Rn = xlmlregex.exec(str))) switch((Rn[3]=Rn[3].replace(/_.*$/,""))) { case 'table': case '工作表': // 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['名称'] || sheetag.name); SheetNames.push(sheetag.name); Sheets[sheetag.name] = ws; } else if(Rn[0].charAt(Rn[0].length-2) !== '/') { sheetag = parsexmltag(Rn[0], false); R = C = -1; range.s.r = range.s.c = 10000000; range.e.r = range.e.c = 0; ws = {}; merges = []; } break; case 'table-row': case '行': // 9.1.3 if(Rn[1] === '/') break; rowtag = parsexmltag(Rn[0], false); if(rowtag['行号']) R = rowtag['行号'] - 1; else ++R; C = -1; break; case 'covered-table-cell': // 9.1.5 table:covered-table-cell ++C; break; /* stub */ case 'table-cell': case '数据': if(Rn[0].charAt(Rn[0].length-2) === '/') { 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], false); q = ({t:ctag['数据类型'] || ctag['value-type'], v:null}); if(opts.cellFormula) { if(ctag['number-matrix-columns-spanned'] && ctag['number-matrix-rows-spanned']) { mR = parseInt(ctag['number-matrix-rows-spanned'],10) || 0; mC = parseInt(ctag['number-matrix-columns-spanned'],10) || 0; mrange = {s: {r:R,c:C}, e:{r:R + mR-1,c:C + mC-1}}; q.F = get_utils().encode_range(mrange); arrayf.push([mrange, q.F]); } if(ctag.formula) q.f = ods_to_csf_formula(ctag.formula); else for(i = 0; i < arrayf.length; ++i) if(R >= arrayf[i][0].s.r && R <= arrayf[i][0].e.r) if(C >= arrayf[i][0].s.c && C <= arrayf[i][0].e.c) q.F = arrayf[i][1]; } if(ctag['number-columns-spanned'] || ctag['number-rows-spanned']) { mR = parseInt(ctag['number-rows-spanned'],10) || 0; mC = parseInt(ctag['number-columns-spanned'],10) || 0; 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; case 'float': q.t = 'n'; q.v = parseFloat(ctag.value); break; case 'percentage': q.t = 'n'; q.v = parseFloat(ctag.value); break; 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 'number': q.t = 'n'; q.v = parseFloat(ctag['数据数值']); break; default: if(q.t === 'string' || q.t === 'text' || !q.t) { q.t = 's'; if(ctag['string-value'] != null) textp = ctag['string-value']; } else throw new Error('Unsupported value type ' + q.t); } } else { isstub = false; if(q.t === 's') { q.v = textp || ''; isstub = textpidx == 0; } if(textp) q.w = textp; if(!isstub || opts.cellStubs) { 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; } } else { C += rept; rept = 0; } q = {}; textp = ""; } break; // 9.1.4 /* pure state */ case 'document': // TODO: is the root for FODS case 'document-content': case '电子表格文档': // 3.1.3.2 case 'spreadsheet': case '主体': // 3.7 case 'scripts': // 3.12 case 'styles': // TODO case 'font-face-decls': // 3.14 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], true]); break; /* ignore state */ case 'meta': case '元数据': // TODO: FODS/UOF case 'settings': // TODO: case 'config-item-set': // TODO: case 'config-item-map-indexed': // TODO: case 'config-item-map-entry': // TODO: case 'config-item-map-named': // TODO: 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 case 'event-listeners': // TODO 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]); textp = ""; textpidx = 0; break; case 'scientific-number': // TODO: break; case 'currency-symbol': // TODO: break; case 'currency-style': // TODO: break; case 'number-style': // 16.27.2 case 'percentage-style': // 16.27.9 case 'date-style': // 16.27.10 case 'time-style': // 16.27.18 if(Rn[1]==='/'){ number_format_map[NFtag.name] = NF; 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], false); state.push([Rn[3], true]); } break; case 'script': break; // 3.13 case 'libraries': break; // TODO: case 'automatic-styles': break; // 3.15.3 case 'master-styles': break; // TODO: case 'default-style': // TODO: case 'page-layout': break; // TODO: case 'style': break; // 16.2 case 'map': break; // 16.3 case 'font-face': break; // 16.21 case 'paragraph-properties': break; // 17.6 case 'table-properties': break; // 17.15 case 'table-column-properties': break; // 17.16 case 'table-row-properties': break; // 17.17 case 'table-cell-properties': break; // 17.18 case 'number': // 16.27.3 switch(state[state.length-1][0]) { case 'time-style': case 'date-style': 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 case 'era': // 16.27.14 case 'day-of-week': // 16.27.15 case 'week-of-year': // 16.27.16 case 'quarter': // 16.27.17 case 'hours': // 16.27.19 case 'minutes': // 16.27.20 case 'seconds': // 16.27.21 case 'am-pm': // 16.27.22 switch(state[state.length-1][0]) { case 'time-style': case 'date-style': tag = parsexmltag(Rn[0], false); NF += number_formats[Rn[3]][tag.style==='long'?1:0]; break; } break; case 'boolean-style': break; // 16.27.23 case 'boolean': break; // 16.27.24 case 'text-style': break; // 16.27.25 case 'text': // 16.27.26 if(Rn[0].slice(-2) === "/>") break; else if(Rn[1]==="/") switch(state[state.length-1][0]) { case 'number-style': case 'date-style': case 'time-style': NF += str.slice(pidx, Rn.index); break; } else pidx = Rn.index + Rn[0].length; break; case 'text-content': break; // 16.27.27 case 'text-properties': break; // 16.27.27 case 'body': case '电子表格': break; // 3.3 16.9.6 19.726.3 case 'forms': break; // 12.25.2 13.2 case 'table-column': break; // 9.1.6 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.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': case '文本串': if(Rn[1]==='/') textp = parse_text_p(str.slice(textpidx,Rn.index), textptag); else { textptag = parsexmltag(Rn[0], false); textpidx = Rn.index + Rn[0].length; } break; // case 's': break; // case 'date': break; // <*:date> case 'object': break; // 10.4.6.2 case 'title': case '标题': break; // <*:title> OR 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 case 'list-level-style-bullet': break; // 16.31 case 'page-count': break; // TODO case 'time': break; // TODO /* 9.6 Data Pilot Tables 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 */ if(opts.bookType == "fods") o.push(''); else 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'); if(opts.bookType == "fods") o.push(''); else o.push(''); return o.join(""); }; })(); /* Part 3: Packages */ function parse_ods(zip, opts) { opts = opts || ({}); var ods = !!safegetzipfile(zip, 'objectdata'); if(ods) var manifest = parse_manifest(getzipdata(zip, 'META-INF/manifest.xml'), opts); var content = getzipdata(zip, 'content.xml'); return parse_content_xml(ods ? content : utf8read(content), opts); } function parse_fods(data, opts) { return parse_content_xml(data, opts); } function write_ods(wb, opts) { if(opts.bookType == "fods") return write_content_xml(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 1 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; ODS.parse_fods = parse_fods; })(typeof exports !== 'undefined' ? exports : ODS);