Read/Write Comments
- XLSB/XLSX/XLML write comments - BIFF2 write date cells (see #586 h/t @roccomuso) - ODS read cell comments (fixed #315 h/t @yisk) - XLSX / XLSB emit empty comments when necessary - changed node detection logic (fixes #614 h/t @mhenris) - fixes #605 h/t @ylbweb - fixes #233 h/t @hanxi @osecki - fixes #192 h/t @abarik1981 @stla - fixes #183 h/t @aravindkoneru @ryangallen
This commit is contained in:
parent
b93569badf
commit
3a310bd3a7
@ -7,6 +7,8 @@ changes may not be included if they are not expected to break existing code.
|
||||
|
||||
## Unreleased
|
||||
|
||||
* default output format changed to XLSB
|
||||
* comment text line endings are now normalized
|
||||
|
||||
## 0.9.7 (2017-03-28)
|
||||
|
||||
|
22
README.md
22
README.md
@ -46,6 +46,7 @@ with a unified JS representation, and ES3/ES5 browser compatibility back to IE6.
|
||||
+ [Formulae](#formulae)
|
||||
+ [Column Properties](#column-properties)
|
||||
+ [Hyperlinks](#hyperlinks)
|
||||
+ [Cell Comments](#cell-comments)
|
||||
+ [Sheet Visibility](#sheet-visibility)
|
||||
- [Parsing Options](#parsing-options)
|
||||
* [Input Type](#input-type)
|
||||
@ -756,6 +757,23 @@ ws['A3'].l = { Target:"http://sheetjs.com", Tooltip:"Find us @ SheetJS.com!" };
|
||||
Note that Excel does not automatically style hyperlinks -- they will generally
|
||||
be displayed as normal text.
|
||||
|
||||
#### Cell Comments
|
||||
|
||||
Cell comments are objects stored in the `c` array of cell objects. The actual
|
||||
contents of the comment are split into blocks based on the comment author. The
|
||||
`a` field of each comment object is the author of the comment and the `t` field
|
||||
is the plaintext representation.
|
||||
|
||||
For example, the following snippet appends a cell comment into cell `A1`:
|
||||
|
||||
```js
|
||||
if(!ws.A1.c) ws.A1.c = [];
|
||||
ws.A1.c.push({a:"SheetJS", t:"I'm a little comment, short and stout!"});
|
||||
```
|
||||
|
||||
Note: XLSB enforces a 54 character limit on the Author name. Names longer than
|
||||
54 characters may cause issues with other formats.
|
||||
|
||||
#### Sheet Visibility
|
||||
|
||||
Excel enables hiding sheets in the lower tab bar. The sheet data is stored in
|
||||
@ -780,7 +798,7 @@ With <https://rawgit.com/SheetJS/test_files/master/sheet_visibility.xlsx>:
|
||||
```
|
||||
|
||||
Non-Excel formats do not support the Very Hidden state. The best way to test
|
||||
if a sheet is visible is to check if the `Hidden` property is logical negation:
|
||||
if a sheet is visible is to check if the `Hidden` property is logical truth:
|
||||
|
||||
```js
|
||||
> wb.Workbook.Sheets.map(function(x) { return [x.name, !x.Hidden] })
|
||||
@ -856,7 +874,7 @@ file but Excel will know how to handle it. This library applies similar logic:
|
||||
| `0x50` | ZIP Archive | XLSB or XLSX/M or ODS or UOS2 or plaintext |
|
||||
| `0x49` | Plain Text | SYLK or plaintext |
|
||||
| `0x54` | Plain Text | DIF or plaintext |
|
||||
| `0xFE` | UTF8 Text | SpreadsheetML or Flat ODS or UOS1 or plaintext |
|
||||
| `0xFE` | UTF16 Encoded | SpreadsheetML or Flat ODS or UOS1 or plaintext |
|
||||
|
||||
DBF files are detected based on the first byte as well as the third and fourth
|
||||
bytes (corresponding to month and day of the file date)
|
||||
|
@ -1,4 +1,4 @@
|
||||
var has_buf = (typeof Buffer !== 'undefined');
|
||||
var has_buf = (typeof Buffer !== 'undefined' && typeof process !== 'undefined' && typeof process.versions !== 'undefined' && process.versions.node);
|
||||
|
||||
function new_raw_buf(len/*:number*/) {
|
||||
/* jshint -W056 */
|
||||
|
@ -60,6 +60,9 @@ var xlml_fixstr/*:StringConv*/ = (function() {
|
||||
function entrepl($$/*:string*/,$1/*:string*/)/*:string*/ { return String.fromCharCode(parseInt($1,10)); }
|
||||
return function xlml_fixstr(str/*:string*/)/*:string*/ { return str.replace(entregex,entrepl); };
|
||||
})();
|
||||
var xlml_unfixstr/*:StringConv*/ = (function() {
|
||||
return function xlml_unfixstr(str/*:string*/)/*:string*/ { return str.replace(/(\r\n|[\r\n])/g,"\ "); };
|
||||
})();
|
||||
|
||||
function parsexmlbool(value/*:any*/, tag/*:?string*/)/*:boolean*/ {
|
||||
switch(value) {
|
||||
@ -184,5 +187,7 @@ var XLMLNS = ({
|
||||
'x': 'urn:schemas-microsoft-com:office:excel',
|
||||
'ss': 'urn:schemas-microsoft-com:office:spreadsheet',
|
||||
'dt': 'uuid:C2F41010-65B3-11d1-A29F-00AA00C14882',
|
||||
'mv': 'http://macVmlSchemaUri',
|
||||
'v': 'urn:schemas-microsoft-com:vml',
|
||||
'html': 'http://www.w3.org/TR/REC-html40'
|
||||
}/*:any*/);
|
||||
|
@ -10,7 +10,9 @@ function recordhopper(data, cb/*:RecordHopperCB*/, opts/*:?any*/) {
|
||||
tmpbyte = data.read_shift(1);
|
||||
length = tmpbyte & 0x7F;
|
||||
for(cntbyte = 1; cntbyte <4 && (tmpbyte & 0x80); ++cntbyte) length += ((tmpbyte = data.read_shift(1)) & 0x7F)<<(7*cntbyte);
|
||||
var tgt = data.l + length;
|
||||
var d = R.f(data, length, opts);
|
||||
data.l = tgt;
|
||||
if(cb(d, R, RT)) return;
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ function parse_RichStr(data, length/*:number*/)/*:XLString*/ {
|
||||
for(var i = 0; i != dwSizeStrRun; ++i) rgsStrRun.push(parse_StrRun(data));
|
||||
z.r = rgsStrRun;
|
||||
}
|
||||
else z.r = "<t>" + escapexml(str) + "</t>";
|
||||
else z.r = [{ich:0, ifnt:0}];
|
||||
//if((flags & 2) !== 0) { /* fExtStr */
|
||||
// /* TODO: phonetic string */
|
||||
//}
|
||||
@ -26,10 +26,10 @@ function parse_RichStr(data, length/*:number*/)/*:XLString*/ {
|
||||
}
|
||||
function write_RichStr(str/*:XLString*/, o/*:?Block*/)/*:Block*/ {
|
||||
/* TODO: formatted string */
|
||||
if(o == null) o = new_buf(5+2*str.t.length);
|
||||
var _null = false; if(o == null) { _null = true; o = new_buf(15+4*str.t.length); }
|
||||
o.write_shift(1,0);
|
||||
write_XLWideString(str.t, o);
|
||||
return o;
|
||||
return _null ? o.slice(0, o.l) : o;
|
||||
}
|
||||
|
||||
/* [MS-XLSB] 2.5.9 */
|
||||
@ -59,10 +59,10 @@ function parse_XLNullableWideString(data)/*:string*/ {
|
||||
return cchCharacters === 0 || cchCharacters === 0xFFFFFFFF ? "" : data.read_shift(cchCharacters, 'dbcs');
|
||||
}
|
||||
function write_XLNullableWideString(data/*:string*/, o) {
|
||||
if(!o) o = new_buf(127);
|
||||
var _null = false; if(o == null) { _null = true; o = new_buf(127); }
|
||||
o.write_shift(4, data.length > 0 ? data.length : 0xFFFFFFFF);
|
||||
if(data.length > 0) o.write_shift(0, data, 'dbcs');
|
||||
return o;
|
||||
return _null ? o.slice(0, o.l) : o;
|
||||
}
|
||||
|
||||
/* [MS-XLSB] 2.5.168 */
|
||||
@ -71,10 +71,10 @@ function parse_XLWideString(data)/*:string*/ {
|
||||
return cchCharacters === 0 ? "" : data.read_shift(cchCharacters, 'dbcs');
|
||||
}
|
||||
function write_XLWideString(data/*:string*/, o) {
|
||||
if(o == null) o = new_buf(4+2*data.length);
|
||||
var _null = false; if(o == null) { _null = true; o = new_buf(4+2*data.length); }
|
||||
o.write_shift(4, data.length);
|
||||
if(data.length > 0) o.write_shift(0, data, 'dbcs');
|
||||
return o;
|
||||
return _null ? o.slice(0, o.l) : o;
|
||||
}
|
||||
|
||||
/* [MS-XLSB] 2.5.165 */
|
||||
|
@ -23,10 +23,6 @@ var ct2type/*{[string]:string}*/ = ({
|
||||
"application/vnd.openxmlformats-officedocument.customXmlProperties+xml": "TODO",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.customProperty": "TODO",
|
||||
|
||||
/* Comments */
|
||||
"application/vnd.ms-excel.comments": "comments",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml": "comments",
|
||||
|
||||
/* PivotTable */
|
||||
"application/vnd.ms-excel.pivotTable": "TODO",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml": "TODO",
|
||||
@ -158,6 +154,10 @@ var CT_LIST = (function(){
|
||||
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml",
|
||||
xlsb: "application/vnd.ms-excel.sharedStrings"
|
||||
},
|
||||
comments: { /* Comments */
|
||||
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml",
|
||||
xlsb: "application/vnd.ms-excel.comments"
|
||||
},
|
||||
sheets: { /* Worksheet */
|
||||
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml",
|
||||
xlsb: "application/vnd.ms-excel.worksheet"
|
||||
@ -226,6 +226,7 @@ var CTYPE_XML_ROOT = writextag('Types', null, {
|
||||
var CTYPE_DEFAULTS = [
|
||||
['xml', 'application/xml'],
|
||||
['bin', 'application/vnd.ms-excel.sheet.binary.macroEnabled.main'],
|
||||
['vml', 'application/vnd.openxmlformats-officedocument.vmlDrawing'],
|
||||
/* from test files */
|
||||
['bmp', 'image/bmp'],
|
||||
['png', 'image/png'],
|
||||
@ -276,7 +277,7 @@ function write_ct(ct, opts)/*:string*/ {
|
||||
['strs', 'styles'].forEach(f1);
|
||||
['coreprops', 'extprops', 'custprops'].forEach(f3);
|
||||
f3('vba');
|
||||
f3('comments');
|
||||
f2('comments');
|
||||
if(o.length>2){ o[o.length] = ('</Types>'); o[1]=o[1].replace("/>",">"); }
|
||||
return o.join("");
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ var RELS = ({
|
||||
WB: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
|
||||
SHEET: "http://sheetjs.openxmlformats.org/officeDocument/2006/relationships/officeDocument",
|
||||
HLINK: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink",
|
||||
VML: "http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing",
|
||||
VBA: "http://schemas.microsoft.com/office/2006/relationships/vbaProject"
|
||||
}/*:any*/);
|
||||
|
||||
|
@ -359,3 +359,56 @@ var DIF = (function() {
|
||||
from_sheet: sheet_to_dif
|
||||
};
|
||||
})();
|
||||
|
||||
var PRN = (function() {
|
||||
function prn_to_aoa(d/*:RawData*/, opts)/*:AOA*/ {
|
||||
switch(opts.type) {
|
||||
case 'base64': return prn_to_aoa_str(Base64.decode(d), opts);
|
||||
case 'binary': return prn_to_aoa_str(d, opts);
|
||||
case 'buffer': return prn_to_aoa_str(d.toString('binary'), opts);
|
||||
case 'array': return prn_to_aoa_str(cc2str(d), opts);
|
||||
}
|
||||
throw new Error("Unrecognized type " + opts.type);
|
||||
}
|
||||
|
||||
function set_text_arr(data/*:string*/, arr/*:AOA*/, R/*:number*/, C/*:number*/) {
|
||||
if(data === 'TRUE') arr[R][C] = true;
|
||||
else if(data === 'FALSE') arr[R][C] = false;
|
||||
else if(+data == +data) arr[R][C] = +data;
|
||||
else if(data !== "") arr[R][C] = data;
|
||||
}
|
||||
|
||||
function prn_to_aoa_str(f/*:string*/, opts)/*:AOA*/ {
|
||||
var arr/*:AOA*/ = ([]/*:any*/);
|
||||
if(!f || f.length === 0) return arr;
|
||||
var lines = f.split(/[\r\n]/);
|
||||
var L = lines.length - 1;
|
||||
while(L >= 0 && lines[L].length === 0) --L;
|
||||
var start = 10, idx = 0;
|
||||
var R = 0;
|
||||
for(; R <= L; ++R) {
|
||||
idx = lines[R].indexOf(" ");
|
||||
if(idx == -1) idx = lines[R].length; else idx++;
|
||||
start = Math.max(start, idx);
|
||||
}
|
||||
for(R = 0; R <= L; ++R) {
|
||||
arr[R] = [];
|
||||
/* TODO: confirm that widths are always 10 */
|
||||
var C = 0;
|
||||
set_text_arr(lines[R].slice(0, start).trim(), arr, R, C);
|
||||
for(C = 1; C <= (lines[R].length - start)/10 + 1; ++C)
|
||||
set_text_arr(lines[R].slice(start+(C-1)*10,start+C*10).trim(),arr,R,C);
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
function prn_to_sheet(str/*:string*/, opts)/*:Worksheet*/ { return aoa_to_sheet(prn_to_aoa(str, opts), opts); }
|
||||
|
||||
function prn_to_workbook(str/*:string*/, opts)/*:Workbook*/ { return sheet_to_workbook(prn_to_sheet(str, opts), opts); }
|
||||
|
||||
return {
|
||||
to_workbook: prn_to_workbook,
|
||||
to_sheet: prn_to_sheet
|
||||
};
|
||||
})();
|
||||
|
||||
|
@ -61,7 +61,7 @@ function cycle_width(collw) { return char2width(px2char(width2px(collw))); }
|
||||
/* XLSX/XLSB/XLS specify width in units of MDW */
|
||||
function find_mdw_colw(collw) {
|
||||
var delta = Infinity, _MDW = MIN_MDW;
|
||||
for(MDW=MIN_MDW; MDW<MAX_MDW; ++MDW) if(Math.abs(collw - cycle_width(collw)) < delta) { delta = Math.abs(collw - cycle_width(collw)); _MDW = MDW; }
|
||||
for(MDW=MIN_MDW; MDW<MAX_MDW; ++MDW) if(Math.abs(collw - cycle_width(collw)) <= delta) { delta = Math.abs(collw - cycle_width(collw)); _MDW = MDW; }
|
||||
MDW = _MDW;
|
||||
}
|
||||
/* XLML specifies width in terms of pixels */
|
||||
|
43
bits/55_vml.js
Normal file
43
bits/55_vml.js
Normal file
@ -0,0 +1,43 @@
|
||||
/* L.5.5.2 SpreadsheetML Comments + VML Schema */
|
||||
var _shapeid = 1024;
|
||||
function write_comments_vml(rId, comments) {
|
||||
var csize = [21600, 21600];
|
||||
/* L.5.2.1.2 Path Attribute */
|
||||
var bbox = ["m0,0l0",csize[1],csize[0],csize[1],csize[0],"0xe"].join(",");
|
||||
var o = [
|
||||
writextag("xml", null, { 'xmlns:v': XLMLNS.v, 'xmlns:o': XLMLNS.o, 'xmlns:x': XLMLNS.x, 'xmlns:mv': XLMLNS.mv }).replace(/\/>/,">"),
|
||||
writextag("o:shapelayout", writextag("o:idmap", null, {'v:ext':"edit", 'data':rId}), {'v:ext':"edit"}),
|
||||
writextag("v:shapetype", [
|
||||
writextag("v:stroke", null, {joinstyle:"miter"}),
|
||||
writextag("v:path", null, {gradientshapeok:"t", 'o:connecttype':"rect"})
|
||||
].join(""), {id:"_x0000_t202", 'o:spt':202, coordsize:csize.join(","),path:bbox})
|
||||
];
|
||||
while(_shapeid < rId * 1000) _shapeid += 1000;
|
||||
|
||||
comments.map(function(x) { return decode_cell(x[0]); }).forEach(function(c,i) { o = o.concat([
|
||||
'<v:shape' + wxt_helper({
|
||||
id:'_x0000_s' + (++_shapeid),
|
||||
type:"#_x0000_t202",
|
||||
style:"position:absolute; margin-left:80pt;margin-top:5pt;width:104pt;height:64pt;z-index:10;visibility:hidden",
|
||||
fillcolor:"#ECFAD4",
|
||||
strokecolor:"#edeaa1"
|
||||
}) + '>',
|
||||
writextag('v:fill', writextag("o:fill", null, {type:"gradientUnscaled", 'v:ext':"view"}), {'color2':"#BEFF82", 'angle':"-180", 'type':"gradient"}),
|
||||
writextag("v:shadow", null, {on:"t", 'obscured':"t"}),
|
||||
writextag("v:path", null, {'o:connecttype':"none"}),
|
||||
'<v:textbox><div style="text-align:left"></div></v:textbox>',
|
||||
'<x:ClientData ObjectType="Note">',
|
||||
'<x:MoveWithCells/>',
|
||||
'<x:SizeWithCells/>',
|
||||
/* Part 4 19.4.2.3 Anchor (Anchor) */
|
||||
writetag('x:Anchor', [c.c, 0, c.r, 0, c.c+3, 100, c.r+5, 100].join(",")),
|
||||
writetag('x:AutoFill', "False"),
|
||||
writetag('x:Row', String(c.r)),
|
||||
writetag('x:Column', String(c.c)),
|
||||
'</x:ClientData>',
|
||||
'</v:shape>'
|
||||
]); });
|
||||
o.push('</xml>');
|
||||
return o.join("");
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
RELS.CMNT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments";
|
||||
|
||||
function parse_comments(zip, dirComments, sheets, sheetRels, opts) {
|
||||
for(var i = 0; i != dirComments.length; ++i) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
/* 18.7.3 CT_Comment */
|
||||
/* 18.7 Comments */
|
||||
function parse_comments_xml(data/*:string*/, opts)/*:Array<Comment>*/ {
|
||||
/* 18.7.6 CT_Comments */
|
||||
if(data.match(/<(?:\w+:)?comments *\/>/)) return [];
|
||||
var authors = [];
|
||||
var commentList = [];
|
||||
@ -15,19 +16,43 @@ function parse_comments_xml(data/*:string*/, opts)/*:Array<Comment>*/ {
|
||||
var cm = x.match(/<(?:\w+:)?comment[^>]*>/);
|
||||
if(!cm) return;
|
||||
var y = parsexmltag(cm[0]);
|
||||
var comment/*:Comment*/ = ({ author: y.authorId && authors[y.authorId] ? authors[y.authorId] : undefined, ref: y.ref, guid: y.guid }/*:any*/);
|
||||
var comment/*:Comment*/ = ({ author: y.authorId && authors[y.authorId] ? authors[y.authorId] : "sheetjsghost", ref: y.ref, guid: y.guid }/*:any*/);
|
||||
var cell = decode_cell(y.ref);
|
||||
if(opts.sheetRows && opts.sheetRows <= cell.r) return;
|
||||
var textMatch = x.match(/<(?:\w+:)?text>([^\u2603]*)<\/(?:\w+:)?text>/);
|
||||
if (!textMatch || !textMatch[1]) return; // a comment may contain an empty text tag.
|
||||
var rt = parse_si(textMatch[1]);
|
||||
if(!rt) return;
|
||||
var rt = (!textMatch || !textMatch[1]) ? {r:"",t:"",h:""} : parse_si(textMatch[1]);
|
||||
comment.r = rt.r;
|
||||
comment.t = rt.t;
|
||||
comment.t = rt.t.replace(/\r\n/g,"\n").replace(/\r/g,"\n");
|
||||
if(opts.cellHTML) comment.h = rt.h;
|
||||
commentList.push(comment);
|
||||
});
|
||||
return commentList;
|
||||
}
|
||||
|
||||
function write_comments_xml(data, opts) { }
|
||||
var CMNT_XML_ROOT = writextag('comments', null, { 'xmlns': XMLNS.main[0] });
|
||||
function write_comments_xml(data, opts) {
|
||||
var o = [XML_HEADER, CMNT_XML_ROOT];
|
||||
|
||||
var iauthor = [];
|
||||
o.push("<authors>");
|
||||
data.map(function(x) { return x[1]; }).forEach(function(comment) {
|
||||
comment.map(function(x) { return escapexml(x.a); }).forEach(function(a) {
|
||||
if(iauthor.indexOf(a) > -1) return;
|
||||
iauthor.push(a);
|
||||
o.push("<author>" + a + "</author>");
|
||||
});
|
||||
});
|
||||
o.push("</authors>");
|
||||
o.push("<commentList>");
|
||||
data.forEach(function(d) {
|
||||
d[1].forEach(function(c) {
|
||||
/* 18.7.3 CT_Comment */
|
||||
o.push('<comment ref="' + d[0] + '" authorId="' + iauthor.indexOf(escapexml(c.a)) + '"><text>');
|
||||
o.push(writetag("t", c.t));
|
||||
o.push('</text></comment>');
|
||||
});
|
||||
});
|
||||
o.push("</commentList>");
|
||||
if(o.length>2) { o[o.length] = ('</comments>'); o[1]=o[1].replace("/>",">"); }
|
||||
return o.join("");
|
||||
}
|
||||
|
@ -8,6 +8,16 @@ function parse_BrtBeginComment(data, length) {
|
||||
data.l += 16; /*var guid = parse_GUID(data); */
|
||||
return out;
|
||||
}
|
||||
function write_BrtBeginComment(data, o) {
|
||||
if(o == null) o = new_buf(36);
|
||||
o.write_shift(4, data[1].iauthor);
|
||||
write_UncheckedRfX(data[0], o);
|
||||
o.write_shift(4, 0);
|
||||
o.write_shift(4, 0);
|
||||
o.write_shift(4, 0);
|
||||
o.write_shift(4, 0);
|
||||
return o;
|
||||
}
|
||||
|
||||
/* [MS-XLSB] 2.4.324 BrtCommentAuthor */
|
||||
var parse_BrtCommentAuthor = parse_XLWideString;
|
||||
@ -30,6 +40,7 @@ function parse_comments_bin(data, opts) {
|
||||
c.author = authors[c.iauthor];
|
||||
delete c.iauthor;
|
||||
if(opts.sheetRows && opts.sheetRows <= c.rfx.r) break;
|
||||
if(!c.t) c.t = "";
|
||||
delete c.rfx; out.push(c); break;
|
||||
case 'BrtBeginComments': break;
|
||||
case 'BrtEndComments': break;
|
||||
@ -43,4 +54,35 @@ function parse_comments_bin(data, opts) {
|
||||
return out;
|
||||
}
|
||||
|
||||
function write_comments_bin(data, opts) { }
|
||||
function write_comments_bin(data, opts) {
|
||||
var ba = buf_array();
|
||||
var iauthor = [];
|
||||
write_record(ba, "BrtBeginComments");
|
||||
{ /* COMMENTAUTHORS */
|
||||
write_record(ba, "BrtBeginCommentAuthors");
|
||||
data.forEach(function(comment) {
|
||||
comment[1].forEach(function(c) {
|
||||
if(iauthor.indexOf(c.a) > -1) return;
|
||||
iauthor.push(c.a.substr(0,54));
|
||||
write_record(ba, "BrtCommentAuthor", write_XLWideString(c.a.substr(0, 54)));
|
||||
});
|
||||
});
|
||||
write_record(ba, "BrtEndCommentAuthors");
|
||||
}
|
||||
{ /* COMMENTLIST */
|
||||
write_record(ba, "BrtBeginCommentList");
|
||||
data.forEach(function(comment) {
|
||||
comment[1].forEach(function(c) {
|
||||
c.iauthor = iauthor.indexOf(c.a);
|
||||
var range = {s:decode_cell(comment[0])}; range.e = range.s;
|
||||
write_record(ba, "BrtBeginComment", write_BrtBeginComment([range, c]));
|
||||
if(c.t && c.t.length > 0) write_record(ba, "BrtCommentText", write_RichStr(c));
|
||||
write_record(ba, "BrtEndComment");
|
||||
delete c.iauthor;
|
||||
});
|
||||
});
|
||||
write_record(ba, "BrtEndCommentList");
|
||||
}
|
||||
write_record(ba, "BrtEndComments");
|
||||
return ba.end();
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ function col_obj_w(C/*:number*/, col) {
|
||||
else if(col.wpx != null) width = px2char(col.wpx);
|
||||
else if(col.wch != null) width = col.wch;
|
||||
if(width > -1) { p.width = char2width(width); p.customWidth = 1; }
|
||||
else p.width = col.width;
|
||||
return p;
|
||||
}
|
||||
|
||||
|
@ -156,6 +156,7 @@ function write_ws_xml_cell(cell, ref, ws, opts, idx, wb) {
|
||||
v = writextag('f', escapexml(cell.f), ff) + (cell.v != null ? v : "");
|
||||
}
|
||||
if(cell.l) ws['!links'].push([ref, cell.l]);
|
||||
if(cell.c) ws['!comments'].push([ref, cell.c]);
|
||||
return writextag('c', v, o);
|
||||
}
|
||||
|
||||
@ -330,6 +331,7 @@ function write_ws_xml(idx/*:number*/, opts, wb/*:Workbook*/, rels)/*:string*/ {
|
||||
if(ws === undefined) ws = {};
|
||||
var ref = ws['!ref']; if(ref === undefined) ref = 'A1';
|
||||
if(!rels) rels = {};
|
||||
ws['!comments'] = [];
|
||||
|
||||
o[o.length] = (writextag('sheetPr', null, {'codeName': escapexml(wb.SheetNames[idx])}));
|
||||
o[o.length] = (writextag('dimension', null, {'ref': ref}));
|
||||
@ -348,12 +350,12 @@ function write_ws_xml(idx/*:number*/, opts, wb/*:Workbook*/, rels)/*:string*/ {
|
||||
|
||||
if(ws['!merges'] != null && ws['!merges'].length > 0) o[o.length] = (write_ws_xml_merges(ws['!merges']));
|
||||
|
||||
var relc = -1, rel;
|
||||
var relc = -1, rel, rId = -1;
|
||||
if(ws['!links'].length > 0) {
|
||||
o[o.length] = "<hyperlinks>";
|
||||
ws['!links'].forEach(function(l) {
|
||||
if(!l[1].Target) return;
|
||||
var rId = add_rels(rels, -1, escapexml(l[1].Target).replace(/#.*$/, ""), RELS.HLINK);
|
||||
rId = add_rels(rels, -1, escapexml(l[1].Target).replace(/#.*$/, ""), RELS.HLINK);
|
||||
rel = ({"ref":l[0], "r:id":"rId"+rId}/*:any*/);
|
||||
if((relc = l[1].Target.indexOf("#")) > -1) rel.location = escapexml(l[1].Target.substr(relc+1));
|
||||
if(l[1].Tooltip) rel.tooltip = escapexml(l[1].Tooltip);
|
||||
@ -362,6 +364,14 @@ function write_ws_xml(idx/*:number*/, opts, wb/*:Workbook*/, rels)/*:string*/ {
|
||||
o[o.length] = "</hyperlinks>";
|
||||
}
|
||||
delete ws['!links'];
|
||||
|
||||
if(ws['!comments'].length > 0) {
|
||||
rId = add_rels(rels, -1, "../drawings/vmlDrawing" + (idx+1) + ".vml", RELS.VML);
|
||||
o[o.length] = writextag("legacyDrawing", null, {"r:id":"rId" + rId});
|
||||
ws['!legacy'] = rId;
|
||||
}
|
||||
// <legacyDrawing r:id="rId1"/>
|
||||
|
||||
if(o.length>2) { o[o.length] = ('</worksheet>'); o[1]=o[1].replace("/>",">"); }
|
||||
return o.join("");
|
||||
}
|
||||
|
@ -570,6 +570,7 @@ function write_ws_bin_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:num
|
||||
/* TODO: cell style */
|
||||
//o.s = get_cell_style(opts.cellXfs, cell, opts);
|
||||
if(cell.l) ws['!links'].push([encode_cell(o), cell.l]);
|
||||
if(cell.c) ws['!comments'].push([encode_cell(o), cell.c]);
|
||||
switch(cell.t) {
|
||||
case 's': case 'str':
|
||||
if(opts.bookSST) {
|
||||
@ -639,12 +640,22 @@ function write_HLINKS(ba, ws/*:Worksheet*/, rels) {
|
||||
});
|
||||
delete ws['!links'];
|
||||
}
|
||||
function write_LEGACYDRAWING(ba, ws/*:Worksheet*/, idx/*:number*/, rels) {
|
||||
/* [BrtLegacyDrawing] */
|
||||
if(ws['!comments'].length > 0) {
|
||||
var rId = add_rels(rels, -1, "../drawings/vmlDrawing" + (idx+1) + ".vml", RELS.VML);
|
||||
write_record(ba, "BrtLegacyDrawing", write_RelID("rId" + rId));
|
||||
ws['!legacy'] = rId;
|
||||
}
|
||||
}
|
||||
|
||||
function write_ws_bin(idx/*:number*/, opts, wb/*:Workbook*/, rels) {
|
||||
var ba = buf_array();
|
||||
var s = wb.SheetNames[idx], ws = wb.Sheets[s] || {};
|
||||
var r = safe_decode_range(ws['!ref'] || "A1");
|
||||
ws['!links'] = [];
|
||||
/* passed back to write_zip and removed there */
|
||||
ws['!comments'] = [];
|
||||
write_record(ba, "BrtBeginSheet");
|
||||
write_record(ba, "BrtWsProp", write_BrtWsProp(s));
|
||||
write_record(ba, "BrtWsDim", write_BrtWsDim(r));
|
||||
@ -676,7 +687,7 @@ function write_ws_bin(idx/*:number*/, opts, wb/*:Workbook*/, rels) {
|
||||
/* [IGNOREECS] */
|
||||
/* [SMARTTAGS] */
|
||||
/* [BrtDrawing] */
|
||||
/* [BrtLegacyDrawing] */
|
||||
write_LEGACYDRAWING(ba, ws, idx, rels);
|
||||
/* [BrtLegacyDrawingHF] */
|
||||
/* [BrtBkHim] */
|
||||
/* [OLEOBJECTS] */
|
||||
|
@ -62,11 +62,11 @@ function write_sty(data, name/*:string*/, opts) {
|
||||
function write_sst(data/*:SST*/, name/*:string*/, opts) {
|
||||
return (name.slice(-4)===".bin" ? write_sst_bin : write_sst_xml)(data, opts);
|
||||
}
|
||||
/*
|
||||
function write_cmnt(data, name:string, opts) {
|
||||
|
||||
function write_cmnt(data/*:Array<any>*/, name/*:string*/, opts) {
|
||||
return (name.slice(-4)===".bin" ? write_comments_bin : write_comments_xml)(data, opts);
|
||||
}
|
||||
|
||||
/*
|
||||
function write_cc(data, name:string, opts) {
|
||||
return (name.slice(-4)===".bin" ? write_cc_bin : write_cc_xml)(data, opts);
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ function parse_xlml_data(xml, ss, data, cell/*:any*/, base, styles, csty, row, a
|
||||
if(!cell.t) cell.t = 'n';
|
||||
break;
|
||||
case 'Error': cell.t = 'e'; cell.v = RBErr[xml]; cell.w = xml; break;
|
||||
default: cell.t = 's'; cell.v = xlml_fixstr(ss); break;
|
||||
default: cell.t = 's'; cell.v = xlml_fixstr(ss||xml); break;
|
||||
}
|
||||
safe_format_xlml(cell, nf, o);
|
||||
if(o.cellFormula != null) {
|
||||
@ -129,7 +129,7 @@ function parse_xlml_data(xml, ss, data, cell/*:any*/, base, styles, csty, row, a
|
||||
/* strictly speaking, the leading = is required but some writers omit */
|
||||
if(fstr.charCodeAt(0) == 61 /* = */) fstr = fstr.substr(1);
|
||||
cell.f = rc_to_a1(fstr, base);
|
||||
cell.Formula = undefined;
|
||||
delete cell.Formula;
|
||||
if(cell.ArrayRange == "RC") cell.F = rc_to_a1("RC:RC", base);
|
||||
else if(cell.ArrayRange) {
|
||||
cell.F = rc_to_a1(cell.ArrayRange, base);
|
||||
@ -152,7 +152,8 @@ function parse_xlml_data(xml, ss, data, cell/*:any*/, base, styles, csty, row, a
|
||||
}
|
||||
|
||||
function xlml_clean_comment(comment/*:any*/) {
|
||||
comment.t = comment.v;
|
||||
comment.t = comment.v || "";
|
||||
comment.t = comment.t.replace(/\r\n/g,"\n").replace(/\r/g,"\n");
|
||||
comment.v = comment.w = comment.ixfe = undefined;
|
||||
}
|
||||
|
||||
@ -803,7 +804,14 @@ function write_ws_xlml_wsopts(ws/*:Worksheet*/, opts, idx/*:number*/, wb/*:Workb
|
||||
if(o.length == 0) return "";
|
||||
return writextag("WorksheetOptions", o.join(""), {xmlns:XLMLNS.x});
|
||||
}
|
||||
/* TODO */
|
||||
function write_ws_xlml_comment(comments) {
|
||||
return comments.map(function(c) {
|
||||
// TODO: formatted text
|
||||
var t = xlml_unfixstr(c.t||"");
|
||||
var d =writextag("ss:Data", t, {"xmlns":"http://www.w3.org/TR/REC-html40"});
|
||||
return writextag("Comment", d, {"ss:Author":c.a});
|
||||
}).join("");
|
||||
}
|
||||
function write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr)/*:string*/{
|
||||
if(!cell || cell.v == undefined && cell.f == undefined) return "<Cell></Cell>";
|
||||
|
||||
@ -846,6 +854,8 @@ function write_ws_xlml_cell(cell, ref, ws, opts, idx, wb, addr)/*:string*/{
|
||||
}
|
||||
var m = '<Data ss:Type="' + t + '">' + _v + '</Data>';
|
||||
|
||||
if((cell.c||[]).length > 0) m += write_ws_xlml_comment(cell.c);
|
||||
|
||||
return writextag("Cell", m, attr);
|
||||
}
|
||||
/* TODO */
|
||||
|
@ -58,11 +58,12 @@ function write_BIFF2LABEL(r, c, val) {
|
||||
|
||||
function write_ws_biff_cell(ba/*:BufArray*/, cell/*:Cell*/, R/*:number*/, C/*:number*/, opts) {
|
||||
if(cell.v != null) switch(cell.t) {
|
||||
case 'n':
|
||||
if((cell.v == (cell.v|0)) && (cell.v >= 0) && (cell.v < 65536))
|
||||
write_biff_rec(ba, 0x0002, write_BIFF2INT(R, C, cell.v));
|
||||
case 'd': case 'n':
|
||||
var v = cell.t == 'd' ? datenum(cell.v) : cell.v;
|
||||
if((v == (v|0)) && (v >= 0) && (v < 65536))
|
||||
write_biff_rec(ba, 0x0002, write_BIFF2INT(R, C, v));
|
||||
else
|
||||
write_biff_rec(ba, 0x0003, write_BIFF2NUMBER(R,C, cell.v));
|
||||
write_biff_rec(ba, 0x0003, write_BIFF2NUMBER(R,C, v));
|
||||
return;
|
||||
case 'b': case 'e': write_biff_rec(ba, 0x0005, write_BIFF2BERR(R, C, cell.v, cell.t)); return;
|
||||
/* TODO: codepage, sst */
|
||||
|
@ -32,6 +32,8 @@ var parse_content_xml = (function() {
|
||||
var number_format_map = {};
|
||||
var merges = [], mrange = {}, mR = 0, mC = 0;
|
||||
var arrayf = [];
|
||||
var comments = [], comment = {};
|
||||
var creator = "", creatoridx = 0;
|
||||
var rept = 1, isstub = false;
|
||||
var i = 0;
|
||||
xlmlregex.lastIndex = 0;
|
||||
@ -77,6 +79,7 @@ var parse_content_xml = (function() {
|
||||
if(C < range.s.c) range.s.c = C;
|
||||
if(R < range.s.r) range.s.r = R;
|
||||
ctag = parsexmltag(Rn[0], false);
|
||||
comments = []; comment = {};
|
||||
q = ({t:ctag['数据类型'] || ctag['value-type'], v:null/*:: , z:null, w:""*/}/*:any*/);
|
||||
if(opts.cellFormula) {
|
||||
if(ctag.formula) ctag.formula = unescapexml(ctag.formula);
|
||||
@ -126,6 +129,7 @@ var parse_content_xml = (function() {
|
||||
q.v = textp || '';
|
||||
isstub = textpidx == 0;
|
||||
}
|
||||
if(comments.length > 0) { q.c = comments; comments = []; }
|
||||
if(textp) q.w = textp;
|
||||
if(!isstub || opts.sheetStubs) {
|
||||
if(!(opts.sheetRows && opts.sheetRows < R)) {
|
||||
@ -150,6 +154,23 @@ var parse_content_xml = (function() {
|
||||
else if(Rn[0].charAt(Rn[0].length-2) !== '/') state.push([Rn[3], true]);
|
||||
break;
|
||||
|
||||
case 'annotation': // 14.1 <office:annotation>
|
||||
if(Rn[1]==='/'){
|
||||
if((tmp=state.pop())[0]!==Rn[3]) throw "Bad state: "+tmp;
|
||||
comment.t = textp;
|
||||
comment.a = creator;
|
||||
comments.push(comment);
|
||||
}
|
||||
else if(Rn[0].charAt(Rn[0].length-2) !== '/') {state.push([Rn[3], false]);}
|
||||
creator = ""; creatorpidx = 0;
|
||||
textp = ""; textpidx = 0;
|
||||
break;
|
||||
|
||||
case 'creator': // 4.3.2.7 <dc:creator>
|
||||
if(Rn[1]==='/') { creator = str.slice(creatoridx,Rn.index); }
|
||||
else creatoridx = Rn.index + Rn[0].length;
|
||||
break;
|
||||
|
||||
/* ignore state */
|
||||
case 'meta': case '元数据': // TODO: <office:meta> <uof:元数据> FODS/UOF
|
||||
case 'settings': // TODO: <office:settings>
|
||||
@ -165,7 +186,6 @@ var parse_content_xml = (function() {
|
||||
case 'list-style': // 16.30 <text:list-style>
|
||||
case 'form': // 13.13 <form:form>
|
||||
case 'dde-links': // 9.8 <table:dde-links>
|
||||
case 'annotation': // 14.1 <office:annotation>
|
||||
case 'event-listeners': // TODO
|
||||
if(Rn[1]==='/'){if((tmp=state.pop())[0]!==Rn[3]) throw "Bad state: "+tmp;}
|
||||
else if(Rn[0].charAt(Rn[0].length-2) !== '/') state.push([Rn[3], false]);
|
||||
@ -272,7 +292,7 @@ var parse_content_xml = (function() {
|
||||
case 'span': break; // <text:span>
|
||||
case 'line-break': break; // 6.1.5 <text:line-break>
|
||||
case 'p': case '文本串':
|
||||
if(Rn[1]==='/') textp = parse_text_p(str.slice(textpidx,Rn.index), textptag);
|
||||
if(Rn[1]==='/') textp = (textp.length > 0 ? textp + "\n" : "") + parse_text_p(str.slice(textpidx,Rn.index), textptag);
|
||||
else { textptag = parsexmltag(Rn[0], false); textpidx = Rn.index + Rn[0].length; }
|
||||
break; // <text:p>
|
||||
case 's': break; // <text:s>
|
||||
@ -326,7 +346,6 @@ var parse_content_xml = (function() {
|
||||
case 'event-listener': // TODO
|
||||
/* TODO: FODS Properties */
|
||||
case 'initial-creator':
|
||||
case 'creator':
|
||||
case 'creation-date':
|
||||
case 'generator':
|
||||
case 'document-statistic':
|
||||
|
@ -1,4 +1,5 @@
|
||||
function write_zip(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ {
|
||||
_shapeid = 1024;
|
||||
if(opts.bookType == "ods") return write_ods(wb, opts);
|
||||
if(wb && !wb.SSF) {
|
||||
wb.SSF = SSF.get_table();
|
||||
@ -57,8 +58,26 @@ function write_zip(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ {
|
||||
f = "xl/worksheets/sheet" + rId + "." + wbext;
|
||||
var wsrels = {'!id':{}};
|
||||
zip.file(f, write_ws(rId-1, f, opts, wb, wsrels));
|
||||
|
||||
ct.sheets.push(f);
|
||||
add_rels(opts.wbrels, rId, "worksheets/sheet" + rId + "." + wbext, RELS.WS[0]);
|
||||
add_rels(opts.wbrels, -1, "worksheets/sheet" + rId + "." + wbext, RELS.WS[0]);
|
||||
|
||||
var ws = wb.Sheets[wb.SheetNames[rId-1]];
|
||||
if(ws) {
|
||||
var comments = ws['!comments'];
|
||||
if(comments && comments.length > 0) {
|
||||
var cf = "xl/comments" + rId + "." + wbext;
|
||||
zip.file(cf, write_cmnt(comments, cf, opts));
|
||||
ct.comments.push(cf);
|
||||
add_rels(wsrels, -1, "../comments" + rId + "." + wbext, RELS.CMNT);
|
||||
}
|
||||
if(ws['!legacy']) {
|
||||
zip.file("xl/drawings/vmlDrawing" + (rId) + ".vml", write_comments_vml(rId, ws['!comments']));
|
||||
}
|
||||
delete ws['!comments'];
|
||||
delete ws['!legacy'];
|
||||
}
|
||||
|
||||
if(wsrels['!id'].rId1) zip.file(get_rels_path(f), write_rels(wsrels)); // get_rels_path('')
|
||||
}
|
||||
|
||||
@ -66,7 +85,7 @@ function write_zip(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ {
|
||||
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);
|
||||
add_rels(opts.wbrels, -1, "sharedStrings." + wbext, RELS.SST);
|
||||
}
|
||||
|
||||
/* TODO: something more intelligent with themes */
|
||||
@ -74,20 +93,20 @@ function write_zip(wb/*:Workbook*/, opts/*:WriteOpts*/)/*:ZIP*/ {
|
||||
f = "xl/theme/theme1.xml";
|
||||
zip.file(f, write_theme(wb.Themes, opts));
|
||||
ct.themes.push(f);
|
||||
add_rels(opts.wbrels, ++rId, "theme/theme1.xml", RELS.THEME);
|
||||
add_rels(opts.wbrels, -1, "theme/theme1.xml", RELS.THEME);
|
||||
|
||||
/* TODO: something more intelligent with styles */
|
||||
|
||||
f = "xl/styles." + wbext;
|
||||
zip.file(f, write_sty(wb, f, opts));
|
||||
ct.styles.push(f);
|
||||
add_rels(opts.wbrels, ++rId, "styles." + wbext, RELS.STY);
|
||||
add_rels(opts.wbrels, -1, "styles." + wbext, RELS.STY);
|
||||
|
||||
if(wb.vbaraw && vbafmt) {
|
||||
f = "xl/vbaProject.bin";
|
||||
zip.file(f, wb.vbaraw);
|
||||
ct.vba.push(f);
|
||||
add_rels(opts.wbrels, ++rId, "vbaProject.bin", RELS.VBA);
|
||||
add_rels(opts.wbrels, -1, "vbaProject.bin", RELS.VBA);
|
||||
}
|
||||
|
||||
zip.file("[Content_Types].xml", write_ct(ct, opts));
|
||||
|
@ -45,7 +45,7 @@ function write_binary_type(out, opts/*:WriteOpts*/) {
|
||||
function writeSync(wb/*:Workbook*/, opts/*:?WriteOpts*/) {
|
||||
check_wb(wb);
|
||||
var o = opts||{};
|
||||
switch(o.bookType || 'xlsx') {
|
||||
switch(o.bookType || 'xlsb') {
|
||||
case 'xml':
|
||||
case 'xlml': return write_string_type(write_xlml(wb, o), o);
|
||||
case 'slk':
|
||||
@ -63,21 +63,20 @@ function writeSync(wb/*:Workbook*/, opts/*:?WriteOpts*/) {
|
||||
}
|
||||
|
||||
function resolve_book_type(o/*?WriteFileOpts*/) {
|
||||
if(!o.bookType) switch(o.file.slice(-5).toLowerCase()) {
|
||||
if(!o.bookType) switch(o.file.slice(o.file.lastIndexOf(".")).toLowerCase()) {
|
||||
case '.xlsx': o.bookType = 'xlsx'; break;
|
||||
case '.xlsm': o.bookType = 'xlsm'; break;
|
||||
case '.xlsb': o.bookType = 'xlsb'; break;
|
||||
case '.fods': o.bookType = 'fods'; break;
|
||||
case '.xlml': o.bookType = 'xlml'; break;
|
||||
case '.sylk': o.bookType = 'sylk'; break;
|
||||
default: switch(o.file.slice(-4).toLowerCase()) {
|
||||
case '.xls': o.bookType = 'biff2'; break;
|
||||
case '.xml': o.bookType = 'xml'; break;
|
||||
case '.ods': o.bookType = 'ods'; break;
|
||||
case '.csv': o.bookType = 'csv'; break;
|
||||
case '.dif': o.bookType = 'dif'; break;
|
||||
case '.slk': o.bookType = 'sylk'; break;
|
||||
}}
|
||||
}
|
||||
}
|
||||
|
||||
function writeFileSync(wb/*:Workbook*/, filename/*:string*/, opts/*:?WriteFileOpts*/) {
|
||||
|
17
docbits/66_comments.md
Normal file
17
docbits/66_comments.md
Normal file
@ -0,0 +1,17 @@
|
||||
#### Cell Comments
|
||||
|
||||
Cell comments are objects stored in the `c` array of cell objects. The actual
|
||||
contents of the comment are split into blocks based on the comment author. The
|
||||
`a` field of each comment object is the author of the comment and the `t` field
|
||||
is the plaintext representation.
|
||||
|
||||
For example, the following snippet appends a cell comment into cell `A1`:
|
||||
|
||||
```js
|
||||
if(!ws.A1.c) ws.A1.c = [];
|
||||
ws.A1.c.push({a:"SheetJS", t:"I'm a little comment, short and stout!"});
|
||||
```
|
||||
|
||||
Note: XLSB enforces a 54 character limit on the Author name. Names longer than
|
||||
54 characters may cause issues with other formats.
|
||||
|
@ -22,7 +22,7 @@ With <https://rawgit.com/SheetJS/test_files/master/sheet_visibility.xlsx>:
|
||||
```
|
||||
|
||||
Non-Excel formats do not support the Very Hidden state. The best way to test
|
||||
if a sheet is visible is to check if the `Hidden` property is logical negation:
|
||||
if a sheet is visible is to check if the `Hidden` property is logical truth:
|
||||
|
||||
```js
|
||||
> wb.Workbook.Sheets.map(function(x) { return [x.name, !x.Hidden] })
|
||||
|
@ -67,7 +67,7 @@ file but Excel will know how to handle it. This library applies similar logic:
|
||||
| `0x50` | ZIP Archive | XLSB or XLSX/M or ODS or UOS2 or plaintext |
|
||||
| `0x49` | Plain Text | SYLK or plaintext |
|
||||
| `0x54` | Plain Text | DIF or plaintext |
|
||||
| `0xFE` | UTF8 Text | SpreadsheetML or Flat ODS or UOS1 or plaintext |
|
||||
| `0xFE` | UTF16 Encoded | SpreadsheetML or Flat ODS or UOS1 or plaintext |
|
||||
|
||||
DBF files are detected based on the first byte as well as the third and fourth
|
||||
bytes (corresponding to month and day of the file date)
|
||||
|
@ -26,6 +26,7 @@
|
||||
+ [Formulae](README.md#formulae)
|
||||
+ [Column Properties](README.md#column-properties)
|
||||
+ [Hyperlinks](README.md#hyperlinks)
|
||||
+ [Cell Comments](README.md#cell-comments)
|
||||
+ [Sheet Visibility](README.md#sheet-visibility)
|
||||
- [Parsing Options](README.md#parsing-options)
|
||||
* [Input Type](README.md#input-type)
|
||||
|
50
test.js
50
test.js
@ -44,6 +44,7 @@ var paths = {
|
||||
cstxml: dir + 'comments_stress_test.xls.xml',
|
||||
cstxlsx: dir + 'comments_stress_test.xlsx',
|
||||
cstxlsb: dir + 'comments_stress_test.xlsb',
|
||||
cstods: dir + 'comments_stress_test.ods',
|
||||
fstxls: dir + 'formula_stress_test.xls',
|
||||
fstxml: dir + 'formula_stress_test.xls.xml',
|
||||
fstxlsx: dir + 'formula_stress_test.xlsx',
|
||||
@ -221,6 +222,21 @@ describe('should parse test files', function() {
|
||||
});
|
||||
});
|
||||
|
||||
/* comments_stress_test family */
|
||||
function check_comments(wb) {
|
||||
var ws0 = wb.Sheets.Sheet2;
|
||||
assert.equal(ws0.A1.c[0].a, 'Author');
|
||||
assert.equal(ws0.A1.c[0].t, 'Author:\nGod thinks this is good');
|
||||
assert.equal(ws0.C1.c[0].a, 'Author');
|
||||
assert.equal(ws0.C1.c[0].t, 'I really hope that xlsx decides not to use magic like rPr');
|
||||
|
||||
var ws3 = wb.Sheets.Sheet4;
|
||||
assert.equal(ws3.B1.c[0].a, 'Author');
|
||||
assert.equal(ws3.B1.c[0].t, 'The next comment is empty');
|
||||
assert.equal(ws3.B2.c[0].a, 'Author');
|
||||
assert.equal(ws3.B2.c[0].t, '');
|
||||
}
|
||||
|
||||
describe('parse options', function() {
|
||||
var html_cell_types = ['s'];
|
||||
var bef = (function() {
|
||||
@ -664,12 +680,27 @@ describe('parse features', function() {
|
||||
[wb1,wb2,wb3,wb4].map(function(wb) { return wb.Sheets[sheet]; }).forEach(function(ws, i) {
|
||||
assert.equal(ws.B1.c.length, 1,"must have 1 comment");
|
||||
assert.equal(ws.B1.c[0].a, "Yegor Kozlov","must have the same author");
|
||||
assert.equal(ws.B1.c[0].t.replace(/\r\n/g,"\n").replace(/\r/g,"\n"), "Yegor Kozlov:\nfirst cell", "must have the concatenated texts");
|
||||
assert.equal(ws.B1.c[0].t, "Yegor Kozlov:\nfirst cell", "must have the concatenated texts");
|
||||
if(i > 0) return;
|
||||
assert.equal(ws.B1.c[0].r, '<r><rPr><b/><sz val="8"/><color indexed="81"/><rFont val="Tahoma"/></rPr><t>Yegor Kozlov:</t></r><r><rPr><sz val="8"/><color indexed="81"/><rFont val="Tahoma"/></rPr><t xml:space="preserve">\r\nfirst cell</t></r>', "must have the rich text representation");
|
||||
assert.equal(ws.B1.c[0].h, '<span style="font-weight: bold;">Yegor Kozlov:</span><span style=""><br/>first cell</span>', "must have the html representation");
|
||||
});
|
||||
});
|
||||
[
|
||||
['xlsx', paths.cstxlsx],
|
||||
['xlsb', paths.cstxlsb],
|
||||
['xls', paths.cstxls],
|
||||
['xlml', paths.cstxml],
|
||||
['ods', paths.cstods]
|
||||
].forEach(function(m) { it(m[0] + ' stress test', function() {
|
||||
var wb = X.readFile(m[1]);
|
||||
check_comments(wb);
|
||||
var ws0 = wb.Sheets.Sheet2;
|
||||
assert.equal(ws0.A1.c[0].a, 'Author');
|
||||
assert.equal(ws0.A1.c[0].t, 'Author:\nGod thinks this is good');
|
||||
assert.equal(ws0.C1.c[0].a, 'Author');
|
||||
assert.equal(ws0.C1.c[0].t, 'I really hope that xlsx decides not to use magic like rPr');
|
||||
}); });
|
||||
});
|
||||
|
||||
describe('should parse core properties and custom properties', function() {
|
||||
@ -1043,6 +1074,7 @@ describe('roundtrip features', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should preserve sheet visibility', function() { [
|
||||
['xlml', paths.svxml],
|
||||
['xlsx', paths.svxlsx],
|
||||
@ -1061,6 +1093,22 @@ describe('roundtrip features', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('should preserve cell comments', function() { [
|
||||
['xlsx', paths.cstxlsx],
|
||||
['xlsb', paths.cstxlsb],
|
||||
//['xls', paths.cstxlsx],
|
||||
['xlml', paths.cstxml]
|
||||
//['ods', paths.cstods]
|
||||
].forEach(function(w) {
|
||||
it(w[0], function() {
|
||||
var wb1 = X.readFile(w[1]);
|
||||
var wb2 = X.read(X.write(wb1, {bookType:w[0], type:"buffer"}), {type:"buffer"});
|
||||
check_comments(wb1);
|
||||
check_comments(wb2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function password_file(x){return x.match(/^password.*\.xls$/); }
|
||||
|
@ -77,9 +77,6 @@ ws['A3'].l = { Target: "http://sheetjs.com", Tooltip: "Visit us <SheetJS.com!>"
|
||||
//ws['B2'].z = "0.0"; wb.SSF[60] = "0.0"; // Custom
|
||||
console.log("JSON Data: "); console.log(XLSX.utils.sheet_to_json(ws, {header:1}));
|
||||
|
||||
console.log("Worksheet Model:")
|
||||
console.log(ws);
|
||||
|
||||
/* TEST: hidden sheets */
|
||||
wb.SheetNames.push("Hidden");
|
||||
wb.Sheets["Hidden"] = XLSX.utils.aoa_to_sheet(["Hidden".split(""), [1,2,3]]);
|
||||
@ -100,6 +97,13 @@ wb.Props = {
|
||||
CreatedDate: new Date(2017,1,19)
|
||||
}
|
||||
|
||||
/* TEST: comments */
|
||||
ws['A4'].c = [];
|
||||
ws['A4'].c.push({a:"SheetJS",t:"I'm a little comment, short and stout!"});
|
||||
|
||||
console.log("Worksheet Model:")
|
||||
console.log(ws);
|
||||
|
||||
/* write file */
|
||||
XLSX.writeFile(wb, 'sheetjs.xlsx', {bookSST:true});
|
||||
XLSX.writeFile(wb, 'sheetjs.xlsm');
|
||||
|
339
xlsx.flow.js
339
xlsx.flow.js
@ -77,7 +77,7 @@ var Base64 = (function make_b64(){
|
||||
}
|
||||
};
|
||||
})();
|
||||
var has_buf = (typeof Buffer !== 'undefined');
|
||||
var has_buf = (typeof Buffer !== 'undefined' && typeof process !== 'undefined' && typeof process.versions !== 'undefined' && process.versions.node);
|
||||
|
||||
function new_raw_buf(len/*:number*/) {
|
||||
/* jshint -W056 */
|
||||
@ -1608,6 +1608,9 @@ var xlml_fixstr/*:StringConv*/ = (function() {
|
||||
function entrepl($$/*:string*/,$1/*:string*/)/*:string*/ { return String.fromCharCode(parseInt($1,10)); }
|
||||
return function xlml_fixstr(str/*:string*/)/*:string*/ { return str.replace(entregex,entrepl); };
|
||||
})();
|
||||
var xlml_unfixstr/*:StringConv*/ = (function() {
|
||||
return function xlml_unfixstr(str/*:string*/)/*:string*/ { return str.replace(/(\r\n|[\r\n])/g,"\ "); };
|
||||
})();
|
||||
|
||||
function parsexmlbool(value/*:any*/, tag/*:?string*/)/*:boolean*/ {
|
||||
switch(value) {
|
||||
@ -1732,6 +1735,8 @@ var XLMLNS = ({
|
||||
'x': 'urn:schemas-microsoft-com:office:excel',
|
||||
'ss': 'urn:schemas-microsoft-com:office:spreadsheet',
|
||||
'dt': 'uuid:C2F41010-65B3-11d1-A29F-00AA00C14882',
|
||||
'mv': 'http://macVmlSchemaUri',
|
||||
'v': 'urn:schemas-microsoft-com:vml',
|
||||
'html': 'http://www.w3.org/TR/REC-html40'
|
||||
}/*:any*/);
|
||||
function read_double_le(b, idx/*:number*/)/*:number*/ {
|
||||
@ -1937,7 +1942,9 @@ function recordhopper(data, cb/*:RecordHopperCB*/, opts/*:?any*/) {
|
||||
tmpbyte = data.read_shift(1);
|
||||
length = tmpbyte & 0x7F;
|
||||
for(cntbyte = 1; cntbyte <4 && (tmpbyte & 0x80); ++cntbyte) length += ((tmpbyte = data.read_shift(1)) & 0x7F)<<(7*cntbyte);
|
||||
var tgt = data.l + length;
|
||||
var d = R.f(data, length, opts);
|
||||
data.l = tgt;
|
||||
if(cb(d, R, RT)) return;
|
||||
}
|
||||
}
|
||||
@ -2130,7 +2137,7 @@ function parse_RichStr(data, length/*:number*/)/*:XLString*/ {
|
||||
for(var i = 0; i != dwSizeStrRun; ++i) rgsStrRun.push(parse_StrRun(data));
|
||||
z.r = rgsStrRun;
|
||||
}
|
||||
else z.r = "<t>" + escapexml(str) + "</t>";
|
||||
else z.r = [{ich:0, ifnt:0}];
|
||||
//if((flags & 2) !== 0) { /* fExtStr */
|
||||
// /* TODO: phonetic string */
|
||||
//}
|
||||
@ -2139,10 +2146,10 @@ function parse_RichStr(data, length/*:number*/)/*:XLString*/ {
|
||||
}
|
||||
function write_RichStr(str/*:XLString*/, o/*:?Block*/)/*:Block*/ {
|
||||
/* TODO: formatted string */
|
||||
if(o == null) o = new_buf(5+2*str.t.length);
|
||||
var _null = false; if(o == null) { _null = true; o = new_buf(15+4*str.t.length); }
|
||||
o.write_shift(1,0);
|
||||
write_XLWideString(str.t, o);
|
||||
return o;
|
||||
return _null ? o.slice(0, o.l) : o;
|
||||
}
|
||||
|
||||
/* [MS-XLSB] 2.5.9 */
|
||||
@ -2172,10 +2179,10 @@ function parse_XLNullableWideString(data)/*:string*/ {
|
||||
return cchCharacters === 0 || cchCharacters === 0xFFFFFFFF ? "" : data.read_shift(cchCharacters, 'dbcs');
|
||||
}
|
||||
function write_XLNullableWideString(data/*:string*/, o) {
|
||||
if(!o) o = new_buf(127);
|
||||
var _null = false; if(o == null) { _null = true; o = new_buf(127); }
|
||||
o.write_shift(4, data.length > 0 ? data.length : 0xFFFFFFFF);
|
||||
if(data.length > 0) o.write_shift(0, data, 'dbcs');
|
||||
return o;
|
||||
return _null ? o.slice(0, o.l) : o;
|
||||
}
|
||||
|
||||
/* [MS-XLSB] 2.5.168 */
|
||||
@ -2184,10 +2191,10 @@ function parse_XLWideString(data)/*:string*/ {
|
||||
return cchCharacters === 0 ? "" : data.read_shift(cchCharacters, 'dbcs');
|
||||
}
|
||||
function write_XLWideString(data/*:string*/, o) {
|
||||
if(o == null) o = new_buf(4+2*data.length);
|
||||
var _null = false; if(o == null) { _null = true; o = new_buf(4+2*data.length); }
|
||||
o.write_shift(4, data.length);
|
||||
if(data.length > 0) o.write_shift(0, data, 'dbcs');
|
||||
return o;
|
||||
return _null ? o.slice(0, o.l) : o;
|
||||
}
|
||||
|
||||
/* [MS-XLSB] 2.5.165 */
|
||||
@ -2580,10 +2587,6 @@ var ct2type/*{[string]:string}*/ = ({
|
||||
"application/vnd.openxmlformats-officedocument.customXmlProperties+xml": "TODO",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.customProperty": "TODO",
|
||||
|
||||
/* Comments */
|
||||
"application/vnd.ms-excel.comments": "comments",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml": "comments",
|
||||
|
||||
/* PivotTable */
|
||||
"application/vnd.ms-excel.pivotTable": "TODO",
|
||||
"application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml": "TODO",
|
||||