Skip to content

Commit

Permalink
XLML write support
Browse files Browse the repository at this point in the history
- XLML write (fixes #173 h/t @SheetJSDev)
- removed old iteration style from README (see #592)
- CellXF & StyleXF fields (fixes #414 h/t @ronnywang)
  • Loading branch information
SheetJSDev committed Mar 14, 2017
1 parent 456ab63 commit 7cb978b
Show file tree
Hide file tree
Showing 17 changed files with 668 additions and 96 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -5,6 +5,10 @@ but not limited to API changes and file location changes. Minor behavioral
changes may not be included if they are not expected to break existing code.


## Unreleased

* XLML property names are more closely mapped to the XLSX equivalent

## 0.9.2 (2017-03-13)

* Removed stale TypeScript definition files. Flowtype comments are used in the
Expand Down
3 changes: 3 additions & 0 deletions Makefile
Expand Up @@ -140,6 +140,9 @@ misc/coverage.html: $(TARGET) test.js
coveralls: ## Coverage Test + Send to coveralls.io
mocha --require blanket --reporter mocha-lcov-reporter -t 20000 | node ./node_modules/coveralls/bin/coveralls.js

.PHONY: readme
readme: ## Update README Table of Contents
markdown-toc -i README.md

.PHONY: help
help:
Expand Down
26 changes: 6 additions & 20 deletions README.md
Expand Up @@ -274,24 +274,10 @@ var worksheet = workbook.Sheets[first_sheet_name];
var desired_cell = worksheet[address_of_cell];

/* Get the value */
var desired_value = desired_cell.v;
var desired_value = (desired_cell ? desired_cell.v : undefined);
```

This example iterates through every nonempty of every sheet and dumps values:

```js
var sheet_name_list = workbook.SheetNames;
sheet_name_list.forEach(function(y) { /* iterate through sheets */
var worksheet = workbook.Sheets[y];
for (var z in worksheet) {
/* all keys that do not begin with "!" correspond to cell addresses */
if(z[0] === '!') continue;
console.log(y + "!" + z + "=" + JSON.stringify(worksheet[z].v));
}
});
```

Complete examples:
**Complete examples:**

- <http://oss.sheetjs.com/js-xlsx/> HTML5 File API / Base64 Text / Web Workers

Expand Down Expand Up @@ -359,7 +345,7 @@ function s2ab(s) {
saveAs(new Blob([s2ab(wbout)],{type:"application/octet-stream"}), "test.xlsx");
```

Complete examples:
**Complete examples:**

- <http://sheetjs.com/demos/writexlsx.html> generates a simple file
- <http://git.io/WEK88Q> writing an array of arrays in nodejs
Expand Down Expand Up @@ -393,7 +379,7 @@ Write options are described in the [Writing Options](#writing-options) section.

Utilities are available in the `XLSX.utils` object:

Exporting:
**Exporting:**

- `sheet_to_json` converts a worksheet object to an array of JSON objects.
`sheet_to_row_object_array` is an alias that will be removed in the future.
Expand All @@ -403,7 +389,7 @@ Exporting:
Exporters are described in the [Utility Functions](#utility-functions) section.


Cell and cell address manipulation:
**Cell and cell address manipulation:**

- `format_cell` generates the text value for a cell (using number formats)
- `{en,de}code_{row,col}` convert between 0-indexed rows/cols and A1 forms.
Expand Down Expand Up @@ -808,7 +794,7 @@ Despite the library name `xlsx`, it supports numerous spreadsheet file formats:
| **Excel Worksheet/Workbook Formats** |:-----:|:-----:|
| Excel 2007+ XML Formats (XLSX/XLSM) | :o: | :o: |
| Excel 2007+ Binary Format (XLSB BIFF12) | :o: | :o: |
| Excel 2003-2004 XML Format (XML "SpreadsheetML") | :o: | |
| Excel 2003-2004 XML Format (XML "SpreadsheetML") | :o: | :o: |
| Excel 97-2004 (XLS BIFF8) | :o: | |
| Excel 5.0/95 (XLS BIFF5) | :o: | |
| Excel 4.0 (XLS/XLW BIFF4) | :o: | |
Expand Down
20 changes: 18 additions & 2 deletions bin/xlsx.njs
Expand Up @@ -20,6 +20,7 @@ program
.option('-X, --xlsx', 'emit XLSX to <sheetname> or <file>.xlsx')
.option('-Y, --ods', 'emit ODS to <sheetname> or <file>.ods')
.option('-2, --biff2','emit XLS to <sheetname> or <file>.xls (BIFF2)')
.option('-6, --xlml', 'emit SSML to <sheetname> or <file>.xls (2003 XML)')
.option('-T, --fods', 'emit FODS to <sheetname> or <file>.xls (Flat ODS)')

.option('-S, --formulae', 'print formulae')
Expand All @@ -33,7 +34,7 @@ program
.option('--sst', 'generate shared string table for XLS* formats')
.option('--compress', 'use compression when writing XLSX/M/B and ODS')
.option('--perf', 'do not generate output')
.option('--all', 'parse everything; XLS[XMB] write as much as possible')
.option('--all', 'parse everything; write as much as possible')
.option('--dev', 'development mode')
.option('--read', 'read but do not print out contents')
.option('-q, --quiet', 'quiet mode');
Expand All @@ -46,6 +47,10 @@ program.on('--help', function() {

/* output formats, update list with full option name */
var workbook_formats = ['xlsx', 'xlsm', 'xlsb', 'ods', 'fods'];
/* flag, bookType, default ext */
var wb_formats_2 = [
['xlml', 'xlml', 'xls']
];
program.parse(process.argv);

/* see https://github.com/SheetJS/j/issues/4 */
Expand Down Expand Up @@ -81,11 +86,16 @@ var opts = {}, wb/*:?Workbook*/;
if(program.listSheets) opts.bookSheets = true;
if(program.sheetRows) opts.sheetRows = program.sheetRows;
if(program.password) opts.password = program.password;
if(program.xlsx || program.xlsm || program.xlsb) {
var seen = false;
function wb_fmt() {
seen = true;
opts.cellFormula = true;
opts.cellNF = true;
if(program.output) sheetname = program.output;
}
workbook_formats.forEach(function(m) { if(program[m]) { wb_fmt(); } });
wb_formats_2.forEach(function(m) { if(program[m[0]]) { wb_fmt(); } });
if(seen);
else if(program.formulae) opts.cellFormula = true;
else opts.cellFormula = false;

Expand Down Expand Up @@ -125,6 +135,12 @@ workbook_formats.forEach(function(m) { if(program[m]) {
process.exit(0);
} });

wb_formats_2.forEach(function(m) { if(program[m[0]]) {
wopts.bookType = m[1];
X.writeFile(wb, sheetname || ((filename || "") + "." + m[2]), wopts);
process.exit(0);
} });

var target_sheet = sheetname || '';
if(target_sheet === '') {
if(program.sheetIndex < (wb.SheetNames||[]).length) target_sheet = wb.SheetNames[program.sheetIndex];
Expand Down
8 changes: 8 additions & 0 deletions bits/22_xmlutils.js
Expand Up @@ -52,6 +52,7 @@ function escapexml(text/*:string*/)/*:string*/{
var s = text + '';
return s.replace(decregex, function(y) { return rencoding[y]; }).replace(charegex,function(s) { return "_x" + ("000"+s.charCodeAt(0).toString(16)).slice(-4) + "_";});
}
function escapexmltag(text/*:string*/)/*:string*/{ return escapexml(text).replace(/ /g,"_x0020_"); }

/* TODO: handle codepages */
var xlml_fixstr/*:StringConv*/ = (function() {
Expand Down Expand Up @@ -178,3 +179,10 @@ XMLNS.main = [
'http://schemas.microsoft.com/office/excel/2006/2'
];

var XLMLNS = ({
'o': 'urn:schemas-microsoft-com:office:office',
'x': 'urn:schemas-microsoft-com:office:excel',
'ss': 'urn:schemas-microsoft-com:office:spreadsheet',
'dt': 'uuid:C2F41010-65B3-11d1-A29F-00AA00C14882',
'html': 'http://www.w3.org/TR/REC-html40'
}/*:any*/);
59 changes: 59 additions & 0 deletions bits/36_xlsprops.js
Expand Up @@ -2,7 +2,66 @@ function xlml_set_prop(Props, tag/*:string*/, val) {
/* TODO: Normalize the properties */
switch(tag) {
case 'Description': tag = 'Comments'; break;
case 'Created': tag = 'CreatedDate'; break;
case 'LastSaved': tag = 'ModifiedDate'; break;
}
Props[tag] = val;
}

var XLMLDocumentProperties = [
['Title', 'Title'],
['Subject', 'Subject'],
['Author', 'Author'],
['Keywords', 'Keywords'],
['Comments', 'Description'],
['LastAuthor', 'LastAuthor'],
['CreatedDate', 'Created', 'date'],
['ModifiedDate', 'LastSaved', 'date'],
['Category', 'Category'],
['Manager', 'Manager'],
['Company', 'Company'],
['AppVersion', 'Version']
];

/* TODO: verify */
function xlml_write_docprops(Props) {
var T = 'DocumentProperties';
var o = [];
XLMLDocumentProperties.forEach(function(p) {
if(!Props[p[0]]) return;
var m = Props[p[0]];
switch(p[2]) {
case 'date': m = new Date(m).toISOString(); break;
}
o.push(writetag(p[1], m));
});
return '<' + T + ' xmlns="' + XLMLNS.o + '">' + o.join("") + '</' + T + '>';
}
function xlml_write_custprops(Props, Custprops) {
var T = 'CustomDocumentProperties';
var o = [];
if(Props) keys(Props).forEach(function(k) {
/*:: if(!Props) return; */
if(!Props.hasOwnProperty(k)) return;
for(var i = 0; i < XLMLDocumentProperties.length; ++i)
if(k == XLMLDocumentProperties[i][0]) return;
var m = Props[k];
var t = "string";
if(typeof m == 'number') { t = "float"; m = String(m); }
else if(m === true || m === false) { t = "boolean"; m = m ? "1" : "0"; }
else m = String(m);
o.push(writextag(escapexmltag(k), m, {"dt:dt":t}));
});
if(Custprops) keys(Custprops).forEach(function(k) {
/*:: if(!Custprops) return; */
if(!Custprops.hasOwnProperty(k)) return;
var m = Custprops[k];
var t = "string";
if(typeof m == 'number') { t = "float"; m = String(m); }
else if(m === true || m === false) { t = "boolean"; m = m ? "1" : "0"; }
else if(m instanceof Date) { t = "dateTime.tz"; m = m.toISOString(); }
else m = String(m);
o.push(writextag(escapexmltag(k), m, {"dt:dt":t}));
});
return '<' + T + ' xmlns="' + XLMLNS.o + '">' + o.join("") + '</' + T + '>';
}
32 changes: 31 additions & 1 deletion bits/39_xlsbiff.js
Expand Up @@ -316,14 +316,44 @@ function parse_MulRk(blob, length) {
return {r:rw, c:col, C:lastcol, rkrec:rkrecs};
}

/* 2.5.20 2.5.249 TODO */
/* 2.5.20 2.5.249 TODO: interpret values here */
function parse_CellStyleXF(blob, length, style) {
var o = {};
var a = blob.read_shift(4), b = blob.read_shift(4);
var c = blob.read_shift(4), d = blob.read_shift(2);
o.patternType = XLSFillPattern[c >> 26];

o.alc = a & 0x07;
o.fWrap = (a >> 3) & 0x01;
o.alcV = (a >> 4) & 0x07;
o.fJustLast = (a >> 7) & 0x01;
o.trot = (a >> 8) & 0xFF;
o.cIndent = (a >> 16) & 0x0F;
o.fShrinkToFit = (a >> 20) & 0x01;
o.iReadOrder = (a >> 22) & 0x02;
o.fAtrNum = (a >> 26) & 0x01;
o.fAtrFnt = (a >> 27) & 0x01;
o.fAtrAlc = (a >> 28) & 0x01;
o.fAtrBdr = (a >> 29) & 0x01;
o.fAtrPat = (a >> 30) & 0x01;
o.fAtrProt = (a >> 31) & 0x01;

o.dgLeft = b & 0x0F;
o.dgRight = (b >> 4) & 0x0F;
o.dgTop = (b >> 8) & 0x0F;
o.dgBottom = (b >> 12) & 0x0F;
o.icvLeft = (b >> 16) & 0x7F;
o.icvRight = (b >> 23) & 0x7F;
o.grbitDiag = (b >> 30) & 0x03;

o.icvTop = c & 0x7F;
o.icvBottom = (c >> 7) & 0x7F;
o.icvDiag = (c >> 14) & 0x7F;
o.dgDiag = (c >> 21) & 0x0F;

o.icvFore = d & 0x7F;
o.icvBack = (d >> 7) & 0x7F;
o.fsxButton = (d >> 14) & 0x01;
return o;
}
function parse_CellXF(blob, length) {return parse_CellStyleXF(blob,length,0);}
Expand Down
13 changes: 12 additions & 1 deletion bits/61_fcommon.js
Expand Up @@ -16,8 +16,19 @@ var rc_to_a1 = (function(){
};
})();

/* no defined name can collide with a valid cell address A1:XFD1048576 ... except LOG10! */
var crefregex = /(^|[^._A-Z0-9])([$]?)([A-Z]{1,2}|[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D])([$]?)([1-9]\d{0,5}|10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6])(?![_.\(A-Za-z0-9])/g;
var a1_to_rc =(function(){
return function a1_to_rc(fstr, base) {
return fstr.replace(crefregex, function($0, $1, $2, $3, $4, $5, off, str) {
/* TODO: handle fixcol / fixrow */
var c = decode_col($3) - base.c;
var r = decode_row($5) - base.r;
return $1 + "R" + (r == 0 ? "" : "[" + r + "]") + "C" + (c == 0 ? "" : "[" + c + "]");
});
};
})();

/* no defined name can collide with a valid cell address A1:XFD1048576 ... except LOG10! */
function shift_formula_str(f/*:string*/, delta/*:Cell*/)/*:string*/ {
return f.replace(crefregex, function($0, $1, $2, $3, $4, $5, off, str) {
return $1+($2=="$" ? $2+$3 : encode_col(decode_col($3)+delta.c))+($4=="$" ? $4+$5 : encode_row(decode_row($5) + delta.r));
Expand Down
82 changes: 80 additions & 2 deletions bits/75_xlml.js
Expand Up @@ -728,7 +728,85 @@ function parse_xlml(data, opts)/*:Workbook*/ {
}

/* TODO */
function write_xlml(wb, opts)/*:string*/ {
var o = [XML_HEADER];
function write_props_xlml(wb, opts) {
var o = [];
/* DocumentProperties */
if(wb.Props) o.push(xlml_write_docprops(wb.Props));
/* CustomDocumentProperties */
if(wb.Custprops) o.push(xlml_write_custprops(wb.Props, wb.Custprops));
return o.join("");
}
/* TODO */
function write_wb_xlml(wb, opts) {
/* OfficeDocumentSettings */
/* ExcelWorkbook */
return "";
}
/* TODO */
function write_sty_xlml(wb, opts)/*:string*/ {
/* Styles */
return "";
}
/* TODO */
function write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr)/*:string*/{
if(!cell || cell.v === undefined) return "<Cell></Cell>";

var attr = {};
if(cell.f) attr["ss:Formula"] = "=" + escapexml(a1_to_rc(cell.f, addr));

var t = "", p = "";
switch(cell.t) {
case 'n': t = 'Number'; p = String(cell.v); break;
case 'b': t = 'Boolean'; p = (cell.v ? "1" : "0"); break;
case 'e': t = 'Error'; p = BErr[cell.v]; break;
case 'd': t = 'DateTime'; p = new Date(cell.v).toISOString(); break;
default: t = 'String'; p = escapexml(cell.v||"");
}
var m = '<Data ss:Type="' + t + '">' + p + '</Data>';

return writextag("Cell", m, attr);
}
/* TODO */
function write_ws_xlml_table(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workbook*/)/*:string*/ {
if(!ws['!ref']) return "";
var range = safe_decode_range(ws['!ref']);
var o = [];
for(var R = range.s.r; R <= range.e.r; ++R) {
var row = ["<Row>"];
for(var C = range.s.c; C <= range.e.c; ++C) {
var addr = {r:R,c:C};
var ref = encode_cell(addr), cell = ws[ref];
row.push(write_ws_xlml_cell(ws[ref], ref, ws, opts, idx, wb, addr));
}
row.push("</Row>");
o.push(row.join(""));
}
return o.join("");
}
function write_ws_xlml(idx/*:number*/, opts, wb/*:Workbook*/)/*:string*/ {
var o = [];
var s = wb.SheetNames[idx];
var ws = wb.Sheets[s];

/* Table */
var t = ws ? write_ws_xlml_table(ws, opts, idx, wb) : "";
if(t.length > 0) o.push("<Table>" + t + "</Table>");
/* WorksheetOptions */
return o.join("");
}
function write_xlml(wb, opts)/*:string*/ {
var d = [];
d.push(write_props_xlml(wb, opts));
d.push(write_wb_xlml(wb, opts));
d.push(write_sty_xlml(wb, opts));
for(var i = 0; i < wb.SheetNames.length; ++i)
d.push(writextag("Worksheet", write_ws_xlml(i, opts, wb), {"ss:Name":escapexml(wb.SheetNames[i])}));
return XML_HEADER + writextag("Workbook", d.join(""), {
'xmlns': XLMLNS.ss,
'xmlns:o': XLMLNS.o,
'xmlns:x': XLMLNS.x,
'xmlns:ss': XLMLNS.ss,
'xmlns:dt': XLMLNS.dt,
'xmlns:html': XLMLNS.html
});
}

0 comments on commit 7cb978b

Please sign in to comment.