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:
parent
b645f6ef98
commit
d15b81e0e9
1
.gitignore
vendored
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
|
||||
|
2
LICENSE
2
LICENSE
@ -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.
|
||||
|
8
Makefile
8
Makefile
@ -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 > $@
|
||||
|
||||
|
44
README.md
44
README.md
@ -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
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 = {
|
||||
'"': '"',
|
||||
''': "'",
|
||||
@ -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
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
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
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
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
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 |