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
View File

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

View File

@ -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"

View File

@ -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

View File

@ -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.

View File

@ -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 > $@

View File

@ -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

View File

@ -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);

View File

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

13
bits/30_jsutils.js Normal file
View 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;
}

View File

@ -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'
];

View File

@ -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 = [];
};

View File

@ -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
View 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
View 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
View 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
View 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("");
}

View File

@ -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("");
};

View File

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

View File

@ -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

View File

@ -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];

View File

@ -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;
}

View File

@ -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("");
};

View File

@ -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) {};

View File

@ -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("");
};

View File

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

View File

@ -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)===".bin" ? parse_wb_bin : parse_wb_xml)(data, opts);
}
function parse_ws(data, name, opts, rels) {
return name.substr(-4)===".bin" ? parse_ws_bin(data, opts, rels) : parse_ws_xml(data, opts, rels);
return (name.substr(-4)===".bin" ? parse_ws_bin : parse_ws_xml)(data, opts, rels);
}
function parse_sty(data, name, opts) {
return name.substr(-4)===".bin" ? parse_sty_bin(data, opts) : parse_sty_xml(data, opts);
return (name.substr(-4)===".bin" ? parse_sty_bin : parse_sty_xml)(data, opts);
}
function parse_sst(data, name, opts) {
return name.substr(-4)===".bin" ? parse_sst_bin(data, opts) : parse_sst_xml(data, opts);
return (name.substr(-4)===".bin" ? parse_sst_bin : parse_sst_xml)(data, opts);
}
function parse_cmnt(data, name, opts) {
return name.substr(-4)===".bin" ? parse_comments_bin(data, opts) : parse_comments_xml(data, opts);
return (name.substr(-4)===".bin" ? parse_comments_bin : parse_comments_xml)(data, opts);
}
function parse_cc(data, name, opts) {
return name.substr(-4)===".bin" ? parse_cc_bin(data, opts) : parse_cc_xml(data, opts);
return (name.substr(-4)===".bin" ? parse_cc_bin : parse_cc_xml)(data, opts);
}
function write_wb(wb, name, opts) {
return (name.substr(-4)===".bin" ? write_wb_bin : write_wb_xml)(wb, opts);
}
function write_ws(data, name, opts, wb) {
return (name.substr(-4)===".bin" ? write_ws_bin : write_ws_xml)(data, opts, wb);
}
function write_sty(data, name, opts) {
return (name.substr(-4)===".bin" ? write_sty_bin : write_sty_xml)(data, opts);
}
function write_sst(data, name, opts) {
return (name.substr(-4)===".bin" ? write_sst_bin : write_sst_xml)(data, opts);
}
/*
function write_cmnt(data, name, opts) {
return (name.substr(-4)===".bin" ? write_comments_bin : write_comments_xml)(data, opts);
}
function write_cc(data, name, opts) {
return (name.substr(-4)===".bin" ? write_cc_bin : write_cc_xml)(data, opts);
}
*/

View File

