version bump 0.7.0: Basic write support

- very basic XLSX / XLSM write support with roundtrip tests (XLSB stubs)
- reorganized source tree
- new XLSB range check ensures that A1 is not emitted for empty sheets
- SSF table emitted in output (consistent with js-xls)
- CLI supports writing

Backwards-incompatible changes:
o new Property aliases (see CORE_PROPS and EXT_PROPS)
o FILETIME custom properties parsed as JS Dates
o `xlsx2csv` -> `xlsx` (and `bin/xlsx{2csv,}.njs`)
This commit is contained in:
SheetJS 2014-05-15 17:33:34 -07:00
parent b645f6ef98
commit d15b81e0e9
47 changed files with 2974 additions and 1063 deletions

1
.gitignore vendored

@ -1,2 +1,3 @@
node_modules
misc/coverage.html
tmp

@ -5,6 +5,7 @@ node_js:
before_install:
- "npm install -g mocha"
- "npm install blanket"
- "npm install xlsjs"
- "npm install coveralls mocha-lcov-reporter"
before_script:
- "make init"

@ -5,7 +5,7 @@ order to maintain that, every contributor must be vigilant.
There have been many projects in the past that have been very lax regarding
licensing, and we are of the opinion that those are ticking timebombs and that
no corporate product should depend on them.
no corporate product should depend on them.
# Required Reading
@ -23,7 +23,7 @@ Before thinking about contributing, make sure that:
- You are not, nor have ever been, an employee of Microsoft Corporation.
- You have not signed any NDAs or Shared Source Agreements with Microsoft
- You have not signed any NDAs or Shared Source Agreements with Microsoft
Corporation or a subsidiary
- You have not consulted any existing relevant codebase (if you have, please
@ -42,11 +42,11 @@ Keep these in mind as you work:
consult in the process (and be extra careful not to use unlicensed code on
the internet.
- You are working on your own time. Unless they explicitly grant permission,
- You are working on your own time. Unless they explicitly grant permission,
your employer may be the ultimate owner of your IP
# Post-Contribution
# Post-Contribution
Before contributions are merged, you will receive an email (at the address
associated with the git commit) and will be asked to confirm the aforementioned

@ -1,4 +1,4 @@
Copyright (C) 2012-2014 SheetJS
Copyright (C) 2012-2014 SheetJS
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.

@ -1,7 +1,7 @@
LIB=xlsx
DEPS=$(wildcard bits/*.js)
TARGET=$(LIB).js
FMT=xlsx xlsm xlsb misc
FMT=xlsx xlsm xlsb misc full
REQS=jszip.js
ADDONS=dist/cpexcel.js
@ -25,6 +25,7 @@ init:
.PHONY: test mocha
test mocha: test.js
mkdir -p tmp
mocha -R spec
TESTFMT=$(patsubst %,test_%,$(FMT))
@ -42,6 +43,11 @@ cov: misc/coverage.html
cov-spin:
make cov & bash misc/spin.sh $$!
COVFMT=$(patsubst %,cov_%,$(FMT))
.PHONY: $(COVFMT)
$(COVFMT): cov_%:
FMTS=$* make cov
misc/coverage.html: $(TARGET) test.js
mocha --require blanket -R html-cov > $@

@ -1,6 +1,6 @@
# xlsx
Currently a parser for XLSX/XLSM/XLSB files. Cleanroom implementation from the
Parser and writer for XLSX/XLSM/XLSB files. Cleanroom implementation from the
ISO 29500 Office Open XML specifications, [MS-XLSB], and related documents.
## Installation
@ -45,7 +45,7 @@ The complete single-file version is generated at `dist/xlsx.full.min.js`
Simple usage (walks through every cell of every sheet and dumps the values):
var XLSX = require('xlsx');
if(typeof require !== 'undefined') XLSX = require('xlsx');
var workbook = XLSX.readFile('test.xlsx');
var sheet_name_list = workbook.SheetNames;
sheet_name_list.forEach(function(y) {
@ -56,9 +56,9 @@ Simple usage (walks through every cell of every sheet and dumps the values):
}
});
The node version installs a binary `xlsx2csv` which can read XLSX/XLSM/XLSB
The node version installs a binary `xlsx` which can read XLSX/XLSM/XLSB
files and output the contents in various formats. The source is available at
`xlsx2csv.njs` in the bin directory.
`xlsx.njs` in the bin directory.
See <http://oss.sheetjs.com/js-xlsx/> for a browser example.
@ -76,12 +76,27 @@ Some helper functions in `XLSX.utils` generate different views of the sheets:
For more details:
- `bin/xlsx2csv.njs` is a tool for node
- `bin/xlsx.njs` is a tool for node
- `index.html` is the live demo
- `bits/90_utils.js` contains the logic for generating CSV and JSON from sheets
## Interface
`XLSX` is the exposed variable in the browser and the exported variable in node
`XLSX.read(data, read_opts)` attempts to parse `data`.
`XLSX.readFile(filename, read_opts)` attempts to read `filename` and parse.
`XLSX.write(wb, write_opts)` attempts to write the workbook `wb`
`XLSX.writeFile(wb, filename, write_opts)` attempts to write `wb` to `filename`
## Cell Object Description
js-xlsx conforms to the Common Spreadsheet Format (CSF):
`.SheetNames` is an ordered list of the sheets in the workbook
`.Sheets[sheetname]` returns a data structure representing the sheet. Each key
@ -102,7 +117,7 @@ that does not start with `!` corresponds to a cell (using `A-1` notation).
For dates, `.v` holds the raw date code from the sheet and `.w` holds the text
## Options
## Parsing Options
The exported `read` and `readFile` functions accept an options argument:
@ -133,6 +148,21 @@ The exported `read` and `readFile` functions accept an options argument:
The defaults are enumerated in bits/84_defaults.js
## Writing Options
The exported `write` and `writeFile` functions accept an options argument:
| Option Name | Default | Description |
| :---------- | ------: | :---------- |
| bookSST | false | Generate Shared String Table ** |
| bookType | 'xlsx' | Type of Workbook ("xlsx" or "xlsm" or "xlsb") |
- `bookSST` is slower and more memory intensive, but has better compatibility
with iOS Numbers
- `bookType = 'xlsb'` is stubbed and far from complete
- The raw data is the only thing guaranteed to be saved. Formulae, formatting,
and other niceties are not serialized (pending CSF standardization)
## Tested Environments
- Node 0.8, 0.10 (latest release)
@ -165,6 +195,8 @@ $ simplehttpserver # or "python -mSimpleHTTPServer" or "serve"
$ open -a Chromium.app http://localhost:8000/stress.html
```
For a much smaller test, run `make test_misc`.
## Contributing
Due to the precarious nature of the Open Specifications Promise, it is very

@ -10,9 +10,13 @@ program
.option('-f, --file <file>', 'use specified workbook')
.option('-s, --sheet <sheet>', 'print specified sheet (default first sheet)')
.option('-l, --list-sheets', 'list sheet names and exit')
.option('-o, --output <file>', 'output to specified file')
/*.option('-B, --xlsb', 'emit XLSB to <sheetname> or <file>.xlsb') */
.option('-M, --xlsm', 'emit XLSM to <sheetname> or <file>.xlsm')
.option('-X, --xlsx', 'emit XLSX to <sheetname> or <file>.xlsx')
.option('-S, --formulae', 'print formulae')
.option('-j, --json', 'emit formatted JSON rather than CSV (all fields text)')
.option('-J, --raw-js', 'emit raw JS object rather than CSV (raw numbers)')
.option('-j, --json', 'emit formatted JSON (all fields text)')
.option('-J, --raw-js', 'emit raw JS object (raw numbers)')
.option('-F, --field-sep <sep>', 'CSV field separator', ",")
.option('-R, --row-sep <sep>', 'CSV row separator', "\n")
.option('-n, --sheet-rows <num>', 'Number of rows to process (0=all rows)')
@ -21,6 +25,7 @@ program
.option('-q, --quiet', 'quiet mode');
program.on('--help', function() {
console.log(' Default output format is CSV');
console.log(' Support email: dev@sheetjs.com');
console.log(' Web Demo: http://oss.sheetjs.com/js-'+n+'/');
});
@ -36,19 +41,22 @@ if(program.sheet) sheetname = program.sheet;
if(program.file) filename = program.file;
if(!filename) {
console.error(n + "2csv: must specify a filename");
console.error(n + ": must specify a filename");
process.exit(1);
}
if(!fs.existsSync(filename)) {
console.error(n + "2csv: " + filename + ": No such file or directory");
console.error(n + ": " + filename + ": No such file or directory");
process.exit(2);
}
var opts = {}, wb;
if(program.listSheets) opts.bookSheets = true;
if(program.sheetRows) opts.sheetRows = program.sheetRows;
if(program.xlsx || program.xlsm || program.xlsb) {
opts.cellNF = true;
if(program.output) sheetname = program.output;
}
if(program.dev) {
X.verbose = 2;
opts.WTF = true;
@ -57,7 +65,7 @@ if(program.dev) {
else try {
wb = X.readFile(filename, opts);
} catch(e) {
var msg = (program.quiet) ? "" : n + "2csv: error parsing ";
var msg = (program.quiet) ? "" : n + ": error parsing ";
msg += filename + ": " + e;
console.error(msg);
process.exit(3);
@ -69,6 +77,12 @@ if(program.listSheets) {
process.exit(0);
}
var wopts = {WTF:opts.WTF};
if(program.xlsx) return X.writeFile(wb, sheetname || (filename + ".xlsx"), wopts);
if(program.xlsm) return X.writeFile(wb, sheetname || (filename + ".xlsm"), wopts);
if(program.xlsb) return X.writeFile(wb, sheetname || (filename + ".xlsb"), wopts);
var target_sheet = sheetname || '';
if(target_sheet === '') target_sheet = wb.SheetNames[0];
@ -77,12 +91,15 @@ try {
ws = wb.Sheets[target_sheet];
if(!ws) throw "Sheet " + target_sheet + " cannot be found";
} catch(e) {
console.error(n + "2csv: error parsing "+filename+" "+target_sheet+": " + e);
console.error(n + ": error parsing "+filename+" "+target_sheet+": " + e);
process.exit(4);
}
if(!program.quiet) console.error(target_sheet);
if(program.formulae) console.log(X.utils.get_formulae(ws).join("\n"));
else if(program.json) console.log(JSON.stringify(X.utils.sheet_to_row_object_array(ws)));
else if(program.rawJs) console.log(JSON.stringify(X.utils.sheet_to_row_object_array(ws,{raw:true})));
else console.log(X.utils.make_csv(ws, {FS:program.fieldSep, RS:program.rowSep}));
var oo = "";
if(program.formulae) oo = X.utils.get_formulae(ws).join("\n");
else if(program.json) oo = JSON.stringify(X.utils.sheet_to_row_object_array(ws));
else if(program.rawJs) oo = JSON.stringify(X.utils.sheet_to_row_object_array(ws,{raw:true}));
else oo = X.utils.make_csv(ws, {FS:program.fieldSep, RS:program.rowSep});
if(program.output) fs.writeFileSync(program.output, oo);

@ -1 +1 @@
XLSX.version = '0.6.2';
XLSX.version = '0.7.0';

13
bits/30_jsutils.js Normal file

@ -0,0 +1,13 @@
function isval(x) { return typeof x !== "undefined" && x !== null; }
function keys(o) { return Object.keys(o).filter(function(x) { return o.hasOwnProperty(x); }); }
function evert(obj, arr) {
var o = {};
keys(obj).forEach(function(k) {
if(!obj.hasOwnProperty(k)) return;
if(!arr) o[obj[k]] = k;
else (o[obj[k]]=o[obj[k]]||[]).push(k);
});
return o;
}

@ -1,4 +1,5 @@
var _chr = function(c) { return String.fromCharCode(c); };
var _ord = function(c) { return c.charCodeAt(0); };
var attregexg=/([\w:]+)=((?:")([^"]*)(?:")|(?:')([^']*)(?:'))/g;
var attregex=/([\w:]+)=((?:")(?:[^"]*)(?:")|(?:')(?:[^']*)(?:'))/;
function parsexmltag(tag) {
@ -13,12 +14,6 @@ function parsexmltag(tag) {
return z;
}
function evert(obj) {
var o = {};
Object.keys(obj).forEach(function(k) { if(obj.hasOwnProperty(k)) o[obj[k]] = k; });
return o;
}
var encodings = {
'&quot;': '"',
'&apos;': "'",
@ -38,6 +33,7 @@ function unescapexml(text){
function escapexml(text){
var s = text + '';
rencstr.forEach(function(y){s=s.replace(new RegExp(y,'g'), rencoding[y]);});
s = s.replace(/[\u0000-\u0007]/g,function(s) { return "_x" + ("0000"+_ord(s).toString(16)).substr(-4) + "_";}); /* TODO: verify range */
return s;
}
@ -83,4 +79,37 @@ function parseVector(data) {
return res;
}
function isval(x) { return typeof x !== "undefined" && x !== null; }
function writetag(f,g) {return '<' + f + (g.match(/(^\s|\s$|\n)/)?' xml:space="preserve"' : "") + '>' + g + '</' + f + '>';}
/*jshint -W041 */
function writextag(f,g,h) { return '<' + f + (h != null ? keys(h).map(function(k) { return " " + k + '="' + h[k] + '"';}).join("") : "") + (g == null ? "/" : (g.match(/(^\s|\s$|\n)/)?' xml:space="preserve"' : "") + '>' + g + '</' + f) + '>';}
function write_w3cdtf(d, t) { try { return d.toISOString().replace(/\.\d*/,""); } catch(e) { if(t) throw e; } }
function write_vt(s) {
if(typeof s == 'string') return writextag('vt:lpwstr', s);
if(typeof s == 'number') return writextag((s|0)==s?'vt:i4':'vt:r8', String(s));
if(typeof s == 'boolean') return writextag('vt:bool', s?'true':'false');
if(s instanceof Date) return writextag('vt:filetime', write_w3cdtf(s));
throw new Error("Unable to serialize " + s);
}
var XML_HEADER = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\r\n';
var XMLNS = {
'dc': 'http://purl.org/dc/elements/1.1/',
'dcterms': 'http://purl.org/dc/terms/',
'dcmitype': 'http://purl.org/dc/dcmitype/',
'mx': 'http://schemas.microsoft.com/office/mac/excel/2008/main',
'r': 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',
'sjs': 'http://schemas.openxmlformats.org/package/2006/sheetjs/core-properties',
'vt': 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes',
'xsi': 'http://www.w3.org/2001/XMLSchema-instance',
'xsd': 'http://www.w3.org/2001/XMLSchema'
};
XMLNS.main = [
'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
'http://purl.oclc.org/ooxml/spreadsheetml/main',
'http://schemas.microsoft.com/office/excel/2006/main',
'http://schemas.microsoft.com/office/excel/2006/2'
];

@ -13,3 +13,8 @@ var recordhopper = function(data, cb, opts) {
if(cb(d, R, RT)) return;
}
};
/* control buffer usage for fixed-length buffers */
var blobhopper = function() {
var bufs = [];
};

@ -5,15 +5,9 @@
/* [MS-XLSB] 2.1.7 Part Enumeration */
var ct2type = {
/* Workbook */
"application/vnd.ms-excel.main": "workbooks",
"application/vnd.ms-excel.sheet.macroEnabled.main+xml": "workbooks",
"application/vnd.ms-excel.sheet.binary.macroEnabled.main": "workbooks",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml": "workbooks",
"application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml": "TODO", /* Template */
/* Worksheet */
"application/vnd.ms-excel.worksheet": "sheets",
"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml": "sheets",
"application/vnd.ms-excel.binIndexWs": "TODO", /* Binary Index */
/* Chartsheet */
@ -30,14 +24,6 @@ var ct2type = {
"application/vnd.ms-excel.intlmacrosheet": "TODO",
"application/vnd.ms-excel.binIndexMs": "TODO", /* Binary Index */
/* Shared Strings */
"application/vnd.ms-excel.sharedStrings": "strs",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml": "strs",
/* Styles */
"application/vnd.ms-excel.styles": "styles",
"application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml": "styles",
/* File Properties */
"application/vnd.openxmlformats-package.core-properties+xml": "coreprops",
"application/vnd.openxmlformats-officedocument.custom-properties+xml": "custprops",
@ -151,99 +137,48 @@ var ct2type = {
/* VML */
"application/vnd.openxmlformats-officedocument.vmlDrawing": "TODO",
"application/vnd.openxmlformats-package.relationships+xml": "TODO",
"application/vnd.openxmlformats-package.relationships+xml": "rels",
"application/vnd.openxmlformats-officedocument.oleObject": "TODO",
"foo": "bar"
"sheet": "js"
};
var XMLNS_CT = 'http://schemas.openxmlformats.org/package/2006/content-types';
function parseProps(data) {
var p = { Company:'' }, q = {};
var strings = ["Application", "DocSecurity", "Company", "AppVersion"];
var bools = ["HyperlinksChanged","SharedDoc","LinksUpToDate","ScaleCrop"];
var xtra = ["HeadingPairs", "TitlesOfParts"];
var xtracp = ["category", "contentStatus", "lastModifiedBy", "lastPrinted", "revision", "version"];
var xtradc = ["creator", "description", "identifier", "language", "subject", "title"];
var xtradcterms = ["created", "modified"];
xtra = xtra.concat(xtracp.map(function(x) { return "cp:" + x; }));
xtra = xtra.concat(xtradc.map(function(x) { return "dc:" + x; }));
xtra = xtra.concat(xtradcterms.map(function(x) { return "dcterms:" + x; }));
strings.forEach(function(f){p[f] = (data.match(matchtag(f))||[])[1];});
bools.forEach(function(f){p[f] = (data.match(matchtag(f))||[])[1] == "true";});
xtra.forEach(function(f) {
var cur = data.match(new RegExp("<" + f + "[^>]*>(.*)<\/" + f + ">"));
if(cur && cur.length > 0) q[f] = cur[1];
});
if(q.HeadingPairs && q.TitlesOfParts) {
var v = parseVector(q.HeadingPairs);
var j = 0, widx = 0;
for(var i = 0; i !== v.length; ++i) {
switch(v[i].v) {
case "Worksheets": widx = j; p.Worksheets = +(v[++i].v); break;
case "Named Ranges": ++i; break; // TODO: Handle Named Ranges
}
var CT_LIST = (function(){
var o = {
workbooks: {
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
xlsm: "application/vnd.ms-excel.sheet.macroEnabled.main+xml",
xlsb: "application/vnd.ms-excel.sheet.binary.macroEnabled.main",
xltx: "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml"
},
strs: { /* Shared Strings */
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml",
xlsb: "application/vnd.ms-excel.sharedStrings"
},
sheets: {
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
xlsb: "application/vnd.ms-excel.worksheet"
},
styles: {/* Styles */
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml",
xlsb: "application/vnd.ms-excel.styles"
}
var parts = parseVector(q.TitlesOfParts).map(function(x) { return utf8read(x.v); });
p.SheetNames = parts.slice(widx, widx + p.Worksheets);
}
p.Creator = q["dc:creator"];
p.LastModifiedBy = q["cp:lastModifiedBy"];
p.CreatedDate = new Date(q["dcterms:created"]);
p.ModifiedDate = new Date(q["dcterms:modified"]);
return p;
}
};
keys(o).forEach(function(k) { if(!o[k].xlsm) o[k].xlsm = o[k].xlsx; });
keys(o).forEach(function(k){ keys(o[k]).forEach(function(v) { ct2type[o[k][v]] = k; }); });
return o;
})();
/* 15.2.12.2 Custom File Properties Part */
function parseCustomProps(data) {
var p = {}, name;
data.match(/<[^>]+>([^<]*)/g).forEach(function(x) {
var y = parsexmltag(x);
switch(y[0]) {
case '<property': name = y.name; break;
case '</property>': name = null; break;
default: if (x.indexOf('<vt:') === 0) {
var toks = x.split('>');
var type = toks[0].substring(4), text = toks[1];
/* 22.4.2.32 (CT_Variant). Omit the binary types from 22.4 (Variant Types) */
switch(type) {
case 'lpstr': case 'lpwstr': case 'bstr': case 'lpwstr':
p[name] = unescapexml(text);
break;
case 'bool':
p[name] = parsexmlbool(text, '<vt:bool>');
break;
case 'i1': case 'i2': case 'i4': case 'i8': case 'int': case 'uint':
p[name] = parseInt(text, 10);
break;
case 'r4': case 'r8': case 'decimal':
p[name] = parseFloat(text);
break;
case 'filetime': case 'date':
p[name] = text; // should we make this into a date?
break;
case 'cy': case 'error':
p[name] = unescapexml(text);
break;
default:
console.warn('Unexpected', x, type, toks);
}
}
}
});
return p;
}
var type2ct = evert(ct2type, true);
var ctext = {};
function parseCT(data, opts) {
XMLNS.CT = 'http://schemas.openxmlformats.org/package/2006/content-types';
function parse_ct(data, opts) {
var ctext = {};
if(!data || !data.match) return data;
var ct = { workbooks: [], sheets: [], calcchains: [], themes: [], styles: [],
coreprops: [], extprops: [], custprops: [], strs:[], comments: [], vba: [],
TODO:[], xmlns: "" };
TODO:[], rels:[], xmlns: "" };
(data.match(/<[^>]*>/g)||[]).forEach(function(x) {
var y = parsexmltag(x);
switch(y[0]) {
@ -252,11 +187,11 @@ function parseCT(data, opts) {
case '<Default': ctext[y.Extension] = y.ContentType; break;
case '<Override':
if(y.ContentType in ct2type)ct[ct2type[y.ContentType]].push(y.PartName);
else if(opts.WTF) console.error(y.ContentType);
else if(opts.WTF) console.error(y);
break;
}
});
if(ct.xmlns !== XMLNS_CT) throw new Error("Unknown Namespace: " + ct.xmlns);
if(ct.xmlns !== XMLNS.CT) throw new Error("Unknown Namespace: " + ct.xmlns);
ct.calcchain = ct.calcchains.length > 0 ? ct.calcchains[0] : "";
ct.sst = ct.strs.length > 0 ? ct.strs[0] : "";
ct.style = ct.styles.length > 0 ? ct.styles[0] : "";
@ -265,44 +200,54 @@ function parseCT(data, opts) {
return ct;
}
var CTYPE_XML_ROOT = writextag('Types', null, {
'xmlns': XMLNS.CT,
'xmlns:xsd': XMLNS.xsd,
'xmlns:xsi': XMLNS.xsi
});
var CTYPE_DEFAULTS = [
['xml', 'application/xml'],
['rels', type2ct.rels[0]]
].map(function(x) {
return writextag('Default', null, {'Extension':x[0], 'ContentType': x[1]});
});
/* 9.3.2 OPC Relationships Markup */
function parseRels(data, currentFilePath) {
if (!data) return data;
if (currentFilePath.charAt(0) !== '/') {
currentFilePath = '/'+currentFilePath;
}
var rels = {};
var hash = {};
var resolveRelativePathIntoAbsolute = function (to) {
var toksFrom = currentFilePath.split('/');
toksFrom.pop(); // folder path
var toksTo = to.split('/');
var reversed = [];
while (toksTo.length !== 0) {
var tokTo = toksTo.shift();
if (tokTo === '..') {
toksFrom.pop();
} else if (tokTo !== '.') {
toksFrom.push(tokTo);
}
function write_ct(ct, opts) {
var o = [], v;
o.push(XML_HEADER);
o.push(CTYPE_XML_ROOT);
o = o.concat(CTYPE_DEFAULTS);
var f1 = function(w) {
if(ct[w] && ct[w].length > 0) {
v = ct[w][0];
o.push(writextag('Override', null, {
'PartName': (v[0] == '/' ? "":"/") + v,
'ContentType': CT_LIST[w][opts.bookType || 'xlsx']
}));
}
return toksFrom.join('/');
};
data.match(/<[^>]*>/g).forEach(function(x) {
var y = parsexmltag(x);
/* 9.3.2.2 OPC_Relationships */
if (y[0] === '<Relationship') {
var rel = {}; rel.Type = y.Type; rel.Target = y.Target; rel.Id = y.Id; rel.TargetMode = y.TargetMode;
var canonictarget = y.TargetMode === 'External' ? y.Target : resolveRelativePathIntoAbsolute(y.Target);
rels[canonictarget] = rel;
hash[y.Id] = rel;
}
});
rels["!id"] = hash;
return rels;
var f2 = function(w) {
ct[w].forEach(function(v) {
o.push(writextag('Override', null, {
'PartName': (v[0] == '/' ? "":"/") + v,
'ContentType': CT_LIST[w][opts.bookType || 'xlsx']
}));
});
};
var f3 = function(t) {
(ct[t]||[]).forEach(function(v) {
o.push(writextag('Override', null, {
'PartName': (v[0] == '/' ? "":"/") + v,
'ContentType': type2ct[t][0]
}));
});
};
f1('workbooks');
f2('sheets');
f3('themes');
['strs', 'styles'].forEach(f1);
['coreprops', 'extprops', 'custprops'].forEach(f3);
if(o.length>2){ o.push('</Types>'); o[1]=o[1].replace("/>",">"); }
return o.join("");
}

61
bits/41_rels.js Normal file

@ -0,0 +1,61 @@
/* 9.3.2 OPC Relationships Markup */
var RELS = {
WB: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
SHEET: "http://sheetjs.openxmlformats.org/officeDocument/2006/relationships/officeDocument"
};
function parse_rels(data, currentFilePath) {
if (!data) return data;
if (currentFilePath.charAt(0) !== '/') {
currentFilePath = '/'+currentFilePath;
}
var rels = {};
var hash = {};
var resolveRelativePathIntoAbsolute = function (to) {
var toksFrom = currentFilePath.split('/');
toksFrom.pop(); // folder path
var toksTo = to.split('/');
var reversed = [];
while (toksTo.length !== 0) {
var tokTo = toksTo.shift();
if (tokTo === '..') {
toksFrom.pop();
} else if (tokTo !== '.') {
toksFrom.push(tokTo);
}
}
return toksFrom.join('/');
};
data.match(/<[^>]*>/g).forEach(function(x) {
var y = parsexmltag(x);
/* 9.3.2.2 OPC_Relationships */
if (y[0] === '<Relationship') {
var rel = {}; rel.Type = y.Type; rel.Target = y.Target; rel.Id = y.Id; rel.TargetMode = y.TargetMode;
var canonictarget = y.TargetMode === 'External' ? y.Target : resolveRelativePathIntoAbsolute(y.Target);
rels[canonictarget] = rel;
hash[y.Id] = rel;
}
});
rels["!id"] = hash;
return rels;
}
XMLNS.RELS = 'http://schemas.openxmlformats.org/package/2006/relationships';
var RELS_ROOT = writextag('Relationships', null, {
//'xmlns:ns0': XMLNS.RELS,
'xmlns': XMLNS.RELS
});
/* TODO */
function write_rels(rels) {
var o = [];
o.push(XML_HEADER);
o.push(RELS_ROOT);
keys(rels['!id']).forEach(function(rid) { var rel = rels['!id'][rid];
o.push(writextag('Relationship', null, rel));
});
if(o.length>2){ o.push('</Relationships>'); o[1]=o[1].replace("/>",">"); }
return o.join("");
}

66
bits/43_coreprops.js Normal file

@ -0,0 +1,66 @@
/* ECMA-376 Part II 11.1 Core Properties Part */
/* [MS-OSHARED] 2.3.3.2.[1-2].1 (PIDSI/PIDDSI) */
var CORE_PROPS = [
["cp:category", "Category"],
["cp:contentStatus", "ContentStatus"],
["cp:keywords", "Keywords"],
["cp:lastModifiedBy", "LastAuthor"],
["cp:lastPrinted", "LastPrinted"],
["cp:revision", "RevNumber"],
["cp:version", "Version"],
["dc:creator", "Author"],
["dc:description", "Comments"],
["dc:identifier", "Identifier"],
["dc:language", "Language"],
["dc:subject", "Subject"],
["dc:title", "Title"],
["dcterms:created", "CreatedDate", 'date'],
["dcterms:modified", "ModifiedDate", 'date']
];
XMLNS.CORE_PROPS = "http://schemas.openxmlformats.org/package/2006/metadata/core-properties";
RELS.CORE_PROPS = 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties';
function parse_core_props(data) {
var p = {};
CORE_PROPS.forEach(function(f) {
var g = "(?:"+ f[0].substr(0,f[0].indexOf(":")) +":)"+ f[0].substr(f[0].indexOf(":")+1);
var cur = data.match(new RegExp("<" + g + "[^>]*>(.*)<\/" + g + ">"));
if(cur && cur.length > 0) p[f[1]] = cur[1];
if(f[2] === 'date' && p[f[1]]) p[f[1]] = new Date(p[f[1]]);
});
return p;
}
var CORE_PROPS_XML_ROOT = writextag('cp:coreProperties', null, {
//'xmlns': XMLNS.CORE_PROPS,
'xmlns:cp': XMLNS.CORE_PROPS,
'xmlns:dc': XMLNS.dc,
'xmlns:dcterms': XMLNS.dcterms,
'xmlns:dcmitype': XMLNS.dcmitype,
'xmlns:xsi': XMLNS.xsi
});
function write_core_props(cp, opts) {
var o = [], p = {};
o.push(XML_HEADER);
o.push(CORE_PROPS_XML_ROOT);
if(!cp) return o.join("");
var doit = function(f, g, h) {
if(p[f] || typeof g === 'undefined' || g === "") return;
if(typeof g !== 'string') g = String(g); /* TODO: remove */
p[f] = g;
o.push(h ? writextag(f,g,h) : writetag(f,g));
};
if(typeof cp.CreatedDate !== 'undefined') doit("dcterms:created", write_w3cdtf(cp.CreatedDate, opts.WTF), {"xsi:type":"dcterms:W3CDTF"});
if(typeof cp.ModifiedDate !== 'undefined') doit("dcterms:modified", write_w3cdtf(cp.ModifiedDate, opts.WTF), {"xsi:type":"dcterms:W3CDTF"});
CORE_PROPS.forEach(function(f) { doit(f[0], cp[f[1]]); });
if(o.length>2){ o.push('</cp:coreProperties>'); o[1]=o[1].replace("/>",">"); }
return o.join("");
}

75
bits/44_extprops.js Normal file

@ -0,0 +1,75 @@
/* 15.2.12.3 Extended File Properties Part */
/* [MS-OSHARED] 2.3.3.2.[1-2].1 (PIDSI/PIDDSI) */
var EXT_PROPS = [
["Application", "Application", "string"],
["AppVersion", "AppVersion", "string"],
["Company", "Company", "string"],
["DocSecurity", "DocSecurity", "string"],
["Manager", "Manager", "string"],
["HyperlinksChanged", "HyperlinksChanged", "bool"],
["SharedDoc", "SharedDoc", "bool"],
["LinksUpToDate", "LinksUpToDate", "bool"],
["ScaleCrop", "ScaleCrop", "bool"],
["HeadingPairs", "HeadingPairs", "raw"],
["TitlesOfParts", "TitlesOfParts", "raw"],
];
XMLNS.EXT_PROPS = "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties";
RELS.EXT_PROPS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties';
function parse_ext_props(data, p) {
var q = {}; if(!p) p = {};
EXT_PROPS.forEach(function(f) {
switch(f[2]) {
case "string": p[f[1]] = (data.match(matchtag(f[0]))||[])[1]; break;
case "bool": p[f[1]] = (data.match(matchtag(f[0]))||[])[1] === "true"; break;
case "raw":
var cur = data.match(new RegExp("<" + f[0] + "[^>]*>(.*)<\/" + f[0] + ">"));
if(cur && cur.length > 0) q[f[1]] = cur[1];
break;
}
});
if(q.HeadingPairs && q.TitlesOfParts) {
var v = parseVector(q.HeadingPairs);
var j = 0, widx = 0;
for(var i = 0; i !== v.length; ++i) {
switch(v[i].v) {
case "Worksheets": widx = j; p.Worksheets = +(v[++i].v); break;
case "Named Ranges": ++i; break; // TODO: Handle Named Ranges
}
}
var parts = parseVector(q.TitlesOfParts).map(function(x) { return utf8read(x.v); });
p.SheetNames = parts.slice(widx, widx + p.Worksheets);
}
return p;
}
var EXT_PROPS_XML_ROOT = writextag('Properties', null, {
'xmlns': XMLNS.EXT_PROPS,
'xmlns:vt': XMLNS.vt
});
function write_ext_props(cp, opts) {
var o = [], p = {}, W = writextag;
o.push(XML_HEADER);
o.push(EXT_PROPS_XML_ROOT);
if(!cp) return o.join("");
EXT_PROPS.forEach(function(f) {
if(typeof cp[f[1]] === 'undefined') return;
var v;
switch(f[2]) {
case 'string': v = cp[f[1]]; break;
case 'bool': v = cp[f[1]] ? 'true' : 'false'; break;
}
if(typeof v !== 'undefined') o.push(W(f[0], v));
});
/* TODO: HeadingPairs, TitlesOfParts */
o.push(W('HeadingPairs', W('vt:vector', W('vt:variant', '<vt:lpstr>Worksheets</vt:lpstr>')+W('vt:variant', W('vt:i4', String(cp.Worksheets))), {size:2, baseType:"variant"})));
o.push(W('TitlesOfParts', W('vt:vector', cp.SheetNames.map(function(s) { return "<vt:lpstr>" + s + "</vt:lpstr>"; }).join(""), {size: cp.Worksheets, baseType:"lpstr"})));
if(o.length>2){ o.push('</Properties>'); o[1]=o[1].replace("/>",">"); }
return o.join("");
}

70
bits/45_custprops.js Normal file

@ -0,0 +1,70 @@
/* 15.2.12.2 Custom File Properties Part */
XMLNS.CUST_PROPS = "http://schemas.openxmlformats.org/officeDocument/2006/custom-properties";
RELS.CUST_PROPS = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties';
function parse_cust_props(data, opts) {
var p = {}, name;
data.match(/<[^>]+>([^<]*)/g).forEach(function(x) {
var y = parsexmltag(x);
switch(y[0]) {
case '<?xml': break;
case '<Properties':
if(y.xmlns !== XMLNS.CUST_PROPS) throw "unrecognized xmlns " + y.xmlns;
if(y.xmlnsvt && y.xmlnsvt !== XMLNS.vt) throw "unrecognized vt " + y.xmlnsvt;
break;
case '<property': name = y.name; break;
case '</property>': name = null; break;
default: if (x.indexOf('<vt:') === 0) {
var toks = x.split('>');
var type = toks[0].substring(4), text = toks[1];
/* 22.4.2.32 (CT_Variant). Omit the binary types from 22.4 (Variant Types) */
switch(type) {
case 'lpstr': case 'lpwstr': case 'bstr': case 'lpwstr':
p[name] = unescapexml(text);
break;
case 'bool':
p[name] = parsexmlbool(text, '<vt:bool>');
break;
case 'i1': case 'i2': case 'i4': case 'i8': case 'int': case 'uint':
p[name] = parseInt(text, 10);
break;
case 'r4': case 'r8': case 'decimal':
p[name] = parseFloat(text);
break;
case 'filetime': case 'date':
p[name] = new Date(text);
break;
case 'cy': case 'error':
p[name] = unescapexml(text);
break;
default:
console.warn('Unexpected', x, type, toks);
}
} else if(x.substr(0,2) === "</") {
} else if(opts.WTF) throw new Error(x);
}
});
return p;
}
var CUST_PROPS_XML_ROOT = writextag('Properties', null, {
'xmlns': XMLNS.CUST_PROPS,
'xmlns:vt': XMLNS.vt
});
function write_cust_props(cp, opts) {
var o = [], p = {};
o.push(XML_HEADER);
o.push(CUST_PROPS_XML_ROOT);
if(!cp) return o.join("");
var pid = 1;
keys(cp).forEach(function(k) { ++pid;
o.push(writextag('property', write_vt(cp[k]), {
'fmtid': '{D5CDD505-2E9C-101B-9397-08002B2CF9AE}',
'pid': pid,
'name': k
}));
});
if(o.length>2){ o.push('</Properties>'); o[1]=o[1].replace("/>",">"); }
return o.join("");
}

@ -19,7 +19,7 @@ var CS2CP = {
222: 874, /* THAI */
238: 1250, /* EASTEUROPE */
255: 1252, /* OEM */
69: 6969 /* MISC */
69: 6969 /* MISC */
};
/* Parse a list of <r> tags */
@ -169,3 +169,18 @@ var parse_sst_xml = function(data, opts) {
return s;
};
RELS.SST = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings";
var write_sst_xml = function(sst, opts) {
if(!opts.bookSST) return "";
var o = [];
o.push(XML_HEADER);
o.push(writextag('sst', null, {
xmlns: XMLNS.main[0],
count: sst.Count,
uniqueCount: sst.Unique
}));
sst.forEach(function(s) { o.push("<si>" + (s.r ? s.r : "<t>" + escapexml(s.t) + "</t>") + "</si>"); });
if(o.length>2){ o.push('</sst>'); o[1]=o[1].replace("/>",">"); }
return o.join("");
};

@ -20,3 +20,5 @@ var parse_sst_bin = function(data, opts) {
});
return s;
};
var write_sst_bin = function(sst, opts) { };

@ -1,5 +1,5 @@
/* 18.8.31 numFmts CT_NumFmts */
function parseNumFmts(t, opts) {
function parse_numFmts(t, opts) {
styles.NumberFmt = [];
for(var y in SSF._table) styles.NumberFmt[y] = SSF._table[y];
t[0].match(/<[^>]*>/g).forEach(function(x) {
@ -15,8 +15,21 @@ function parseNumFmts(t, opts) {
});
}
function write_numFmts(NF, opts) {
var o = [];
o.push("<numFmts>");
[[5,8],[23,26],[41,44],[63,66],[164,392]].forEach(function(r) {
for(var i = r[0]; i <= r[1]; ++i) if(NF[i])
o.push(writextag('numFmt',null,{numFmtId:i,formatCode:escapexml(NF[i])}));
});
o.push("</numFmts>");
if(o.length === 2) return "";
o[0] = writextag('numFmts', null, { count:o.length-2 }).replace("/>", ">");
return o.join("");
}
/* 18.8.10 cellXfs CT_CellXfs */
function parseCXfs(t, opts) {
function parse_cellXfs(t, opts) {
styles.CellXf = [];
t[0].match(/<[^>]*>/g).forEach(function(x) {
var y = parsexmltag(x);
@ -42,13 +55,23 @@ function parseCXfs(t, opts) {
});
}
function write_cellXfs(cellXfs) {
var o = [];
o.push(writextag('cellXfs',null));
cellXfs.forEach(function(c) { o.push(writextag('xf', null, c)); });
o.push("</cellXfs>");
if(o.length === 2) return "";
o[0] = writextag('cellXfs',null, {count:o.length-2}).replace("/>",">");
return o.join("");
}
/* 18.8 Styles CT_Stylesheet*/
function parse_sty_xml(data, opts) {
/* 18.8.39 styleSheet CT_Stylesheet */
var t;
/* numFmts CT_NumFmts ? */
if((t=data.match(/<numFmts([^>]*)>.*<\/numFmts>/))) parseNumFmts(t, opts);
if((t=data.match(/<numFmts([^>]*)>.*<\/numFmts>/))) parse_numFmts(t, opts);
/* fonts CT_Fonts ? */
/* fills CT_Fills ? */
@ -56,7 +79,7 @@ function parse_sty_xml(data, opts) {
/* cellStyleXfs CT_CellStyleXfs ? */
/* cellXfs CT_CellXfs ? */
if((t=data.match(/<cellXfs([^>]*)>.*<\/cellXfs>/))) parseCXfs(t, opts);
if((t=data.match(/<cellXfs([^>]*)>.*<\/cellXfs>/))) parse_cellXfs(t, opts);
/* dxfs CT_Dxfs ? */
/* tableStyles CT_TableStyles ? */
@ -65,3 +88,28 @@ function parse_sty_xml(data, opts) {
return styles;
}
var STYLES_XML_ROOT = writextag('styleSheet', null, {
'xmlns': XMLNS.main[0],
'xmlns:vt': XMLNS.vt
});
RELS.STY = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles";
function write_sty_xml(wb, opts) {
var o = [], p = {}, W = writextag, w;
o.push(XML_HEADER);
o.push(STYLES_XML_ROOT);
if((w = write_numFmts(wb.SSF))) o.push(w);
o.push('<fonts count="1"><font><sz val="12"/><color theme="1"/><name val="Calibri"/><family val="2"/><scheme val="minor"/></font></fonts>');
o.push('<fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills>');
o.push('<borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders>');
o.push('<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>');
if((w = write_cellXfs(opts.cellXfs))) o.push(w);
o.push('<cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles>');
o.push('<dxfs count="0"/>');
o.push('<tableStyles count="0" defaultTableStyle="TableStyleMedium9" defaultPivotStyle="PivotStyleMedium4"/>');
if(o.length>2){ o.push('</styleSheet>'); o[1]=o[1].replace("/>",">"); }
return o.join("");
}

3
bits/59_theme.js Normal file

File diff suppressed because one or more lines are too long

@ -5,7 +5,7 @@ function parse_comments(zip, dirComments, sheets, sheetRels, opts) {
var comments=parse_cmnt(getzipdata(zip, canonicalpath.replace(/^\//,''), true), canonicalpath, opts);
if(!comments || !comments.length) continue;
// find the sheets targeted by these comments
var sheetNames = Object.keys(sheets);
var sheetNames = keys(sheets);
for(var j = 0; j != sheetNames.length; ++j) {
var sheetName = sheetNames[j];
var rels = sheetRels[sheetName];

@ -1,3 +1,23 @@
var strs = {}; // shared strings
var _ssfopts = {}; // spreadsheet formatting options
RELS.WS = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet";
function get_sst_id(sst, str) {
for(var i = 0; i != sst.length; ++i) if(sst[i].t === str) { sst.Count ++; return i; }
sst[sst.length] = {t:str}; sst.Count ++; sst.Unique ++; return sst.length-1;
}
function get_cell_style(styles, cell, opts) {
var z = opts.revssf[cell.z];
for(var i = 0; i != styles.length; ++i) if(styles[i].numFmtId === z) return i;
styles[styles.length] = {
numFmtId:z,
fontId:0,
fillId:0,
borderId:0,
xfId:0,
applyNumberFormat:1
};
return styles.length-1;
}

@ -12,7 +12,7 @@ function parse_ws_xml(data, opts, rels) {
var mergecells = [];
if(data.match(/<\/mergeCells>/)) {
var merges = data.match(/<mergeCell ref="([A-Z0-9:]+)"\s*\/>/g);
mergecells = merges.map(function(range) {
mergecells = merges.map(function(range) {
return decode_range(/<mergeCell ref="([A-Z0-9:]+)"\s*\/>/.exec(range)[1]);
});
}
@ -123,3 +123,55 @@ function parse_ws_xml(data, opts, rels) {
return s;
}
var WS_XML_ROOT = writextag('worksheet', null, {
'xmlns': XMLNS.main[0],
'xmlns:r': XMLNS.r
});
var write_ws_xml_cell = function(cell, ref, ws, opts, idx, wb) {
var v = writextag('v', escapexml(String(cell.v))), o = {r:ref};
if(cell.z) o.s = get_cell_style(opts.cellXfs, cell, opts);
/* TODO: cell style */
if(typeof cell.v === 'undefined') return "";
switch(cell.t) {
case 's': case 'str': {
if(opts.bookSST) {
v = writextag('v', String(get_sst_id(opts.Strings, cell.v)));
o.t = "s"; return writextag('c', v, o);
} else { o.t = "str"; return writextag('c', v, o); }
} break;
case 'n': o.t = "n"; return writextag('c', v, o);
case 'b': o.t = "b"; return writextag('c', v, o);
case 'e': o.t = "e"; return writextag('c', v, o);
}
};
var write_ws_xml_data = function(ws, opts, idx, wb) {
var o = [], r = [], range = utils.decode_range(ws['!ref']), cell, ref;
for(var R = range.s.r; R <= range.e.r; ++R) {
r = [];
for(var C = range.s.c; C <= range.e.c; ++C) {
ref = utils.encode_cell({c:C, r:R});
if(!ws[ref]) continue;
if((cell = write_ws_xml_cell(ws[ref], ref, ws, opts, idx, wb))) r.push(cell);
}
if(r.length) o.push(writextag('row', r.join(""), {r:encode_row(R)}));
}
return o.join("");
};
var write_ws_xml = function(idx, opts, wb) {
var o = [], s = wb.SheetNames[idx], ws = wb.Sheets[s] || {}, sidx = 0, rdata = "";
o.push(XML_HEADER);
o.push(WS_XML_ROOT);
o.push(writextag('dimension', null, {'ref': ws['!ref'] || 'A1'}));
sidx = o.length;
o.push(writextag('sheetData', null));
if(ws['!ref']) rdata = write_ws_xml_data(ws, opts, idx, wb);
if(rdata.length) o.push(rdata);
if(o.length>sidx+1){ o.push('</sheetData>'); o[sidx]=o[sidx].replace("/>",">"); }
if(o.length>2){ o.push('</worksheet>'); o[1]=o[1].replace("/>",">"); }
return o.join("");
};

@ -317,7 +317,7 @@ var parse_ws_bin = function(data, opts, rels) {
default: if(!pass || opts.WTF) throw new Error("Unexpected record " + R.n);
}
}, opts);
if(!s["!ref"] && ref) s["!ref"] = encode_range(ref);
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(opts.sheetRows && s["!ref"]) {
var tmpref = decode_range(s["!ref"]);
if(opts.sheetRows < +tmpref.e.r) {
@ -334,3 +334,4 @@ var parse_ws_bin = function(data, opts, rels) {
return s;
};
var write_ws_bin = function(wb, opts, rels) {};

@ -1,10 +1,3 @@
var XMLNS_WB = [
'http://purl.oclc.org/ooxml/spreadsheetml/main',
'http://schemas.openxmlformats.org/spreadsheetml/2006/main',
'http://schemas.microsoft.com/office/excel/2006/main',
'http://schemas.microsoft.com/office/excel/2006/2'
];
/* 18.2 Workbook */
function parse_wb_xml(data) {
var wb = { AppVersion:{}, WBProps:{}, WBView:[], Sheets:[], CalcPr:{}, xmlns: "" };
@ -110,7 +103,7 @@ function parse_wb_xml(data) {
case '</mc:AlternateContent>': pass=false; break;
}
});
if(XMLNS_WB.indexOf(wb.xmlns) === -1) throw new Error("Unknown Namespace: " + wb.xmlns);
if(XMLNS.main.indexOf(wb.xmlns) === -1) throw new Error("Unknown Namespace: " + wb.xmlns);
var z;
/* defaults */
@ -125,3 +118,28 @@ function parse_wb_xml(data) {
return wb;
}
var WB_XML_ROOT = writextag('workbook', null, {
'xmlns': XMLNS.main[0],
//'xmlns:mx': XMLNS.mx,
//'xmlns:s': XMLNS.main[0],
'xmlns:r': XMLNS.r
});
var write_wb_xml = function(wb, opts) {
var o = [];
o.push(XML_HEADER);
o.push(WB_XML_ROOT);
/* TODO: put this somewhere else */
var date1904 = "false";
try { date1904 = parsexmlbool(wb.Workbook.WBProps.date1904) ? "true" : "false"; } catch(e) { date1904 = "false"; }
o.push(writextag('workbookPr', null, {date1904:date1904}));
o.push("<sheets>");
var i = 1;
wb.SheetNames.forEach(function(s) {
o.push(writextag('sheet',null,{name:s, sheetId:String(i), "r:id":"rId"+i}));
++i;
});
o.push("</sheets>");
if(o.length>2){ o.push('</workbook>'); o[1]=o[1].replace("/>",">"); }
return o.join("");
};

@ -69,3 +69,7 @@ var parse_wb_bin = function(data, opts) {
return wb;
};
var write_wb_bin = function(wb, opts) {
};

@ -1,23 +1,48 @@
function parse_wb(data, name, opts) {
return name.substr(-4)===".bin" ? parse_wb_bin(data, opts) : parse_wb_xml(data, opts);
return (name.substr(-4