@ -1,22 +1,34 @@
function fixopts(opts) {
var defaults = [
['cellNF', false], /* emit cell number format string as .z */
['cellHTML', true], /* emit html string as .h */
['cellFormula', true], /* emit formulae as .f */
['sheetStubs', false], /* emit empty cells */
['sheetRows', 0, 'n'], /* read n rows (0 = read all rows) */
['bookDeps', false], /* parse calculation chains */
['bookSheets', false], /* only try to get sheet names (no Sheets) */
['bookProps', false], /* only try to get properties (no Sheets) */
['bookFiles', false], /* include raw file structure (keys, files) */
['bookVBA', false], /* include vba raw data (vbaraw) */
['WTF', false] /* WTF mode (throws errors) */
];
defaults.forEach(function(d) {
if(typeof opts[d[0]] === 'undefined') opts[d[0]] = d[1];
if(d[2] === 'n') opts[d[0]] = Number(opts[d[0]]);
});
function fix_opts(defaults) {
return function(opts) {
defaults.forEach(function(d) {
if(typeof opts[d[0]] === 'undefined') opts[d[0]] = d[1];
if(d[2] === 'n') opts[d[0]] = Number(opts[d[0]]);
});
};
}
var fix_read_opts = fix_opts([
['cellNF', false], /* emit cell number format string as .z */
['cellHTML', true], /* emit html string as .h */
['cellFormula', true], /* emit formulae as .f */
['sheetStubs', false], /* emit empty cells */
['sheetRows', 0, 'n'], /* read n rows (0 = read all rows) */
['bookDeps', false], /* parse calculation chains */
['bookSheets', false], /* only try to get sheet names (no Sheets) */
['bookProps', false], /* only try to get properties (no Sheets) */
['bookFiles', false], /* include raw file structure (keys, files) */
['bookVBA', false], /* include vba raw data (vbaraw) */
['WTF', false] /* WTF mode (throws errors) */
]);
var fix_write_opts = fix_opts([
['bookSST', false], /* Generate Shared String Table */
['bookType', 'xlsx'], /* Type of workbook (xlsx/m/b) */
['WTF', false] /* WTF mode (throws errors) */
]);

View File

@ -1,10 +1,10 @@
function parseZip(zip, opts) {
function parse_zip(zip, opts) {
make_ssf(SSF);
opts = opts || {};
fixopts(opts);
fix_read_opts(opts);
reset_cp();
var entries = Object.keys(zip.files);
var keys = entries.filter(function(x){return x.substr(-1) != '/';}).sort();
var dir = parseCT(getzipdata(zip, '[Content_Types].xml'), opts);
var entries = keys(zip.files).filter(function(x){return x.substr(-1) != '/';}).sort();
var dir = parse_ct(getzipdata(zip, '[Content_Types].xml'), opts);
var xlsb = false;
var sheets, binname;
if(dir.workbooks.length === 0) {
@ -19,7 +19,7 @@ function parseZip(zip, opts) {
}
if(!opts.bookSheets && !opts.bookProps) {
strs = {};
strs = [];
if(dir.sst) strs=parse_sst(getzipdata(zip, dir.sst.replace(/^\//,'')), dir.sst, opts);
styles = {};
@ -29,17 +29,21 @@ function parseZip(zip, opts) {
var wb = parse_wb(getzipdata(zip, dir.workbooks[0].replace(/^\//,'')), dir.workbooks[0], opts);
var props = {}, propdata = "";
try {
propdata = dir.coreprops.length !== 0 ? getzipdata(zip, dir.coreprops[0].replace(/^\//,'')) : "";
propdata += dir.extprops.length !== 0 ? getzipdata(zip, dir.extprops[0].replace(/^\//,'')) : "";
props = propdata !== "" ? parseProps(propdata) : {};
} catch(e) { }
if(dir.coreprops.length !== 0) {
propdata = getzipdata(zip, dir.coreprops[0].replace(/^\//,''), true);
if(propdata) props = parse_core_props(propdata);
if(dir.extprops.length !== 0) {
propdata = getzipdata(zip, dir.extprops[0].replace(/^\//,''), true);
if(propdata) parse_ext_props(propdata, props);
}
}
var custprops = {};
if(!opts.bookSheets || opts.bookProps) {
if (dir.custprops.length !== 0) {
propdata = getzipdata(zip, dir.custprops[0].replace(/^\//,''), true);
if(propdata) custprops = parseCustomProps(propdata);
if(propdata) custprops = parse_cust_props(propdata, opts);
}
}
@ -60,7 +64,6 @@ function parseZip(zip, opts) {
var sheetRels = {};
var path, relsPath;
if(!props.Worksheets) {
/* Google Docs doesn't generate the appropriate metadata, so we impute: */
var wbsheets = wb.Sheets;
props.Worksheets = wbsheets.length;
props.SheetNames = [];
@ -76,7 +79,7 @@ function parseZip(zip, opts) {
path = 'xl/worksheets/sheet'+(i+1-nmode)+(xlsb?'.bin':'.xml');
path = path.replace(/sheet0\./,"sheet.");
relsPath = path.replace(/^(.*)(\/)([^\/]*)$/, "$1/_rels/$3.rels");
sheetRels[props.SheetNames[i]]=parseRels(getzipdata(zip, relsPath, true), path);
sheetRels[props.SheetNames[i]]=parse_rels(getzipdata(zip, relsPath, true), path);
sheets[props.SheetNames[i]]=parse_ws(getzipdata(zip, path),path,opts,sheetRels[props.SheetNames[i]]);
} catch(e) { if(opts.WTF) throw e; }
}
@ -93,9 +96,10 @@ function parseZip(zip, opts) {
SheetNames: props.SheetNames,
Strings: strs,
Styles: styles,
SSF: SSF.get_table()
};
if(opts.bookFiles) {
out.keys = keys;
out.keys = entries;
out.files = zip.files;
}
if(opts.bookVBA) {

85
bits/86_writezip.js Normal file
View File

@ -0,0 +1,85 @@
function add_rels(rels, rId, f, type, relobj) {
if(!relobj) relobj = {};
if(!rels['!id']) rels['!id'] = {};
relobj.Id = 'rId' + rId;
relobj.Type = type;
relobj.Target = f;
if(rels['!id'][relobj.Id]) throw new Error("Cannot rewrite rId " + rId);
rels['!id'][relobj.Id] = relobj;
rels[('/' + relobj.Target).replace("//","/")] = relobj;
}
function write_zip(wb, opts) {
if(wb && wb.SSF) {
make_ssf(SSF); SSF.load_table(wb.SSF);
opts.revssf = evert(wb.SSF); opts.revssf[wb.SSF[65535]] = 0;
}
opts.rels = {}; opts.wbrels = {};
opts.Strings = []; opts.Strings.Count = 0; opts.Strings.Unique = 0;
var wbext = opts.bookType == "xlsb" ? "bin" : "xml";
var ct = { workbooks: [], sheets: [], calcchains: [], themes: [], styles: [],
coreprops: [], extprops: [], custprops: [], strs:[], comments: [], vba: [],
TODO:[], rels:[], xmlns: "" };
fix_write_opts(opts = opts || {});
var zip = new jszip();
var f = "", rId = 0;
opts.cellXfs = [];
f = "docProps/core.xml";
zip.file(f, write_core_props(wb.Props, opts));
ct.coreprops.push(f);
add_rels(opts.rels, 3, f, RELS.CORE_PROPS);
f = "docProps/app.xml";
wb.Props.SheetNames = wb.SheetNames;
wb.Props.Worksheets = wb.SheetNames.length;
zip.file(f, write_ext_props(wb.Props, opts));
ct.extprops.push(f);
add_rels(opts.rels, 4, f, RELS.EXT_PROPS);
if(wb.Custprops !== wb.Props) { /* TODO: fix xlsjs */
f = "docProps/custom.xml";
zip.file(f, write_cust_props(wb.Custprops, opts));
ct.custprops.push(f);
add_rels(opts.rels, 5, f, RELS.CUST_PROPS);
}
f = "xl/workbook." + wbext;
zip.file(f, write_wb(wb, f, opts));
ct.workbooks.push(f);
add_rels(opts.rels, 1, f, RELS.WB);
wb.SheetNames.forEach(function(s, i) {
rId = i+1; f = "xl/worksheets/sheet" + rId + "." + wbext;
zip.file(f, write_ws(i, f, opts, wb));
ct.sheets.push(f);
add_rels(opts.wbrels, rId, "worksheets/sheet" + rId + "." + wbext, RELS.WS);
});
if((opts.Strings||[]).length > 0) {
f = "xl/sharedStrings." + wbext;
zip.file(f, write_sst(opts.Strings, f, opts));
ct.strs.push(f);
add_rels(opts.wbrels, ++rId, "sharedStrings." + wbext, RELS.SST);
}
/* TODO: something more intelligent with themes */
/* f = "xl/theme/theme1.xml"
zip.file(f, write_theme());
ct.themes.push(f);
add_rels(opts.wbrels, ++rId, "theme/theme1.xml", RELS.THEME);*/
/* TODO: something more intelligent with styles */
f = "xl/styles.xml";
zip.file(f, write_sty(wb, f, opts));
ct.styles.push(f);
add_rels(opts.wbrels, ++rId, "styles." + wbext, RELS.STY);
zip.file("[Content_Types].xml", write_ct(ct, opts));
zip.file('_rels/.rels', write_rels(opts.rels));
zip.file('xl/_rels/workbook.xml.rels', write_rels(opts.wbrels));
return zip;
}

View File

@ -1,19 +1,19 @@
function readSync(data, options) {
function readSync(data, opts) {
var zip, d = data;
var o = options||{};
switch((o.type||"base64")){
case "file":
if(typeof Buffer !== 'undefined') { zip=new jszip(d=_fs.readFileSync(data)); break; }
d = _fs.readFileSync(data).toString('base64');
/* falls through */
var o = opts||{};
if(!o.type) o.type = (typeof Buffer !== 'undefined' && data instanceof Buffer) ? "buffer" : "base64";
switch(o.type) {
case "base64": zip = new jszip(d, { base64:true }); break;
case "binary": zip = new jszip(d, { base64:false }); break;
case "buffer": zip = new jszip(d); break;
case "file": zip=new jszip(d=_fs.readFileSync(data)); break;
default: throw new Error("Unrecognized type " + o.type);
}
return parseZip(zip, o);
return parse_zip(zip, o);
}
function readFileSync(data, options) {
var o = options||{}; o.type = 'file';
function readFileSync(data, opts) {
var o = opts||{}; o.type = 'file';
return readSync(data, o);
}

22
bits/89_write.js Normal file
View File

@ -0,0 +1,22 @@
function writeSync(wb, opts) {
var o = opts||{};
var z = write_zip(wb, o);
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"}));
default: throw new Error("Unrecognized type " + o.type);
}
}
function writeFileSync(wb, filename, opts) {
var o = opts||{}; o.type = 'file';
o.file = filename;
switch(o.file.substr(-5).toLowerCase()) {
case '.xlsm': o.bookType = 'xlsm'; break;
case '.xlsb': o.bookType = 'xlsb'; break;
}
return writeSync(wb, o);
}

View File

@ -1,5 +1,7 @@
XLSX.parseZip = parseZip;
XLSX.parseZip = parse_zip;
XLSX.read = readSync;
XLSX.readFile = readFileSync;
XLSX.write = writeSync;
XLSX.writeFile = writeFileSync;
XLSX.utils = utils;
XLSX.SSF = SSF;

2
dist/LICENSE vendored
View File

@ -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.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1353
dist/xlsx.js vendored

File diff suppressed because one or more lines are too long

7
dist/xlsx.min.js vendored

File diff suppressed because one or more lines are too long

2
dist/xlsx.min.map vendored

File diff suppressed because one or more lines are too long

View File

@ -1,11 +1,11 @@
{
"name": "xlsx",
"version": "0.6.2",
"version": "0.7.0",
"author": "sheetjs",
"description": "XLSB / XLSX / XLSM (Excel 2007+ Spreadsheet) parser",
"keywords": [ "xlsx", "xlsb", "xlsm", "office", "excel", "spreadsheet" ],
"bin": {
"xlsx2csv": "./bin/xlsx2csv.njs"
"xlsx": "./bin/xlsx.njs"
},
"main": "./xlsx",
"dependencies": {

186
test.js
View File

@ -3,9 +3,11 @@ var X;
var fs = require('fs'), assert = require('assert');
describe('source',function(){it('should load',function(){X=require('./');});});
var opts = {};
var opts = {cellNF: true};
if(process.env.WTF) opts.WTF = true;
var ex = [".xlsb", ".xlsm", ".xlsx"];
var fullex = [".xlsb", ".xlsm", ".xlsx"];
var ex = fullex;
if(process.env.FMTS === "full") process.env.FMTS = ex.join(":");
if(process.env.FMTS) ex=process.env.FMTS.split(":").map(function(x){return x[0]==="."?x:"."+x;});
var exp = ex.map(function(x){ return x + ".pending"; });
function test_file(x){return ex.indexOf(x.substr(-5))>=0||exp.indexOf(x.substr(-13))>=0;}
@ -43,8 +45,9 @@ var paths = {
var N1 = 'XLSX';
var N2 = 'XLSB';
function parsetest(x, wb, full) {
describe(x + ' should have all bits', function() {
function parsetest(x, wb, full, ext) {
ext = (ext ? " [" + ext + "]": "");
describe(x + ext + ' should have all bits', function() {
var sname = dir + '2011/' + x + '.sheetnames';
it('should have all sheets', function() {
wb.SheetNames.forEach(function(y) { assert(wb.Sheets[y], 'bad sheet ' + y); });
@ -55,21 +58,21 @@ function parsetest(x, wb, full) {
assert.equal(names, file);
} : null);
});
describe(x + ' should generate CSV', function() {
describe(x + ext + ' should generate CSV', function() {
wb.SheetNames.forEach(function(ws, i) {
it('#' + i + ' (' + ws + ')', function() {
X.utils.make_csv(wb.Sheets[ws]);
});
});
});
describe(x + ' should generate JSON', function() {
describe(x + ext + ' should generate JSON', function() {
wb.SheetNames.forEach(function(ws, i) {
it('#' + i + ' (' + ws + ')', function() {
X.utils.sheet_to_row_object_array(wb.Sheets[ws]);
});
});
});
describe(x + ' should generate formulae', function() {
describe(x + ext + ' should generate formulae', function() {
wb.SheetNames.forEach(function(ws, i) {
it('#' + i + ' (' + ws + ')', function() {
X.utils.get_formulae(wb.Sheets[ws]);
@ -87,7 +90,7 @@ function parsetest(x, wb, full) {
}
return name;
};
describe(x + ' should generate correct CSV output', function() {
describe(x + ext + ' should generate correct CSV output', function() {
wb.SheetNames.forEach(function(ws, i) {
var name = getfile(dir, x, i, ".csv");
it('#' + i + ' (' + ws + ')', fs.existsSync(name) ? function() {
@ -97,7 +100,7 @@ function parsetest(x, wb, full) {
} : null);
});
});
describe(x + ' should generate correct JSON output', function() {
describe(x + ext + ' should generate correct JSON output', function() {
wb.SheetNames.forEach(function(ws, i) {
var rawjson = getfile(dir, x, i, ".rawjson");
if(fs.existsSync(rawjson)) it('#' + i + ' (' + ws + ')', function() {
@ -115,7 +118,7 @@ function parsetest(x, wb, full) {
});
});
if(!fs.existsSync(dir + '2013/' + x + '.xlsb')) return;
describe(x + '.xlsb from 2013', function() {
describe(x + ext + '.xlsb from 2013', function() {
it('should parse', function() {
var wb = X.readFile(dir + '2013/' + x + '.xlsb', opts);
});
@ -127,6 +130,9 @@ describe('should parse test files', function() {
it(x, x.substr(-8) == ".pending" ? null : function() {
var wb = X.readFile(dir + x, opts);
parsetest(x, wb, true);
['.xlsx', '.xlsm'].forEach(function(ext, idx) {
parsetest(x, X.read(X.write(wb, {type:"buffer", bookType:ext.replace(/\./,""), bookSST: idx != 1})), true, ext);
});
});
});
fileA.forEach(function(x) {
@ -137,7 +143,7 @@ describe('should parse test files', function() {
});
});
describe('options', function() {
describe('parse options', function() {
var html_cell_types = ['s'];
before(function() {
X = require('./');
@ -308,9 +314,76 @@ describe('input formats', function() {
X.read(fs.readFileSync(paths.cst1, 'base64'), {type: 'base64'});
X.read(fs.readFileSync(paths.cst2, 'base64'), {type: 'base64'});
});
it('should read buffers', function() {
X.read(fs.readFileSync(paths.cst1), {type: 'buffer'});
X.read(fs.readFileSync(paths.cst2), {type: 'buffer'});
});
it('should throw if format is unknown', function() {
assert.throws(function() { X.read(fs.readFileSync(paths.cst1), {type: 'dafuq'}); });
assert.throws(function() { X.read(fs.readFileSync(paths.cst2), {type: 'dafuq'}); });
});
it('should infer buffer type', function() {
X.read(fs.readFileSync(paths.cst1));
X.read(fs.readFileSync(paths.cst2));
});
it('should default to base64 type', function() {
assert.throws(function() { X.read(fs.readFileSync(paths.cst1, 'binary')); });
assert.throws(function() { X.read(fs.readFileSync(paths.cst2, 'binary')); });
X.read(fs.readFileSync(paths.cst1, 'base64'));
X.read(fs.readFileSync(paths.cst2, 'base64'));
});
});
describe('features', function() {
describe('output formats', function() {
var wb1, wb2;
before(function() {
X = require('./');
wb1 = X.readFile(paths.cp1);
wb2 = X.readFile(paths.cp2);
});
it('should write binary strings', function() {
X.write(wb1, {type: 'binary'});
X.write(wb2, {type: 'binary'});
X.read(X.write(wb1, {type: 'binary'}), {type: 'binary'});
X.read(X.write(wb2, {type: 'binary'}), {type: 'binary'});
});
it('should write base64 strings', function() {
X.write(wb1, {type: 'base64'});
X.write(wb2, {type: 'base64'});
X.read(X.write(wb1, {type: 'base64'}), {type: 'base64'});
X.read(X.write(wb2, {type: 'base64'}), {type: 'base64'});
});
it('should write buffers', function() {
X.write(wb1, {type: 'buffer'});
X.write(wb2, {type: 'buffer'});
X.read(X.write(wb1, {type: 'buffer'}), {type: 'buffer'});
X.read(X.write(wb2, {type: 'buffer'}), {type: 'buffer'});
});
it('should throw if format is unknown', function() {
assert.throws(function() { X.write(wb1, {type: 'dafuq'}); });
assert.throws(function() { X.write(wb2, {type: 'dafuq'}); });
});
});
function coreprop(wb) {
assert.equal(wb.Props.Title, 'Example with properties');
assert.equal(wb.Props.Subject, 'Test it before you code it');
assert.equal(wb.Props.Author, 'Pony Foo');
assert.equal(wb.Props.Manager, 'Despicable Drew');
assert.equal(wb.Props.Company, 'Vector Inc');
assert.equal(wb.Props.Category, 'Quirky');
assert.equal(wb.Props.Keywords, 'example humor');
assert.equal(wb.Props.Comments, 'some comments');
assert.equal(wb.Props.LastAuthor, 'Hugues');
}
function custprop(wb) {
assert.equal(wb.Custprops['I am a boolean'], true);
assert.equal(wb.Custprops['Date completed'].toISOString(), '1967-03-09T16:30:00.000Z');
assert.equal(wb.Custprops.Status, 2);
assert.equal(wb.Custprops.Counter, -3.14);
}
describe('parse features', function() {
it('should have comment as part of cell properties', function(){
var X = require('./');
var sheet = 'Sheet1';
@ -335,17 +408,6 @@ describe('features', function() {
wb2 = X.readFile(paths.cp2);
});
function coreprop(wb) {
assert.equal(wb.Props.Company, 'Vector Inc');
assert.equal(wb.Props.Creator, 'Pony Foo');
}
function custprop(wb) {
assert.equal(wb.Custprops['I am a boolean'], true);
assert.equal(wb.Custprops['Date completed'], '1967-03-09T16:30:00Z');
assert.equal(wb.Custprops.Status, 2);
assert.equal(wb.Custprops.Counter, -3.14);
}
it(N1 + ' should parse core properties', function() { coreprop(wb1); });
it(N2 + ' should parse core properties', function() { coreprop(wb2); });
it(N1 + ' should parse custom properties', function() { custprop(wb1); });
@ -434,17 +496,75 @@ describe('features', function() {
});
});
describe('invalid files', function() {
it('should fail on passwords', function() {
assert.throws(function() { X.readFile(dir + 'excel-reader-xlsx_error03.xlsx'); });
describe('roundtrip features', function() {
before(function() {
X = require('./');
});
it('should fail on XLS files', function() {
assert.throws(function() { X.readFile(dir + 'roo_type_excel.xlsx'); });
describe('should parse core properties and custom properties', function() {
var wb1, wb2, base = './tmp/cp';
before(function() {
wb1 = X.readFile(paths.cp1);
wb2 = X.readFile(paths.cp2);
fullex.forEach(function(p) {
X.writeFile(wb1, base + '.xlsm' + p);
X.writeFile(wb2, base + '.xlsb' + p);
});
});
fullex.forEach(function(p) { ['.xlsm','.xlsb'].forEach(function(q) {
it(q + p + ' should roundtrip core and custom properties', function() {
var wb = X.readFile(base + q + p);
coreprop(wb);
custprop(wb);
}); });
});
});
it('should fail on ODS files', function() {
assert.throws(function() { X.readFile(dir + 'roo_type_openoffice.xlsx');});
});
it('should fail on DOC files', function() {
assert.throws(function() { X.readFile(dir + 'word_doc.doc');});
/* the XLSJS require should not cause the test suite to fail */
var XLSJS;
try {
XLSJS = require('xlsjs');
var xls = XLSJS.readFile('./test_files/formula_stress_test.xls');
var xml = XLSJS.readFile('./test_files/formula_stress_test.xls.xml');
} catch(e) { return; }
describe('xlsjs conversions', function() { [
['XLS', 'formula_stress_test.xls'],
['XML', 'formula_stress_test.xls.xml']
].forEach(function(w) {
it('should be able to write ' + w[0] + ' files from xlsjs', function() {
var xls = XLSJS.readFile('./test_files/' + w[1]);
X.writeFile(xls, './tmp/' + w[1] + '.xlsx');
X.writeFile(xls, './tmp/' + w[1] + '.xlsb');
});
});
});
});
describe('invalid files', function() {
describe('parse', function() { [
['passwords', 'excel-reader-xlsx_error03.xlsx'],
['XLS files', 'roo_type_excel.xlsx'],
['ODS files', 'roo_type_openoffice.xlsx'],
['DOC files', 'word_doc.doc']
].forEach(function(w) { it('should fail on ' + w[0], function() { assert.throws(function() { X.readFile(dir + w[1]); }); }); });
});
describe('write', function() {
it('should pass', function() { X.write(X.readFile(paths.fst1), {type:'binary'}); });
it('should pass if a sheet is missing', function() {
var wb = X.readFile(paths.fst1); delete wb.Sheets[wb.SheetNames[0]];
X.read(X.write(wb, {type:'binary'}), {type:'binary'});
});
it('should fail if SheetNames is missing', function() {
var wb = X.readFile(paths.fst1);
assert.throws(function() {
delete wb.SheetNames;
X.write(wb, {type:'binary'});
});
});
it('should fail if Sheets is missing', function() {
var wb = X.readFile(paths.fst1);
assert.throws(function() {
delete wb.Sheets;
X.write(wb, {type:'binary'});
});
});
});
});

View File

@ -1 +1,3 @@
calendar_stress_test.xlsx
large_strings.xlsb
time_stress_test_1.xlsb

View File

@ -7,7 +7,6 @@ comments_stress_test.xlsb
custom_properties.xlsb
formula_stress_test.xlsb
hyperlink_stress_test_2011.xlsb
large_strings.xlsb
merge_cells.xlsb
named_ranges_2011.xlsb
number_format.xlsb
@ -15,7 +14,6 @@ pivot_table_named_range.xlsb
pivot_table_test.xlsb
rich_text_stress.xlsb
text_and_numbers.xlsb
time_stress_test_1.xlsb
xlsx-stream-d-date-cell.xlsb
2013/apachepoi_29982.xls.xlsb
2013/apachepoi_43251.xls.xlsb

1353
xlsx.js

File diff suppressed because one or more lines are too long