forked from sheetjs/sheetjs
version bump 0.18.9
This commit is contained in:
parent
08f5678c98
commit
a373597294
@ -4,6 +4,13 @@ This log is intended to keep track of backwards-incompatible changes, including
|
||||
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.
|
||||
|
||||
## v0.18.9
|
||||
|
||||
* XLSX / ODS write defined names
|
||||
* sync defined names to AutoFilter setting on export
|
||||
* 1904 date system setting properly roundtripped
|
||||
* ODS read/write number formats
|
||||
|
||||
## v0.18.8
|
||||
|
||||
* Plaintext parsing of dateless meridien time values (`1:23:45 PM`)
|
||||
|
@ -1 +1 @@
|
||||
XLSX.version = '0.18.8';
|
||||
XLSX.version = '0.18.9';
|
||||
|
@ -1041,6 +1041,7 @@ function stringify_formula(formula/*Array<any>*/, range, cell/*:any*/, supbooks,
|
||||
}
|
||||
}
|
||||
if(stack.length > 1 && opts.WTF) throw new Error("bad formula stack");
|
||||
if(stack[0] == "TRUE") return true; if(stack[0] == "FALSE") return false;
|
||||
return stack[0];
|
||||
}
|
||||
|
||||
|
@ -152,6 +152,15 @@ function write_XLSBFormulaErr(val/*:number*/) {
|
||||
oint.write_shift(4, 0);
|
||||
return oint;
|
||||
}
|
||||
/* Writes a PtgBool */
|
||||
function write_XLSBFormulaBool(val/*:boolean*/) {
|
||||
var oint = new_buf(10);
|
||||
oint.write_shift(4, 2);
|
||||
oint.write_shift(1, 0x1D);
|
||||
oint.write_shift(1, val?1:0);
|
||||
oint.write_shift(4, 0);
|
||||
return oint;
|
||||
}
|
||||
|
||||
/* Writes a PtgStr */
|
||||
function write_XLSBFormulaStr(val/*:string*/) {
|
||||
@ -306,7 +315,9 @@ function write_XLSBFormulaArea3D(_str, wb) {
|
||||
|
||||
|
||||
/* General Formula */
|
||||
function write_XLSBFormula(val/*:string*/, wb) {
|
||||
function write_XLSBFormula(val/*:string|number*/, wb) {
|
||||
if(typeof val == "number") return write_XLSBFormulaNum(val);
|
||||
if(typeof val == "boolean") return write_XLSBFormulaBool(val);
|
||||
if(/^#(DIV\/0!|GETTING_DATA|N\/A|NAME\?|NULL!|NUM!|REF!|VALUE!)$/.test(val)) return write_XLSBFormulaErr(+RBErr[val]);
|
||||
if(val.match(/^\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})$/)) return write_XLSBFormulaRef(val);
|
||||
if(val.match(/^\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5}):\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})$/)) return write_XLSBFormulaRange(val);
|
||||
|
@ -12,21 +12,6 @@ function parse_text_p(text/*:string*//*::, tag*/)/*:Array<any>*/ {
|
||||
return [v];
|
||||
}
|
||||
|
||||
var number_formats_ods = {
|
||||
/* ods name: [short ssf fmt, long ssf fmt] */
|
||||
day: ["d", "dd"],
|
||||
month: ["m", "mm"],
|
||||
year: ["y", "yy"],
|
||||
hours: ["h", "hh"],
|
||||
minutes: ["m", "mm"],
|
||||
seconds: ["s", "ss"],
|
||||
"am-pm": ["A/P", "AM/PM"],
|
||||
"day-of-week": ["ddd", "dddd"],
|
||||
era: ["e", "ee"],
|
||||
/* there is no native representation of LO "Q" format */
|
||||
quarter: ["\\Qm", "m\\\"th quarter\""]
|
||||
};
|
||||
|
||||
/* Note: ODS can stick styles in content.xml or styles.xml, FODS blurs lines */
|
||||
function parse_ods_styles(d/*:string*/, _opts, _nfm) {
|
||||
var number_format_map = _nfm || {};
|
||||
@ -232,7 +217,7 @@ function parse_ods_styles(d/*:string*/, _opts, _nfm) {
|
||||
if(parsexmlbool(y["grouping"])) tNF = commaify(fill("#", Math.max(0, 4 - tNF.length)) + tNF);
|
||||
if(+y["min-decimal-places"] || +y["decimal-places"]) tNF += ".";
|
||||
if(+y["min-decimal-places"]) tNF += fill("0", +y["min-decimal-places"] || 1);
|
||||
if(+y["decimal-places"] - (+y["min-decimal-places"]||0)) tNF += fill("#", +y["decimal-places"] - (+y["min-decimal-places"]||0));
|
||||
if(+y["decimal-places"] - (+y["min-decimal-places"]||0)) tNF += fill("0", +y["decimal-places"] - (+y["min-decimal-places"]||0)); // TODO: should this be "#" ?
|
||||
NF += tNF;
|
||||
break;
|
||||
|
||||
@ -531,12 +516,7 @@ function parse_content_xml(d/*:string*/, _opts, _nfm)/*:Workbook*/ {
|
||||
case 'table-cell-properties': break; // 17.18 <style:table-cell-properties>
|
||||
|
||||
case 'number': // 16.27.3 <number:number>
|
||||
switch(state[state.length-1][0]) {
|
||||
case 'time-style':
|
||||
case 'date-style':
|
||||
tag = parsexmltag(Rn[0], false);
|
||||
NF += number_formats_ods[Rn[3]][tag.style==='long'?1:0]; break;
|
||||
} break;
|
||||
break;
|
||||
|
||||
case 'fraction': break; // TODO 16.27.6 <number:fraction>
|
||||
|
||||
@ -551,12 +531,7 @@ function parse_content_xml(d/*:string*/, _opts, _nfm)/*:Workbook*/ {
|
||||
case 'minutes': // 16.27.20 <number:minutes>
|
||||
case 'seconds': // 16.27.21 <number:seconds>
|
||||
case 'am-pm': // 16.27.22 <number:am-pm>
|
||||
switch(state[state.length-1][0]) {
|
||||
case 'time-style':
|
||||
case 'date-style':
|
||||
tag = parsexmltag(Rn[0], false);
|
||||
NF += number_formats_ods[Rn[3]][tag.style==='long'?1:0]; break;
|
||||
} break;
|
||||
break;
|
||||
|
||||
case 'boolean': break; // 16.27.24 <number:boolean>
|
||||
case 'text': // 16.27.26 <number:text>
|
||||
|
30
dist/xlsx.core.min.js
generated
vendored
30
dist/xlsx.core.min.js
generated
vendored
File diff suppressed because one or more lines are too long
2
dist/xlsx.core.min.map
generated
vendored
2
dist/xlsx.core.min.map
generated
vendored
File diff suppressed because one or more lines are too long
973
dist/xlsx.extendscript.js
generated
vendored
973
dist/xlsx.extendscript.js
generated
vendored
File diff suppressed because it is too large
Load Diff
33
dist/xlsx.full.min.js
generated
vendored
33
dist/xlsx.full.min.js
generated
vendored
File diff suppressed because one or more lines are too long
2
dist/xlsx.full.min.map
generated
vendored
2
dist/xlsx.full.min.map
generated
vendored
File diff suppressed because one or more lines are too long
17
dist/xlsx.mini.min.js
generated
vendored
17
dist/xlsx.mini.min.js
generated
vendored
File diff suppressed because one or more lines are too long
2
dist/xlsx.mini.min.map
generated
vendored
2
dist/xlsx.mini.min.map
generated
vendored
File diff suppressed because one or more lines are too long
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xlsx",
|
||||
"version": "0.18.8",
|
||||
"version": "0.18.9",
|
||||
"author": "sheetjs",
|
||||
"description": "SheetJS Spreadsheet data parser and writer",
|
||||
"keywords": [
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 02324c31f26195b3b5c15c38ca46fe6d22c9ac1c
|
||||
Subproject commit 9c45a1abb981222577b8d5a2faa6111c1e4750fa
|
510
xlsx.flow.js
510
xlsx.flow.js
@ -4,7 +4,7 @@
|
||||
/*global global, exports, module, require:false, process:false, Buffer:false, ArrayBuffer:false, DataView:false, Deno:false */
|
||||
var XLSX = {};
|
||||
function make_xlsx_lib(XLSX){
|
||||
XLSX.version = '0.18.8';
|
||||
XLSX.version = '0.18.9';
|
||||
var current_codepage = 1200, current_ansi = 1252;
|
||||
/*:: declare var cptable:any; */
|
||||
/*global cptable:true, window */
|
||||
@ -13508,6 +13508,7 @@ function stringify_formula(formula/*Array<any>*/, range, cell/*:any*/, supbooks,
|
||||
}
|
||||
}
|
||||
if(stack.length > 1 && opts.WTF) throw new Error("bad formula stack");
|
||||
if(stack[0] == "TRUE") return true; if(stack[0] == "FALSE") return false;
|
||||
return stack[0];
|
||||
}
|
||||
|
||||
@ -13665,6 +13666,15 @@ function write_XLSBFormulaErr(val/*:number*/) {
|
||||
oint.write_shift(4, 0);
|
||||
return oint;
|
||||
}
|
||||
/* Writes a PtgBool */
|
||||
function write_XLSBFormulaBool(val/*:boolean*/) {
|
||||
var oint = new_buf(10);
|
||||
oint.write_shift(4, 2);
|
||||
oint.write_shift(1, 0x1D);
|
||||
oint.write_shift(1, val?1:0);
|
||||
oint.write_shift(4, 0);
|
||||
return oint;
|
||||
}
|
||||
|
||||
/* Writes a PtgStr */
|
||||
function write_XLSBFormulaStr(val/*:string*/) {
|
||||
@ -13819,7 +13829,9 @@ function write_XLSBFormulaArea3D(_str, wb) {
|
||||
|
||||
|
||||
/* General Formula */
|
||||
function write_XLSBFormula(val/*:string*/, wb) {
|
||||
function write_XLSBFormula(val/*:string|number*/, wb) {
|
||||
if(typeof val == "number") return write_XLSBFormulaNum(val);
|
||||
if(typeof val == "boolean") return write_XLSBFormulaBool(val);
|
||||
if(/^#(DIV\/0!|GETTING_DATA|N\/A|NAME\?|NULL!|NUM!|REF!|VALUE!)$/.test(val)) return write_XLSBFormulaErr(+RBErr[val]);
|
||||
if(val.match(/^\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})$/)) return write_XLSBFormulaRef(val);
|
||||
if(val.match(/^\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5}):\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})$/)) return write_XLSBFormulaRange(val);
|
||||
@ -21738,29 +21750,236 @@ function parse_text_p(text/*:string*//*::, tag*/)/*:Array<any>*/ {
|
||||
return [v];
|
||||
}
|
||||
|
||||
var number_formats_ods = {
|
||||
/* ods name: [short ssf fmt, long ssf fmt] */
|
||||
day: ["d", "dd"],
|
||||
month: ["m", "mm"],
|
||||
year: ["y", "yy"],
|
||||
hours: ["h", "hh"],
|
||||
minutes: ["m", "mm"],
|
||||
seconds: ["s", "ss"],
|
||||
"am-pm": ["A/P", "AM/PM"],
|
||||
"day-of-week": ["ddd", "dddd"],
|
||||
era: ["e", "ee"],
|
||||
/* there is no native representation of LO "Q" format */
|
||||
quarter: ["\\Qm", "m\\\"th quarter\""]
|
||||
};
|
||||
/* Note: ODS can stick styles in content.xml or styles.xml, FODS blurs lines */
|
||||
function parse_ods_styles(d/*:string*/, _opts, _nfm) {
|
||||
var number_format_map = _nfm || {};
|
||||
var str = xlml_normalize(d);
|
||||
xlmlregex.lastIndex = 0;
|
||||
str = str.replace(/<!--([\s\S]*?)-->/mg,"").replace(/<!DOCTYPE[^\[]*\[[^\]]*\]>/gm,"");
|
||||
var Rn, NFtag, NF = "", tNF = "", y, etpos = 0, tidx = -1, infmt = false, payload = "";
|
||||
while((Rn = xlmlregex.exec(str))) {
|
||||
switch((Rn[3]=Rn[3].replace(/_.*$/,""))) {
|
||||
/* Number Format Definitions */
|
||||
case 'number-style': // <number:number-style> 16.29.2
|
||||
case 'currency-style': // <number:currency-style> 16.29.8
|
||||
case 'percentage-style': // <number:percentage-style> 16.29.10
|
||||
case 'date-style': // <number:date-style> 16.29.11
|
||||
case 'time-style': // <number:time-style> 16.29.19
|
||||
case 'text-style': // <number:text-style> 16.29.26
|
||||
if(Rn[1]==='/') {
|
||||
infmt = false;
|
||||
if(NFtag['truncate-on-overflow'] == "false") {
|
||||
if(NF.match(/h/)) NF = NF.replace(/h+/, "[$&]");
|
||||
else if(NF.match(/m/)) NF = NF.replace(/m+/, "[$&]");
|
||||
else if(NF.match(/s/)) NF = NF.replace(/s+/, "[$&]");
|
||||
}
|
||||
number_format_map[NFtag.name] = NF;
|
||||
NF = "";
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
infmt = true;
|
||||
NF = "";
|
||||
NFtag = parsexmltag(Rn[0], false);
|
||||
} break;
|
||||
|
||||
// LibreOffice bug https://bugs.documentfoundation.org/show_bug.cgi?id=149484
|
||||
case 'boolean-style': // <number:boolean-style> 16.29.24
|
||||
if(Rn[1]==='/') {
|
||||
infmt = false;
|
||||
number_format_map[NFtag.name] = "General";
|
||||
NF = "";
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
infmt = true;
|
||||
NF = "";
|
||||
NFtag = parsexmltag(Rn[0], false);
|
||||
} break;
|
||||
|
||||
/* Number Format Elements */
|
||||
case 'boolean': // <number:boolean> 16.29.25
|
||||
NF += "General"; // ODF spec is unfortunately underspecified here
|
||||
break;
|
||||
|
||||
case 'text': // <number:text> 16.29.27
|
||||
if(Rn[1]==='/') {
|
||||
payload = str.slice(tidx, xlmlregex.lastIndex - Rn[0].length);
|
||||
// NOTE: Excel has a different interpretation of "%%" and friends
|
||||
if(payload == "%" && NFtag[0] == '<number:percentage-style') NF += "%";
|
||||
else NF += '"' + payload.replace(/"/g, '""') + '"';
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
tidx = xlmlregex.lastIndex;
|
||||
} break;
|
||||
|
||||
|
||||
function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
case 'day': { // <number:day> 16.29.12
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "d"; break;
|
||||
case "long": NF += "dd"; break;
|
||||
default: NF += "dd"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'day-of-week': { // <number:day-of-week> 16.29.16
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "ddd"; break;
|
||||
case "long": NF += "dddd"; break;
|
||||
default: NF += "ddd"; break;
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'era': { // <number:era> 16.29.15 TODO: proper mapping
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "ee"; break;
|
||||
case "long": NF += "eeee"; break;
|
||||
default: NF += "eeee"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'hours': { // <number:hours> 16.29.20
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "h"; break;
|
||||
case "long": NF += "hh"; break;
|
||||
default: NF += "hh"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'minutes': { // <number:minutes> 16.29.21
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "m"; break;
|
||||
case "long": NF += "mm"; break;
|
||||
default: NF += "mm"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'month': { // <number:month> 16.29.13
|
||||
y = parsexmltag(Rn[0], false);
|
||||
if(y["textual"]) NF += "mm";
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "m"; break;
|
||||
case "long": NF += "mm"; break;
|
||||
default: NF += "m"; break;
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'seconds': { // <number:seconds> 16.29.22
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "s"; break;
|
||||
case "long": NF += "ss"; break;
|
||||
default: NF += "ss"; break; // TODO: error condition
|
||||
}
|
||||
if(y["decimal-places"]) NF += "." + fill("0", +y["decimal-places"]);
|
||||
} break;
|
||||
|
||||
case 'year': { // <number:year> 16.29.14
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "yy"; break;
|
||||
case "long": NF += "yyyy"; break;
|
||||
default: NF += "yy"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'am-pm': // <number:am-pm> 16.29.23
|
||||
NF += "AM/PM"; // LO autocorrects A/P -> AM/PM
|
||||
break;
|
||||
|
||||
case 'week-of-year': // <number:week-of-year> 16.29.17
|
||||
case 'quarter': // <number:quarter> 16.29.18
|
||||
console.error("Excel does not support ODS format token " + Rn[3]);
|
||||
break;
|
||||
|
||||
case 'fill-character': // <number:fill-character> 16.29.5
|
||||
if(Rn[1]==='/') {
|
||||
payload = str.slice(tidx, xlmlregex.lastIndex - Rn[0].length);
|
||||
// NOTE: Excel has a different interpretation of "%%" and friends
|
||||
NF += '"' + payload.replace(/"/g, '""') + '"*';
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
tidx = xlmlregex.lastIndex;
|
||||
} break;
|
||||
|
||||
case 'scientific-number': // <number:scientific-number> 16.29.6
|
||||
// TODO: find a mapping for all parameters
|
||||
y = parsexmltag(Rn[0], false);
|
||||
NF += "0." + fill("0", +y["min-decimal-places"] || +y["decimal-places"] || 2) + fill("?", +y["decimal-places"] - +y["min-decimal-places"] || 0) + "E" + (parsexmlbool(y["forced-exponent-sign"]) ? "+" : "") + fill("0", +y["min-exponent-digits"] || 2);
|
||||
break;
|
||||
|
||||
case 'fraction': // <number:fraction> 16.29.7
|
||||
// TODO: find a mapping for all parameters
|
||||
y = parsexmltag(Rn[0], false);
|
||||
if(!+y["min-integer-digits"]) NF += "#";
|
||||
else NF += fill("0", +y["min-integer-digits"]);
|
||||
NF += " ";
|
||||
NF += fill("?", +y["min-numerator-digits"] || 1);
|
||||
NF += "/";
|
||||
if(+y["denominator-value"]) NF += y["denominator-value"];
|
||||
else NF += fill("?", +y["min-denominator-digits"] || 1);
|
||||
break;
|
||||
|
||||
case 'currency-symbol': // <number:currency-symbol> 16.29.9
|
||||
// TODO: localization with [$-...]
|
||||
if(Rn[1]==='/') {
|
||||
NF += '"' + str.slice(tidx, xlmlregex.lastIndex - Rn[0].length).replace(/"/g, '""') + '"';
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
tidx = xlmlregex.lastIndex;
|
||||
} else NF += "$";
|
||||
break;
|
||||
|
||||
case 'text-properties': // <style:text-properties> 16.29.29
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch((y["color"]||"").toLowerCase().replace("#", "")) {
|
||||
case "ff0000": case "red": NF = "[Red]" + NF; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'text-content': // <number:text-content> 16.29.28
|
||||
NF += "@";
|
||||
break;
|
||||
|
||||
case 'map': // <style:map> 16.3
|
||||
// TODO: handle more complex maps
|
||||
y = parsexmltag(Rn[0], false);
|
||||
if(unescapexml(y["condition"]) == "value()>=0") NF = number_format_map[y["apply-style-name"]] + ";" + NF;
|
||||
else console.error("ODS number format may be incorrect: " + y["condition"]);
|
||||
break;
|
||||
|
||||
case 'number': // <number:number> 16.29.3
|
||||
// TODO: handle all the attributes
|
||||
if(Rn[1]==='/') break;
|
||||
y = parsexmltag(Rn[0], false);
|
||||
tNF = "";
|
||||
tNF += fill("0", +y["min-integer-digits"] || 1);
|
||||
if(parsexmlbool(y["grouping"])) tNF = commaify(fill("#", Math.max(0, 4 - tNF.length)) + tNF);
|
||||
if(+y["min-decimal-places"] || +y["decimal-places"]) tNF += ".";
|
||||
if(+y["min-decimal-places"]) tNF += fill("0", +y["min-decimal-places"] || 1);
|
||||
if(+y["decimal-places"] - (+y["min-decimal-places"]||0)) tNF += fill("0", +y["decimal-places"] - (+y["min-decimal-places"]||0)); // TODO: should this be "#" ?
|
||||
NF += tNF;
|
||||
break;
|
||||
|
||||
case 'embedded-text': // <number:embedded-text> 16.29.4
|
||||
// TODO: verify interplay with grouping et al
|
||||
if(Rn[1]==='/') {
|
||||
if(etpos == 0) NF += '"' + str.slice(tidx, xlmlregex.lastIndex - Rn[0].length).replace(/"/g, '""') + '"';
|
||||
else NF = NF.slice(0, etpos) + '"' + str.slice(tidx, xlmlregex.lastIndex - Rn[0].length).replace(/"/g, '""') + '"' + NF.slice(etpos);
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
tidx = xlmlregex.lastIndex;
|
||||
etpos = -+parsexmltag(Rn[0], false)["position"] || 0;
|
||||
} break;
|
||||
|
||||
}}
|
||||
return number_format_map;
|
||||
}
|
||||
|
||||
function parse_content_xml(d/*:string*/, _opts, _nfm)/*:Workbook*/ {
|
||||
var opts = _opts || {};
|
||||
if(DENSE != null && opts.dense == null) opts.dense = DENSE;
|
||||
var str = xlml_normalize(d);
|
||||
var state/*:Array<any>*/ = [], tmp;
|
||||
var tag/*:: = {}*/;
|
||||
var NFtag = {name:""}, NF = "", pidx = 0;
|
||||
var nfidx, NF = "", pidx = 0;
|
||||
var sheetag/*:: = {name:"", '名称':""}*/;
|
||||
var rowtag/*:: = {'行号':""}*/;
|
||||
var Sheets = {}, SheetNames/*:Array<string>*/ = [];
|
||||
@ -21771,7 +21990,7 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
var textR = [];
|
||||
var R = -1, C = -1, range = {s: {r:1000000,c:10000000}, e: {r:0, c:0}};
|
||||
var row_ol = 0;
|
||||
var number_format_map = {};
|
||||
var number_format_map = _nfm || {}, styles = {};
|
||||
var merges/*:Array<Range>*/ = [], mrange = {}, mR = 0, mC = 0;
|
||||
var rowinfo/*:Array<RowInfo>*/ = [], rowpeat = 1, colpeat = 1;
|
||||
var arrayf/*:Array<[Range, string]>*/ = [];
|
||||
@ -21782,7 +22001,7 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
var creator = "", creatoridx = 0;
|
||||
var isstub = false, intable = false;
|
||||
var i = 0;
|
||||
var baddate = 1;
|
||||
var baddate = 0;
|
||||
xlmlregex.lastIndex = 0;
|
||||
str = str.replace(/<!--([\s\S]*?)-->/mg,"").replace(/<!DOCTYPE[^\[]*\[[^\]]*\]>/gm,"");
|
||||
while((Rn = xlmlregex.exec(str))) switch((Rn[3]=Rn[3].replace(/_.*$/,""))) {
|
||||
@ -21840,6 +22059,7 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
colpeat = parseInt(ctag['number-columns-repeated']||"1", 10);
|
||||
q = ({t:'z', v:null/*:: , z:null, w:"",c:[]*/}/*:any*/);
|
||||
if(ctag.formula && opts.cellFormula != false) q.f = ods_to_csf_formula(unescapexml(ctag.formula));
|
||||
if(ctag["style-name"] && styles[ctag["style-name"]]) q.z = styles[ctag["style-name"]];
|
||||
if((ctag['数据类型'] || ctag['value-type']) == "string") {
|
||||
q.t = "s"; q.v = unescapexml(ctag['string-value'] || "");
|
||||
if(opts.dense) {
|
||||
@ -21862,6 +22082,7 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
ctag = parsexmltag(Rn[0], false);
|
||||
comments = []; comment = ({}/*:any*/);
|
||||
q = ({t:ctag['数据类型'] || ctag['value-type'], v:null/*:: , z:null, w:"",c:[]*/}/*:any*/);
|
||||
if(ctag["style-name"] && styles[ctag["style-name"]]) q.z = styles[ctag["style-name"]];
|
||||
if(opts.cellFormula) {
|
||||
if(ctag.formula) ctag.formula = unescapexml(ctag.formula);
|
||||
if(ctag['number-matrix-columns-spanned'] && ctag['number-matrix-rows-spanned']) {
|
||||
@ -21889,16 +22110,16 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
|
||||
/* 19.385 office:value-type */
|
||||
switch(q.t) {
|
||||
case 'boolean': q.t = 'b'; q.v = parsexmlbool(ctag['boolean-value']); break;
|
||||
case 'boolean': q.t = 'b'; q.v = parsexmlbool(ctag['boolean-value']) || (+ctag['boolean-value'] >= 1); break;
|
||||
case 'float': q.t = 'n'; q.v = parseFloat(ctag.value); break;
|
||||
case 'percentage': q.t = 'n'; q.v = parseFloat(ctag.value); break;
|
||||
case 'currency': q.t = 'n'; q.v = parseFloat(ctag.value); break;
|
||||
case 'date': q.t = 'd'; q.v = parseDate(ctag['date-value']);
|
||||
if(!opts.cellDates) { q.t = 'n'; q.v = datenum(q.v, WB.WBProps.date1904) - baddate; }
|
||||
q.z = 'm/d/yy'; break;
|
||||
if(!q.z) q.z = 'm/d/yy'; break;
|
||||
case 'time': q.t = 'n'; q.v = parse_isodur(ctag['time-value'])/86400;
|
||||
if(opts.cellDates) { q.t = 'd'; q.v = numdate(q.v); }
|
||||
q.z = 'HH:MM:SS'; break;
|
||||
if(!q.z) q.z = 'HH:MM:SS'; break;
|
||||
case 'number': q.t = 'n'; q.v = parseFloat(ctag['数据数值']); break;
|
||||
default:
|
||||
if(q.t === 'string' || q.t === 'text' || !q.t) {
|
||||
@ -21993,23 +22214,24 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
textp = ""; textpidx = 0; textR = [];
|
||||
break;
|
||||
|
||||
case 'scientific-number': // TODO: <number:scientific-number>
|
||||
break;
|
||||
case 'currency-symbol': // TODO: <number:currency-symbol>
|
||||
break;
|
||||
case 'currency-style': // TODO: <number:currency-style>
|
||||
case 'scientific-number': // <number:scientific-number>
|
||||
case 'currency-symbol': // <number:currency-symbol>
|
||||
case 'fill-character': // 16.29.5 <number:fill-character>
|
||||
break;
|
||||
|
||||
case 'text-style': // 16.27.25 <number:text-style>
|
||||
case 'boolean-style': // 16.27.23 <number:boolean-style>
|
||||
case 'number-style': // 16.27.2 <number:number-style>
|
||||
case 'currency-style': // 16.29.8 <number:currency-style>
|
||||
case 'percentage-style': // 16.27.9 <number:percentage-style>
|
||||
case 'date-style': // 16.27.10 <number:date-style>
|
||||
case 'time-style': // 16.27.18 <number:time-style>
|
||||
if(Rn[1]==='/'){
|
||||
number_format_map[NFtag.name] = NF;
|
||||
if((tmp=state.pop())[0]!==Rn[3]) throw "Bad state: "+tmp;
|
||||
var xlmlidx = xlmlregex.lastIndex;
|
||||
parse_ods_styles(str.slice(nfidx, xlmlregex.lastIndex), _opts, number_format_map);
|
||||
xlmlregex.lastIndex = xlmlidx;
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
NF = "";
|
||||
NFtag = parsexmltag(Rn[0], false);
|
||||
state.push([Rn[3], true]);
|
||||
nfidx = xlmlregex.lastIndex - Rn[0].length;
|
||||
} break;
|
||||
|
||||
case 'script': break; // 3.13 <office:script>
|
||||
@ -22018,8 +22240,10 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
|
||||
case 'default-style': // TODO: <style:default-style>
|
||||
case 'page-layout': break; // TODO: <style:page-layout>
|
||||
case 'style': // 16.2 <style:style>
|
||||
break;
|
||||
case 'style': { // 16.2 <style:style>
|
||||
var styletag = parsexmltag(Rn[0], false);
|
||||
if(styletag["family"] == "table-cell" && number_format_map[styletag["data-style-name"]]) styles[styletag["name"]] = number_format_map[styletag["data-style-name"]];
|
||||
} break;
|
||||
case 'map': break; // 16.3 <style:map>
|
||||
case 'font-face': break; // 16.21 <style:font-face>
|
||||
|
||||
@ -22030,12 +22254,7 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
case 'table-cell-properties': break; // 17.18 <style:table-cell-properties>
|
||||
|
||||
case 'number': // 16.27.3 <number:number>
|
||||
switch(state[state.length-1][0]) {
|
||||
case 'time-style':
|
||||
case 'date-style':
|
||||
tag = parsexmltag(Rn[0], false);
|
||||
NF += number_formats_ods[Rn[3]][tag.style==='long'?1:0]; break;
|
||||
} break;
|
||||
break;
|
||||
|
||||
case 'fraction': break; // TODO 16.27.6 <number:fraction>
|
||||
|
||||
@ -22050,16 +22269,9 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
case 'minutes': // 16.27.20 <number:minutes>
|
||||
case 'seconds': // 16.27.21 <number:seconds>
|
||||
case 'am-pm': // 16.27.22 <number:am-pm>
|
||||
switch(state[state.length-1][0]) {
|
||||
case 'time-style':
|
||||
case 'date-style':
|
||||
tag = parsexmltag(Rn[0], false);
|
||||
NF += number_formats_ods[Rn[3]][tag.style==='long'?1:0]; break;
|
||||
} break;
|
||||
break;
|
||||
|
||||
case 'boolean-style': break; // 16.27.23 <number:boolean-style>
|
||||
case 'boolean': break; // 16.27.24 <number:boolean>
|
||||
case 'text-style': break; // 16.27.25 <number:text-style>
|
||||
case 'text': // 16.27.26 <number:text>
|
||||
if(Rn[0].slice(-2) === "/>") break;
|
||||
else if(Rn[1]==="/") switch(state[state.length-1][0]) {
|
||||
@ -22290,9 +22502,11 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
function parse_ods(zip/*:ZIPFile*/, opts/*:?ParseOpts*/)/*:Workbook*/ {
|
||||
opts = opts || ({}/*:any*/);
|
||||
if(safegetzipfile(zip, 'META-INF/manifest.xml')) parse_manifest(getzipdata(zip, 'META-INF/manifest.xml'), opts);
|
||||
var styles = getzipstr(zip, 'styles.xml');
|
||||
var Styles = styles && parse_ods_styles(utf8read(styles), opts);
|
||||
var content = getzipstr(zip, 'content.xml');
|
||||
if(!content) throw new Error("Missing content.xml in ODS / UOF file");
|
||||
var wb = parse_content_xml(utf8read(content), opts);
|
||||
var wb = parse_content_xml(utf8read(content), opts, Styles);
|
||||
if(safegetzipfile(zip, 'meta.xml')) wb.Props = parse_core_props(getzipdata(zip, 'meta.xml'));
|
||||
return wb;
|
||||
}
|
||||
@ -22332,6 +22546,157 @@ var write_styles_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function(
|
||||
return XML_HEADER + payload;
|
||||
};
|
||||
})();
|
||||
|
||||
// TODO: find out if anyone actually read the spec. LO has some wild errors
|
||||
function write_number_format_ods(nf/*:string*/, nfidx/*:string*/)/*:string*/ {
|
||||
var type = "number", payload = "", nopts = { "style:name": nfidx }, c = "", i = 0;
|
||||
nf = nf.replace(/"[$]"/g, "$");
|
||||
/* TODO: replace with an actual parser based on a real grammar */
|
||||
j: {
|
||||
// TODO: support style maps
|
||||
if(nf.indexOf(";") > -1) {
|
||||
console.error("Unsupported ODS Style Map exported. Using first branch of " + nf);
|
||||
nf = nf.slice(0, nf.indexOf(";"));
|
||||
}
|
||||
|
||||
if(nf == "@") { type = "text"; payload = "<number:text-content/>"; break j; }
|
||||
|
||||
/* currency flag */
|
||||
if(nf.indexOf(/\$/) > -1) { type = "currency"; }
|
||||
|
||||
/* opening string literal */
|
||||
if(nf[i] == '"') {
|
||||
c = "";
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
if(nf[i+1] == "*") {
|
||||
i++;
|
||||
payload += '<number:fill-character>' + escapexml(c.replace(/""/g, '"')) + '</number:fill-character>';
|
||||
} else {
|
||||
payload += '<number:text>' + escapexml(c.replace(/""/g, '"')) + '</number:text>';
|
||||
}
|
||||
nf = nf.slice(i+1); i = 0;
|
||||
}
|
||||
|
||||
/* fractions */
|
||||
var t = nf.match(/# (\?+)\/(\?+)/);
|
||||
if(t) { payload += writextag("number:fraction", null, {"number:min-integer-digits":0, "number:min-numerator-digits": t[1].length, "number:max-denominator-value": Math.max(+(t[1].replace(/./g, "9")), +(t[2].replace(/./g, "9"))) }); break j; }
|
||||
if((t=nf.match(/# (\?+)\/(\d+)/))) { payload += writextag("number:fraction", null, {"number:min-integer-digits":0, "number:min-numerator-digits": t[1].length, "number:denominator-value": +t[2]}); break j; }
|
||||
|
||||
/* percentages */
|
||||
if((t=nf.match(/(\d+)(|\.\d+)%/))) { type = "percentage"; payload += writextag("number:number", null, {"number:decimal-places": t[2] && t.length - 1 || 0, "number:min-decimal-places": t[2] && t.length - 1 || 0, "number:min-integer-digits": t[1].length }) + "<number:text>%</number:text>"; break j; }
|
||||
|
||||
/* datetime */
|
||||
var has_time = false;
|
||||
if(["y","m","d"].indexOf(nf[0]) > -1) {
|
||||
type = "date";
|
||||
k: for(; i < nf.length; ++i) switch((c = nf[i].toLowerCase())) {
|
||||
case "h": case "s": has_time = true; --i; break k;
|
||||
case "m":
|
||||
l: for(var h = i+1; h < nf.length; ++h) switch(nf[h]) {
|
||||
case "y": case "d": break l;
|
||||
case "h": case "s": has_time = true; --i; break k;
|
||||
}
|
||||
/* falls through */
|
||||
case "y": case "d":
|
||||
while((nf[++i]||"").toLowerCase() == c[0]) c += c[0]; --i;
|
||||
switch(c) {
|
||||
case "y": case "yy": payload += "<number:year/>"; break;
|
||||
case "yyy": case "yyyy": payload += '<number:year number:style="long"/>'; break;
|
||||
case "mmmmm": console.error("ODS has no equivalent of format |mmmmm|");
|
||||
/* falls through */
|
||||
case "m": case "mm": case "mmm": case "mmmm":
|
||||
payload += '<number:month number:style="' + (c.length % 2 ? "short" : "long") + '" number:textual="' + (c.length >= 3 ? "true" : "false") + '"/>';
|
||||
break;
|
||||
case "d": case "dd": payload += '<number:day number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
|
||||
case "ddd": case "dddd": payload += '<number:day-of-week number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
|
||||
}
|
||||
break;
|
||||
case '"':
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
payload += '<number:text>' + escapexml(c.slice(1).replace(/""/g, '"')) + '</number:text>';
|
||||
break;
|
||||
case '/': payload += '<number:text>' + escapexml(c) + '</number:text>'; break;
|
||||
default: console.error("unrecognized character " + c + " in ODF format " + nf);
|
||||
}
|
||||
if(!has_time) break j;
|
||||
nf = nf.slice(i+1); i = 0;
|
||||
}
|
||||
if(nf.match(/^\[?[hms]/)) {
|
||||
if(type == "number") type = "time";
|
||||
if(nf.match(/\[/)) {
|
||||
nf = nf.replace(/[\[\]]/g, "");
|
||||
nopts['number:truncate-on-overflow'] = "false";
|
||||
}
|
||||
for(; i < nf.length; ++i) switch((c = nf[i].toLowerCase())) {
|
||||
case "h": case "m": case "s":
|
||||
while((nf[++i]||"").toLowerCase() == c[0]) c += c[0]; --i;
|
||||
switch(c) {
|
||||
case "h": case "hh": payload += '<number:hours number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
|
||||
case "m": case "mm": payload += '<number:minutes number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
|
||||
case "s": case "ss":
|
||||
if(nf[i+1] == ".") do { c += nf[i+1]; ++i; } while(nf[i+1] == "0");
|
||||
payload += '<number:seconds number:style="' + (c.match("ss") ? "long" : "short") + '"' + (c.match(/\./) ? ' number:decimal-places="' + (c.match(/0+/)||[""])[0].length + '"' : "")+ '/>'; break;
|
||||
}
|
||||
break;
|
||||
case '"':
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
payload += '<number:text>' + escapexml(c.slice(1).replace(/""/g, '"')) + '</number:text>';
|
||||
break;
|
||||
case '/': payload += '<number:text>' + escapexml(c) + '</number:text>'; break;
|
||||
case "a":
|
||||
if(nf.slice(i, i+3).toLowerCase() == "a/p") { payload += '<number:am-pm/>'; i += 2; break; } // Note: ODF does not support A/P
|
||||
if(nf.slice(i, i+5).toLowerCase() == "am/pm") { payload += '<number:am-pm/>'; i += 4; break; }
|
||||
/* falls through */
|
||||
default: console.error("unrecognized character " + c + " in ODF format " + nf);
|
||||
}
|
||||
break j;
|
||||
}
|
||||
|
||||
/* currency flag */
|
||||
if(nf.indexOf(/\$/) > -1) { type = "currency"; }
|
||||
|
||||
/* should be in a char loop */
|
||||
if(nf[0] == "$") { payload += '<number:currency-symbol number:language="en" number:country="US">$</number:currency-symbol>'; nf = nf.slice(1); i = 0; }
|
||||
i = 0; if(nf[i] == '"') {
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
if(nf[i+1] == "*") {
|
||||
i++;
|
||||
payload += '<number:fill-character>' + escapexml(c.replace(/""/g, '"')) + '</number:fill-character>';
|
||||
} else {
|
||||
payload += '<number:text>' + escapexml(c.replace(/""/g, '"')) + '</number:text>';
|
||||
}
|
||||
nf = nf.slice(i+1); i = 0;
|
||||
}
|
||||
|
||||
/* number TODO: interstitial text e.g. 000)000-0000 */
|
||||
var np = nf.match(/([#0][0#,]*)(\.[0#]*|)(E[+]?0*|)/i);
|
||||
if(!np || !np[0]) console.error("Could not find numeric part of " + nf);
|
||||
else {
|
||||
var base = np[1].replace(/,/g, "");
|
||||
payload += '<number:' + (np[3] ? "scientific-" : "")+ 'number' +
|
||||
' number:min-integer-digits="' + (base.indexOf("0") == -1 ? "0" : base.length - base.indexOf("0")) + '"' +
|
||||
(np[0].indexOf(",") > -1 ? ' number:grouping="true"' : "") +
|
||||
(np[2] && ' number:decimal-places="' + (np[2].length - 1) + '"' || ' number:decimal-places="0"') +
|
||||
(np[3] && np[3].indexOf("+") > -1 ? ' number:forced-exponent-sign="true"' : "" ) +
|
||||
(np[3] ? ' number:min-exponent-digits="' + np[3].match(/0+/)[0].length + '"' : "" ) +
|
||||
'>' +
|
||||
/* TODO: interstitial text placeholders */
|
||||
'</number:' + (np[3] ? "scientific-" : "") + 'number>';
|
||||
i = np.index + np[0].length;
|
||||
}
|
||||
|
||||
/* residual text */
|
||||
if(nf[i] == '"') {
|
||||
c = "";
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
payload += '<number:text>' + escapexml(c.replace(/""/g, '"')) + '</number:text>';
|
||||
}
|
||||
}
|
||||
|
||||
if(!payload) { console.error("Could not generate ODS number format for |" + nf + "|"); return ""; }
|
||||
return writextag("number:" + type + "-style", payload, nopts);
|
||||
}
|
||||
|
||||
function write_names_ods(Names, SheetNames, idx) {
|
||||
var scoped = Names.filter(function(name) { return name.Sheet == (idx == -1 ? null : idx); });
|
||||
if(!scoped.length) return "";
|
||||
@ -22356,7 +22721,7 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function
|
||||
|
||||
var null_cell_xml = ' <table:table-cell />\n';
|
||||
var covered_cell_xml = ' <table:covered-table-cell/>\n';
|
||||
var write_ws = function(ws, wb/*:Workbook*/, i/*:number*//*::, opts*/)/*:string*/ {
|
||||
var write_ws = function(ws, wb/*:Workbook*/, i/*:number*/, opts, nfs)/*:string*/ {
|
||||
/* Section 9 Tables */
|
||||
var o/*:Array<string>*/ = [];
|
||||
o.push(' <table:table table:name="' + escapexml(wb.SheetNames[i]) + '" table:style-name="ta1">\n');
|
||||
@ -22432,6 +22797,7 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function
|
||||
if(_tgt.charAt(0) != "#" && !_tgt.match(/^\w+:/)) _tgt = '../' + _tgt;
|
||||
text_p = writextag('text:a', text_p, {'xlink:href': _tgt.replace(/&/g, "&")});
|
||||
}
|
||||
if(nfs[cell.z]) ct["table:style-name"] = "ce" + nfs[cell.z].slice(1);
|
||||
o.push(' ' + writextag('table:table-cell', writextag('text:p', text_p, {}), ct) + '\n');
|
||||
}
|
||||
o.push(' </table:table-row>\n');
|
||||
@ -22444,14 +22810,6 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function
|
||||
var write_automatic_styles_ods = function(o/*:Array<string>*/, wb) {
|
||||
o.push(' <office:automatic-styles>\n');
|
||||
|
||||
o.push(' <number:date-style style:name="N37" number:automatic-order="true">\n');
|
||||
o.push(' <number:month number:style="long"/>\n');
|
||||
o.push(' <number:text>/</number:text>\n');
|
||||
o.push(' <number:day number:style="long"/>\n');
|
||||
o.push(' <number:text>/</number:text>\n');
|
||||
o.push(' <number:year/>\n');
|
||||
o.push(' </number:date-style>\n');
|
||||
|
||||
/* column styles */
|
||||
var cidx = 0;
|
||||
wb.SheetNames.map(function(n) { return wb.Sheets[n]; }).forEach(function(ws) {
|
||||
@ -22492,12 +22850,38 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function
|
||||
o.push(' <style:table-properties table:display="true" style:writing-mode="lr-tb"/>\n');
|
||||
o.push(' </style:style>\n');
|
||||
|
||||
/* table cells, text */
|
||||
o.push(' <number:date-style style:name="N37" number:automatic-order="true">\n');
|
||||
o.push(' <number:month number:style="long"/>\n');
|
||||
o.push(' <number:text>/</number:text>\n');
|
||||
o.push(' <number:day number:style="long"/>\n');
|
||||
o.push(' <number:text>/</number:text>\n');
|
||||
o.push(' <number:year/>\n');
|
||||
o.push(' </number:date-style>\n');
|
||||
|
||||
/* number formats, table cells, text */
|
||||
var nfs = {};
|
||||
var nfi = 69;
|
||||
wb.SheetNames.map(function(n) { return wb.Sheets[n]; }).forEach(function(ws) {
|
||||
if(!ws) return;
|
||||
var range = decode_range(ws["!ref"]);
|
||||
for(var R = 0; R <= range.e.r; ++R) for(var C = 0; C <= range.e.c; ++C) {
|
||||
var c = Array.isArray(ws) ? (ws[R]||[])[C] : ws[encode_cell({r:R,c:C})];
|
||||
if(!c || !c.z || c.z.toLowerCase() == "general") continue;
|
||||
if(!nfs[c.z]) {
|
||||
var out = write_number_format_ods(c.z, "N" + nfi);
|
||||
if(out) { nfs[c.z] = "N" + nfi; ++nfi; o.push(out + "\n"); }
|
||||
}
|
||||
}
|
||||
});
|
||||
o.push(' <style:style style:name="ce1" style:family="table-cell" style:parent-style-name="Default" style:data-style-name="N37"/>\n');
|
||||
keys(nfs).forEach(function(nf) {
|
||||
o.push('<style:style style:name="ce' + nfs[nf].slice(1) + '" style:family="table-cell" style:parent-style-name="Default" style:data-style-name="' + nfs[nf] + '"/>\n');
|
||||
});
|
||||
|
||||
/* page-layout */
|
||||
|
||||
o.push(' </office:automatic-styles>\n');
|
||||
return nfs;
|
||||
};
|
||||
|
||||
return function wcx(wb, opts) {
|
||||
@ -22550,15 +22934,15 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function
|
||||
|
||||
if(opts.bookType == "fods") {
|
||||
o.push('<office:document' + attr + fods + '>\n');
|
||||
o.push(write_meta_ods().replace(/office:document-meta/g, "office:meta"));
|
||||
o.push(write_meta_ods().replace(/<office:document-meta.*?>/, "").replace(/<\/office:document-meta>/, "") + "\n");
|
||||
// TODO: settings (equiv of settings.xml for ODS)
|
||||
} else o.push('<office:document-content' + attr + '>\n');
|
||||
// o.push(' <office:scripts/>\n');
|
||||
write_automatic_styles_ods(o, wb);
|
||||
var nfs = write_automatic_styles_ods(o, wb);
|
||||
o.push(' <office:body>\n');
|
||||
o.push(' <office:spreadsheet>\n');
|
||||
if(((wb.Workbook||{}).WBProps||{}).date1904) o.push(' <table:calculation-settings table:case-sensitive="false" table:search-criteria-must-apply-to-whole-cell="true" table:use-wildcards="true" table:use-regular-expressions="false" table:automatic-find-labels="false">\n <table:null-date table:date-value="1904-01-01"/>\n </table:calculation-settings>\n');
|
||||
for(var i = 0; i != wb.SheetNames.length; ++i) o.push(write_ws(wb.Sheets[wb.SheetNames[i]], wb, i, opts));
|
||||
for(var i = 0; i != wb.SheetNames.length; ++i) o.push(write_ws(wb.Sheets[wb.SheetNames[i]], wb, i, opts, nfs));
|
||||
if((wb.Workbook||{}).Names) o.push(write_names_ods(wb.Workbook.Names, wb.SheetNames, -1));
|
||||
o.push(' </office:spreadsheet>\n');
|
||||
o.push(' </office:body>\n');
|
||||
|
508
xlsx.js
generated
508
xlsx.js
generated
@ -4,7 +4,7 @@
|
||||
/*global global, exports, module, require:false, process:false, Buffer:false, ArrayBuffer:false, DataView:false, Deno:false */
|
||||
var XLSX = {};
|
||||
function make_xlsx_lib(XLSX){
|
||||
XLSX.version = '0.18.8';
|
||||
XLSX.version = '0.18.9';
|
||||
var current_codepage = 1200, current_ansi = 1252;
|
||||
/*global cptable:true, window */
|
||||
var $cptable;
|
||||
@ -13414,6 +13414,7 @@ ixti = f[1][1]; r = f[1][2];
|
||||
}
|
||||
}
|
||||
if(stack.length > 1 && opts.WTF) throw new Error("bad formula stack");
|
||||
if(stack[0] == "TRUE") return true; if(stack[0] == "FALSE") return false;
|
||||
return stack[0];
|
||||
}
|
||||
|
||||
@ -13571,6 +13572,15 @@ function write_XLSBFormulaErr(val) {
|
||||
oint.write_shift(4, 0);
|
||||
return oint;
|
||||
}
|
||||
/* Writes a PtgBool */
|
||||
function write_XLSBFormulaBool(val) {
|
||||
var oint = new_buf(10);
|
||||
oint.write_shift(4, 2);
|
||||
oint.write_shift(1, 0x1D);
|
||||
oint.write_shift(1, val?1:0);
|
||||
oint.write_shift(4, 0);
|
||||
return oint;
|
||||
}
|
||||
|
||||
/* Writes a PtgStr */
|
||||
function write_XLSBFormulaStr(val) {
|
||||
@ -13726,6 +13736,8 @@ function write_XLSBFormulaArea3D(_str, wb) {
|
||||
|
||||
/* General Formula */
|
||||
function write_XLSBFormula(val, wb) {
|
||||
if(typeof val == "number") return write_XLSBFormulaNum(val);
|
||||
if(typeof val == "boolean") return write_XLSBFormulaBool(val);
|
||||
if(/^#(DIV\/0!|GETTING_DATA|N\/A|NAME\?|NULL!|NUM!|REF!|VALUE!)$/.test(val)) return write_XLSBFormulaErr(+RBErr[val]);
|
||||
if(val.match(/^\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})$/)) return write_XLSBFormulaRef(val);
|
||||
if(val.match(/^\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5}):\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})$/)) return write_XLSBFormulaRange(val);
|
||||
@ -21628,29 +21640,236 @@ function parse_text_p(text) {
|
||||
return [v];
|
||||
}
|
||||
|
||||
var number_formats_ods = {
|
||||
/* ods name: [short ssf fmt, long ssf fmt] */
|
||||
day: ["d", "dd"],
|
||||
month: ["m", "mm"],
|
||||
year: ["y", "yy"],
|
||||
hours: ["h", "hh"],
|
||||
minutes: ["m", "mm"],
|
||||
seconds: ["s", "ss"],
|
||||
"am-pm": ["A/P", "AM/PM"],
|
||||
"day-of-week": ["ddd", "dddd"],
|
||||
era: ["e", "ee"],
|
||||
/* there is no native representation of LO "Q" format */
|
||||
quarter: ["\\Qm", "m\\\"th quarter\""]
|
||||
};
|
||||
/* Note: ODS can stick styles in content.xml or styles.xml, FODS blurs lines */
|
||||
function parse_ods_styles(d, _opts, _nfm) {
|
||||
var number_format_map = _nfm || {};
|
||||
var str = xlml_normalize(d);
|
||||
xlmlregex.lastIndex = 0;
|
||||
str = str.replace(/<!--([\s\S]*?)-->/mg,"").replace(/<!DOCTYPE[^\[]*\[[^\]]*\]>/gm,"");
|
||||
var Rn, NFtag, NF = "", tNF = "", y, etpos = 0, tidx = -1, infmt = false, payload = "";
|
||||
while((Rn = xlmlregex.exec(str))) {
|
||||
switch((Rn[3]=Rn[3].replace(/_.*$/,""))) {
|
||||
/* Number Format Definitions */
|
||||
case 'number-style': // <number:number-style> 16.29.2
|
||||
case 'currency-style': // <number:currency-style> 16.29.8
|
||||
case 'percentage-style': // <number:percentage-style> 16.29.10
|
||||
case 'date-style': // <number:date-style> 16.29.11
|
||||
case 'time-style': // <number:time-style> 16.29.19
|
||||
case 'text-style': // <number:text-style> 16.29.26
|
||||
if(Rn[1]==='/') {
|
||||
infmt = false;
|
||||
if(NFtag['truncate-on-overflow'] == "false") {
|
||||
if(NF.match(/h/)) NF = NF.replace(/h+/, "[$&]");
|
||||
else if(NF.match(/m/)) NF = NF.replace(/m+/, "[$&]");
|
||||
else if(NF.match(/s/)) NF = NF.replace(/s+/, "[$&]");
|
||||
}
|
||||
number_format_map[NFtag.name] = NF;
|
||||
NF = "";
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
infmt = true;
|
||||
NF = "";
|
||||
NFtag = parsexmltag(Rn[0], false);
|
||||
} break;
|
||||
|
||||
// LibreOffice bug https://bugs.documentfoundation.org/show_bug.cgi?id=149484
|
||||
case 'boolean-style': // <number:boolean-style> 16.29.24
|
||||
if(Rn[1]==='/') {
|
||||
infmt = false;
|
||||
number_format_map[NFtag.name] = "General";
|
||||
NF = "";
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
infmt = true;
|
||||
NF = "";
|
||||
NFtag = parsexmltag(Rn[0], false);
|
||||
} break;
|
||||
|
||||
/* Number Format Elements */
|
||||
case 'boolean': // <number:boolean> 16.29.25
|
||||
NF += "General"; // ODF spec is unfortunately underspecified here
|
||||
break;
|
||||
|
||||
case 'text': // <number:text> 16.29.27
|
||||
if(Rn[1]==='/') {
|
||||
payload = str.slice(tidx, xlmlregex.lastIndex - Rn[0].length);
|
||||
// NOTE: Excel has a different interpretation of "%%" and friends
|
||||
if(payload == "%" && NFtag[0] == '<number:percentage-style') NF += "%";
|
||||
else NF += '"' + payload.replace(/"/g, '""') + '"';
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
tidx = xlmlregex.lastIndex;
|
||||
} break;
|
||||
|
||||
|
||||
function parse_content_xml(d, _opts) {
|
||||
case 'day': { // <number:day> 16.29.12
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "d"; break;
|
||||
case "long": NF += "dd"; break;
|
||||
default: NF += "dd"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'day-of-week': { // <number:day-of-week> 16.29.16
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "ddd"; break;
|
||||
case "long": NF += "dddd"; break;
|
||||
default: NF += "ddd"; break;
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'era': { // <number:era> 16.29.15 TODO: proper mapping
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "ee"; break;
|
||||
case "long": NF += "eeee"; break;
|
||||
default: NF += "eeee"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'hours': { // <number:hours> 16.29.20
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "h"; break;
|
||||
case "long": NF += "hh"; break;
|
||||
default: NF += "hh"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'minutes': { // <number:minutes> 16.29.21
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "m"; break;
|
||||
case "long": NF += "mm"; break;
|
||||
default: NF += "mm"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'month': { // <number:month> 16.29.13
|
||||
y = parsexmltag(Rn[0], false);
|
||||
if(y["textual"]) NF += "mm";
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "m"; break;
|
||||
case "long": NF += "mm"; break;
|
||||
default: NF += "m"; break;
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'seconds': { // <number:seconds> 16.29.22
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "s"; break;
|
||||
case "long": NF += "ss"; break;
|
||||
default: NF += "ss"; break; // TODO: error condition
|
||||
}
|
||||
if(y["decimal-places"]) NF += "." + fill("0", +y["decimal-places"]);
|
||||
} break;
|
||||
|
||||
case 'year': { // <number:year> 16.29.14
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "yy"; break;
|
||||
case "long": NF += "yyyy"; break;
|
||||
default: NF += "yy"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'am-pm': // <number:am-pm> 16.29.23
|
||||
NF += "AM/PM"; // LO autocorrects A/P -> AM/PM
|
||||
break;
|
||||
|
||||
case 'week-of-year': // <number:week-of-year> 16.29.17
|
||||
case 'quarter': // <number:quarter> 16.29.18
|
||||
console.error("Excel does not support ODS format token " + Rn[3]);
|
||||
break;
|
||||
|
||||
case 'fill-character': // <number:fill-character> 16.29.5
|
||||
if(Rn[1]==='/') {
|
||||
payload = str.slice(tidx, xlmlregex.lastIndex - Rn[0].length);
|
||||
// NOTE: Excel has a different interpretation of "%%" and friends
|
||||
NF += '"' + payload.replace(/"/g, '""') + '"*';
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
tidx = xlmlregex.lastIndex;
|
||||
} break;
|
||||
|
||||
case 'scientific-number': // <number:scientific-number> 16.29.6
|
||||
// TODO: find a mapping for all parameters
|
||||
y = parsexmltag(Rn[0], false);
|
||||
NF += "0." + fill("0", +y["min-decimal-places"] || +y["decimal-places"] || 2) + fill("?", +y["decimal-places"] - +y["min-decimal-places"] || 0) + "E" + (parsexmlbool(y["forced-exponent-sign"]) ? "+" : "") + fill("0", +y["min-exponent-digits"] || 2);
|
||||
break;
|
||||
|
||||
case 'fraction': // <number:fraction> 16.29.7
|
||||
// TODO: find a mapping for all parameters
|
||||
y = parsexmltag(Rn[0], false);
|
||||
if(!+y["min-integer-digits"]) NF += "#";
|
||||
else NF += fill("0", +y["min-integer-digits"]);
|
||||
NF += " ";
|
||||
NF += fill("?", +y["min-numerator-digits"] || 1);
|
||||
NF += "/";
|
||||
if(+y["denominator-value"]) NF += y["denominator-value"];
|
||||
else NF += fill("?", +y["min-denominator-digits"] || 1);
|
||||
break;
|
||||
|
||||
case 'currency-symbol': // <number:currency-symbol> 16.29.9
|
||||
// TODO: localization with [$-...]
|
||||
if(Rn[1]==='/') {
|
||||
NF += '"' + str.slice(tidx, xlmlregex.lastIndex - Rn[0].length).replace(/"/g, '""') + '"';
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
tidx = xlmlregex.lastIndex;
|
||||
} else NF += "$";
|
||||
break;
|
||||
|
||||
case 'text-properties': // <style:text-properties> 16.29.29
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch((y["color"]||"").toLowerCase().replace("#", "")) {
|
||||
case "ff0000": case "red": NF = "[Red]" + NF; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'text-content': // <number:text-content> 16.29.28
|
||||
NF += "@";
|
||||
break;
|
||||
|
||||
case 'map': // <style:map> 16.3
|
||||
// TODO: handle more complex maps
|
||||
y = parsexmltag(Rn[0], false);
|
||||
if(unescapexml(y["condition"]) == "value()>=0") NF = number_format_map[y["apply-style-name"]] + ";" + NF;
|
||||
else console.error("ODS number format may be incorrect: " + y["condition"]);
|
||||
break;
|
||||
|
||||
case 'number': // <number:number> 16.29.3
|
||||
// TODO: handle all the attributes
|
||||
if(Rn[1]==='/') break;
|
||||
y = parsexmltag(Rn[0], false);
|
||||
tNF = "";
|
||||
tNF += fill("0", +y["min-integer-digits"] || 1);
|
||||
if(parsexmlbool(y["grouping"])) tNF = commaify(fill("#", Math.max(0, 4 - tNF.length)) + tNF);
|
||||
if(+y["min-decimal-places"] || +y["decimal-places"]) tNF += ".";
|
||||
if(+y["min-decimal-places"]) tNF += fill("0", +y["min-decimal-places"] || 1);
|
||||
if(+y["decimal-places"] - (+y["min-decimal-places"]||0)) tNF += fill("0", +y["decimal-places"] - (+y["min-decimal-places"]||0)); // TODO: should this be "#" ?
|
||||
NF += tNF;
|
||||
break;
|
||||
|
||||
case 'embedded-text': // <number:embedded-text> 16.29.4
|
||||
// TODO: verify interplay with grouping et al
|
||||
if(Rn[1]==='/') {
|
||||
if(etpos == 0) NF += '"' + str.slice(tidx, xlmlregex.lastIndex - Rn[0].length).replace(/"/g, '""') + '"';
|
||||
else NF = NF.slice(0, etpos) + '"' + str.slice(tidx, xlmlregex.lastIndex - Rn[0].length).replace(/"/g, '""') + '"' + NF.slice(etpos);
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
tidx = xlmlregex.lastIndex;
|
||||
etpos = -+parsexmltag(Rn[0], false)["position"] || 0;
|
||||
} break;
|
||||
|
||||
}}
|
||||
return number_format_map;
|
||||
}
|
||||
|
||||
function parse_content_xml(d, _opts, _nfm) {
|
||||
var opts = _opts || {};
|
||||
if(DENSE != null && opts.dense == null) opts.dense = DENSE;
|
||||
var str = xlml_normalize(d);
|
||||
var state = [], tmp;
|
||||
var tag;
|
||||
var NFtag = {name:""}, NF = "", pidx = 0;
|
||||
var nfidx, NF = "", pidx = 0;
|
||||
var sheetag;
|
||||
var rowtag;
|
||||
var Sheets = {}, SheetNames = [];
|
||||
@ -21661,7 +21880,7 @@ function parse_content_xml(d, _opts) {
|
||||
var textR = [];
|
||||
var R = -1, C = -1, range = {s: {r:1000000,c:10000000}, e: {r:0, c:0}};
|
||||
var row_ol = 0;
|
||||
var number_format_map = {};
|
||||
var number_format_map = _nfm || {}, styles = {};
|
||||
var merges = [], mrange = {}, mR = 0, mC = 0;
|
||||
var rowinfo = [], rowpeat = 1, colpeat = 1;
|
||||
var arrayf = [];
|
||||
@ -21672,7 +21891,7 @@ function parse_content_xml(d, _opts) {
|
||||
var creator = "", creatoridx = 0;
|
||||
var isstub = false, intable = false;
|
||||
var i = 0;
|
||||
var baddate = 1;
|
||||
var baddate = 0;
|
||||
xlmlregex.lastIndex = 0;
|
||||
str = str.replace(/<!--([\s\S]*?)-->/mg,"").replace(/<!DOCTYPE[^\[]*\[[^\]]*\]>/gm,"");
|
||||
while((Rn = xlmlregex.exec(str))) switch((Rn[3]=Rn[3].replace(/_.*$/,""))) {
|
||||
@ -21730,6 +21949,7 @@ function parse_content_xml(d, _opts) {
|
||||
colpeat = parseInt(ctag['number-columns-repeated']||"1", 10);
|
||||
q = ({t:'z', v:null});
|
||||
if(ctag.formula && opts.cellFormula != false) q.f = ods_to_csf_formula(unescapexml(ctag.formula));
|
||||
if(ctag["style-name"] && styles[ctag["style-name"]]) q.z = styles[ctag["style-name"]];
|
||||
if((ctag['数据类型'] || ctag['value-type']) == "string") {
|
||||
q.t = "s"; q.v = unescapexml(ctag['string-value'] || "");
|
||||
if(opts.dense) {
|
||||
@ -21752,6 +21972,7 @@ function parse_content_xml(d, _opts) {
|
||||
ctag = parsexmltag(Rn[0], false);
|
||||
comments = []; comment = ({});
|
||||
q = ({t:ctag['数据类型'] || ctag['value-type'], v:null});
|
||||
if(ctag["style-name"] && styles[ctag["style-name"]]) q.z = styles[ctag["style-name"]];
|
||||
if(opts.cellFormula) {
|
||||
if(ctag.formula) ctag.formula = unescapexml(ctag.formula);
|
||||
if(ctag['number-matrix-columns-spanned'] && ctag['number-matrix-rows-spanned']) {
|
||||
@ -21779,16 +22000,16 @@ function parse_content_xml(d, _opts) {
|
||||
|
||||
/* 19.385 office:value-type */
|
||||
switch(q.t) {
|
||||
case 'boolean': q.t = 'b'; q.v = parsexmlbool(ctag['boolean-value']); break;
|
||||
case 'boolean': q.t = 'b'; q.v = parsexmlbool(ctag['boolean-value']) || (+ctag['boolean-value'] >= 1); break;
|
||||
case 'float': q.t = 'n'; q.v = parseFloat(ctag.value); break;
|
||||
case 'percentage': q.t = 'n'; q.v = parseFloat(ctag.value); break;
|
||||
case 'currency': q.t = 'n'; q.v = parseFloat(ctag.value); break;
|
||||
case 'date': q.t = 'd'; q.v = parseDate(ctag['date-value']);
|
||||
if(!opts.cellDates) { q.t = 'n'; q.v = datenum(q.v, WB.WBProps.date1904) - baddate; }
|
||||
q.z = 'm/d/yy'; break;
|
||||
if(!q.z) q.z = 'm/d/yy'; break;
|
||||
case 'time': q.t = 'n'; q.v = parse_isodur(ctag['time-value'])/86400;
|
||||
if(opts.cellDates) { q.t = 'd'; q.v = numdate(q.v); }
|
||||
q.z = 'HH:MM:SS'; break;
|
||||
if(!q.z) q.z = 'HH:MM:SS'; break;
|
||||
case 'number': q.t = 'n'; q.v = parseFloat(ctag['数据数值']); break;
|
||||
default:
|
||||
if(q.t === 'string' || q.t === 'text' || !q.t) {
|
||||
@ -21883,23 +22104,24 @@ function parse_content_xml(d, _opts) {
|
||||
textp = ""; textpidx = 0; textR = [];
|
||||
break;
|
||||
|
||||
case 'scientific-number': // TODO: <number:scientific-number>
|
||||
break;
|
||||
case 'currency-symbol': // TODO: <number:currency-symbol>
|
||||
break;
|
||||
case 'currency-style': // TODO: <number:currency-style>
|
||||
case 'scientific-number': // <number:scientific-number>
|
||||
case 'currency-symbol': // <number:currency-symbol>
|
||||
case 'fill-character': // 16.29.5 <number:fill-character>
|
||||
break;
|
||||
|
||||
case 'text-style': // 16.27.25 <number:text-style>
|
||||
case 'boolean-style': // 16.27.23 <number:boolean-style>
|
||||
case 'number-style': // 16.27.2 <number:number-style>
|
||||
case 'currency-style': // 16.29.8 <number:currency-style>
|
||||
case 'percentage-style': // 16.27.9 <number:percentage-style>
|
||||
case 'date-style': // 16.27.10 <number:date-style>
|
||||
case 'time-style': // 16.27.18 <number:time-style>
|
||||
if(Rn[1]==='/'){
|
||||
number_format_map[NFtag.name] = NF;
|
||||
if((tmp=state.pop())[0]!==Rn[3]) throw "Bad state: "+tmp;
|
||||
var xlmlidx = xlmlregex.lastIndex;
|
||||
parse_ods_styles(str.slice(nfidx, xlmlregex.lastIndex), _opts, number_format_map);
|
||||
xlmlregex.lastIndex = xlmlidx;
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
NF = "";
|
||||
NFtag = parsexmltag(Rn[0], false);
|
||||
state.push([Rn[3], true]);
|
||||
nfidx = xlmlregex.lastIndex - Rn[0].length;
|
||||
} break;
|
||||
|
||||
case 'script': break; // 3.13 <office:script>
|
||||
@ -21908,8 +22130,10 @@ function parse_content_xml(d, _opts) {
|
||||
|
||||
case 'default-style': // TODO: <style:default-style>
|
||||
case 'page-layout': break; // TODO: <style:page-layout>
|
||||
case 'style': // 16.2 <style:style>
|
||||
break;
|
||||
case 'style': { // 16.2 <style:style>
|
||||
var styletag = parsexmltag(Rn[0], false);
|
||||
if(styletag["family"] == "table-cell" && number_format_map[styletag["data-style-name"]]) styles[styletag["name"]] = number_format_map[styletag["data-style-name"]];
|
||||
} break;
|
||||
case 'map': break; // 16.3 <style:map>
|
||||
case 'font-face': break; // 16.21 <style:font-face>
|
||||
|
||||
@ -21920,12 +22144,7 @@ function parse_content_xml(d, _opts) {
|
||||
case 'table-cell-properties': break; // 17.18 <style:table-cell-properties>
|
||||
|
||||
case 'number': // 16.27.3 <number:number>
|
||||
switch(state[state.length-1][0]) {
|
||||
case 'time-style':
|
||||
case 'date-style':
|
||||
tag = parsexmltag(Rn[0], false);
|
||||
NF += number_formats_ods[Rn[3]][tag.style==='long'?1:0]; break;
|
||||
} break;
|
||||
break;
|
||||
|
||||
case 'fraction': break; // TODO 16.27.6 <number:fraction>
|
||||
|
||||
@ -21940,16 +22159,9 @@ function parse_content_xml(d, _opts) {
|
||||
case 'minutes': // 16.27.20 <number:minutes>
|
||||
case 'seconds': // 16.27.21 <number:seconds>
|
||||
case 'am-pm': // 16.27.22 <number:am-pm>
|
||||
switch(state[state.length-1][0]) {
|
||||
case 'time-style':
|
||||
case 'date-style':
|
||||
tag = parsexmltag(Rn[0], false);
|
||||
NF += number_formats_ods[Rn[3]][tag.style==='long'?1:0]; break;
|
||||
} break;
|
||||
break;
|
||||
|
||||
case 'boolean-style': break; // 16.27.23 <number:boolean-style>
|
||||
case 'boolean': break; // 16.27.24 <number:boolean>
|
||||
case 'text-style': break; // 16.27.25 <number:text-style>
|
||||
case 'text': // 16.27.26 <number:text>
|
||||
if(Rn[0].slice(-2) === "/>") break;
|
||||
else if(Rn[1]==="/") switch(state[state.length-1][0]) {
|
||||
@ -22180,9 +22392,11 @@ function parse_content_xml(d, _opts) {
|
||||
function parse_ods(zip, opts) {
|
||||
opts = opts || ({});
|
||||
if(safegetzipfile(zip, 'META-INF/manifest.xml')) parse_manifest(getzipdata(zip, 'META-INF/manifest.xml'), opts);
|
||||
var styles = getzipstr(zip, 'styles.xml');
|
||||
var Styles = styles && parse_ods_styles(utf8read(styles), opts);
|
||||
var content = getzipstr(zip, 'content.xml');
|
||||
if(!content) throw new Error("Missing content.xml in ODS / UOF file");
|
||||
var wb = parse_content_xml(utf8read(content), opts);
|
||||
var wb = parse_content_xml(utf8read(content), opts, Styles);
|
||||
if(safegetzipfile(zip, 'meta.xml')) wb.Props = parse_core_props(getzipdata(zip, 'meta.xml'));
|
||||
return wb;
|
||||
}
|
||||
@ -22222,6 +22436,157 @@ var write_styles_ods = /* @__PURE__ */(function() {
|
||||
return XML_HEADER + payload;
|
||||
};
|
||||
})();
|
||||
|
||||
// TODO: find out if anyone actually read the spec. LO has some wild errors
|
||||
function write_number_format_ods(nf, nfidx) {
|
||||
var type = "number", payload = "", nopts = { "style:name": nfidx }, c = "", i = 0;
|
||||
nf = nf.replace(/"[$]"/g, "$");
|
||||
/* TODO: replace with an actual parser based on a real grammar */
|
||||
j: {
|
||||
// TODO: support style maps
|
||||
if(nf.indexOf(";") > -1) {
|
||||
console.error("Unsupported ODS Style Map exported. Using first branch of " + nf);
|
||||
nf = nf.slice(0, nf.indexOf(";"));
|
||||
}
|
||||
|
||||
if(nf == "@") { type = "text"; payload = "<number:text-content/>"; break j; }
|
||||
|
||||
/* currency flag */
|
||||
if(nf.indexOf(/\$/) > -1) { type = "currency"; }
|
||||
|
||||
/* opening string literal */
|
||||
if(nf[i] == '"') {
|
||||
c = "";
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
if(nf[i+1] == "*") {
|
||||
i++;
|
||||
payload += '<number:fill-character>' + escapexml(c.replace(/""/g, '"')) + '</number:fill-character>';
|
||||
} else {
|
||||
payload += '<number:text>' + escapexml(c.replace(/""/g, '"')) + '</number:text>';
|
||||
}
|
||||
nf = nf.slice(i+1); i = 0;
|
||||
}
|
||||
|
||||
/* fractions */
|
||||
var t = nf.match(/# (\?+)\/(\?+)/);
|
||||
if(t) { payload += writextag("number:fraction", null, {"number:min-integer-digits":0, "number:min-numerator-digits": t[1].length, "number:max-denominator-value": Math.max(+(t[1].replace(/./g, "9")), +(t[2].replace(/./g, "9"))) }); break j; }
|
||||
if((t=nf.match(/# (\?+)\/(\d+)/))) { payload += writextag("number:fraction", null, {"number:min-integer-digits":0, "number:min-numerator-digits": t[1].length, "number:denominator-value": +t[2]}); break j; }
|
||||
|
||||
/* percentages */
|
||||
if((t=nf.match(/(\d+)(|\.\d+)%/))) { type = "percentage"; payload += writextag("number:number", null, {"number:decimal-places": t[2] && t.length - 1 || 0, "number:min-decimal-places": t[2] && t.length - 1 || 0, "number:min-integer-digits": t[1].length }) + "<number:text>%</number:text>"; break j; }
|
||||
|
||||
/* datetime */
|
||||
var has_time = false;
|
||||
if(["y","m","d"].indexOf(nf[0]) > -1) {
|
||||
type = "date";
|
||||
k: for(; i < nf.length; ++i) switch((c = nf[i].toLowerCase())) {
|
||||
case "h": case "s": has_time = true; --i; break k;
|
||||
case "m":
|
||||
l: for(var h = i+1; h < nf.length; ++h) switch(nf[h]) {
|
||||
case "y": case "d": break l;
|
||||
case "h": case "s": has_time = true; --i; break k;
|
||||
}
|
||||
/* falls through */
|
||||
case "y": case "d":
|
||||
while((nf[++i]||"").toLowerCase() == c[0]) c += c[0]; --i;
|
||||
switch(c) {
|
||||
case "y": case "yy": payload += "<number:year/>"; break;
|
||||
case "yyy": case "yyyy": payload += '<number:year number:style="long"/>'; break;
|
||||
case "mmmmm": console.error("ODS has no equivalent of format |mmmmm|");
|
||||
/* falls through */
|
||||
case "m": case "mm": case "mmm": case "mmmm":
|
||||
payload += '<number:month number:style="' + (c.length % 2 ? "short" : "long") + '" number:textual="' + (c.length >= 3 ? "true" : "false") + '"/>';
|
||||
break;
|
||||
case "d": case "dd": payload += '<number:day number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
|
||||
case "ddd": case "dddd": payload += '<number:day-of-week number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
|
||||
}
|
||||
break;
|
||||
case '"':
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
payload += '<number:text>' + escapexml(c.slice(1).replace(/""/g, '"')) + '</number:text>';
|
||||
break;
|
||||
case '/': payload += '<number:text>' + escapexml(c) + '</number:text>'; break;
|
||||
default: console.error("unrecognized character " + c + " in ODF format " + nf);
|
||||
}
|
||||
if(!has_time) break j;
|
||||
nf = nf.slice(i+1); i = 0;
|
||||
}
|
||||
if(nf.match(/^\[?[hms]/)) {
|
||||
if(type == "number") type = "time";
|
||||
if(nf.match(/\[/)) {
|
||||
nf = nf.replace(/[\[\]]/g, "");
|
||||
nopts['number:truncate-on-overflow'] = "false";
|
||||
}
|
||||
for(; i < nf.length; ++i) switch((c = nf[i].toLowerCase())) {
|
||||
case "h": case "m": case "s":
|
||||
while((nf[++i]||"").toLowerCase() == c[0]) c += c[0]; --i;
|
||||
switch(c) {
|
||||
case "h": case "hh": payload += '<number:hours number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
|
||||
case "m": case "mm": payload += '<number:minutes number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
|
||||
case "s": case "ss":
|
||||
if(nf[i+1] == ".") do { c += nf[i+1]; ++i; } while(nf[i+1] == "0");
|
||||
payload += '<number:seconds number:style="' + (c.match("ss") ? "long" : "short") + '"' + (c.match(/\./) ? ' number:decimal-places="' + (c.match(/0+/)||[""])[0].length + '"' : "")+ '/>'; break;
|
||||
}
|
||||
break;
|
||||
case '"':
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
payload += '<number:text>' + escapexml(c.slice(1).replace(/""/g, '"')) + '</number:text>';
|
||||
break;
|
||||
case '/': payload += '<number:text>' + escapexml(c) + '</number:text>'; break;
|
||||
case "a":
|
||||
if(nf.slice(i, i+3).toLowerCase() == "a/p") { payload += '<number:am-pm/>'; i += 2; break; } // Note: ODF does not support A/P
|
||||
if(nf.slice(i, i+5).toLowerCase() == "am/pm") { payload += '<number:am-pm/>'; i += 4; break; }
|
||||
/* falls through */
|
||||
default: console.error("unrecognized character " + c + " in ODF format " + nf);
|
||||
}
|
||||
break j;
|
||||
}
|
||||
|
||||
/* currency flag */
|
||||
if(nf.indexOf(/\$/) > -1) { type = "currency"; }
|
||||
|
||||
/* should be in a char loop */
|
||||
if(nf[0] == "$") { payload += '<number:currency-symbol number:language="en" number:country="US">$</number:currency-symbol>'; nf = nf.slice(1); i = 0; }
|
||||
i = 0; if(nf[i] == '"') {
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
if(nf[i+1] == "*") {
|
||||
i++;
|
||||
payload += '<number:fill-character>' + escapexml(c.replace(/""/g, '"')) + '</number:fill-character>';
|
||||
} else {
|
||||
payload += '<number:text>' + escapexml(c.replace(/""/g, '"')) + '</number:text>';
|
||||
}
|
||||
nf = nf.slice(i+1); i = 0;
|
||||
}
|
||||
|
||||
/* number TODO: interstitial text e.g. 000)000-0000 */
|
||||
var np = nf.match(/([#0][0#,]*)(\.[0#]*|)(E[+]?0*|)/i);
|
||||
if(!np || !np[0]) console.error("Could not find numeric part of " + nf);
|
||||
else {
|
||||
var base = np[1].replace(/,/g, "");
|
||||
payload += '<number:' + (np[3] ? "scientific-" : "")+ 'number' +
|
||||
' number:min-integer-digits="' + (base.indexOf("0") == -1 ? "0" : base.length - base.indexOf("0")) + '"' +
|
||||
(np[0].indexOf(",") > -1 ? ' number:grouping="true"' : "") +
|
||||
(np[2] && ' number:decimal-places="' + (np[2].length - 1) + '"' || ' number:decimal-places="0"') +
|
||||
(np[3] && np[3].indexOf("+") > -1 ? ' number:forced-exponent-sign="true"' : "" ) +
|
||||
(np[3] ? ' number:min-exponent-digits="' + np[3].match(/0+/)[0].length + '"' : "" ) +
|
||||
'>' +
|
||||
/* TODO: interstitial text placeholders */
|
||||
'</number:' + (np[3] ? "scientific-" : "") + 'number>';
|
||||
i = np.index + np[0].length;
|
||||
}
|
||||
|
||||
/* residual text */
|
||||
if(nf[i] == '"') {
|
||||
c = "";
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
payload += '<number:text>' + escapexml(c.replace(/""/g, '"')) + '</number:text>';
|
||||
}
|
||||
}
|
||||
|
||||
if(!payload) { console.error("Could not generate ODS number format for |" + nf + "|"); return ""; }
|
||||
return writextag("number:" + type + "-style", payload, nopts);
|
||||
}
|
||||
|
||||
function write_names_ods(Names, SheetNames, idx) {
|
||||
var scoped = Names.filter(function(name) { return name.Sheet == (idx == -1 ? null : idx); });
|
||||
if(!scoped.length) return "";
|
||||
@ -22246,7 +22611,7 @@ var write_content_ods = /* @__PURE__ */(function() {
|
||||
|
||||
var null_cell_xml = ' <table:table-cell />\n';
|
||||
var covered_cell_xml = ' <table:covered-table-cell/>\n';
|
||||
var write_ws = function(ws, wb, i) {
|
||||
var write_ws = function(ws, wb, i, opts, nfs) {
|
||||
/* Section 9 Tables */
|
||||
var o = [];
|
||||
o.push(' <table:table table:name="' + escapexml(wb.SheetNames[i]) + '" table:style-name="ta1">\n');
|
||||
@ -22322,6 +22687,7 @@ var write_content_ods = /* @__PURE__ */(function() {
|
||||
if(_tgt.charAt(0) != "#" && !_tgt.match(/^\w+:/)) _tgt = '../' + _tgt;
|
||||
text_p = writextag('text:a', text_p, {'xlink:href': _tgt.replace(/&/g, "&")});
|
||||
}
|
||||
if(nfs[cell.z]) ct["table:style-name"] = "ce" + nfs[cell.z].slice(1);
|
||||
o.push(' ' + writextag('table:table-cell', writextag('text:p', text_p, {}), ct) + '\n');
|
||||
}
|
||||
o.push(' </table:table-row>\n');
|
||||
@ -22334,14 +22700,6 @@ var write_content_ods = /* @__PURE__ */(function() {
|
||||
var write_automatic_styles_ods = function(o, wb) {
|
||||
o.push(' <office:automatic-styles>\n');
|
||||
|
||||
o.push(' <number:date-style style:name="N37" number:automatic-order="true">\n');
|
||||
o.push(' <number:month number:style="long"/>\n');
|
||||
o.push(' <number:text>/</number:text>\n');
|
||||
o.push(' <number:day number:style="long"/>\n');
|
||||
o.push(' <number:text>/</number:text>\n');
|
||||
o.push(' <number:year/>\n');
|
||||
o.push(' </number:date-style>\n');
|
||||
|
||||
/* column styles */
|
||||
var cidx = 0;
|
||||
wb.SheetNames.map(function(n) { return wb.Sheets[n]; }).forEach(function(ws) {
|
||||
@ -22382,12 +22740,38 @@ var write_content_ods = /* @__PURE__ */(function() {
|
||||
o.push(' <style:table-properties table:display="true" style:writing-mode="lr-tb"/>\n');
|
||||
o.push(' </style:style>\n');
|
||||
|
||||
/* table cells, text */
|
||||
o.push(' <number:date-style style:name="N37" number:automatic-order="true">\n');
|
||||
o.push(' <number:month number:style="long"/>\n');
|
||||
o.push(' <number:text>/</number:text>\n');
|
||||
o.push(' <number:day number:style="long"/>\n');
|
||||
o.push(' <number:text>/</number:text>\n');
|
||||
o.push(' <number:year/>\n');
|
||||
o.push(' </number:date-style>\n');
|
||||
|
||||
/* number formats, table cells, text */
|
||||
var nfs = {};
|
||||
var nfi = 69;
|
||||
wb.SheetNames.map(function(n) { return wb.Sheets[n]; }).forEach(function(ws) {
|
||||
if(!ws) return;
|
||||
var range = decode_range(ws["!ref"]);
|
||||
for(var R = 0; R <= range.e.r; ++R) for(var C = 0; C <= range.e.c; ++C) {
|
||||
var c = Array.isArray(ws) ? (ws[R]||[])[C] : ws[encode_cell({r:R,c:C})];
|
||||
if(!c || !c.z || c.z.toLowerCase() == "general") continue;
|
||||
if(!nfs[c.z]) {
|
||||
var out = write_number_format_ods(c.z, "N" + nfi);
|
||||
if(out) { nfs[c.z] = "N" + nfi; ++nfi; o.push(out + "\n"); }
|
||||
}
|
||||
}
|
||||
});
|
||||
o.push(' <style:style style:name="ce1" style:family="table-cell" style:parent-style-name="Default" style:data-style-name="N37"/>\n');
|
||||
keys(nfs).forEach(function(nf) {
|
||||
o.push('<style:style style:name="ce' + nfs[nf].slice(1) + '" style:family="table-cell" style:parent-style-name="Default" style:data-style-name="' + nfs[nf] + '"/>\n');
|
||||
});
|
||||
|
||||
/* page-layout */
|
||||
|
||||
o.push(' </office:automatic-styles>\n');
|
||||
return nfs;
|
||||
};
|
||||
|
||||
return function wcx(wb, opts) {
|
||||
@ -22440,15 +22824,15 @@ var write_content_ods = /* @__PURE__ */(function() {
|
||||
|
||||
if(opts.bookType == "fods") {
|
||||
o.push('<office:document' + attr + fods + '>\n');
|
||||
o.push(write_meta_ods().replace(/office:document-meta/g, "office:meta"));
|
||||
o.push(write_meta_ods().replace(/<office:document-meta.*?>/, "").replace(/<\/office:document-meta>/, "") + "\n");
|
||||
// TODO: settings (equiv of settings.xml for ODS)
|
||||
} else o.push('<office:document-content' + attr + '>\n');
|
||||
// o.push(' <office:scripts/>\n');
|
||||
write_automatic_styles_ods(o, wb);
|
||||
var nfs = write_automatic_styles_ods(o, wb);
|
||||
o.push(' <office:body>\n');
|
||||
o.push(' <office:spreadsheet>\n');
|
||||
if(((wb.Workbook||{}).WBProps||{}).date1904) o.push(' <table:calculation-settings table:case-sensitive="false" table:search-criteria-must-apply-to-whole-cell="true" table:use-wildcards="true" table:use-regular-expressions="false" table:automatic-find-labels="false">\n <table:null-date table:date-value="1904-01-01"/>\n </table:calculation-settings>\n');
|
||||
for(var i = 0; i != wb.SheetNames.length; ++i) o.push(write_ws(wb.Sheets[wb.SheetNames[i]], wb, i, opts));
|
||||
for(var i = 0; i != wb.SheetNames.length; ++i) o.push(write_ws(wb.Sheets[wb.SheetNames[i]], wb, i, opts, nfs));
|
||||
if((wb.Workbook||{}).Names) o.push(write_names_ods(wb.Workbook.Names, wb.SheetNames, -1));
|
||||
o.push(' </office:spreadsheet>\n');
|
||||
o.push(' </office:body>\n');
|
||||
|
@ -4,7 +4,7 @@
|
||||
/*global global, exports, module, require:false, process:false, Buffer:false, ArrayBuffer:false, DataView:false, Deno:false */
|
||||
var XLSX = {};
|
||||
function make_xlsx_lib(XLSX){
|
||||
XLSX.version = '0.18.8';
|
||||
XLSX.version = '0.18.9';
|
||||
var current_codepage = 1200, current_ansi = 1252;
|
||||
/*:: declare var cptable:any; */
|
||||
/*global cptable:true, window */
|
||||
@ -4255,6 +4255,17 @@ function encode_range(cs/*:CellAddrSpec|Range*/,ce/*:?CellAddrSpec*/)/*:string*/
|
||||
/*:: if(typeof ce !== 'string') throw "unreachable"; */
|
||||
return cs == ce ? cs : cs + ":" + ce;
|
||||
}
|
||||
function fix_range(a1/*:string*/)/*:string*/ {
|
||||
var s = decode_range(a1);
|
||||
return "$" + encode_col(s.s.c) + "$" + encode_row(s.s.r) + ":$" + encode_col(s.e.c) + "$" + encode_row(s.e.r);
|
||||
}
|
||||
|
||||
// List of invalid characters needs to be tested further
|
||||
function formula_quote_sheet_name(sname/*:string*/, opts)/*:string*/ {
|
||||
if(!sname && !(opts && opts.biff <= 5 && opts.biff >= 2)) throw new Error("empty sheet name");
|
||||
if (/[^\w\u4E00-\u9FFF\u3040-\u30FF]/.test(sname)) return "'" + sname.replace(/'/g, "''") + "'";
|
||||
return sname;
|
||||
}
|
||||
|
||||
function safe_decode_range(range/*:string*/)/*:Range*/ {
|
||||
var o = {s:{c:0,r:0},e:{c:0,r:0}};
|
||||
@ -4357,8 +4368,8 @@ function sheet_add_aoa(_ws/*:?Worksheet*/, data/*:AOA*/, opts/*:?any*/)/*:Worksh
|
||||
else if(typeof cell.v === 'boolean') cell.t = 'b';
|
||||
else if(cell.v instanceof Date) {
|
||||
cell.z = o.dateNF || table_fmt[14];
|
||||
if(o.cellDates) { cell.t = 'd'; cell.w = SSF_format(cell.z, datenum(cell.v)); }
|
||||
else { cell.t = 'n'; cell.v = datenum(cell.v); cell.w = SSF_format(cell.z, cell.v); }
|
||||
if(o.cellDates) { cell.t = 'd'; cell.w = SSF_format(cell.z, datenum(cell.v, o.date1904)); }
|
||||
else { cell.t = 'n'; cell.v = datenum(cell.v, o.date1904); cell.w = SSF_format(cell.z, cell.v); }
|
||||
}
|
||||
else cell.t = 's';
|
||||
}
|
||||
@ -4684,6 +4695,23 @@ var RBErr = {
|
||||
"#WTF?": 0xFF
|
||||
};
|
||||
|
||||
var XLSLblBuiltIn = [
|
||||
"_xlnm.Consolidate_Area",
|
||||
"_xlnm.Auto_Open",
|
||||
"_xlnm.Auto_Close",
|
||||
"_xlnm.Extract",
|
||||
"_xlnm.Database",
|
||||
"_xlnm.Criteria",
|
||||
"_xlnm.Print_Area",
|
||||
"_xlnm.Print_Titles",
|
||||
"_xlnm.Recorder",
|
||||
"_xlnm.Data_Form",
|
||||
"_xlnm.Auto_Activate",
|
||||
"_xlnm.Auto_Deactivate",
|
||||
"_xlnm.Sheet_Title",
|
||||
"_xlnm._FilterDatabase"
|
||||
];
|
||||
|
||||
/* Parts enumerated in OPC spec, MS-XLSB and MS-XLSX */
|
||||
/* 12.3 Part Summary <SpreadsheetML> */
|
||||
/* 14.2 Part Summary <DrawingML> */
|
||||
@ -5869,7 +5897,7 @@ var SYLK = /*#__PURE__*/(function() {
|
||||
wb.Workbook.Names.push(nn);
|
||||
} break;
|
||||
case 'C': /* cell */
|
||||
var C_seen_K = false, C_seen_X = false, C_seen_S = false, C_seen_E = false, _R = -1, _C = -1;
|
||||
var C_seen_K = false, C_seen_X = false, C_seen_S = false, C_seen_E = false, _R = -1, _C = -1, formula = "", cell_t = "z";
|
||||
for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) {
|
||||
case 'A': break; // TODO: comment
|
||||
case 'X': C = parseInt(record[rj].slice(1), 10)-1; C_seen_X = true; break;
|
||||
@ -5879,26 +5907,24 @@ var SYLK = /*#__PURE__*/(function() {
|
||||
break;
|
||||
case 'K':
|
||||
val = record[rj].slice(1);
|
||||
if(val.charAt(0) === '"') val = val.slice(1,val.length - 1);
|
||||
else if(val === 'TRUE') val = true;
|
||||
else if(val === 'FALSE') val = false;
|
||||
if(val.charAt(0) === '"') { val = val.slice(1,val.length - 1); cell_t = "s"; }
|
||||
else if(val === 'TRUE' || val === 'FALSE') { val = val === 'TRUE'; cell_t = "b"; }
|
||||
else if(!isNaN(fuzzynum(val))) {
|
||||
val = fuzzynum(val);
|
||||
if(next_cell_format !== null && fmt_is_date(next_cell_format)) val = numdate(wb.Workbook.WBProps.date1904 ? val + 1462 : val);
|
||||
val = fuzzynum(val); cell_t = "n";
|
||||
if(next_cell_format !== null && fmt_is_date(next_cell_format) && opts.cellDates) { val = numdate(wb.Workbook.WBProps.date1904 ? val + 1462 : val); cell_t = "d"; }
|
||||
} else if(!isNaN(fuzzydate(val).getDate())) {
|
||||
val = parseDate(val);
|
||||
val = parseDate(val); cell_t = "d";
|
||||
if(!opts.cellDates) { cell_t = "n"; val = datenum(val, wb.Workbook.WBProps.date1904); }
|
||||
}
|
||||
if(typeof $cptable !== 'undefined' && typeof val == "string" && ((opts||{}).type != "string") && (opts||{}).codepage) val = $cptable.utils.decode(opts.codepage, val);
|
||||
C_seen_K = true;
|
||||
break;
|
||||
case 'E':
|
||||
C_seen_E = true;
|
||||
var formula = rc_to_a1(record[rj].slice(1), {r:R,c:C});
|
||||
arr[R][C] = [arr[R][C], formula];
|
||||
formula = rc_to_a1(record[rj].slice(1), {r:R,c:C});
|
||||
break;
|
||||
case 'S':
|
||||
C_seen_S = true;
|
||||
arr[R][C] = [arr[R][C], "S5S"];
|
||||
break;
|
||||
case 'G': break; // unknown
|
||||
case 'R': _R = parseInt(record[rj].slice(1), 10)-1; break;
|
||||
@ -5906,15 +5932,21 @@ var SYLK = /*#__PURE__*/(function() {
|
||||
default: if(opts && opts.WTF) throw new Error("SYLK bad record " + rstr);
|
||||
}
|
||||
if(C_seen_K) {
|
||||
if(arr[R][C] && arr[R][C].length == 2) arr[R][C][0] = val;
|
||||
else arr[R][C] = val;
|
||||
if(!arr[R][C]) arr[R][C] = { t: cell_t, v: val };
|
||||
else { arr[R][C].t = cell_t; arr[R][C].v = val; }
|
||||
if(next_cell_format) arr[R][C].z = next_cell_format;
|
||||
if(opts.cellText !== false && next_cell_format) arr[R][C].w = SSF_format(arr[R][C].z, arr[R][C].v, { date1904: wb.Workbook.WBProps.date1904 });
|
||||
next_cell_format = null;
|
||||
}
|
||||
if(C_seen_S) {
|
||||
if(C_seen_E) throw new Error("SYLK shared formula cannot have own formula");
|
||||
var shrbase = _R > -1 && arr[_R][_C];
|
||||
if(!shrbase || !shrbase[1]) throw new Error("SYLK shared formula cannot find base");
|
||||
arr[R][C][1] = shift_formula_str(shrbase[1], {r: R - _R, c: C - _C});
|
||||
formula = shift_formula_str(shrbase[1], {r: R - _R, c: C - _C});
|
||||
}
|
||||
if(formula) {
|
||||
if(!arr[R][C]) arr[R][C] = { t: 'n', f: formula };
|
||||
else arr[R][C].f = formula;
|
||||
}
|
||||
break;
|
||||
case 'F': /* Format */
|
||||
@ -5966,7 +5998,8 @@ var SYLK = /*#__PURE__*/(function() {
|
||||
function sylk_to_workbook(d/*:RawData*/, opts)/*:Workbook*/ {
|
||||
var aoasht = sylk_to_aoa(d, opts);
|
||||
var aoa = aoasht[0], ws = aoasht[1], wb = aoasht[2];
|
||||
var o = aoa_to_sheet(aoa, opts);
|
||||
var _opts = dup(opts); _opts.date1904 = (((wb||{}).Workbook || {}).WBProps || {}).date1904;
|
||||
var o = aoa_to_sheet(aoa, _opts);
|
||||
keys(ws).forEach(function(k) { o[k] = ws[k]; });
|
||||
var outwb = sheet_to_workbook(o, opts);
|
||||
keys(wb).forEach(function(k) { outwb[k] = wb[k]; });
|
||||
@ -6010,11 +6043,12 @@ var SYLK = /*#__PURE__*/(function() {
|
||||
});
|
||||
}
|
||||
|
||||
function sheet_to_sylk(ws/*:Worksheet*/, opts/*:?any*/)/*:string*/ {
|
||||
function sheet_to_sylk(ws/*:Worksheet*/, opts/*:?any*/, wb/*:?WorkBook*/)/*:string*/ {
|
||||
var preamble/*:Array<string>*/ = ["ID;PSheetJS;N;E"], o/*:Array<string>*/ = [];
|
||||
var r = safe_decode_range(ws['!ref']), cell/*:Cell*/;
|
||||
var dense = Array.isArray(ws);
|
||||
var RS = "\r\n";
|
||||
var d1904 = (((wb||{}).Workbook||{}).WBProps||{}).date1904;
|
||||
|
||||
preamble.push("P;PGeneral");
|
||||
preamble.push("F;P0;DG0G8;M255");
|
||||
@ -6022,12 +6056,13 @@ var SYLK = /*#__PURE__*/(function() {
|
||||
if(ws['!rows']) write_ws_rows_sylk(preamble, ws['!rows']);
|
||||
|
||||
preamble.push("B;Y" + (r.e.r - r.s.r + 1) + ";X" + (r.e.c - r.s.c + 1) + ";D" + [r.s.c,r.s.r,r.e.c,r.e.r].join(" "));
|
||||
preamble.push("O;L;D;B" + (d1904 ? ";V4" : "") + ";K47;G100 0.001");
|
||||
for(var R = r.s.r; R <= r.e.r; ++R) {
|
||||
for(var C = r.s.c; C <= r.e.c; ++C) {
|
||||
var coord = encode_cell({r:R,c:C});
|
||||
cell = dense ? (ws[R]||[])[C]: ws[coord];
|
||||
if(!cell || (cell.v == null && (!cell.f || cell.F))) continue;
|
||||
o.push(write_ws_cell_sylk(cell, ws, R, C, opts));
|
||||
o.push(write_ws_cell_sylk(cell, ws, R, C, opts)); // TODO: pass date1904 info
|
||||
}
|
||||
}
|
||||
return preamble.join(RS) + RS + o.join(RS) + RS + "E" + RS;
|
||||
@ -8024,8 +8059,8 @@ var a1_to_rc = /*#__PURE__*/(function(){
|
||||
return fstr.replace(crefregex, function($0, $1, $2, $3, $4, $5) {
|
||||
var c = decode_col($3) - ($2 ? 0 : base.c);
|
||||
var r = decode_row($5) - ($4 ? 0 : base.r);
|
||||
var R = (r == 0 ? "" : !$4 ? "[" + r + "]" : (r+1));
|
||||
var C = (c == 0 ? "" : !$2 ? "[" + c + "]" : (c+1));
|
||||
var R = $4 == "$" ? (r+1) : (r == 0 ? "" : "[" + r + "]");
|
||||
var C = $2 == "$" ? (c+1) : (c == 0 ? "" : "[" + c + "]");
|
||||
return $1 + "R" + R + "C" + C;
|
||||
});
|
||||
};
|
||||
@ -8064,6 +8099,8 @@ function ods_to_csf_formula(f/*:string*/)/*:string*/ {
|
||||
f = f.replace(/COM\.MICROSOFT\./g, "");
|
||||
/* Part 3 Section 5.8 References */
|
||||
f = f.replace(/\[((?:\.[A-Z]+[0-9]+)(?::\.[A-Z]+[0-9]+)?)\]/g, function($$, $1) { return $1.replace(/\./g,""); });
|
||||
f = f.replace(/\$'([^']|'')+'/g, function($$) { return $$.slice(1); });
|
||||
f = f.replace(/\$([^\]\. #$]+)/g, function($$, $1) { return ($1).match(/^([A-Z]{1,2}|[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D])?(10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})?$/) ? $$ : $1; });
|
||||
/* TODO: something other than this */
|
||||
f = f.replace(/\[.(#[A-Z]*[?!])\]/g, "$1");
|
||||
return f.replace(/[;~]/g,",").replace(/\|/g,";");
|
||||
@ -8076,13 +8113,15 @@ function csf_to_ods_formula(f/*:string*/)/*:string*/ {
|
||||
}
|
||||
|
||||
function ods_to_csf_3D(r/*:string*/)/*:[string, string]*/ {
|
||||
r = r.replace(/\$'([^']|'')+'/g, function($$) { return $$.slice(1); });
|
||||
r = r.replace(/\$([^\]\. #$]+)/g, function($$, $1) { return ($1).match(/^([A-Z]{1,2}|[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D])?(10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})?$/) ? $$ : $1; });
|
||||
var a = r.split(":");
|
||||
var s = a[0].split(".")[0];
|
||||
return [s, a[0].split(".")[1] + (a.length > 1 ? (":" + (a[1].split(".")[1] || a[1].split(".")[0])) : "")];
|
||||
}
|
||||
|
||||
function csf_to_ods_3D(r/*:string*/)/*:string*/ {
|
||||
return r.replace(/\./,"!");
|
||||
return r.replace(/!/,".");
|
||||
}
|
||||
|
||||
var strs = {}; // shared strings
|
||||
@ -8443,7 +8482,7 @@ function write_ws_xml_autofilter(data, ws, wb, idx)/*:string*/ {
|
||||
var name = names[i];
|
||||
if(name.Name != '_xlnm._FilterDatabase') continue;
|
||||
if(name.Sheet != idx) continue;
|
||||
name.Ref = "'" + wb.SheetNames[idx] + "'!" + ref; break;
|
||||
name.Ref = formula_quote_sheet_name(wb.SheetNames[idx]) + "!" + fix_range(ref); break;
|
||||
}
|
||||
if(i == names.length) names.push({ Name: '_xlnm._FilterDatabase', Sheet: idx, Ref: "'" + wb.SheetNames[idx] + "'!" + ref });
|
||||
return writextag("autoFilter", null, {ref:ref});
|
||||
@ -8451,7 +8490,7 @@ function write_ws_xml_autofilter(data, ws, wb, idx)/*:string*/ {
|
||||
|
||||
/* 18.3.1.88 sheetViews CT_SheetViews */
|
||||
/* 18.3.1.87 sheetView CT_SheetView */
|
||||
var sviewregex = /<(?:\w:)?sheetView(?:[^>a-z][^>]*)?\/?>/;
|
||||
var sviewregex = /<(?:\w:)?sheetView(?:[^>a-z][^>]*)?\/?>/g;
|
||||
function parse_ws_xml_sheetviews(data, wb/*:WBWBProps*/) {
|
||||
if(!wb.Views) wb.Views = [{}];
|
||||
(data.match(sviewregex)||[]).forEach(function(r/*:string*/, i/*:number*/) {
|
||||
@ -8491,7 +8530,7 @@ function write_ws_xml_cell(cell/*:Cell*/, ref, ws, opts/*::, idx, wb*/)/*:string
|
||||
break;
|
||||
default: vv = cell.v; break;
|
||||
}
|
||||
var v = writetag('v', escapexml(vv)), o = ({r:ref}/*:any*/);
|
||||
var v = (cell.t == "z" || cell.v == null)? "" : writetag('v', escapexml(vv)), o = ({r:ref}/*:any*/);
|
||||
/* TODO: cell style */
|
||||
var os = get_cell_style(opts.cellXfs, cell, opts);
|
||||
if(os !== 0) o.s = os;
|
||||
@ -8507,7 +8546,7 @@ function write_ws_xml_cell(cell/*:Cell*/, ref, ws, opts/*::, idx, wb*/)/*:string
|
||||
v = writetag('v', ''+get_sst_id(opts.Strings, cell.v, opts.revStrings));
|
||||
o.t = "s"; break;
|
||||
}
|
||||
o.t = "str"; break;
|
||||
else o.t = "str"; break;
|
||||
}
|
||||
if(cell.t != oldt) { cell.t = oldt; cell.v = oldv; }
|
||||
if(typeof cell.f == "string" && cell.f) {
|
||||
@ -9083,6 +9122,17 @@ function check_wb(wb) {
|
||||
var Sheets = (wb.Workbook && wb.Workbook.Sheets) || [];
|
||||
check_wb_names(wb.SheetNames, Sheets, !!wb.vbaraw);
|
||||
for(var i = 0; i < wb.SheetNames.length; ++i) check_ws(wb.Sheets[wb.SheetNames[i]], wb.SheetNames[i], i);
|
||||
wb.SheetNames.forEach(function(n, i) {
|
||||
var ws = wb.Sheets[n];
|
||||
if(!ws || !ws["!autofilter"]) return;
|
||||
var DN;
|
||||
if(!wb.Workbook) wb.Workbook = {};
|
||||
if(!wb.Workbook.Names) wb.Workbook.Names = [];
|
||||
wb.Workbook.Names.forEach(function(dn) { if(dn.Name == "_xlnm._FilterDatabase" && dn.Sheet == i) DN = dn; });
|
||||
var nn = formula_quote_sheet_name(n) + "!" + fix_range(ws["!autofilter"].ref);
|
||||
if(DN) DN.Ref = nn;
|
||||
else wb.Workbook.Names.push({Name: "_xlnm._FilterDatabase", Sheet: i, Ref: nn});
|
||||
});
|
||||
/* TODO: validate workbook */
|
||||
}
|
||||
/* 18.2 Workbook */
|
||||
@ -9448,6 +9498,7 @@ function html_to_sheet(str/*:string*/, _opts)/*:Workbook*/ {
|
||||
function make_html_row(ws/*:Worksheet*/, r/*:Range*/, R/*:number*/, o/*:Sheet2HTMLOpts*/)/*:string*/ {
|
||||
var M/*:Array<Range>*/ = (ws['!merges'] ||[]);
|
||||
var oo/*:Array<string>*/ = [];
|
||||
var sp = ({}/*:any*/);
|
||||
for(var C = r.s.c; C <= r.e.c; ++C) {
|
||||
var RS = 0, CS = 0;
|
||||
for(var j = 0; j < M.length; ++j) {
|
||||
@ -9461,7 +9512,7 @@ function make_html_row(ws/*:Worksheet*/, r/*:Range*/, R/*:number*/, o/*:Sheet2HT
|
||||
var cell = o.dense ? (ws[R]||[])[C] : ws[coord];
|
||||
/* TODO: html entities */
|
||||
var w = (cell && cell.v != null) && (cell.h || escapehtml(cell.w || (format_cell(cell), cell.w) || "")) || "";
|
||||
var sp = ({}/*:any*/);
|
||||
sp = ({}/*:any*/);
|
||||
if(RS > 1) sp.rowspan = RS;
|
||||
if(CS > 1) sp.colspan = CS;
|
||||
if(o.editable) w = '<span contenteditable="true">' + w + '</span>';
|
||||
@ -9509,6 +9560,12 @@ function sheet_to_html(ws/*:Worksheet*/, opts/*:?Sheet2HTMLOpts*//*, wb:?Workboo
|
||||
}
|
||||
|
||||
function sheet_add_dom(ws/*:Worksheet*/, table/*:HTMLElement*/, _opts/*:?any*/)/*:Worksheet*/ {
|
||||
var rows/*:HTMLCollection<HTMLTableRowElement>*/ = table.rows;
|
||||
if(!rows) {
|
||||
/* not an HTML TABLE */
|
||||
throw "Unsupported origin when " + table.tagName + " is not a TABLE";
|
||||
}
|
||||
|
||||
var opts = _opts || {};
|
||||
if(DENSE != null) opts.dense = DENSE;
|
||||
var or_R = 0, or_C = 0;
|
||||
@ -9520,7 +9577,6 @@ function sheet_add_dom(ws/*:Worksheet*/, table/*:HTMLElement*/, _opts/*:?any*/)/
|
||||
}
|
||||
}
|
||||
|
||||
var rows/*:HTMLCollection<HTMLTableRowElement>*/ = table.getElementsByTagName('tr');
|
||||
var sheetRows = Math.min(opts.sheetRows||10000000, rows.length);
|
||||
var range/*:Range*/ = {s:{r:0,c:0},e:{r:or_R,c:or_C}};
|
||||
if(ws["!ref"]) {
|
||||
@ -9541,7 +9597,7 @@ function sheet_add_dom(ws/*:Worksheet*/, table/*:HTMLElement*/, _opts/*:?any*/)/
|
||||
if (opts.display) continue;
|
||||
rowinfo[R] = {hidden: true};
|
||||
}
|
||||
var elts/*:HTMLCollection<HTMLTableCellElement>*/ = (row.children/*:any*/);
|
||||
var elts/*:HTMLCollection<HTMLTableCellElement>*/ = (row.cells);
|
||||
for(_C = C = 0; _C < elts.length; ++_C) {
|
||||
var elt/*:HTMLTableCellElement*/ = elts[_C];
|
||||
if (opts.display && is_dom_element_hidden(elt)) continue;
|
||||
@ -9615,7 +9671,8 @@ function get_get_computed_style_function(element/*:HTMLElement*/)/*:?function*/
|
||||
// If it is not available, try to get one from the global namespace
|
||||
if(typeof getComputedStyle === 'function') return getComputedStyle;
|
||||
return null;
|
||||
}/* OpenDocument */
|
||||
}
|
||||
/* OpenDocument */
|
||||
function parse_text_p(text/*:string*//*::, tag*/)/*:Array<any>*/ {
|
||||
/* 6.1.2 White Space Characters */
|
||||
var fixed = text
|
||||
@ -9629,29 +9686,236 @@ function parse_text_p(text/*:string*//*::, tag*/)/*:Array<any>*/ {
|
||||
return [v];
|
||||
}
|
||||
|
||||
var number_formats_ods = {
|
||||
/* ods name: [short ssf fmt, long ssf fmt] */
|
||||
day: ["d", "dd"],
|
||||
month: ["m", "mm"],
|
||||
year: ["y", "yy"],
|
||||
hours: ["h", "hh"],
|
||||
minutes: ["m", "mm"],
|
||||
seconds: ["s", "ss"],
|
||||
"am-pm": ["A/P", "AM/PM"],
|
||||
"day-of-week": ["ddd", "dddd"],
|
||||
era: ["e", "ee"],
|
||||
/* there is no native representation of LO "Q" format */
|
||||
quarter: ["\\Qm", "m\\\"th quarter\""]
|
||||
};
|
||||
/* Note: ODS can stick styles in content.xml or styles.xml, FODS blurs lines */
|
||||
function parse_ods_styles(d/*:string*/, _opts, _nfm) {
|
||||
var number_format_map = _nfm || {};
|
||||
var str = xlml_normalize(d);
|
||||
xlmlregex.lastIndex = 0;
|
||||
str = str.replace(/<!--([\s\S]*?)-->/mg,"").replace(/<!DOCTYPE[^\[]*\[[^\]]*\]>/gm,"");
|
||||
var Rn, NFtag, NF = "", tNF = "", y, etpos = 0, tidx = -1, infmt = false, payload = "";
|
||||
while((Rn = xlmlregex.exec(str))) {
|
||||
switch((Rn[3]=Rn[3].replace(/_.*$/,""))) {
|
||||
/* Number Format Definitions */
|
||||
case 'number-style': // <number:number-style> 16.29.2
|
||||
case 'currency-style': // <number:currency-style> 16.29.8
|
||||
case 'percentage-style': // <number:percentage-style> 16.29.10
|
||||
case 'date-style': // <number:date-style> 16.29.11
|
||||
case 'time-style': // <number:time-style> 16.29.19
|
||||
case 'text-style': // <number:text-style> 16.29.26
|
||||
if(Rn[1]==='/') {
|
||||
infmt = false;
|
||||
if(NFtag['truncate-on-overflow'] == "false") {
|
||||
if(NF.match(/h/)) NF = NF.replace(/h+/, "[$&]");
|
||||
else if(NF.match(/m/)) NF = NF.replace(/m+/, "[$&]");
|
||||
else if(NF.match(/s/)) NF = NF.replace(/s+/, "[$&]");
|
||||
}
|
||||
number_format_map[NFtag.name] = NF;
|
||||
NF = "";
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
infmt = true;
|
||||
NF = "";
|
||||
NFtag = parsexmltag(Rn[0], false);
|
||||
} break;
|
||||
|
||||
// LibreOffice bug https://bugs.documentfoundation.org/show_bug.cgi?id=149484
|
||||
case 'boolean-style': // <number:boolean-style> 16.29.24
|
||||
if(Rn[1]==='/') {
|
||||
infmt = false;
|
||||
number_format_map[NFtag.name] = "General";
|
||||
NF = "";
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
infmt = true;
|
||||
NF = "";
|
||||
NFtag = parsexmltag(Rn[0], false);
|
||||
} break;
|
||||
|
||||
/* Number Format Elements */
|
||||
case 'boolean': // <number:boolean> 16.29.25
|
||||
NF += "General"; // ODF spec is unfortunately underspecified here
|
||||
break;
|
||||
|
||||
case 'text': // <number:text> 16.29.27
|
||||
if(Rn[1]==='/') {
|
||||
payload = str.slice(tidx, xlmlregex.lastIndex - Rn[0].length);
|
||||
// NOTE: Excel has a different interpretation of "%%" and friends
|
||||
if(payload == "%" && NFtag[0] == '<number:percentage-style') NF += "%";
|
||||
else NF += '"' + payload.replace(/"/g, '""') + '"';
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
tidx = xlmlregex.lastIndex;
|
||||
} break;
|
||||
|
||||
|
||||
function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
case 'day': { // <number:day> 16.29.12
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "d"; break;
|
||||
case "long": NF += "dd"; break;
|
||||
default: NF += "dd"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'day-of-week': { // <number:day-of-week> 16.29.16
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "ddd"; break;
|
||||
case "long": NF += "dddd"; break;
|
||||
default: NF += "ddd"; break;
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'era': { // <number:era> 16.29.15 TODO: proper mapping
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "ee"; break;
|
||||
case "long": NF += "eeee"; break;
|
||||
default: NF += "eeee"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'hours': { // <number:hours> 16.29.20
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "h"; break;
|
||||
case "long": NF += "hh"; break;
|
||||
default: NF += "hh"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'minutes': { // <number:minutes> 16.29.21
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "m"; break;
|
||||
case "long": NF += "mm"; break;
|
||||
default: NF += "mm"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'month': { // <number:month> 16.29.13
|
||||
y = parsexmltag(Rn[0], false);
|
||||
if(y["textual"]) NF += "mm";
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "m"; break;
|
||||
case "long": NF += "mm"; break;
|
||||
default: NF += "m"; break;
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'seconds': { // <number:seconds> 16.29.22
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "s"; break;
|
||||
case "long": NF += "ss"; break;
|
||||
default: NF += "ss"; break; // TODO: error condition
|
||||
}
|
||||
if(y["decimal-places"]) NF += "." + fill("0", +y["decimal-places"]);
|
||||
} break;
|
||||
|
||||
case 'year': { // <number:year> 16.29.14
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "yy"; break;
|
||||
case "long": NF += "yyyy"; break;
|
||||
default: NF += "yy"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'am-pm': // <number:am-pm> 16.29.23
|
||||
NF += "AM/PM"; // LO autocorrects A/P -> AM/PM
|
||||
break;
|
||||
|
||||
case 'week-of-year': // <number:week-of-year> 16.29.17
|
||||
case 'quarter': // <number:quarter> 16.29.18
|
||||
console.error("Excel does not support ODS format token " + Rn[3]);
|
||||
break;
|
||||
|
||||
case 'fill-character': // <number:fill-character> 16.29.5
|
||||
if(Rn[1]==='/') {
|
||||
payload = str.slice(tidx, xlmlregex.lastIndex - Rn[0].length);
|
||||
// NOTE: Excel has a different interpretation of "%%" and friends
|
||||
NF += '"' + payload.replace(/"/g, '""') + '"*';
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
tidx = xlmlregex.lastIndex;
|
||||
} break;
|
||||
|
||||
case 'scientific-number': // <number:scientific-number> 16.29.6
|
||||
// TODO: find a mapping for all parameters
|
||||
y = parsexmltag(Rn[0], false);
|
||||
NF += "0." + fill("0", +y["min-decimal-places"] || +y["decimal-places"] || 2) + fill("?", +y["decimal-places"] - +y["min-decimal-places"] || 0) + "E" + (parsexmlbool(y["forced-exponent-sign"]) ? "+" : "") + fill("0", +y["min-exponent-digits"] || 2);
|
||||
break;
|
||||
|
||||
case 'fraction': // <number:fraction> 16.29.7
|
||||
// TODO: find a mapping for all parameters
|
||||
y = parsexmltag(Rn[0], false);
|
||||
if(!+y["min-integer-digits"]) NF += "#";
|
||||
else NF += fill("0", +y["min-integer-digits"]);
|
||||
NF += " ";
|
||||
NF += fill("?", +y["min-numerator-digits"] || 1);
|
||||
NF += "/";
|
||||
if(+y["denominator-value"]) NF += y["denominator-value"];
|
||||
else NF += fill("?", +y["min-denominator-digits"] || 1);
|
||||
break;
|
||||
|
||||
case 'currency-symbol': // <number:currency-symbol> 16.29.9
|
||||
// TODO: localization with [$-...]
|
||||
if(Rn[1]==='/') {
|
||||
NF += '"' + str.slice(tidx, xlmlregex.lastIndex - Rn[0].length).replace(/"/g, '""') + '"';
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
tidx = xlmlregex.lastIndex;
|
||||
} else NF += "$";
|
||||
break;
|
||||
|
||||
case 'text-properties': // <style:text-properties> 16.29.29
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch((y["color"]||"").toLowerCase().replace("#", "")) {
|
||||
case "ff0000": case "red": NF = "[Red]" + NF; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'text-content': // <number:text-content> 16.29.28
|
||||
NF += "@";
|
||||
break;
|
||||
|
||||
case 'map': // <style:map> 16.3
|
||||
// TODO: handle more complex maps
|
||||
y = parsexmltag(Rn[0], false);
|
||||
if(unescapexml(y["condition"]) == "value()>=0") NF = number_format_map[y["apply-style-name"]] + ";" + NF;
|
||||
else console.error("ODS number format may be incorrect: " + y["condition"]);
|
||||
break;
|
||||
|
||||
case 'number': // <number:number> 16.29.3
|
||||
// TODO: handle all the attributes
|
||||
if(Rn[1]==='/') break;
|
||||
y = parsexmltag(Rn[0], false);
|
||||
tNF = "";
|
||||
tNF += fill("0", +y["min-integer-digits"] || 1);
|
||||
if(parsexmlbool(y["grouping"])) tNF = commaify(fill("#", Math.max(0, 4 - tNF.length)) + tNF);
|
||||
if(+y["min-decimal-places"] || +y["decimal-places"]) tNF += ".";
|
||||
if(+y["min-decimal-places"]) tNF += fill("0", +y["min-decimal-places"] || 1);
|
||||
if(+y["decimal-places"] - (+y["min-decimal-places"]||0)) tNF += fill("0", +y["decimal-places"] - (+y["min-decimal-places"]||0)); // TODO: should this be "#" ?
|
||||
NF += tNF;
|
||||
break;
|
||||
|
||||
case 'embedded-text': // <number:embedded-text> 16.29.4
|
||||
// TODO: verify interplay with grouping et al
|
||||
if(Rn[1]==='/') {
|
||||
if(etpos == 0) NF += '"' + str.slice(tidx, xlmlregex.lastIndex - Rn[0].length).replace(/"/g, '""') + '"';
|
||||
else NF = NF.slice(0, etpos) + '"' + str.slice(tidx, xlmlregex.lastIndex - Rn[0].length).replace(/"/g, '""') + '"' + NF.slice(etpos);
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
tidx = xlmlregex.lastIndex;
|
||||
etpos = -+parsexmltag(Rn[0], false)["position"] || 0;
|
||||
} break;
|
||||
|
||||
}}
|
||||
return number_format_map;
|
||||
}
|
||||
|
||||
function parse_content_xml(d/*:string*/, _opts, _nfm)/*:Workbook*/ {
|
||||
var opts = _opts || {};
|
||||
if(DENSE != null && opts.dense == null) opts.dense = DENSE;
|
||||
var str = xlml_normalize(d);
|
||||
var state/*:Array<any>*/ = [], tmp;
|
||||
var tag/*:: = {}*/;
|
||||
var NFtag = {name:""}, NF = "", pidx = 0;
|
||||
var nfidx, NF = "", pidx = 0;
|
||||
var sheetag/*:: = {name:"", '名称':""}*/;
|
||||
var rowtag/*:: = {'行号':""}*/;
|
||||
var Sheets = {}, SheetNames/*:Array<string>*/ = [];
|
||||
@ -9662,17 +9926,18 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
var textR = [];
|
||||
var R = -1, C = -1, range = {s: {r:1000000,c:10000000}, e: {r:0, c:0}};
|
||||
var row_ol = 0;
|
||||
var number_format_map = {};
|
||||
var number_format_map = _nfm || {}, styles = {};
|
||||
var merges/*:Array<Range>*/ = [], mrange = {}, mR = 0, mC = 0;
|
||||
var rowinfo/*:Array<RowInfo>*/ = [], rowpeat = 1, colpeat = 1;
|
||||
var arrayf/*:Array<[Range, string]>*/ = [];
|
||||
var WB = {Names:[]};
|
||||
var WB = {Names:[], WBProps:{}};
|
||||
var atag = ({}/*:any*/);
|
||||
var _Ref/*:[string, string]*/ = ["", ""];
|
||||
var comments/*:Array<Comment>*/ = [], comment/*:Comment*/ = ({}/*:any*/);
|
||||
var creator = "", creatoridx = 0;
|
||||
var isstub = false, intable = false;
|
||||
var i = 0;
|
||||
var baddate = 0;
|
||||
xlmlregex.lastIndex = 0;
|
||||
str = str.replace(/<!--([\s\S]*?)-->/mg,"").replace(/<!DOCTYPE[^\[]*\[[^\]]*\]>/gm,"");
|
||||
while((Rn = xlmlregex.exec(str))) switch((Rn[3]=Rn[3].replace(/_.*$/,""))) {
|
||||
@ -9730,6 +9995,7 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
colpeat = parseInt(ctag['number-columns-repeated']||"1", 10);
|
||||
q = ({t:'z', v:null/*:: , z:null, w:"",c:[]*/}/*:any*/);
|
||||
if(ctag.formula && opts.cellFormula != false) q.f = ods_to_csf_formula(unescapexml(ctag.formula));
|
||||
if(ctag["style-name"] && styles[ctag["style-name"]]) q.z = styles[ctag["style-name"]];
|
||||
if((ctag['数据类型'] || ctag['value-type']) == "string") {
|
||||
q.t = "s"; q.v = unescapexml(ctag['string-value'] || "");
|
||||
if(opts.dense) {
|
||||
@ -9752,6 +10018,7 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
ctag = parsexmltag(Rn[0], false);
|
||||
comments = []; comment = ({}/*:any*/);
|
||||
q = ({t:ctag['数据类型'] || ctag['value-type'], v:null/*:: , z:null, w:"",c:[]*/}/*:any*/);
|
||||
if(ctag["style-name"] && styles[ctag["style-name"]]) q.z = styles[ctag["style-name"]];
|
||||
if(opts.cellFormula) {
|
||||
if(ctag.formula) ctag.formula = unescapexml(ctag.formula);
|
||||
if(ctag['number-matrix-columns-spanned'] && ctag['number-matrix-rows-spanned']) {
|
||||
@ -9779,16 +10046,16 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
|
||||
/* 19.385 office:value-type */
|
||||
switch(q.t) {
|
||||
case 'boolean': q.t = 'b'; q.v = parsexmlbool(ctag['boolean-value']); break;
|
||||
case 'boolean': q.t = 'b'; q.v = parsexmlbool(ctag['boolean-value']) || (+ctag['boolean-value'] >= 1); break;
|
||||
case 'float': q.t = 'n'; q.v = parseFloat(ctag.value); break;
|
||||
case 'percentage': q.t = 'n'; q.v = parseFloat(ctag.value); break;
|
||||
case 'currency': q.t = 'n'; q.v = parseFloat(ctag.value); break;
|
||||
case 'date': q.t = 'd'; q.v = parseDate(ctag['date-value']);
|
||||
if(!opts.cellDates) { q.t = 'n'; q.v = datenum(q.v); }
|
||||
q.z = 'm/d/yy'; break;
|
||||
if(!opts.cellDates) { q.t = 'n'; q.v = datenum(q.v, WB.WBProps.date1904) - baddate; }
|
||||
if(!q.z) q.z = 'm/d/yy'; break;
|
||||
case 'time': q.t = 'n'; q.v = parse_isodur(ctag['time-value'])/86400;
|
||||
if(opts.cellDates) { q.t = 'd'; q.v = numdate(q.v); }
|
||||
q.z = 'HH:MM:SS'; break;
|
||||
if(!q.z) q.z = 'HH:MM:SS'; break;
|
||||
case 'number': q.t = 'n'; q.v = parseFloat(ctag['数据数值']); break;
|
||||
default:
|
||||
if(q.t === 'string' || q.t === 'text' || !q.t) {
|
||||
@ -9883,23 +10150,24 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
textp = ""; textpidx = 0; textR = [];
|
||||
break;
|
||||
|
||||
case 'scientific-number': // TODO: <number:scientific-number>
|
||||
break;
|
||||
case 'currency-symbol': // TODO: <number:currency-symbol>
|
||||
break;
|
||||
case 'currency-style': // TODO: <number:currency-style>
|
||||
case 'scientific-number': // <number:scientific-number>
|
||||
case 'currency-symbol': // <number:currency-symbol>
|
||||
case 'fill-character': // 16.29.5 <number:fill-character>
|
||||
break;
|
||||
|
||||
case 'text-style': // 16.27.25 <number:text-style>
|
||||
case 'boolean-style': // 16.27.23 <number:boolean-style>
|
||||
case 'number-style': // 16.27.2 <number:number-style>
|
||||
case 'currency-style': // 16.29.8 <number:currency-style>
|
||||
case 'percentage-style': // 16.27.9 <number:percentage-style>
|
||||
case 'date-style': // 16.27.10 <number:date-style>
|
||||
case 'time-style': // 16.27.18 <number:time-style>
|
||||
if(Rn[1]==='/'){
|
||||
number_format_map[NFtag.name] = NF;
|
||||
if((tmp=state.pop())[0]!==Rn[3]) throw "Bad state: "+tmp;
|
||||
var xlmlidx = xlmlregex.lastIndex;
|
||||
parse_ods_styles(str.slice(nfidx, xlmlregex.lastIndex), _opts, number_format_map);
|
||||
xlmlregex.lastIndex = xlmlidx;
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
NF = "";
|
||||
NFtag = parsexmltag(Rn[0], false);
|
||||
state.push([Rn[3], true]);
|
||||
nfidx = xlmlregex.lastIndex - Rn[0].length;
|
||||
} break;
|
||||
|
||||
case 'script': break; // 3.13 <office:script>
|
||||
@ -9908,8 +10176,10 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
|
||||
case 'default-style': // TODO: <style:default-style>
|
||||
case 'page-layout': break; // TODO: <style:page-layout>
|
||||
case 'style': // 16.2 <style:style>
|
||||
break;
|
||||
case 'style': { // 16.2 <style:style>
|
||||
var styletag = parsexmltag(Rn[0], false);
|
||||
if(styletag["family"] == "table-cell" && number_format_map[styletag["data-style-name"]]) styles[styletag["name"]] = number_format_map[styletag["data-style-name"]];
|
||||
} break;
|
||||
case 'map': break; // 16.3 <style:map>
|
||||
case 'font-face': break; // 16.21 <style:font-face>
|
||||
|
||||
@ -9920,12 +10190,7 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
case 'table-cell-properties': break; // 17.18 <style:table-cell-properties>
|
||||
|
||||
case 'number': // 16.27.3 <number:number>
|
||||
switch(state[state.length-1][0]) {
|
||||
case 'time-style':
|
||||
case 'date-style':
|
||||
tag = parsexmltag(Rn[0], false);
|
||||
NF += number_formats_ods[Rn[3]][tag.style==='long'?1:0]; break;
|
||||
} break;
|
||||
break;
|
||||
|
||||
case 'fraction': break; // TODO 16.27.6 <number:fraction>
|
||||
|
||||
@ -9940,16 +10205,9 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
case 'minutes': // 16.27.20 <number:minutes>
|
||||
case 'seconds': // 16.27.21 <number:seconds>
|
||||
case 'am-pm': // 16.27.22 <number:am-pm>
|
||||
switch(state[state.length-1][0]) {
|
||||
case 'time-style':
|
||||
case 'date-style':
|
||||
tag = parsexmltag(Rn[0], false);
|
||||
NF += number_formats_ods[Rn[3]][tag.style==='long'?1:0]; break;
|
||||
} break;
|
||||
break;
|
||||
|
||||
case 'boolean-style': break; // 16.27.23 <number:boolean-style>
|
||||
case 'boolean': break; // 16.27.24 <number:boolean>
|
||||
case 'text-style': break; // 16.27.25 <number:text-style>
|
||||
case 'text': // 16.27.26 <number:text>
|
||||
if(Rn[0].slice(-2) === "/>") break;
|
||||
else if(Rn[1]==="/") switch(state[state.length-1][0]) {
|
||||
@ -9985,7 +10243,14 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
case 'table-header-columns': break; // 9.1.11 <table:table-header-columns>
|
||||
case 'table-columns': break; // 9.1.12 <table:table-columns>
|
||||
|
||||
case 'null-date': break; // 9.4.2 <table:null-date> TODO: date1904
|
||||
case 'null-date': // 9.4.2 <table:null-date>
|
||||
tag = parsexmltag(Rn[0], false);
|
||||
switch(tag["date-value"]) {
|
||||
case "1904-01-01": WB.WBProps.date1904 = true;
|
||||
/* falls through */
|
||||
case "1900-01-01": baddate = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'graphic-properties': break; // 17.21 <style:graphic-properties>
|
||||
case 'calculation-settings': break; // 9.4.1 <table:calculation-settings>
|
||||
@ -10173,9 +10438,11 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
function parse_ods(zip/*:ZIPFile*/, opts/*:?ParseOpts*/)/*:Workbook*/ {
|
||||
opts = opts || ({}/*:any*/);
|
||||
if(safegetzipfile(zip, 'META-INF/manifest.xml')) parse_manifest(getzipdata(zip, 'META-INF/manifest.xml'), opts);
|
||||
var styles = getzipstr(zip, 'styles.xml');
|
||||
var Styles = styles && parse_ods_styles(utf8read(styles), opts);
|
||||
var content = getzipstr(zip, 'content.xml');
|
||||
if(!content) throw new Error("Missing content.xml in ODS / UOF file");
|
||||
var wb = parse_content_xml(utf8read(content), opts);
|
||||
var wb = parse_content_xml(utf8read(content), opts, Styles);
|
||||
if(safegetzipfile(zip, 'meta.xml')) wb.Props = parse_core_props(getzipdata(zip, 'meta.xml'));
|
||||
return wb;
|
||||
}
|
||||
@ -10215,6 +10482,169 @@ var write_styles_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function(
|
||||
return XML_HEADER + payload;
|
||||
};
|
||||
})();
|
||||
|
||||
// TODO: find out if anyone actually read the spec. LO has some wild errors
|
||||
function write_number_format_ods(nf/*:string*/, nfidx/*:string*/)/*:string*/ {
|
||||
var type = "number", payload = "", nopts = { "style:name": nfidx }, c = "", i = 0;
|
||||
nf = nf.replace(/"[$]"/g, "$");
|
||||
/* TODO: replace with an actual parser based on a real grammar */
|
||||
j: {
|
||||
// TODO: support style maps
|
||||
if(nf.indexOf(";") > -1) {
|
||||
console.error("Unsupported ODS Style Map exported. Using first branch of " + nf);
|
||||
nf = nf.slice(0, nf.indexOf(";"));
|
||||
}
|
||||
|
||||
if(nf == "@") { type = "text"; payload = "<number:text-content/>"; break j; }
|
||||
|
||||
/* currency flag */
|
||||
if(nf.indexOf(/\$/) > -1) { type = "currency"; }
|
||||
|
||||
/* opening string literal */
|
||||
if(nf[i] == '"') {
|
||||
c = "";
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
if(nf[i+1] == "*") {
|
||||
i++;
|
||||
payload += '<number:fill-character>' + escapexml(c.replace(/""/g, '"')) + '</number:fill-character>';
|
||||
} else {
|
||||
payload += '<number:text>' + escapexml(c.replace(/""/g, '"')) + '</number:text>';
|
||||
}
|
||||
nf = nf.slice(i+1); i = 0;
|
||||
}
|
||||
|
||||
/* fractions */
|
||||
var t = nf.match(/# (\?+)\/(\?+)/);
|
||||
if(t) { payload += writextag("number:fraction", null, {"number:min-integer-digits":0, "number:min-numerator-digits": t[1].length, "number:max-denominator-value": Math.max(+(t[1].replace(/./g, "9")), +(t[2].replace(/./g, "9"))) }); break j; }
|
||||
if((t=nf.match(/# (\?+)\/(\d+)/))) { payload += writextag("number:fraction", null, {"number:min-integer-digits":0, "number:min-numerator-digits": t[1].length, "number:denominator-value": +t[2]}); break j; }
|
||||
|
||||
/* percentages */
|
||||
if((t=nf.match(/(\d+)(|\.\d+)%/))) { type = "percentage"; payload += writextag("number:number", null, {"number:decimal-places": t[2] && t.length - 1 || 0, "number:min-decimal-places": t[2] && t.length - 1 || 0, "number:min-integer-digits": t[1].length }) + "<number:text>%</number:text>"; break j; }
|
||||
|
||||
/* datetime */
|
||||
var has_time = false;
|
||||
if(["y","m","d"].indexOf(nf[0]) > -1) {
|
||||
type = "date";
|
||||
k: for(; i < nf.length; ++i) switch((c = nf[i].toLowerCase())) {
|
||||
case "h": case "s": has_time = true; --i; break k;
|
||||
case "m":
|
||||
l: for(var h = i+1; h < nf.length; ++h) switch(nf[h]) {
|
||||
case "y": case "d": break l;
|
||||
case "h": case "s": has_time = true; --i; break k;
|
||||
}
|
||||
/* falls through */
|
||||
case "y": case "d":
|
||||
while((nf[++i]||"").toLowerCase() == c[0]) c += c[0]; --i;
|
||||
switch(c) {
|
||||
case "y": case "yy": payload += "<number:year/>"; break;
|
||||
case "yyy": case "yyyy": payload += '<number:year number:style="long"/>'; break;
|
||||
case "mmmmm": console.error("ODS has no equivalent of format |mmmmm|");
|
||||
/* falls through */
|
||||
case "m": case "mm": case "mmm": case "mmmm":
|
||||
payload += '<number:month number:style="' + (c.length % 2 ? "short" : "long") + '" number:textual="' + (c.length >= 3 ? "true" : "false") + '"/>';
|
||||
break;
|
||||
case "d": case "dd": payload += '<number:day number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
|
||||
case "ddd": case "dddd": payload += '<number:day-of-week number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
|
||||
}
|
||||
break;
|
||||
case '"':
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
payload += '<number:text>' + escapexml(c.slice(1).replace(/""/g, '"')) + '</number:text>';
|
||||
break;
|
||||
case '/': payload += '<number:text>' + escapexml(c) + '</number:text>'; break;
|
||||
default: console.error("unrecognized character " + c + " in ODF format " + nf);
|
||||
}
|
||||
if(!has_time) break j;
|
||||
nf = nf.slice(i+1); i = 0;
|
||||
}
|
||||
if(nf.match(/^\[?[hms]/)) {
|
||||
if(type == "number") type = "time";
|
||||
if(nf.match(/\[/)) {
|
||||
nf = nf.replace(/[\[\]]/g, "");
|
||||
nopts['number:truncate-on-overflow'] = "false";
|
||||
}
|
||||
for(; i < nf.length; ++i) switch((c = nf[i].toLowerCase())) {
|
||||
case "h": case "m": case "s":
|
||||
while((nf[++i]||"").toLowerCase() == c[0]) c += c[0]; --i;
|
||||
switch(c) {
|
||||
case "h": case "hh": payload += '<number:hours number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
|
||||
case "m": case "mm": payload += '<number:minutes number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
|
||||
case "s": case "ss":
|
||||
if(nf[i+1] == ".") do { c += nf[i+1]; ++i; } while(nf[i+1] == "0");
|
||||
payload += '<number:seconds number:style="' + (c.match("ss") ? "long" : "short") + '"' + (c.match(/\./) ? ' number:decimal-places="' + (c.match(/0+/)||[""])[0].length + '"' : "")+ '/>'; break;
|
||||
}
|
||||
break;
|
||||
case '"':
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
payload += '<number:text>' + escapexml(c.slice(1).replace(/""/g, '"')) + '</number:text>';
|
||||
break;
|
||||
case '/': payload += '<number:text>' + escapexml(c) + '</number:text>'; break;
|
||||
case "a":
|
||||
if(nf.slice(i, i+3).toLowerCase() == "a/p") { payload += '<number:am-pm/>'; i += 2; break; } // Note: ODF does not support A/P
|
||||
if(nf.slice(i, i+5).toLowerCase() == "am/pm") { payload += '<number:am-pm/>'; i += 4; break; }
|
||||
/* falls through */
|
||||
default: console.error("unrecognized character " + c + " in ODF format " + nf);
|
||||
}
|
||||
break j;
|
||||
}
|
||||
|
||||
/* currency flag */
|
||||
if(nf.indexOf(/\$/) > -1) { type = "currency"; }
|
||||
|
||||
/* should be in a char loop */
|
||||
if(nf[0] == "$") { payload += '<number:currency-symbol number:language="en" number:country="US">$</number:currency-symbol>'; nf = nf.slice(1); i = 0; }
|
||||
i = 0; if(nf[i] == '"') {
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
if(nf[i+1] == "*") {
|
||||
i++;
|
||||
payload += '<number:fill-character>' + escapexml(c.replace(/""/g, '"')) + '</number:fill-character>';
|
||||
} else {
|
||||
payload += '<number:text>' + escapexml(c.replace(/""/g, '"')) + '</number:text>';
|
||||
}
|
||||
nf = nf.slice(i+1); i = 0;
|
||||
}
|
||||
|
||||
/* number TODO: interstitial text e.g. 000)000-0000 */
|
||||
var np = nf.match(/([#0][0#,]*)(\.[0#]*|)(E[+]?0*|)/i);
|
||||
if(!np || !np[0]) console.error("Could not find numeric part of " + nf);
|
||||
else {
|
||||
var base = np[1].replace(/,/g, "");
|
||||
payload += '<number:' + (np[3] ? "scientific-" : "")+ 'number' +
|
||||
' number:min-integer-digits="' + (base.indexOf("0") == -1 ? "0" : base.length - base.indexOf("0")) + '"' +
|
||||
(np[0].indexOf(",") > -1 ? ' number:grouping="true"' : "") +
|
||||
(np[2] && ' number:decimal-places="' + (np[2].length - 1) + '"' || ' number:decimal-places="0"') +
|
||||
(np[3] && np[3].indexOf("+") > -1 ? ' number:forced-exponent-sign="true"' : "" ) +
|
||||
(np[3] ? ' number:min-exponent-digits="' + np[3].match(/0+/)[0].length + '"' : "" ) +
|
||||
'>' +
|
||||
/* TODO: interstitial text placeholders */
|
||||
'</number:' + (np[3] ? "scientific-" : "") + 'number>';
|
||||
i = np.index + np[0].length;
|
||||
}
|
||||
|
||||
/* residual text */
|
||||
if(nf[i] == '"') {
|
||||
c = "";
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
payload += '<number:text>' + escapexml(c.replace(/""/g, '"')) + '</number:text>';
|
||||
}
|
||||
}
|
||||
|
||||
if(!payload) { console.error("Could not generate ODS number format for |" + nf + "|"); return ""; }
|
||||
return writextag("number:" + type + "-style", payload, nopts);
|
||||
}
|
||||
|
||||
function write_names_ods(Names, SheetNames, idx) {
|
||||
var scoped = Names.filter(function(name) { return name.Sheet == (idx == -1 ? null : idx); });
|
||||
if(!scoped.length) return "";
|
||||
return " <table:named-expressions>\n" + scoped.map(function(name) {
|
||||
var odsref = csf_to_ods_3D(name.Ref);
|
||||
return " " + writextag("table:named-range", null, {
|
||||
"table:name": name.Name,
|
||||
"table:cell-range-address": odsref,
|
||||
"table:base-cell-address": odsref.replace(/[\.]?[^\.]*$/, ".$A$1")
|
||||
});
|
||||
}).join("\n") + "\n </table:named-expressions>\n";
|
||||
}
|
||||
var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function() {
|
||||
/* 6.1.2 White Space Characters */
|
||||
var write_text_p = function(text/*:string*/)/*:string*/ {
|
||||
@ -10227,7 +10657,7 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function
|
||||
|
||||
var null_cell_xml = ' <table:table-cell />\n';
|
||||
var covered_cell_xml = ' <table:covered-table-cell/>\n';
|
||||
var write_ws = function(ws, wb/*:Workbook*/, i/*:number*//*::, opts*/)/*:string*/ {
|
||||
var write_ws = function(ws, wb/*:Workbook*/, i/*:number*/, opts, nfs)/*:string*/ {
|
||||
/* Section 9 Tables */
|
||||
var o/*:Array<string>*/ = [];
|
||||
o.push(' <table:table table:name="' + escapexml(wb.SheetNames[i]) + '" table:style-name="ta1">\n');
|
||||
@ -10303,10 +10733,12 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function
|
||||
if(_tgt.charAt(0) != "#" && !_tgt.match(/^\w+:/)) _tgt = '../' + _tgt;
|
||||
text_p = writextag('text:a', text_p, {'xlink:href': _tgt.replace(/&/g, "&")});
|
||||
}
|
||||
if(nfs[cell.z]) ct["table:style-name"] = "ce" + nfs[cell.z].slice(1);
|
||||
o.push(' ' + writextag('table:table-cell', writextag('text:p', text_p, {}), ct) + '\n');
|
||||
}
|
||||
o.push(' </table:table-row>\n');
|
||||
}
|
||||
if((wb.Workbook||{}).Names) o.push(write_names_ods(wb.Workbook.Names, wb.SheetNames, i));
|
||||
o.push(' </table:table>\n');
|
||||
return o.join("");
|
||||
};
|
||||
@ -10314,14 +10746,6 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function
|
||||
var write_automatic_styles_ods = function(o/*:Array<string>*/, wb) {
|
||||
o.push(' <office:automatic-styles>\n');
|
||||
|
||||
o.push(' <number:date-style style:name="N37" number:automatic-order="true">\n');
|
||||
o.push(' <number:month number:style="long"/>\n');
|
||||
o.push(' <number:text>/</number:text>\n');
|
||||
o.push(' <number:day number:style="long"/>\n');
|
||||
o.push(' <number:text>/</number:text>\n');
|
||||
o.push(' <number:year/>\n');
|
||||
o.push(' </number:date-style>\n');
|
||||
|
||||
/* column styles */
|
||||
var cidx = 0;
|
||||
wb.SheetNames.map(function(n) { return wb.Sheets[n]; }).forEach(function(ws) {
|
||||
@ -10362,12 +10786,38 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function
|
||||
o.push(' <style:table-properties table:display="true" style:writing-mode="lr-tb"/>\n');
|
||||
o.push(' </style:style>\n');
|
||||
|
||||
/* table cells, text */
|
||||
o.push(' <number:date-style style:name="N37" number:automatic-order="true">\n');
|
||||
o.push(' <number:month number:style="long"/>\n');
|
||||
o.push(' <number:text>/</number:text>\n');
|
||||
o.push(' <number:day number:style="long"/>\n');
|
||||
o.push(' <number:text>/</number:text>\n');
|
||||
o.push(' <number:year/>\n');
|
||||
o.push(' </number:date-style>\n');
|
||||
|
||||
/* number formats, table cells, text */
|
||||
var nfs = {};
|
||||
var nfi = 69;
|
||||
wb.SheetNames.map(function(n) { return wb.Sheets[n]; }).forEach(function(ws) {
|
||||
if(!ws) return;
|
||||
var range = decode_range(ws["!ref"]);
|
||||
for(var R = 0; R <= range.e.r; ++R) for(var C = 0; C <= range.e.c; ++C) {
|
||||
var c = Array.isArray(ws) ? (ws[R]||[])[C] : ws[encode_cell({r:R,c:C})];
|
||||
if(!c || !c.z || c.z.toLowerCase() == "general") continue;
|
||||
if(!nfs[c.z]) {
|
||||
var out = write_number_format_ods(c.z, "N" + nfi);
|
||||
if(out) { nfs[c.z] = "N" + nfi; ++nfi; o.push(out + "\n"); }
|
||||
}
|
||||
}
|
||||
});
|
||||
o.push(' <style:style style:name="ce1" style:family="table-cell" style:parent-style-name="Default" style:data-style-name="N37"/>\n');
|
||||
keys(nfs).forEach(function(nf) {
|
||||
o.push('<style:style style:name="ce' + nfs[nf].slice(1) + '" style:family="table-cell" style:parent-style-name="Default" style:data-style-name="' + nfs[nf] + '"/>\n');
|
||||
});
|
||||
|
||||
/* page-layout */
|
||||
|
||||
o.push(' </office:automatic-styles>\n');
|
||||
return nfs;
|
||||
};
|
||||
|
||||
return function wcx(wb, opts) {
|
||||
@ -10420,14 +10870,16 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function
|
||||
|
||||
if(opts.bookType == "fods") {
|
||||
o.push('<office:document' + attr + fods + '>\n');
|
||||
o.push(write_meta_ods().replace(/office:document-meta/g, "office:meta"));
|
||||
o.push(write_meta_ods().replace(/<office:document-meta.*?>/, "").replace(/<\/office:document-meta>/, "") + "\n");
|
||||
// TODO: settings (equiv of settings.xml for ODS)
|
||||
} else o.push('<office:document-content' + attr + '>\n');
|
||||
// o.push(' <office:scripts/>\n');
|
||||
write_automatic_styles_ods(o, wb);
|
||||
var nfs = write_automatic_styles_ods(o, wb);
|
||||
o.push(' <office:body>\n');
|
||||
o.push(' <office:spreadsheet>\n');
|
||||
for(var i = 0; i != wb.SheetNames.length; ++i) o.push(write_ws(wb.Sheets[wb.SheetNames[i]], wb, i, opts));
|
||||
if(((wb.Workbook||{}).WBProps||{}).date1904) o.push(' <table:calculation-settings table:case-sensitive="false" table:search-criteria-must-apply-to-whole-cell="true" table:use-wildcards="true" table:use-regular-expressions="false" table:automatic-find-labels="false">\n <table:null-date table:date-value="1904-01-01"/>\n </table:calculation-settings>\n');
|
||||
for(var i = 0; i != wb.SheetNames.length; ++i) o.push(write_ws(wb.Sheets[wb.SheetNames[i]], wb, i, opts, nfs));
|
||||
if((wb.Workbook||{}).Names) o.push(write_names_ods(wb.Workbook.Names, wb.SheetNames, -1));
|
||||
o.push(' </office:spreadsheet>\n');
|
||||
o.push(' </office:body>\n');
|
||||
if(opts.bookType == "fods") o.push('</office:document>');
|
||||
@ -11364,7 +11816,7 @@ function writeSync(wb/*:Workbook*/, opts/*:?WriteOpts*/) {
|
||||
case 'xml':
|
||||
case 'xlml': return write_string_type(write_xlml(wb, o), o);
|
||||
case 'slk':
|
||||
case 'sylk': return write_string_type(SYLK.from_sheet(wb.Sheets[wb.SheetNames[idx]], o), o);
|
||||
case 'sylk': return write_string_type(SYLK.from_sheet(wb.Sheets[wb.SheetNames[idx]], o, wb), o);
|
||||
case 'htm':
|
||||
case 'html': return write_string_type(sheet_to_html(wb.Sheets[wb.SheetNames[idx]], o), o);
|
||||
case 'txt': return write_stxt_type(sheet_to_txt(wb.Sheets[wb.SheetNames[idx]], o), o);
|
||||
@ -11663,7 +12115,7 @@ function sheet_add_json(_ws/*:?Worksheet*/, js/*:Array<any>*/, opts)/*:Worksheet
|
||||
else if(v instanceof Date) {
|
||||
t = 'd';
|
||||
if(!o.cellDates) { t = 'n'; v = datenum(v); }
|
||||
z = (o.dateNF || table_fmt[14]);
|
||||
z = (cell.z && fmt_is_date(cell.z)) ? cell.z : (o.dateNF || table_fmt[14]);
|
||||
}
|
||||
else if(v === null && o.nullError) { t = 'e'; v = 0; }
|
||||
if(!cell) ws[ref] = cell = ({t:t, v:v}/*:any*/);
|
||||
|
642
xlsx.mini.js
642
xlsx.mini.js
@ -4,7 +4,7 @@
|
||||
/*global global, exports, module, require:false, process:false, Buffer:false, ArrayBuffer:false, DataView:false, Deno:false */
|
||||
var XLSX = {};
|
||||
function make_xlsx_lib(XLSX){
|
||||
XLSX.version = '0.18.8';
|
||||
XLSX.version = '0.18.9';
|
||||
var current_codepage = 1200, current_ansi = 1252;
|
||||
/*global cptable:true, window */
|
||||
var $cptable;
|
||||
@ -4170,6 +4170,17 @@ if(typeof cs !== 'string') cs = encode_cell((cs));
|
||||
if(typeof ce !== 'string') ce = encode_cell((ce));
|
||||
return cs == ce ? cs : cs + ":" + ce;
|
||||
}
|
||||
function fix_range(a1) {
|
||||
var s = decode_range(a1);
|
||||
return "$" + encode_col(s.s.c) + "$" + encode_row(s.s.r) + ":$" + encode_col(s.e.c) + "$" + encode_row(s.e.r);
|
||||
}
|
||||
|
||||
// List of invalid characters needs to be tested further
|
||||
function formula_quote_sheet_name(sname, opts) {
|
||||
if(!sname && !(opts && opts.biff <= 5 && opts.biff >= 2)) throw new Error("empty sheet name");
|
||||
if (/[^\w\u4E00-\u9FFF\u3040-\u30FF]/.test(sname)) return "'" + sname.replace(/'/g, "''") + "'";
|
||||
return sname;
|
||||
}
|
||||
|
||||
function safe_decode_range(range) {
|
||||
var o = {s:{c:0,r:0},e:{c:0,r:0}};
|
||||
@ -4272,8 +4283,8 @@ function sheet_add_aoa(_ws, data, opts) {
|
||||
else if(typeof cell.v === 'boolean') cell.t = 'b';
|
||||
else if(cell.v instanceof Date) {
|
||||
cell.z = o.dateNF || table_fmt[14];
|
||||
if(o.cellDates) { cell.t = 'd'; cell.w = SSF_format(cell.z, datenum(cell.v)); }
|
||||
else { cell.t = 'n'; cell.v = datenum(cell.v); cell.w = SSF_format(cell.z, cell.v); }
|
||||
if(o.cellDates) { cell.t = 'd'; cell.w = SSF_format(cell.z, datenum(cell.v, o.date1904)); }
|
||||
else { cell.t = 'n'; cell.v = datenum(cell.v, o.date1904); cell.w = SSF_format(cell.z, cell.v); }
|
||||
}
|
||||
else cell.t = 's';
|
||||
}
|
||||
@ -4599,6 +4610,23 @@ var RBErr = {
|
||||
"#WTF?": 0xFF
|
||||
};
|
||||
|
||||
var XLSLblBuiltIn = [
|
||||
"_xlnm.Consolidate_Area",
|
||||
"_xlnm.Auto_Open",
|
||||
"_xlnm.Auto_Close",
|
||||
"_xlnm.Extract",
|
||||
"_xlnm.Database",
|
||||
"_xlnm.Criteria",
|
||||
"_xlnm.Print_Area",
|
||||
"_xlnm.Print_Titles",
|
||||
"_xlnm.Recorder",
|
||||
"_xlnm.Data_Form",
|
||||
"_xlnm.Auto_Activate",
|
||||
"_xlnm.Auto_Deactivate",
|
||||
"_xlnm.Sheet_Title",
|
||||
"_xlnm._FilterDatabase"
|
||||
];
|
||||
|
||||
/* Parts enumerated in OPC spec, MS-XLSB and MS-XLSX */
|
||||
/* 12.3 Part Summary <SpreadsheetML> */
|
||||
/* 14.2 Part Summary <DrawingML> */
|
||||
@ -5783,7 +5811,7 @@ var SYLK = (function() {
|
||||
wb.Workbook.Names.push(nn);
|
||||
} break;
|
||||
case 'C': /* cell */
|
||||
var C_seen_K = false, C_seen_X = false, C_seen_S = false, C_seen_E = false, _R = -1, _C = -1;
|
||||
var C_seen_K = false, C_seen_X = false, C_seen_S = false, C_seen_E = false, _R = -1, _C = -1, formula = "", cell_t = "z";
|
||||
for(rj=1; rj<record.length; ++rj) switch(record[rj].charAt(0)) {
|
||||
case 'A': break; // TODO: comment
|
||||
case 'X': C = parseInt(record[rj].slice(1), 10)-1; C_seen_X = true; break;
|
||||
@ -5793,26 +5821,24 @@ var SYLK = (function() {
|
||||
break;
|
||||
case 'K':
|
||||
val = record[rj].slice(1);
|
||||
if(val.charAt(0) === '"') val = val.slice(1,val.length - 1);
|
||||
else if(val === 'TRUE') val = true;
|
||||
else if(val === 'FALSE') val = false;
|
||||
if(val.charAt(0) === '"') { val = val.slice(1,val.length - 1); cell_t = "s"; }
|
||||
else if(val === 'TRUE' || val === 'FALSE') { val = val === 'TRUE'; cell_t = "b"; }
|
||||
else if(!isNaN(fuzzynum(val))) {
|
||||
val = fuzzynum(val);
|
||||
if(next_cell_format !== null && fmt_is_date(next_cell_format)) val = numdate(wb.Workbook.WBProps.date1904 ? val + 1462 : val);
|
||||
val = fuzzynum(val); cell_t = "n";
|
||||
if(next_cell_format !== null && fmt_is_date(next_cell_format) && opts.cellDates) { val = numdate(wb.Workbook.WBProps.date1904 ? val + 1462 : val); cell_t = "d"; }
|
||||
} else if(!isNaN(fuzzydate(val).getDate())) {
|
||||
val = parseDate(val);
|
||||
val = parseDate(val); cell_t = "d";
|
||||
if(!opts.cellDates) { cell_t = "n"; val = datenum(val, wb.Workbook.WBProps.date1904); }
|
||||
}
|
||||
if(typeof $cptable !== 'undefined' && typeof val == "string" && ((opts||{}).type != "string") && (opts||{}).codepage) val = $cptable.utils.decode(opts.codepage, val);
|
||||
C_seen_K = true;
|
||||
break;
|
||||
case 'E':
|
||||
C_seen_E = true;
|
||||
var formula = rc_to_a1(record[rj].slice(1), {r:R,c:C});
|
||||
arr[R][C] = [arr[R][C], formula];
|
||||
formula = rc_to_a1(record[rj].slice(1), {r:R,c:C});
|
||||
break;
|
||||
case 'S':
|
||||
C_seen_S = true;
|
||||
arr[R][C] = [arr[R][C], "S5S"];
|
||||
break;
|
||||
case 'G': break; // unknown
|
||||
case 'R': _R = parseInt(record[rj].slice(1), 10)-1; break;
|
||||
@ -5820,15 +5846,21 @@ var SYLK = (function() {
|
||||
default: if(opts && opts.WTF) throw new Error("SYLK bad record " + rstr);
|
||||
}
|
||||
if(C_seen_K) {
|
||||
if(arr[R][C] && arr[R][C].length == 2) arr[R][C][0] = val;
|
||||
else arr[R][C] = val;
|
||||
if(!arr[R][C]) arr[R][C] = { t: cell_t, v: val };
|
||||
else { arr[R][C].t = cell_t; arr[R][C].v = val; }
|
||||
if(next_cell_format) arr[R][C].z = next_cell_format;
|
||||
if(opts.cellText !== false && next_cell_format) arr[R][C].w = SSF_format(arr[R][C].z, arr[R][C].v, { date1904: wb.Workbook.WBProps.date1904 });
|
||||
next_cell_format = null;
|
||||
}
|
||||
if(C_seen_S) {
|
||||
if(C_seen_E) throw new Error("SYLK shared formula cannot have own formula");
|
||||
var shrbase = _R > -1 && arr[_R][_C];
|
||||
if(!shrbase || !shrbase[1]) throw new Error("SYLK shared formula cannot find base");
|
||||
arr[R][C][1] = shift_formula_str(shrbase[1], {r: R - _R, c: C - _C});
|
||||
formula = shift_formula_str(shrbase[1], {r: R - _R, c: C - _C});
|
||||
}
|
||||
if(formula) {
|
||||
if(!arr[R][C]) arr[R][C] = { t: 'n', f: formula };
|
||||
else arr[R][C].f = formula;
|
||||
}
|
||||
break;
|
||||
case 'F': /* Format */
|
||||
@ -5880,7 +5912,8 @@ var SYLK = (function() {
|
||||
function sylk_to_workbook(d, opts) {
|
||||
var aoasht = sylk_to_aoa(d, opts);
|
||||
var aoa = aoasht[0], ws = aoasht[1], wb = aoasht[2];
|
||||
var o = aoa_to_sheet(aoa, opts);
|
||||
var _opts = dup(opts); _opts.date1904 = (((wb||{}).Workbook || {}).WBProps || {}).date1904;
|
||||
var o = aoa_to_sheet(aoa, _opts);
|
||||
keys(ws).forEach(function(k) { o[k] = ws[k]; });
|
||||
var outwb = sheet_to_workbook(o, opts);
|
||||
keys(wb).forEach(function(k) { outwb[k] = wb[k]; });
|
||||
@ -5924,11 +5957,12 @@ var SYLK = (function() {
|
||||
});
|
||||
}
|
||||
|
||||
function sheet_to_sylk(ws, opts) {
|
||||
function sheet_to_sylk(ws, opts, wb) {
|
||||
var preamble = ["ID;PSheetJS;N;E"], o = [];
|
||||
var r = safe_decode_range(ws['!ref']), cell;
|
||||
var dense = Array.isArray(ws);
|
||||
var RS = "\r\n";
|
||||
var d1904 = (((wb||{}).Workbook||{}).WBProps||{}).date1904;
|
||||
|
||||
preamble.push("P;PGeneral");
|
||||
preamble.push("F;P0;DG0G8;M255");
|
||||
@ -5936,12 +5970,13 @@ var SYLK = (function() {
|
||||
if(ws['!rows']) write_ws_rows_sylk(preamble, ws['!rows']);
|
||||
|
||||
preamble.push("B;Y" + (r.e.r - r.s.r + 1) + ";X" + (r.e.c - r.s.c + 1) + ";D" + [r.s.c,r.s.r,r.e.c,r.e.r].join(" "));
|
||||
preamble.push("O;L;D;B" + (d1904 ? ";V4" : "") + ";K47;G100 0.001");
|
||||
for(var R = r.s.r; R <= r.e.r; ++R) {
|
||||
for(var C = r.s.c; C <= r.e.c; ++C) {
|
||||
var coord = encode_cell({r:R,c:C});
|
||||
cell = dense ? (ws[R]||[])[C]: ws[coord];
|
||||
if(!cell || (cell.v == null && (!cell.f || cell.F))) continue;
|
||||
o.push(write_ws_cell_sylk(cell, ws, R, C, opts));
|
||||
o.push(write_ws_cell_sylk(cell, ws, R, C, opts)); // TODO: pass date1904 info
|
||||
}
|
||||
}
|
||||
return preamble.join(RS) + RS + o.join(RS) + RS + "E" + RS;
|
||||
@ -7938,8 +7973,8 @@ var a1_to_rc = (function(){
|
||||
return fstr.replace(crefregex, function($0, $1, $2, $3, $4, $5) {
|
||||
var c = decode_col($3) - ($2 ? 0 : base.c);
|
||||
var r = decode_row($5) - ($4 ? 0 : base.r);
|
||||
var R = (r == 0 ? "" : !$4 ? "[" + r + "]" : (r+1));
|
||||
var C = (c == 0 ? "" : !$2 ? "[" + c + "]" : (c+1));
|
||||
var R = $4 == "$" ? (r+1) : (r == 0 ? "" : "[" + r + "]");
|
||||
var C = $2 == "$" ? (c+1) : (c == 0 ? "" : "[" + c + "]");
|
||||
return $1 + "R" + R + "C" + C;
|
||||
});
|
||||
};
|
||||
@ -7978,6 +8013,8 @@ function ods_to_csf_formula(f) {
|
||||
f = f.replace(/COM\.MICROSOFT\./g, "");
|
||||
/* Part 3 Section 5.8 References */
|
||||
f = f.replace(/\[((?:\.[A-Z]+[0-9]+)(?::\.[A-Z]+[0-9]+)?)\]/g, function($$, $1) { return $1.replace(/\./g,""); });
|
||||
f = f.replace(/\$'([^']|'')+'/g, function($$) { return $$.slice(1); });
|
||||
f = f.replace(/\$([^\]\. #$]+)/g, function($$, $1) { return ($1).match(/^([A-Z]{1,2}|[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D])?(10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})?$/) ? $$ : $1; });
|
||||
/* TODO: something other than this */
|
||||
f = f.replace(/\[.(#[A-Z]*[?!])\]/g, "$1");
|
||||
return f.replace(/[;~]/g,",").replace(/\|/g,";");
|
||||
@ -7990,13 +8027,15 @@ function csf_to_ods_formula(f) {
|
||||
}
|
||||
|
||||
function ods_to_csf_3D(r) {
|
||||
r = r.replace(/\$'([^']|'')+'/g, function($$) { return $$.slice(1); });
|
||||
r = r.replace(/\$([^\]\. #$]+)/g, function($$, $1) { return ($1).match(/^([A-Z]{1,2}|[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D])?(10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})?$/) ? $$ : $1; });
|
||||
var a = r.split(":");
|
||||
var s = a[0].split(".")[0];
|
||||
return [s, a[0].split(".")[1] + (a.length > 1 ? (":" + (a[1].split(".")[1] || a[1].split(".")[0])) : "")];
|
||||
}
|
||||
|
||||
function csf_to_ods_3D(r) {
|
||||
return r.replace(/\./,"!");
|
||||
return r.replace(/!/,".");
|
||||
}
|
||||
|
||||
var strs = {}; // shared strings
|
||||
@ -8357,7 +8396,7 @@ function write_ws_xml_autofilter(data, ws, wb, idx) {
|
||||
var name = names[i];
|
||||
if(name.Name != '_xlnm._FilterDatabase') continue;
|
||||
if(name.Sheet != idx) continue;
|
||||
name.Ref = "'" + wb.SheetNames[idx] + "'!" + ref; break;
|
||||
name.Ref = formula_quote_sheet_name(wb.SheetNames[idx]) + "!" + fix_range(ref); break;
|
||||
}
|
||||
if(i == names.length) names.push({ Name: '_xlnm._FilterDatabase', Sheet: idx, Ref: "'" + wb.SheetNames[idx] + "'!" + ref });
|
||||
return writextag("autoFilter", null, {ref:ref});
|
||||
@ -8365,7 +8404,7 @@ function write_ws_xml_autofilter(data, ws, wb, idx) {
|
||||
|
||||
/* 18.3.1.88 sheetViews CT_SheetViews */
|
||||
/* 18.3.1.87 sheetView CT_SheetView */
|
||||
var sviewregex = /<(?:\w:)?sheetView(?:[^>a-z][^>]*)?\/?>/;
|
||||
var sviewregex = /<(?:\w:)?sheetView(?:[^>a-z][^>]*)?\/?>/g;
|
||||
function parse_ws_xml_sheetviews(data, wb) {
|
||||
if(!wb.Views) wb.Views = [{}];
|
||||
(data.match(sviewregex)||[]).forEach(function(r, i) {
|
||||
@ -8405,7 +8444,7 @@ function write_ws_xml_cell(cell, ref, ws, opts) {
|
||||
break;
|
||||
default: vv = cell.v; break;
|
||||
}
|
||||
var v = writetag('v', escapexml(vv)), o = ({r:ref});
|
||||
var v = (cell.t == "z" || cell.v == null)? "" : writetag('v', escapexml(vv)), o = ({r:ref});
|
||||
/* TODO: cell style */
|
||||
var os = get_cell_style(opts.cellXfs, cell, opts);
|
||||
if(os !== 0) o.s = os;
|
||||
@ -8421,7 +8460,7 @@ function write_ws_xml_cell(cell, ref, ws, opts) {
|
||||
v = writetag('v', ''+get_sst_id(opts.Strings, cell.v, opts.revStrings));
|
||||
o.t = "s"; break;
|
||||
}
|
||||
o.t = "str"; break;
|
||||
else o.t = "str"; break;
|
||||
}
|
||||
if(cell.t != oldt) { cell.t = oldt; cell.v = oldv; }
|
||||
if(typeof cell.f == "string" && cell.f) {
|
||||
@ -8997,6 +9036,17 @@ function check_wb(wb) {
|
||||
var Sheets = (wb.Workbook && wb.Workbook.Sheets) || [];
|
||||
check_wb_names(wb.SheetNames, Sheets, !!wb.vbaraw);
|
||||
for(var i = 0; i < wb.SheetNames.length; ++i) check_ws(wb.Sheets[wb.SheetNames[i]], wb.SheetNames[i], i);
|
||||
wb.SheetNames.forEach(function(n, i) {
|
||||
var ws = wb.Sheets[n];
|
||||
if(!ws || !ws["!autofilter"]) return;
|
||||
var DN;
|
||||
if(!wb.Workbook) wb.Workbook = {};
|
||||
if(!wb.Workbook.Names) wb.Workbook.Names = [];
|
||||
wb.Workbook.Names.forEach(function(dn) { if(dn.Name == "_xlnm._FilterDatabase" && dn.Sheet == i) DN = dn; });
|
||||
var nn = formula_quote_sheet_name(n) + "!" + fix_range(ws["!autofilter"].ref);
|
||||
if(DN) DN.Ref = nn;
|
||||
else wb.Workbook.Names.push({Name: "_xlnm._FilterDatabase", Sheet: i, Ref: nn});
|
||||
});
|
||||
/* TODO: validate workbook */
|
||||
}
|
||||
/* 18.2 Workbook */
|
||||
@ -9360,6 +9410,7 @@ function html_to_sheet(str, _opts) {
|
||||
function make_html_row(ws, r, R, o) {
|
||||
var M = (ws['!merges'] ||[]);
|
||||
var oo = [];
|
||||
var sp = ({});
|
||||
for(var C = r.s.c; C <= r.e.c; ++C) {
|
||||
var RS = 0, CS = 0;
|
||||
for(var j = 0; j < M.length; ++j) {
|
||||
@ -9373,7 +9424,7 @@ function make_html_row(ws, r, R, o) {
|
||||
var cell = o.dense ? (ws[R]||[])[C] : ws[coord];
|
||||
/* TODO: html entities */
|
||||
var w = (cell && cell.v != null) && (cell.h || escapehtml(cell.w || (format_cell(cell), cell.w) || "")) || "";
|
||||
var sp = ({});
|
||||
sp = ({});
|
||||
if(RS > 1) sp.rowspan = RS;
|
||||
if(CS > 1) sp.colspan = CS;
|
||||
if(o.editable) w = '<span contenteditable="true">' + w + '</span>';
|
||||
@ -9421,6 +9472,12 @@ function sheet_to_html(ws, opts/*, wb:?Workbook*/) {
|
||||
}
|
||||
|
||||
function sheet_add_dom(ws, table, _opts) {
|
||||
var rows = table.rows;
|
||||
if(!rows) {
|
||||
/* not an HTML TABLE */
|
||||
throw "Unsupported origin when " + table.tagName + " is not a TABLE";
|
||||
}
|
||||
|
||||
var opts = _opts || {};
|
||||
if(DENSE != null) opts.dense = DENSE;
|
||||
var or_R = 0, or_C = 0;
|
||||
@ -9432,7 +9489,6 @@ function sheet_add_dom(ws, table, _opts) {
|
||||
}
|
||||
}
|
||||
|
||||
var rows = table.getElementsByTagName('tr');
|
||||
var sheetRows = Math.min(opts.sheetRows||10000000, rows.length);
|
||||
var range = {s:{r:0,c:0},e:{r:or_R,c:or_C}};
|
||||
if(ws["!ref"]) {
|
||||
@ -9453,7 +9509,7 @@ function sheet_add_dom(ws, table, _opts) {
|
||||
if (opts.display) continue;
|
||||
rowinfo[R] = {hidden: true};
|
||||
}
|
||||
var elts = (row.children);
|
||||
var elts = (row.cells);
|
||||
for(_C = C = 0; _C < elts.length; ++_C) {
|
||||
var elt = elts[_C];
|
||||
if (opts.display && is_dom_element_hidden(elt)) continue;
|
||||
@ -9527,7 +9583,8 @@ function get_get_computed_style_function(element) {
|
||||
// If it is not available, try to get one from the global namespace
|
||||
if(typeof getComputedStyle === 'function') return getComputedStyle;
|
||||
return null;
|
||||
}/* OpenDocument */
|
||||
}
|
||||
/* OpenDocument */
|
||||
function parse_text_p(text) {
|
||||
/* 6.1.2 White Space Characters */
|
||||
var fixed = text
|
||||
@ -9541,29 +9598,236 @@ function parse_text_p(text) {
|
||||
return [v];
|
||||
}
|
||||
|
||||
var number_formats_ods = {
|
||||
/* ods name: [short ssf fmt, long ssf fmt] */
|
||||
day: ["d", "dd"],
|
||||
month: ["m", "mm"],
|
||||
year: ["y", "yy"],
|
||||
hours: ["h", "hh"],
|
||||
minutes: ["m", "mm"],
|
||||
seconds: ["s", "ss"],
|
||||
"am-pm": ["A/P", "AM/PM"],
|
||||
"day-of-week": ["ddd", "dddd"],
|
||||
era: ["e", "ee"],
|
||||
/* there is no native representation of LO "Q" format */
|
||||
quarter: ["\\Qm", "m\\\"th quarter\""]
|
||||
};
|
||||
/* Note: ODS can stick styles in content.xml or styles.xml, FODS blurs lines */
|
||||
function parse_ods_styles(d, _opts, _nfm) {
|
||||
var number_format_map = _nfm || {};
|
||||
var str = xlml_normalize(d);
|
||||
xlmlregex.lastIndex = 0;
|
||||
str = str.replace(/<!--([\s\S]*?)-->/mg,"").replace(/<!DOCTYPE[^\[]*\[[^\]]*\]>/gm,"");
|
||||
var Rn, NFtag, NF = "", tNF = "", y, etpos = 0, tidx = -1, infmt = false, payload = "";
|
||||
while((Rn = xlmlregex.exec(str))) {
|
||||
switch((Rn[3]=Rn[3].replace(/_.*$/,""))) {
|
||||
/* Number Format Definitions */
|
||||
case 'number-style': // <number:number-style> 16.29.2
|
||||
case 'currency-style': // <number:currency-style> 16.29.8
|
||||
case 'percentage-style': // <number:percentage-style> 16.29.10
|
||||
case 'date-style': // <number:date-style> 16.29.11
|
||||
case 'time-style': // <number:time-style> 16.29.19
|
||||
case 'text-style': // <number:text-style> 16.29.26
|
||||
if(Rn[1]==='/') {
|
||||
infmt = false;
|
||||
if(NFtag['truncate-on-overflow'] == "false") {
|
||||
if(NF.match(/h/)) NF = NF.replace(/h+/, "[$&]");
|
||||
else if(NF.match(/m/)) NF = NF.replace(/m+/, "[$&]");
|
||||
else if(NF.match(/s/)) NF = NF.replace(/s+/, "[$&]");
|
||||
}
|
||||
number_format_map[NFtag.name] = NF;
|
||||
NF = "";
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
infmt = true;
|
||||
NF = "";
|
||||
NFtag = parsexmltag(Rn[0], false);
|
||||
} break;
|
||||
|
||||
// LibreOffice bug https://bugs.documentfoundation.org/show_bug.cgi?id=149484
|
||||
case 'boolean-style': // <number:boolean-style> 16.29.24
|
||||
if(Rn[1]==='/') {
|
||||
infmt = false;
|
||||
number_format_map[NFtag.name] = "General";
|
||||
NF = "";
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
infmt = true;
|
||||
NF = "";
|
||||
NFtag = parsexmltag(Rn[0], false);
|
||||
} break;
|
||||
|
||||
/* Number Format Elements */
|
||||
case 'boolean': // <number:boolean> 16.29.25
|
||||
NF += "General"; // ODF spec is unfortunately underspecified here
|
||||
break;
|
||||
|
||||
case 'text': // <number:text> 16.29.27
|
||||
if(Rn[1]==='/') {
|
||||
payload = str.slice(tidx, xlmlregex.lastIndex - Rn[0].length);
|
||||
// NOTE: Excel has a different interpretation of "%%" and friends
|
||||
if(payload == "%" && NFtag[0] == '<number:percentage-style') NF += "%";
|
||||
else NF += '"' + payload.replace(/"/g, '""') + '"';
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
tidx = xlmlregex.lastIndex;
|
||||
} break;
|
||||
|
||||
|
||||
function parse_content_xml(d, _opts) {
|
||||
case 'day': { // <number:day> 16.29.12
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "d"; break;
|
||||
case "long": NF += "dd"; break;
|
||||
default: NF += "dd"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'day-of-week': { // <number:day-of-week> 16.29.16
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "ddd"; break;
|
||||
case "long": NF += "dddd"; break;
|
||||
default: NF += "ddd"; break;
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'era': { // <number:era> 16.29.15 TODO: proper mapping
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "ee"; break;
|
||||
case "long": NF += "eeee"; break;
|
||||
default: NF += "eeee"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'hours': { // <number:hours> 16.29.20
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "h"; break;
|
||||
case "long": NF += "hh"; break;
|
||||
default: NF += "hh"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'minutes': { // <number:minutes> 16.29.21
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "m"; break;
|
||||
case "long": NF += "mm"; break;
|
||||
default: NF += "mm"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'month': { // <number:month> 16.29.13
|
||||
y = parsexmltag(Rn[0], false);
|
||||
if(y["textual"]) NF += "mm";
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "m"; break;
|
||||
case "long": NF += "mm"; break;
|
||||
default: NF += "m"; break;
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'seconds': { // <number:seconds> 16.29.22
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "s"; break;
|
||||
case "long": NF += "ss"; break;
|
||||
default: NF += "ss"; break; // TODO: error condition
|
||||
}
|
||||
if(y["decimal-places"]) NF += "." + fill("0", +y["decimal-places"]);
|
||||
} break;
|
||||
|
||||
case 'year': { // <number:year> 16.29.14
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "yy"; break;
|
||||
case "long": NF += "yyyy"; break;
|
||||
default: NF += "yy"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'am-pm': // <number:am-pm> 16.29.23
|
||||
NF += "AM/PM"; // LO autocorrects A/P -> AM/PM
|
||||
break;
|
||||
|
||||
case 'week-of-year': // <number:week-of-year> 16.29.17
|
||||
case 'quarter': // <number:quarter> 16.29.18
|
||||
console.error("Excel does not support ODS format token " + Rn[3]);
|
||||
break;
|
||||
|
||||
case 'fill-character': // <number:fill-character> 16.29.5
|
||||
if(Rn[1]==='/') {
|
||||
payload = str.slice(tidx, xlmlregex.lastIndex - Rn[0].length);
|
||||
// NOTE: Excel has a different interpretation of "%%" and friends
|
||||
NF += '"' + payload.replace(/"/g, '""') + '"*';
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
tidx = xlmlregex.lastIndex;
|
||||
} break;
|
||||
|
||||
case 'scientific-number': // <number:scientific-number> 16.29.6
|
||||
// TODO: find a mapping for all parameters
|
||||
y = parsexmltag(Rn[0], false);
|
||||
NF += "0." + fill("0", +y["min-decimal-places"] || +y["decimal-places"] || 2) + fill("?", +y["decimal-places"] - +y["min-decimal-places"] || 0) + "E" + (parsexmlbool(y["forced-exponent-sign"]) ? "+" : "") + fill("0", +y["min-exponent-digits"] || 2);
|
||||
break;
|
||||
|
||||
case 'fraction': // <number:fraction> 16.29.7
|
||||
// TODO: find a mapping for all parameters
|
||||
y = parsexmltag(Rn[0], false);
|
||||
if(!+y["min-integer-digits"]) NF += "#";
|
||||
else NF += fill("0", +y["min-integer-digits"]);
|
||||
NF += " ";
|
||||
NF += fill("?", +y["min-numerator-digits"] || 1);
|
||||
NF += "/";
|
||||
if(+y["denominator-value"]) NF += y["denominator-value"];
|
||||
else NF += fill("?", +y["min-denominator-digits"] || 1);
|
||||
break;
|
||||
|
||||
case 'currency-symbol': // <number:currency-symbol> 16.29.9
|
||||
// TODO: localization with [$-...]
|
||||
if(Rn[1]==='/') {
|
||||
NF += '"' + str.slice(tidx, xlmlregex.lastIndex - Rn[0].length).replace(/"/g, '""') + '"';
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
tidx = xlmlregex.lastIndex;
|
||||
} else NF += "$";
|
||||
break;
|
||||
|
||||
case 'text-properties': // <style:text-properties> 16.29.29
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch((y["color"]||"").toLowerCase().replace("#", "")) {
|
||||
case "ff0000": case "red": NF = "[Red]" + NF; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'text-content': // <number:text-content> 16.29.28
|
||||
NF += "@";
|
||||
break;
|
||||
|
||||
case 'map': // <style:map> 16.3
|
||||
// TODO: handle more complex maps
|
||||
y = parsexmltag(Rn[0], false);
|
||||
if(unescapexml(y["condition"]) == "value()>=0") NF = number_format_map[y["apply-style-name"]] + ";" + NF;
|
||||
else console.error("ODS number format may be incorrect: " + y["condition"]);
|
||||
break;
|
||||
|
||||
case 'number': // <number:number> 16.29.3
|
||||
// TODO: handle all the attributes
|
||||
if(Rn[1]==='/') break;
|
||||
y = parsexmltag(Rn[0], false);
|
||||
tNF = "";
|
||||
tNF += fill("0", +y["min-integer-digits"] || 1);
|
||||
if(parsexmlbool(y["grouping"])) tNF = commaify(fill("#", Math.max(0, 4 - tNF.length)) + tNF);
|
||||
if(+y["min-decimal-places"] || +y["decimal-places"]) tNF += ".";
|
||||
if(+y["min-decimal-places"]) tNF += fill("0", +y["min-decimal-places"] || 1);
|
||||
if(+y["decimal-places"] - (+y["min-decimal-places"]||0)) tNF += fill("0", +y["decimal-places"] - (+y["min-decimal-places"]||0)); // TODO: should this be "#" ?
|
||||
NF += tNF;
|
||||
break;
|
||||
|
||||
case 'embedded-text': // <number:embedded-text> 16.29.4
|
||||
// TODO: verify interplay with grouping et al
|
||||
if(Rn[1]==='/') {
|
||||
if(etpos == 0) NF += '"' + str.slice(tidx, xlmlregex.lastIndex - Rn[0].length).replace(/"/g, '""') + '"';
|
||||
else NF = NF.slice(0, etpos) + '"' + str.slice(tidx, xlmlregex.lastIndex - Rn[0].length).replace(/"/g, '""') + '"' + NF.slice(etpos);
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
tidx = xlmlregex.lastIndex;
|
||||
etpos = -+parsexmltag(Rn[0], false)["position"] || 0;
|
||||
} break;
|
||||
|
||||
}}
|
||||
return number_format_map;
|
||||
}
|
||||
|
||||
function parse_content_xml(d, _opts, _nfm) {
|
||||
var opts = _opts || {};
|
||||
if(DENSE != null && opts.dense == null) opts.dense = DENSE;
|
||||
var str = xlml_normalize(d);
|
||||
var state = [], tmp;
|
||||
var tag;
|
||||
var NFtag = {name:""}, NF = "", pidx = 0;
|
||||
var nfidx, NF = "", pidx = 0;
|
||||
var sheetag;
|
||||
var rowtag;
|
||||
var Sheets = {}, SheetNames = [];
|
||||
@ -9574,17 +9838,18 @@ function parse_content_xml(d, _opts) {
|
||||
var textR = [];
|
||||
var R = -1, C = -1, range = {s: {r:1000000,c:10000000}, e: {r:0, c:0}};
|
||||
var row_ol = 0;
|
||||
var number_format_map = {};
|
||||
var number_format_map = _nfm || {}, styles = {};
|
||||
var merges = [], mrange = {}, mR = 0, mC = 0;
|
||||
var rowinfo = [], rowpeat = 1, colpeat = 1;
|
||||
var arrayf = [];
|
||||
var WB = {Names:[]};
|
||||
var WB = {Names:[], WBProps:{}};
|
||||
var atag = ({});
|
||||
var _Ref = ["", ""];
|
||||
var comments = [], comment = ({});
|
||||
var creator = "", creatoridx = 0;
|
||||
var isstub = false, intable = false;
|
||||
var i = 0;
|
||||
var baddate = 0;
|
||||
xlmlregex.lastIndex = 0;
|
||||
str = str.replace(/<!--([\s\S]*?)-->/mg,"").replace(/<!DOCTYPE[^\[]*\[[^\]]*\]>/gm,"");
|
||||
while((Rn = xlmlregex.exec(str))) switch((Rn[3]=Rn[3].replace(/_.*$/,""))) {
|
||||
@ -9642,6 +9907,7 @@ function parse_content_xml(d, _opts) {
|
||||
colpeat = parseInt(ctag['number-columns-repeated']||"1", 10);
|
||||
q = ({t:'z', v:null});
|
||||
if(ctag.formula && opts.cellFormula != false) q.f = ods_to_csf_formula(unescapexml(ctag.formula));
|
||||
if(ctag["style-name"] && styles[ctag["style-name"]]) q.z = styles[ctag["style-name"]];
|
||||
if((ctag['数据类型'] || ctag['value-type']) == "string") {
|
||||
q.t = "s"; q.v = unescapexml(ctag['string-value'] || "");
|
||||
if(opts.dense) {
|
||||
@ -9664,6 +9930,7 @@ function parse_content_xml(d, _opts) {
|
||||
ctag = parsexmltag(Rn[0], false);
|
||||
comments = []; comment = ({});
|
||||
q = ({t:ctag['数据类型'] || ctag['value-type'], v:null});
|
||||
if(ctag["style-name"] && styles[ctag["style-name"]]) q.z = styles[ctag["style-name"]];
|
||||
if(opts.cellFormula) {
|
||||
if(ctag.formula) ctag.formula = unescapexml(ctag.formula);
|
||||
if(ctag['number-matrix-columns-spanned'] && ctag['number-matrix-rows-spanned']) {
|
||||
@ -9691,16 +9958,16 @@ function parse_content_xml(d, _opts) {
|
||||
|
||||
/* 19.385 office:value-type */
|
||||
switch(q.t) {
|
||||
case 'boolean': q.t = 'b'; q.v = parsexmlbool(ctag['boolean-value']); break;
|
||||
case 'boolean': q.t = 'b'; q.v = parsexmlbool(ctag['boolean-value']) || (+ctag['boolean-value'] >= 1); break;
|
||||
case 'float': q.t = 'n'; q.v = parseFloat(ctag.value); break;
|
||||
case 'percentage': q.t = 'n'; q.v = parseFloat(ctag.value); break;
|
||||
case 'currency': q.t = 'n'; q.v = parseFloat(ctag.value); break;
|
||||
case 'date': q.t = 'd'; q.v = parseDate(ctag['date-value']);
|
||||
if(!opts.cellDates) { q.t = 'n'; q.v = datenum(q.v); }
|
||||
q.z = 'm/d/yy'; break;
|
||||
if(!opts.cellDates) { q.t = 'n'; q.v = datenum(q.v, WB.WBProps.date1904) - baddate; }
|
||||
if(!q.z) q.z = 'm/d/yy'; break;
|
||||
case 'time': q.t = 'n'; q.v = parse_isodur(ctag['time-value'])/86400;
|
||||
if(opts.cellDates) { q.t = 'd'; q.v = numdate(q.v); }
|
||||
q.z = 'HH:MM:SS'; break;
|
||||
if(!q.z) q.z = 'HH:MM:SS'; break;
|
||||
case 'number': q.t = 'n'; q.v = parseFloat(ctag['数据数值']); break;
|
||||
default:
|
||||
if(q.t === 'string' || q.t === 'text' || !q.t) {
|
||||
@ -9795,23 +10062,24 @@ function parse_content_xml(d, _opts) {
|
||||
textp = ""; textpidx = 0; textR = [];
|
||||
break;
|
||||
|
||||
case 'scientific-number': // TODO: <number:scientific-number>
|
||||
break;
|
||||
case 'currency-symbol': // TODO: <number:currency-symbol>
|
||||
break;
|
||||
case 'currency-style': // TODO: <number:currency-style>
|
||||
case 'scientific-number': // <number:scientific-number>
|
||||
case 'currency-symbol': // <number:currency-symbol>
|
||||
case 'fill-character': // 16.29.5 <number:fill-character>
|
||||
break;
|
||||
|
||||
case 'text-style': // 16.27.25 <number:text-style>
|
||||
case 'boolean-style': // 16.27.23 <number:boolean-style>
|
||||
case 'number-style': // 16.27.2 <number:number-style>
|
||||
case 'currency-style': // 16.29.8 <number:currency-style>
|
||||
case 'percentage-style': // 16.27.9 <number:percentage-style>
|
||||
case 'date-style': // 16.27.10 <number:date-style>
|
||||
case 'time-style': // 16.27.18 <number:time-style>
|
||||
if(Rn[1]==='/'){
|
||||
number_format_map[NFtag.name] = NF;
|
||||
if((tmp=state.pop())[0]!==Rn[3]) throw "Bad state: "+tmp;
|
||||
var xlmlidx = xlmlregex.lastIndex;
|
||||
parse_ods_styles(str.slice(nfidx, xlmlregex.lastIndex), _opts, number_format_map);
|
||||
xlmlregex.lastIndex = xlmlidx;
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
NF = "";
|
||||
NFtag = parsexmltag(Rn[0], false);
|
||||
state.push([Rn[3], true]);
|
||||
nfidx = xlmlregex.lastIndex - Rn[0].length;
|
||||
} break;
|
||||
|
||||
case 'script': break; // 3.13 <office:script>
|
||||
@ -9820,8 +10088,10 @@ function parse_content_xml(d, _opts) {
|
||||
|
||||
case 'default-style': // TODO: <style:default-style>
|
||||
case 'page-layout': break; // TODO: <style:page-layout>
|
||||
case 'style': // 16.2 <style:style>
|
||||
break;
|
||||
case 'style': { // 16.2 <style:style>
|
||||
var styletag = parsexmltag(Rn[0], false);
|
||||
if(styletag["family"] == "table-cell" && number_format_map[styletag["data-style-name"]]) styles[styletag["name"]] = number_format_map[styletag["data-style-name"]];
|
||||
} break;
|
||||
case 'map': break; // 16.3 <style:map>
|
||||
case 'font-face': break; // 16.21 <style:font-face>
|
||||
|
||||
@ -9832,12 +10102,7 @@ function parse_content_xml(d, _opts) {
|
||||
case 'table-cell-properties': break; // 17.18 <style:table-cell-properties>
|
||||
|
||||
case 'number': // 16.27.3 <number:number>
|
||||
switch(state[state.length-1][0]) {
|
||||
case 'time-style':
|
||||
case 'date-style':
|
||||
tag = parsexmltag(Rn[0], false);
|
||||
NF += number_formats_ods[Rn[3]][tag.style==='long'?1:0]; break;
|
||||
} break;
|
||||
break;
|
||||
|
||||
case 'fraction': break; // TODO 16.27.6 <number:fraction>
|
||||
|
||||
@ -9852,16 +10117,9 @@ function parse_content_xml(d, _opts) {
|
||||
case 'minutes': // 16.27.20 <number:minutes>
|
||||
case 'seconds': // 16.27.21 <number:seconds>
|
||||
case 'am-pm': // 16.27.22 <number:am-pm>
|
||||
switch(state[state.length-1][0]) {
|
||||
case 'time-style':
|
||||
case 'date-style':
|
||||
tag = parsexmltag(Rn[0], false);
|
||||
NF += number_formats_ods[Rn[3]][tag.style==='long'?1:0]; break;
|
||||
} break;
|
||||
break;
|
||||
|
||||
case 'boolean-style': break; // 16.27.23 <number:boolean-style>
|
||||
case 'boolean': break; // 16.27.24 <number:boolean>
|
||||
case 'text-style': break; // 16.27.25 <number:text-style>
|
||||
case 'text': // 16.27.26 <number:text>
|
||||
if(Rn[0].slice(-2) === "/>") break;
|
||||
else if(Rn[1]==="/") switch(state[state.length-1][0]) {
|
||||
@ -9897,7 +10155,14 @@ function parse_content_xml(d, _opts) {
|
||||
case 'table-header-columns': break; // 9.1.11 <table:table-header-columns>
|
||||
case 'table-columns': break; // 9.1.12 <table:table-columns>
|
||||
|
||||
case 'null-date': break; // 9.4.2 <table:null-date> TODO: date1904
|
||||
case 'null-date': // 9.4.2 <table:null-date>
|
||||
tag = parsexmltag(Rn[0], false);
|
||||
switch(tag["date-value"]) {
|
||||
case "1904-01-01": WB.WBProps.date1904 = true;
|
||||
/* falls through */
|
||||
case "1900-01-01": baddate = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'graphic-properties': break; // 17.21 <style:graphic-properties>
|
||||
case 'calculation-settings': break; // 9.4.1 <table:calculation-settings>
|
||||
@ -10085,9 +10350,11 @@ function parse_content_xml(d, _opts) {
|
||||
function parse_ods(zip, opts) {
|
||||
opts = opts || ({});
|
||||
if(safegetzipfile(zip, 'META-INF/manifest.xml')) parse_manifest(getzipdata(zip, 'META-INF/manifest.xml'), opts);
|
||||
var styles = getzipstr(zip, 'styles.xml');
|
||||
var Styles = styles && parse_ods_styles(utf8read(styles), opts);
|
||||
var content = getzipstr(zip, 'content.xml');
|
||||
if(!content) throw new Error("Missing content.xml in ODS / UOF file");
|
||||
var wb = parse_content_xml(utf8read(content), opts);
|
||||
var wb = parse_content_xml(utf8read(content), opts, Styles);
|
||||
if(safegetzipfile(zip, 'meta.xml')) wb.Props = parse_core_props(getzipdata(zip, 'meta.xml'));
|
||||
return wb;
|
||||
}
|
||||
@ -10127,6 +10394,169 @@ var write_styles_ods = /* @__PURE__ */(function() {
|
||||
return XML_HEADER + payload;
|
||||
};
|
||||
})();
|
||||
|
||||
// TODO: find out if anyone actually read the spec. LO has some wild errors
|
||||
function write_number_format_ods(nf, nfidx) {
|
||||
var type = "number", payload = "", nopts = { "style:name": nfidx }, c = "", i = 0;
|
||||
nf = nf.replace(/"[$]"/g, "$");
|
||||
/* TODO: replace with an actual parser based on a real grammar */
|
||||
j: {
|
||||
// TODO: support style maps
|
||||
if(nf.indexOf(";") > -1) {
|
||||
console.error("Unsupported ODS Style Map exported. Using first branch of " + nf);
|
||||
nf = nf.slice(0, nf.indexOf(";"));
|
||||
}
|
||||
|
||||
if(nf == "@") { type = "text"; payload = "<number:text-content/>"; break j; }
|
||||
|
||||
/* currency flag */
|
||||
if(nf.indexOf(/\$/) > -1) { type = "currency"; }
|
||||
|
||||
/* opening string literal */
|
||||
if(nf[i] == '"') {
|
||||
c = "";
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
if(nf[i+1] == "*") {
|
||||
i++;
|
||||
payload += '<number:fill-character>' + escapexml(c.replace(/""/g, '"')) + '</number:fill-character>';
|
||||
} else {
|
||||
payload += '<number:text>' + escapexml(c.replace(/""/g, '"')) + '</number:text>';
|
||||
}
|
||||
nf = nf.slice(i+1); i = 0;
|
||||
}
|
||||
|
||||
/* fractions */
|
||||
var t = nf.match(/# (\?+)\/(\?+)/);
|
||||
if(t) { payload += writextag("number:fraction", null, {"number:min-integer-digits":0, "number:min-numerator-digits": t[1].length, "number:max-denominator-value": Math.max(+(t[1].replace(/./g, "9")), +(t[2].replace(/./g, "9"))) }); break j; }
|
||||
if((t=nf.match(/# (\?+)\/(\d+)/))) { payload += writextag("number:fraction", null, {"number:min-integer-digits":0, "number:min-numerator-digits": t[1].length, "number:denominator-value": +t[2]}); break j; }
|
||||
|
||||
/* percentages */
|
||||
if((t=nf.match(/(\d+)(|\.\d+)%/))) { type = "percentage"; payload += writextag("number:number", null, {"number:decimal-places": t[2] && t.length - 1 || 0, "number:min-decimal-places": t[2] && t.length - 1 || 0, "number:min-integer-digits": t[1].length }) + "<number:text>%</number:text>"; break j; }
|
||||
|
||||
/* datetime */
|
||||
var has_time = false;
|
||||
if(["y","m","d"].indexOf(nf[0]) > -1) {
|
||||
type = "date";
|
||||
k: for(; i < nf.length; ++i) switch((c = nf[i].toLowerCase())) {
|
||||
case "h": case "s": has_time = true; --i; break k;
|
||||
case "m":
|
||||
l: for(var h = i+1; h < nf.length; ++h) switch(nf[h]) {
|
||||
case "y": case "d": break l;
|
||||
case "h": case "s": has_time = true; --i; break k;
|
||||
}
|
||||
/* falls through */
|
||||
case "y": case "d":
|
||||
while((nf[++i]||"").toLowerCase() == c[0]) c += c[0]; --i;
|
||||
switch(c) {
|
||||
case "y": case "yy": payload += "<number:year/>"; break;
|
||||
case "yyy": case "yyyy": payload += '<number:year number:style="long"/>'; break;
|
||||
case "mmmmm": console.error("ODS has no equivalent of format |mmmmm|");
|
||||
/* falls through */
|
||||
case "m": case "mm": case "mmm": case "mmmm":
|
||||
payload += '<number:month number:style="' + (c.length % 2 ? "short" : "long") + '" number:textual="' + (c.length >= 3 ? "true" : "false") + '"/>';
|
||||
break;
|
||||
case "d": case "dd": payload += '<number:day number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
|
||||
case "ddd": case "dddd": payload += '<number:day-of-week number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
|
||||
}
|
||||
break;
|
||||
case '"':
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
payload += '<number:text>' + escapexml(c.slice(1).replace(/""/g, '"')) + '</number:text>';
|
||||
break;
|
||||
case '/': payload += '<number:text>' + escapexml(c) + '</number:text>'; break;
|
||||
default: console.error("unrecognized character " + c + " in ODF format " + nf);
|
||||
}
|
||||
if(!has_time) break j;
|
||||
nf = nf.slice(i+1); i = 0;
|
||||
}
|
||||
if(nf.match(/^\[?[hms]/)) {
|
||||
if(type == "number") type = "time";
|
||||
if(nf.match(/\[/)) {
|
||||
nf = nf.replace(/[\[\]]/g, "");
|
||||
nopts['number:truncate-on-overflow'] = "false";
|
||||
}
|
||||
for(; i < nf.length; ++i) switch((c = nf[i].toLowerCase())) {
|
||||
case "h": case "m": case "s":
|
||||
while((nf[++i]||"").toLowerCase() == c[0]) c += c[0]; --i;
|
||||
switch(c) {
|
||||
case "h": case "hh": payload += '<number:hours number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
|
||||
case "m": case "mm": payload += '<number:minutes number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
|
||||
case "s": case "ss":
|
||||
if(nf[i+1] == ".") do { c += nf[i+1]; ++i; } while(nf[i+1] == "0");
|
||||
payload += '<number:seconds number:style="' + (c.match("ss") ? "long" : "short") + '"' + (c.match(/\./) ? ' number:decimal-places="' + (c.match(/0+/)||[""])[0].length + '"' : "")+ '/>'; break;
|
||||
}
|
||||
break;
|
||||
case '"':
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
payload += '<number:text>' + escapexml(c.slice(1).replace(/""/g, '"')) + '</number:text>';
|
||||
break;
|
||||
case '/': payload += '<number:text>' + escapexml(c) + '</number:text>'; break;
|
||||
case "a":
|
||||
if(nf.slice(i, i+3).toLowerCase() == "a/p") { payload += '<number:am-pm/>'; i += 2; break; } // Note: ODF does not support A/P
|
||||
if(nf.slice(i, i+5).toLowerCase() == "am/pm") { payload += '<number:am-pm/>'; i += 4; break; }
|
||||
/* falls through */
|
||||
default: console.error("unrecognized character " + c + " in ODF format " + nf);
|
||||
}
|
||||
break j;
|
||||
}
|
||||
|
||||
/* currency flag */
|
||||
if(nf.indexOf(/\$/) > -1) { type = "currency"; }
|
||||
|
||||
/* should be in a char loop */
|
||||
if(nf[0] == "$") { payload += '<number:currency-symbol number:language="en" number:country="US">$</number:currency-symbol>'; nf = nf.slice(1); i = 0; }
|
||||
i = 0; if(nf[i] == '"') {
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
if(nf[i+1] == "*") {
|
||||
i++;
|
||||
payload += '<number:fill-character>' + escapexml(c.replace(/""/g, '"')) + '</number:fill-character>';
|
||||
} else {
|
||||
payload += '<number:text>' + escapexml(c.replace(/""/g, '"')) + '</number:text>';
|
||||
}
|
||||
nf = nf.slice(i+1); i = 0;
|
||||
}
|
||||
|
||||
/* number TODO: interstitial text e.g. 000)000-0000 */
|
||||
var np = nf.match(/([#0][0#,]*)(\.[0#]*|)(E[+]?0*|)/i);
|
||||
if(!np || !np[0]) console.error("Could not find numeric part of " + nf);
|
||||
else {
|
||||
var base = np[1].replace(/,/g, "");
|
||||
payload += '<number:' + (np[3] ? "scientific-" : "")+ 'number' +
|
||||
' number:min-integer-digits="' + (base.indexOf("0") == -1 ? "0" : base.length - base.indexOf("0")) + '"' +
|
||||
(np[0].indexOf(",") > -1 ? ' number:grouping="true"' : "") +
|
||||
(np[2] && ' number:decimal-places="' + (np[2].length - 1) + '"' || ' number:decimal-places="0"') +
|
||||
(np[3] && np[3].indexOf("+") > -1 ? ' number:forced-exponent-sign="true"' : "" ) +
|
||||
(np[3] ? ' number:min-exponent-digits="' + np[3].match(/0+/)[0].length + '"' : "" ) +
|
||||
'>' +
|
||||
/* TODO: interstitial text placeholders */
|
||||
'</number:' + (np[3] ? "scientific-" : "") + 'number>';
|
||||
i = np.index + np[0].length;
|
||||
}
|
||||
|
||||
/* residual text */
|
||||
if(nf[i] == '"') {
|
||||
c = "";
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
payload += '<number:text>' + escapexml(c.replace(/""/g, '"')) + '</number:text>';
|
||||
}
|
||||
}
|
||||
|
||||
if(!payload) { console.error("Could not generate ODS number format for |" + nf + "|"); return ""; }
|
||||
return writextag("number:" + type + "-style", payload, nopts);
|
||||
}
|
||||
|
||||
function write_names_ods(Names, SheetNames, idx) {
|
||||
var scoped = Names.filter(function(name) { return name.Sheet == (idx == -1 ? null : idx); });
|
||||
if(!scoped.length) return "";
|
||||
return " <table:named-expressions>\n" + scoped.map(function(name) {
|
||||
var odsref = csf_to_ods_3D(name.Ref);
|
||||
return " " + writextag("table:named-range", null, {
|
||||
"table:name": name.Name,
|
||||
"table:cell-range-address": odsref,
|
||||
"table:base-cell-address": odsref.replace(/[\.]?[^\.]*$/, ".$A$1")
|
||||
});
|
||||
}).join("\n") + "\n </table:named-expressions>\n";
|
||||
}
|
||||
var write_content_ods = /* @__PURE__ */(function() {
|
||||
/* 6.1.2 White Space Characters */
|
||||
var write_text_p = function(text) {
|
||||
@ -10139,7 +10569,7 @@ var write_content_ods = /* @__PURE__ */(function() {
|
||||
|
||||
var null_cell_xml = ' <table:table-cell />\n';
|
||||
var covered_cell_xml = ' <table:covered-table-cell/>\n';
|
||||
var write_ws = function(ws, wb, i) {
|
||||
var write_ws = function(ws, wb, i, opts, nfs) {
|
||||
/* Section 9 Tables */
|
||||
var o = [];
|
||||
o.push(' <table:table table:name="' + escapexml(wb.SheetNames[i]) + '" table:style-name="ta1">\n');
|
||||
@ -10215,10 +10645,12 @@ var write_content_ods = /* @__PURE__ */(function() {
|
||||
if(_tgt.charAt(0) != "#" && !_tgt.match(/^\w+:/)) _tgt = '../' + _tgt;
|
||||
text_p = writextag('text:a', text_p, {'xlink:href': _tgt.replace(/&/g, "&")});
|
||||
}
|
||||
if(nfs[cell.z]) ct["table:style-name"] = "ce" + nfs[cell.z].slice(1);
|
||||
o.push(' ' + writextag('table:table-cell', writextag('text:p', text_p, {}), ct) + '\n');
|
||||
}
|
||||
o.push(' </table:table-row>\n');
|
||||
}
|
||||
if((wb.Workbook||{}).Names) o.push(write_names_ods(wb.Workbook.Names, wb.SheetNames, i));
|
||||
o.push(' </table:table>\n');
|
||||
return o.join("");
|
||||
};
|
||||
@ -10226,14 +10658,6 @@ var write_content_ods = /* @__PURE__ */(function() {
|
||||
var write_automatic_styles_ods = function(o, wb) {
|
||||
o.push(' <office:automatic-styles>\n');
|
||||
|
||||
o.push(' <number:date-style style:name="N37" number:automatic-order="true">\n');
|
||||
o.push(' <number:month number:style="long"/>\n');
|
||||
o.push(' <number:text>/</number:text>\n');
|
||||
o.push(' <number:day number:style="long"/>\n');
|
||||
o.push(' <number:text>/</number:text>\n');
|
||||
o.push(' <number:year/>\n');
|
||||
o.push(' </number:date-style>\n');
|
||||
|
||||
/* column styles */
|
||||
var cidx = 0;
|
||||
wb.SheetNames.map(function(n) { return wb.Sheets[n]; }).forEach(function(ws) {
|
||||
@ -10274,12 +10698,38 @@ var write_content_ods = /* @__PURE__ */(function() {
|
||||
o.push(' <style:table-properties table:display="true" style:writing-mode="lr-tb"/>\n');
|
||||
o.push(' </style:style>\n');
|
||||
|
||||
/* table cells, text */
|
||||
o.push(' <number:date-style style:name="N37" number:automatic-order="true">\n');
|
||||
o.push(' <number:month number:style="long"/>\n');
|
||||
o.push(' <number:text>/</number:text>\n');
|
||||
o.push(' <number:day number:style="long"/>\n');
|
||||
o.push(' <number:text>/</number:text>\n');
|
||||
o.push(' <number:year/>\n');
|
||||
o.push(' </number:date-style>\n');
|
||||
|
||||
/* number formats, table cells, text */
|
||||
var nfs = {};
|
||||
var nfi = 69;
|
||||
wb.SheetNames.map(function(n) { return wb.Sheets[n]; }).forEach(function(ws) {
|
||||
if(!ws) return;
|
||||
var range = decode_range(ws["!ref"]);
|
||||
for(var R = 0; R <= range.e.r; ++R) for(var C = 0; C <= range.e.c; ++C) {
|
||||
var c = Array.isArray(ws) ? (ws[R]||[])[C] : ws[encode_cell({r:R,c:C})];
|
||||
if(!c || !c.z || c.z.toLowerCase() == "general") continue;
|
||||
if(!nfs[c.z]) {
|
||||
var out = write_number_format_ods(c.z, "N" + nfi);
|
||||
if(out) { nfs[c.z] = "N" + nfi; ++nfi; o.push(out + "\n"); }
|
||||
}
|
||||
}
|
||||
});
|
||||
o.push(' <style:style style:name="ce1" style:family="table-cell" style:parent-style-name="Default" style:data-style-name="N37"/>\n');
|
||||
keys(nfs).forEach(function(nf) {
|
||||
o.push('<style:style style:name="ce' + nfs[nf].slice(1) + '" style:family="table-cell" style:parent-style-name="Default" style:data-style-name="' + nfs[nf] + '"/>\n');
|
||||
});
|
||||
|
||||
/* page-layout */
|
||||
|
||||
o.push(' </office:automatic-styles>\n');
|
||||
return nfs;
|
||||
};
|
||||
|
||||
return function wcx(wb, opts) {
|
||||
@ -10332,14 +10782,16 @@ var write_content_ods = /* @__PURE__ */(function() {
|
||||
|
||||
if(opts.bookType == "fods") {
|
||||
o.push('<office:document' + attr + fods + '>\n');
|
||||
o.push(write_meta_ods().replace(/office:document-meta/g, "office:meta"));
|
||||
o.push(write_meta_ods().replace(/<office:document-meta.*?>/, "").replace(/<\/office:document-meta>/, "") + "\n");
|
||||
// TODO: settings (equiv of settings.xml for ODS)
|
||||
} else o.push('<office:document-content' + attr + '>\n');
|
||||
// o.push(' <office:scripts/>\n');
|
||||
write_automatic_styles_ods(o, wb);
|
||||
var nfs = write_automatic_styles_ods(o, wb);
|
||||
o.push(' <office:body>\n');
|
||||
o.push(' <office:spreadsheet>\n');
|
||||
for(var i = 0; i != wb.SheetNames.length; ++i) o.push(write_ws(wb.Sheets[wb.SheetNames[i]], wb, i, opts));
|
||||
if(((wb.Workbook||{}).WBProps||{}).date1904) o.push(' <table:calculation-settings table:case-sensitive="false" table:search-criteria-must-apply-to-whole-cell="true" table:use-wildcards="true" table:use-regular-expressions="false" table:automatic-find-labels="false">\n <table:null-date table:date-value="1904-01-01"/>\n </table:calculation-settings>\n');
|
||||
for(var i = 0; i != wb.SheetNames.length; ++i) o.push(write_ws(wb.Sheets[wb.SheetNames[i]], wb, i, opts, nfs));
|
||||
if((wb.Workbook||{}).Names) o.push(write_names_ods(wb.Workbook.Names, wb.SheetNames, -1));
|
||||
o.push(' </office:spreadsheet>\n');
|
||||
o.push(' </office:body>\n');
|
||||
if(opts.bookType == "fods") o.push('</office:document>');
|
||||
@ -11271,7 +11723,7 @@ function writeSync(wb, opts) {
|
||||
case 'xml':
|
||||
case 'xlml': return write_string_type(write_xlml(wb, o), o);
|
||||
case 'slk':
|
||||
case 'sylk': return write_string_type(SYLK.from_sheet(wb.Sheets[wb.SheetNames[idx]], o), o);
|
||||
case 'sylk': return write_string_type(SYLK.from_sheet(wb.Sheets[wb.SheetNames[idx]], o, wb), o);
|
||||
case 'htm':
|
||||
case 'html': return write_string_type(sheet_to_html(wb.Sheets[wb.SheetNames[idx]], o), o);
|
||||
case 'txt': return write_stxt_type(sheet_to_txt(wb.Sheets[wb.SheetNames[idx]], o), o);
|
||||
@ -11564,7 +12016,7 @@ function sheet_add_json(_ws, js, opts) {
|
||||
else if(v instanceof Date) {
|
||||
t = 'd';
|
||||
if(!o.cellDates) { t = 'n'; v = datenum(v); }
|
||||
z = (o.dateNF || table_fmt[14]);
|
||||
z = (cell.z && fmt_is_date(cell.z)) ? cell.z : (o.dateNF || table_fmt[14]);
|
||||
}
|
||||
else if(v === null && o.nullError) { t = 'e'; v = 0; }
|
||||
if(!cell) ws[ref] = cell = ({t:t, v:v});
|
||||
|
510
xlsx.mjs
generated
510
xlsx.mjs
generated
@ -3,7 +3,7 @@
|
||||
/*exported XLSX */
|
||||
/*global process:false, Buffer:false, ArrayBuffer:false, DataView:false, Deno:false */
|
||||
var XLSX = {};
|
||||
XLSX.version = '0.18.8';
|
||||
XLSX.version = '0.18.9';
|
||||
var current_codepage = 1200, current_ansi = 1252;
|
||||
/*:: declare var cptable:any; */
|
||||
/*global cptable:true, window */
|
||||
@ -13503,6 +13503,7 @@ function stringify_formula(formula/*Array<any>*/, range, cell/*:any*/, supbooks,
|
||||
}
|
||||
}
|
||||
if(stack.length > 1 && opts.WTF) throw new Error("bad formula stack");
|
||||
if(stack[0] == "TRUE") return true; if(stack[0] == "FALSE") return false;
|
||||
return stack[0];
|
||||
}
|
||||
|
||||
@ -13660,6 +13661,15 @@ function write_XLSBFormulaErr(val/*:number*/) {
|
||||
oint.write_shift(4, 0);
|
||||
return oint;
|
||||
}
|
||||
/* Writes a PtgBool */
|
||||
function write_XLSBFormulaBool(val/*:boolean*/) {
|
||||
var oint = new_buf(10);
|
||||
oint.write_shift(4, 2);
|
||||
oint.write_shift(1, 0x1D);
|
||||
oint.write_shift(1, val?1:0);
|
||||
oint.write_shift(4, 0);
|
||||
return oint;
|
||||
}
|
||||
|
||||
/* Writes a PtgStr */
|
||||
function write_XLSBFormulaStr(val/*:string*/) {
|
||||
@ -13814,7 +13824,9 @@ function write_XLSBFormulaArea3D(_str, wb) {
|
||||
|
||||
|
||||
/* General Formula */
|
||||
function write_XLSBFormula(val/*:string*/, wb) {
|
||||
function write_XLSBFormula(val/*:string|number*/, wb) {
|
||||
if(typeof val == "number") return write_XLSBFormulaNum(val);
|
||||
if(typeof val == "boolean") return write_XLSBFormulaBool(val);
|
||||
if(/^#(DIV\/0!|GETTING_DATA|N\/A|NAME\?|NULL!|NUM!|REF!|VALUE!)$/.test(val)) return write_XLSBFormulaErr(+RBErr[val]);
|
||||
if(val.match(/^\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})$/)) return write_XLSBFormulaRef(val);
|
||||
if(val.match(/^\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5}):\$?(?:[A-W][A-Z]{2}|X[A-E][A-Z]|XF[A-D]|[A-Z]{1,2})\$?(?:10[0-3]\d{4}|104[0-7]\d{3}|1048[0-4]\d{2}|10485[0-6]\d|104857[0-6]|[1-9]\d{0,5})$/)) return write_XLSBFormulaRange(val);
|
||||
@ -21733,29 +21745,236 @@ function parse_text_p(text/*:string*//*::, tag*/)/*:Array<any>*/ {
|
||||
return [v];
|
||||
}
|
||||
|
||||
var number_formats_ods = {
|
||||
/* ods name: [short ssf fmt, long ssf fmt] */
|
||||
day: ["d", "dd"],
|
||||
month: ["m", "mm"],
|
||||
year: ["y", "yy"],
|
||||
hours: ["h", "hh"],
|
||||
minutes: ["m", "mm"],
|
||||
seconds: ["s", "ss"],
|
||||
"am-pm": ["A/P", "AM/PM"],
|
||||
"day-of-week": ["ddd", "dddd"],
|
||||
era: ["e", "ee"],
|
||||
/* there is no native representation of LO "Q" format */
|
||||
quarter: ["\\Qm", "m\\\"th quarter\""]
|
||||
};
|
||||
/* Note: ODS can stick styles in content.xml or styles.xml, FODS blurs lines */
|
||||
function parse_ods_styles(d/*:string*/, _opts, _nfm) {
|
||||
var number_format_map = _nfm || {};
|
||||
var str = xlml_normalize(d);
|
||||
xlmlregex.lastIndex = 0;
|
||||
str = str.replace(/<!--([\s\S]*?)-->/mg,"").replace(/<!DOCTYPE[^\[]*\[[^\]]*\]>/gm,"");
|
||||
var Rn, NFtag, NF = "", tNF = "", y, etpos = 0, tidx = -1, infmt = false, payload = "";
|
||||
while((Rn = xlmlregex.exec(str))) {
|
||||
switch((Rn[3]=Rn[3].replace(/_.*$/,""))) {
|
||||
/* Number Format Definitions */
|
||||
case 'number-style': // <number:number-style> 16.29.2
|
||||
case 'currency-style': // <number:currency-style> 16.29.8
|
||||
case 'percentage-style': // <number:percentage-style> 16.29.10
|
||||
case 'date-style': // <number:date-style> 16.29.11
|
||||
case 'time-style': // <number:time-style> 16.29.19
|
||||
case 'text-style': // <number:text-style> 16.29.26
|
||||
if(Rn[1]==='/') {
|
||||
infmt = false;
|
||||
if(NFtag['truncate-on-overflow'] == "false") {
|
||||
if(NF.match(/h/)) NF = NF.replace(/h+/, "[$&]");
|
||||
else if(NF.match(/m/)) NF = NF.replace(/m+/, "[$&]");
|
||||
else if(NF.match(/s/)) NF = NF.replace(/s+/, "[$&]");
|
||||
}
|
||||
number_format_map[NFtag.name] = NF;
|
||||
NF = "";
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
infmt = true;
|
||||
NF = "";
|
||||
NFtag = parsexmltag(Rn[0], false);
|
||||
} break;
|
||||
|
||||
// LibreOffice bug https://bugs.documentfoundation.org/show_bug.cgi?id=149484
|
||||
case 'boolean-style': // <number:boolean-style> 16.29.24
|
||||
if(Rn[1]==='/') {
|
||||
infmt = false;
|
||||
number_format_map[NFtag.name] = "General";
|
||||
NF = "";
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
infmt = true;
|
||||
NF = "";
|
||||
NFtag = parsexmltag(Rn[0], false);
|
||||
} break;
|
||||
|
||||
/* Number Format Elements */
|
||||
case 'boolean': // <number:boolean> 16.29.25
|
||||
NF += "General"; // ODF spec is unfortunately underspecified here
|
||||
break;
|
||||
|
||||
case 'text': // <number:text> 16.29.27
|
||||
if(Rn[1]==='/') {
|
||||
payload = str.slice(tidx, xlmlregex.lastIndex - Rn[0].length);
|
||||
// NOTE: Excel has a different interpretation of "%%" and friends
|
||||
if(payload == "%" && NFtag[0] == '<number:percentage-style') NF += "%";
|
||||
else NF += '"' + payload.replace(/"/g, '""') + '"';
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
tidx = xlmlregex.lastIndex;
|
||||
} break;
|
||||
|
||||
|
||||
function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
case 'day': { // <number:day> 16.29.12
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "d"; break;
|
||||
case "long": NF += "dd"; break;
|
||||
default: NF += "dd"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'day-of-week': { // <number:day-of-week> 16.29.16
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "ddd"; break;
|
||||
case "long": NF += "dddd"; break;
|
||||
default: NF += "ddd"; break;
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'era': { // <number:era> 16.29.15 TODO: proper mapping
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "ee"; break;
|
||||
case "long": NF += "eeee"; break;
|
||||
default: NF += "eeee"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'hours': { // <number:hours> 16.29.20
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "h"; break;
|
||||
case "long": NF += "hh"; break;
|
||||
default: NF += "hh"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'minutes': { // <number:minutes> 16.29.21
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "m"; break;
|
||||
case "long": NF += "mm"; break;
|
||||
default: NF += "mm"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'month': { // <number:month> 16.29.13
|
||||
y = parsexmltag(Rn[0], false);
|
||||
if(y["textual"]) NF += "mm";
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "m"; break;
|
||||
case "long": NF += "mm"; break;
|
||||
default: NF += "m"; break;
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'seconds': { // <number:seconds> 16.29.22
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "s"; break;
|
||||
case "long": NF += "ss"; break;
|
||||
default: NF += "ss"; break; // TODO: error condition
|
||||
}
|
||||
if(y["decimal-places"]) NF += "." + fill("0", +y["decimal-places"]);
|
||||
} break;
|
||||
|
||||
case 'year': { // <number:year> 16.29.14
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch(y["style"]) {
|
||||
case "short": NF += "yy"; break;
|
||||
case "long": NF += "yyyy"; break;
|
||||
default: NF += "yy"; break; // TODO: error condition
|
||||
}
|
||||
} break;
|
||||
|
||||
case 'am-pm': // <number:am-pm> 16.29.23
|
||||
NF += "AM/PM"; // LO autocorrects A/P -> AM/PM
|
||||
break;
|
||||
|
||||
case 'week-of-year': // <number:week-of-year> 16.29.17
|
||||
case 'quarter': // <number:quarter> 16.29.18
|
||||
console.error("Excel does not support ODS format token " + Rn[3]);
|
||||
break;
|
||||
|
||||
case 'fill-character': // <number:fill-character> 16.29.5
|
||||
if(Rn[1]==='/') {
|
||||
payload = str.slice(tidx, xlmlregex.lastIndex - Rn[0].length);
|
||||
// NOTE: Excel has a different interpretation of "%%" and friends
|
||||
NF += '"' + payload.replace(/"/g, '""') + '"*';
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
tidx = xlmlregex.lastIndex;
|
||||
} break;
|
||||
|
||||
case 'scientific-number': // <number:scientific-number> 16.29.6
|
||||
// TODO: find a mapping for all parameters
|
||||
y = parsexmltag(Rn[0], false);
|
||||
NF += "0." + fill("0", +y["min-decimal-places"] || +y["decimal-places"] || 2) + fill("?", +y["decimal-places"] - +y["min-decimal-places"] || 0) + "E" + (parsexmlbool(y["forced-exponent-sign"]) ? "+" : "") + fill("0", +y["min-exponent-digits"] || 2);
|
||||
break;
|
||||
|
||||
case 'fraction': // <number:fraction> 16.29.7
|
||||
// TODO: find a mapping for all parameters
|
||||
y = parsexmltag(Rn[0], false);
|
||||
if(!+y["min-integer-digits"]) NF += "#";
|
||||
else NF += fill("0", +y["min-integer-digits"]);
|
||||
NF += " ";
|
||||
NF += fill("?", +y["min-numerator-digits"] || 1);
|
||||
NF += "/";
|
||||
if(+y["denominator-value"]) NF += y["denominator-value"];
|
||||
else NF += fill("?", +y["min-denominator-digits"] || 1);
|
||||
break;
|
||||
|
||||
case 'currency-symbol': // <number:currency-symbol> 16.29.9
|
||||
// TODO: localization with [$-...]
|
||||
if(Rn[1]==='/') {
|
||||
NF += '"' + str.slice(tidx, xlmlregex.lastIndex - Rn[0].length).replace(/"/g, '""') + '"';
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
tidx = xlmlregex.lastIndex;
|
||||
} else NF += "$";
|
||||
break;
|
||||
|
||||
case 'text-properties': // <style:text-properties> 16.29.29
|
||||
y = parsexmltag(Rn[0], false);
|
||||
switch((y["color"]||"").toLowerCase().replace("#", "")) {
|
||||
case "ff0000": case "red": NF = "[Red]" + NF; break;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'text-content': // <number:text-content> 16.29.28
|
||||
NF += "@";
|
||||
break;
|
||||
|
||||
case 'map': // <style:map> 16.3
|
||||
// TODO: handle more complex maps
|
||||
y = parsexmltag(Rn[0], false);
|
||||
if(unescapexml(y["condition"]) == "value()>=0") NF = number_format_map[y["apply-style-name"]] + ";" + NF;
|
||||
else console.error("ODS number format may be incorrect: " + y["condition"]);
|
||||
break;
|
||||
|
||||
case 'number': // <number:number> 16.29.3
|
||||
// TODO: handle all the attributes
|
||||
if(Rn[1]==='/') break;
|
||||
y = parsexmltag(Rn[0], false);
|
||||
tNF = "";
|
||||
tNF += fill("0", +y["min-integer-digits"] || 1);
|
||||
if(parsexmlbool(y["grouping"])) tNF = commaify(fill("#", Math.max(0, 4 - tNF.length)) + tNF);
|
||||
if(+y["min-decimal-places"] || +y["decimal-places"]) tNF += ".";
|
||||
if(+y["min-decimal-places"]) tNF += fill("0", +y["min-decimal-places"] || 1);
|
||||
if(+y["decimal-places"] - (+y["min-decimal-places"]||0)) tNF += fill("0", +y["decimal-places"] - (+y["min-decimal-places"]||0)); // TODO: should this be "#" ?
|
||||
NF += tNF;
|
||||
break;
|
||||
|
||||
case 'embedded-text': // <number:embedded-text> 16.29.4
|
||||
// TODO: verify interplay with grouping et al
|
||||
if(Rn[1]==='/') {
|
||||
if(etpos == 0) NF += '"' + str.slice(tidx, xlmlregex.lastIndex - Rn[0].length).replace(/"/g, '""') + '"';
|
||||
else NF = NF.slice(0, etpos) + '"' + str.slice(tidx, xlmlregex.lastIndex - Rn[0].length).replace(/"/g, '""') + '"' + NF.slice(etpos);
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
tidx = xlmlregex.lastIndex;
|
||||
etpos = -+parsexmltag(Rn[0], false)["position"] || 0;
|
||||
} break;
|
||||
|
||||
}}
|
||||
return number_format_map;
|
||||
}
|
||||
|
||||
function parse_content_xml(d/*:string*/, _opts, _nfm)/*:Workbook*/ {
|
||||
var opts = _opts || {};
|
||||
if(DENSE != null && opts.dense == null) opts.dense = DENSE;
|
||||
var str = xlml_normalize(d);
|
||||
var state/*:Array<any>*/ = [], tmp;
|
||||
var tag/*:: = {}*/;
|
||||
var NFtag = {name:""}, NF = "", pidx = 0;
|
||||
var nfidx, NF = "", pidx = 0;
|
||||
var sheetag/*:: = {name:"", '名称':""}*/;
|
||||
var rowtag/*:: = {'行号':""}*/;
|
||||
var Sheets = {}, SheetNames/*:Array<string>*/ = [];
|
||||
@ -21766,7 +21985,7 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
var textR = [];
|
||||
var R = -1, C = -1, range = {s: {r:1000000,c:10000000}, e: {r:0, c:0}};
|
||||
var row_ol = 0;
|
||||
var number_format_map = {};
|
||||
var number_format_map = _nfm || {}, styles = {};
|
||||
var merges/*:Array<Range>*/ = [], mrange = {}, mR = 0, mC = 0;
|
||||
var rowinfo/*:Array<RowInfo>*/ = [], rowpeat = 1, colpeat = 1;
|
||||
var arrayf/*:Array<[Range, string]>*/ = [];
|
||||
@ -21777,7 +21996,7 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
var creator = "", creatoridx = 0;
|
||||
var isstub = false, intable = false;
|
||||
var i = 0;
|
||||
var baddate = 1;
|
||||
var baddate = 0;
|
||||
xlmlregex.lastIndex = 0;
|
||||
str = str.replace(/<!--([\s\S]*?)-->/mg,"").replace(/<!DOCTYPE[^\[]*\[[^\]]*\]>/gm,"");
|
||||
while((Rn = xlmlregex.exec(str))) switch((Rn[3]=Rn[3].replace(/_.*$/,""))) {
|
||||
@ -21835,6 +22054,7 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
colpeat = parseInt(ctag['number-columns-repeated']||"1", 10);
|
||||
q = ({t:'z', v:null/*:: , z:null, w:"",c:[]*/}/*:any*/);
|
||||
if(ctag.formula && opts.cellFormula != false) q.f = ods_to_csf_formula(unescapexml(ctag.formula));
|
||||
if(ctag["style-name"] && styles[ctag["style-name"]]) q.z = styles[ctag["style-name"]];
|
||||
if((ctag['数据类型'] || ctag['value-type']) == "string") {
|
||||
q.t = "s"; q.v = unescapexml(ctag['string-value'] || "");
|
||||
if(opts.dense) {
|
||||
@ -21857,6 +22077,7 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
ctag = parsexmltag(Rn[0], false);
|
||||
comments = []; comment = ({}/*:any*/);
|
||||
q = ({t:ctag['数据类型'] || ctag['value-type'], v:null/*:: , z:null, w:"",c:[]*/}/*:any*/);
|
||||
if(ctag["style-name"] && styles[ctag["style-name"]]) q.z = styles[ctag["style-name"]];
|
||||
if(opts.cellFormula) {
|
||||
if(ctag.formula) ctag.formula = unescapexml(ctag.formula);
|
||||
if(ctag['number-matrix-columns-spanned'] && ctag['number-matrix-rows-spanned']) {
|
||||
@ -21884,16 +22105,16 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
|
||||
/* 19.385 office:value-type */
|
||||
switch(q.t) {
|
||||
case 'boolean': q.t = 'b'; q.v = parsexmlbool(ctag['boolean-value']); break;
|
||||
case 'boolean': q.t = 'b'; q.v = parsexmlbool(ctag['boolean-value']) || (+ctag['boolean-value'] >= 1); break;
|
||||
case 'float': q.t = 'n'; q.v = parseFloat(ctag.value); break;
|
||||
case 'percentage': q.t = 'n'; q.v = parseFloat(ctag.value); break;
|
||||
case 'currency': q.t = 'n'; q.v = parseFloat(ctag.value); break;
|
||||
case 'date': q.t = 'd'; q.v = parseDate(ctag['date-value']);
|
||||
if(!opts.cellDates) { q.t = 'n'; q.v = datenum(q.v, WB.WBProps.date1904) - baddate; }
|
||||
q.z = 'm/d/yy'; break;
|
||||
if(!q.z) q.z = 'm/d/yy'; break;
|
||||
case 'time': q.t = 'n'; q.v = parse_isodur(ctag['time-value'])/86400;
|
||||
if(opts.cellDates) { q.t = 'd'; q.v = numdate(q.v); }
|
||||
q.z = 'HH:MM:SS'; break;
|
||||
if(!q.z) q.z = 'HH:MM:SS'; break;
|
||||
case 'number': q.t = 'n'; q.v = parseFloat(ctag['数据数值']); break;
|
||||
default:
|
||||
if(q.t === 'string' || q.t === 'text' || !q.t) {
|
||||
@ -21988,23 +22209,24 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
textp = ""; textpidx = 0; textR = [];
|
||||
break;
|
||||
|
||||
case 'scientific-number': // TODO: <number:scientific-number>
|
||||
break;
|
||||
case 'currency-symbol': // TODO: <number:currency-symbol>
|
||||
break;
|
||||
case 'currency-style': // TODO: <number:currency-style>
|
||||
case 'scientific-number': // <number:scientific-number>
|
||||
case 'currency-symbol': // <number:currency-symbol>
|
||||
case 'fill-character': // 16.29.5 <number:fill-character>
|
||||
break;
|
||||
|
||||
case 'text-style': // 16.27.25 <number:text-style>
|
||||
case 'boolean-style': // 16.27.23 <number:boolean-style>
|
||||
case 'number-style': // 16.27.2 <number:number-style>
|
||||
case 'currency-style': // 16.29.8 <number:currency-style>
|
||||
case 'percentage-style': // 16.27.9 <number:percentage-style>
|
||||
case 'date-style': // 16.27.10 <number:date-style>
|
||||
case 'time-style': // 16.27.18 <number:time-style>
|
||||
if(Rn[1]==='/'){
|
||||
number_format_map[NFtag.name] = NF;
|
||||
if((tmp=state.pop())[0]!==Rn[3]) throw "Bad state: "+tmp;
|
||||
var xlmlidx = xlmlregex.lastIndex;
|
||||
parse_ods_styles(str.slice(nfidx, xlmlregex.lastIndex), _opts, number_format_map);
|
||||
xlmlregex.lastIndex = xlmlidx;
|
||||
} else if(Rn[0].charAt(Rn[0].length-2) !== '/') {
|
||||
NF = "";
|
||||
NFtag = parsexmltag(Rn[0], false);
|
||||
state.push([Rn[3], true]);
|
||||
nfidx = xlmlregex.lastIndex - Rn[0].length;
|
||||
} break;
|
||||
|
||||
case 'script': break; // 3.13 <office:script>
|
||||
@ -22013,8 +22235,10 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
|
||||
case 'default-style': // TODO: <style:default-style>
|
||||
case 'page-layout': break; // TODO: <style:page-layout>
|
||||
case 'style': // 16.2 <style:style>
|
||||
break;
|
||||
case 'style': { // 16.2 <style:style>
|
||||
var styletag = parsexmltag(Rn[0], false);
|
||||
if(styletag["family"] == "table-cell" && number_format_map[styletag["data-style-name"]]) styles[styletag["name"]] = number_format_map[styletag["data-style-name"]];
|
||||
} break;
|
||||
case 'map': break; // 16.3 <style:map>
|
||||
case 'font-face': break; // 16.21 <style:font-face>
|
||||
|
||||
@ -22025,12 +22249,7 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
case 'table-cell-properties': break; // 17.18 <style:table-cell-properties>
|
||||
|
||||
case 'number': // 16.27.3 <number:number>
|
||||
switch(state[state.length-1][0]) {
|
||||
case 'time-style':
|
||||
case 'date-style':
|
||||
tag = parsexmltag(Rn[0], false);
|
||||
NF += number_formats_ods[Rn[3]][tag.style==='long'?1:0]; break;
|
||||
} break;
|
||||
break;
|
||||
|
||||
case 'fraction': break; // TODO 16.27.6 <number:fraction>
|
||||
|
||||
@ -22045,16 +22264,9 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
case 'minutes': // 16.27.20 <number:minutes>
|
||||
case 'seconds': // 16.27.21 <number:seconds>
|
||||
case 'am-pm': // 16.27.22 <number:am-pm>
|
||||
switch(state[state.length-1][0]) {
|
||||
case 'time-style':
|
||||
case 'date-style':
|
||||
tag = parsexmltag(Rn[0], false);
|
||||
NF += number_formats_ods[Rn[3]][tag.style==='long'?1:0]; break;
|
||||
} break;
|
||||
break;
|
||||
|
||||
case 'boolean-style': break; // 16.27.23 <number:boolean-style>
|
||||
case 'boolean': break; // 16.27.24 <number:boolean>
|
||||
case 'text-style': break; // 16.27.25 <number:text-style>
|
||||
case 'text': // 16.27.26 <number:text>
|
||||
if(Rn[0].slice(-2) === "/>") break;
|
||||
else if(Rn[1]==="/") switch(state[state.length-1][0]) {
|
||||
@ -22285,9 +22497,11 @@ function parse_content_xml(d/*:string*/, _opts)/*:Workbook*/ {
|
||||
function parse_ods(zip/*:ZIPFile*/, opts/*:?ParseOpts*/)/*:Workbook*/ {
|
||||
opts = opts || ({}/*:any*/);
|
||||
if(safegetzipfile(zip, 'META-INF/manifest.xml')) parse_manifest(getzipdata(zip, 'META-INF/manifest.xml'), opts);
|
||||
var styles = getzipstr(zip, 'styles.xml');
|
||||
var Styles = styles && parse_ods_styles(utf8read(styles), opts);
|
||||
var content = getzipstr(zip, 'content.xml');
|
||||
if(!content) throw new Error("Missing content.xml in ODS / UOF file");
|
||||
var wb = parse_content_xml(utf8read(content), opts);
|
||||
var wb = parse_content_xml(utf8read(content), opts, Styles);
|
||||
if(safegetzipfile(zip, 'meta.xml')) wb.Props = parse_core_props(getzipdata(zip, 'meta.xml'));
|
||||
return wb;
|
||||
}
|
||||
@ -22327,6 +22541,157 @@ var write_styles_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function(
|
||||
return XML_HEADER + payload;
|
||||
};
|
||||
})();
|
||||
|
||||
// TODO: find out if anyone actually read the spec. LO has some wild errors
|
||||
function write_number_format_ods(nf/*:string*/, nfidx/*:string*/)/*:string*/ {
|
||||
var type = "number", payload = "", nopts = { "style:name": nfidx }, c = "", i = 0;
|
||||
nf = nf.replace(/"[$]"/g, "$");
|
||||
/* TODO: replace with an actual parser based on a real grammar */
|
||||
j: {
|
||||
// TODO: support style maps
|
||||
if(nf.indexOf(";") > -1) {
|
||||
console.error("Unsupported ODS Style Map exported. Using first branch of " + nf);
|
||||
nf = nf.slice(0, nf.indexOf(";"));
|
||||
}
|
||||
|
||||
if(nf == "@") { type = "text"; payload = "<number:text-content/>"; break j; }
|
||||
|
||||
/* currency flag */
|
||||
if(nf.indexOf(/\$/) > -1) { type = "currency"; }
|
||||
|
||||
/* opening string literal */
|
||||
if(nf[i] == '"') {
|
||||
c = "";
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
if(nf[i+1] == "*") {
|
||||
i++;
|
||||
payload += '<number:fill-character>' + escapexml(c.replace(/""/g, '"')) + '</number:fill-character>';
|
||||
} else {
|
||||
payload += '<number:text>' + escapexml(c.replace(/""/g, '"')) + '</number:text>';
|
||||
}
|
||||
nf = nf.slice(i+1); i = 0;
|
||||
}
|
||||
|
||||
/* fractions */
|
||||
var t = nf.match(/# (\?+)\/(\?+)/);
|
||||
if(t) { payload += writextag("number:fraction", null, {"number:min-integer-digits":0, "number:min-numerator-digits": t[1].length, "number:max-denominator-value": Math.max(+(t[1].replace(/./g, "9")), +(t[2].replace(/./g, "9"))) }); break j; }
|
||||
if((t=nf.match(/# (\?+)\/(\d+)/))) { payload += writextag("number:fraction", null, {"number:min-integer-digits":0, "number:min-numerator-digits": t[1].length, "number:denominator-value": +t[2]}); break j; }
|
||||
|
||||
/* percentages */
|
||||
if((t=nf.match(/(\d+)(|\.\d+)%/))) { type = "percentage"; payload += writextag("number:number", null, {"number:decimal-places": t[2] && t.length - 1 || 0, "number:min-decimal-places": t[2] && t.length - 1 || 0, "number:min-integer-digits": t[1].length }) + "<number:text>%</number:text>"; break j; }
|
||||
|
||||
/* datetime */
|
||||
var has_time = false;
|
||||
if(["y","m","d"].indexOf(nf[0]) > -1) {
|
||||
type = "date";
|
||||
k: for(; i < nf.length; ++i) switch((c = nf[i].toLowerCase())) {
|
||||
case "h": case "s": has_time = true; --i; break k;
|
||||
case "m":
|
||||
l: for(var h = i+1; h < nf.length; ++h) switch(nf[h]) {
|
||||
case "y": case "d": break l;
|
||||
case "h": case "s": has_time = true; --i; break k;
|
||||
}
|
||||
/* falls through */
|
||||
case "y": case "d":
|
||||
while((nf[++i]||"").toLowerCase() == c[0]) c += c[0]; --i;
|
||||
switch(c) {
|
||||
case "y": case "yy": payload += "<number:year/>"; break;
|
||||
case "yyy": case "yyyy": payload += '<number:year number:style="long"/>'; break;
|
||||
case "mmmmm": console.error("ODS has no equivalent of format |mmmmm|");
|
||||
/* falls through */
|
||||
case "m": case "mm": case "mmm": case "mmmm":
|
||||
payload += '<number:month number:style="' + (c.length % 2 ? "short" : "long") + '" number:textual="' + (c.length >= 3 ? "true" : "false") + '"/>';
|
||||
break;
|
||||
case "d": case "dd": payload += '<number:day number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
|
||||
case "ddd": case "dddd": payload += '<number:day-of-week number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
|
||||
}
|
||||
break;
|
||||
case '"':
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
payload += '<number:text>' + escapexml(c.slice(1).replace(/""/g, '"')) + '</number:text>';
|
||||
break;
|
||||
case '/': payload += '<number:text>' + escapexml(c) + '</number:text>'; break;
|
||||
default: console.error("unrecognized character " + c + " in ODF format " + nf);
|
||||
}
|
||||
if(!has_time) break j;
|
||||
nf = nf.slice(i+1); i = 0;
|
||||
}
|
||||
if(nf.match(/^\[?[hms]/)) {
|
||||
if(type == "number") type = "time";
|
||||
if(nf.match(/\[/)) {
|
||||
nf = nf.replace(/[\[\]]/g, "");
|
||||
nopts['number:truncate-on-overflow'] = "false";
|
||||
}
|
||||
for(; i < nf.length; ++i) switch((c = nf[i].toLowerCase())) {
|
||||
case "h": case "m": case "s":
|
||||
while((nf[++i]||"").toLowerCase() == c[0]) c += c[0]; --i;
|
||||
switch(c) {
|
||||
case "h": case "hh": payload += '<number:hours number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
|
||||
case "m": case "mm": payload += '<number:minutes number:style="' + (c.length % 2 ? "short" : "long") + '"/>'; break;
|
||||
case "s": case "ss":
|
||||
if(nf[i+1] == ".") do { c += nf[i+1]; ++i; } while(nf[i+1] == "0");
|
||||
payload += '<number:seconds number:style="' + (c.match("ss") ? "long" : "short") + '"' + (c.match(/\./) ? ' number:decimal-places="' + (c.match(/0+/)||[""])[0].length + '"' : "")+ '/>'; break;
|
||||
}
|
||||
break;
|
||||
case '"':
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
payload += '<number:text>' + escapexml(c.slice(1).replace(/""/g, '"')) + '</number:text>';
|
||||
break;
|
||||
case '/': payload += '<number:text>' + escapexml(c) + '</number:text>'; break;
|
||||
case "a":
|
||||
if(nf.slice(i, i+3).toLowerCase() == "a/p") { payload += '<number:am-pm/>'; i += 2; break; } // Note: ODF does not support A/P
|
||||
if(nf.slice(i, i+5).toLowerCase() == "am/pm") { payload += '<number:am-pm/>'; i += 4; break; }
|
||||
/* falls through */
|
||||
default: console.error("unrecognized character " + c + " in ODF format " + nf);
|
||||
}
|
||||
break j;
|
||||
}
|
||||
|
||||
/* currency flag */
|
||||
if(nf.indexOf(/\$/) > -1) { type = "currency"; }
|
||||
|
||||
/* should be in a char loop */
|
||||
if(nf[0] == "$") { payload += '<number:currency-symbol number:language="en" number:country="US">$</number:currency-symbol>'; nf = nf.slice(1); i = 0; }
|
||||
i = 0; if(nf[i] == '"') {
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
if(nf[i+1] == "*") {
|
||||
i++;
|
||||
payload += '<number:fill-character>' + escapexml(c.replace(/""/g, '"')) + '</number:fill-character>';
|
||||
} else {
|
||||
payload += '<number:text>' + escapexml(c.replace(/""/g, '"')) + '</number:text>';
|
||||
}
|
||||
nf = nf.slice(i+1); i = 0;
|
||||
}
|
||||
|
||||
/* number TODO: interstitial text e.g. 000)000-0000 */
|
||||
var np = nf.match(/([#0][0#,]*)(\.[0#]*|)(E[+]?0*|)/i);
|
||||
if(!np || !np[0]) console.error("Could not find numeric part of " + nf);
|
||||
else {
|
||||
var base = np[1].replace(/,/g, "");
|
||||
payload += '<number:' + (np[3] ? "scientific-" : "")+ 'number' +
|
||||
' number:min-integer-digits="' + (base.indexOf("0") == -1 ? "0" : base.length - base.indexOf("0")) + '"' +
|
||||
(np[0].indexOf(",") > -1 ? ' number:grouping="true"' : "") +
|
||||
(np[2] && ' number:decimal-places="' + (np[2].length - 1) + '"' || ' number:decimal-places="0"') +
|
||||
(np[3] && np[3].indexOf("+") > -1 ? ' number:forced-exponent-sign="true"' : "" ) +
|
||||
(np[3] ? ' number:min-exponent-digits="' + np[3].match(/0+/)[0].length + '"' : "" ) +
|
||||
'>' +
|
||||
/* TODO: interstitial text placeholders */
|
||||
'</number:' + (np[3] ? "scientific-" : "") + 'number>';
|
||||
i = np.index + np[0].length;
|
||||
}
|
||||
|
||||
/* residual text */
|
||||
if(nf[i] == '"') {
|
||||
c = "";
|
||||
while(nf[++i] != '"' || nf[++i] == '"') c += nf[i]; --i;
|
||||
payload += '<number:text>' + escapexml(c.replace(/""/g, '"')) + '</number:text>';
|
||||
}
|
||||
}
|
||||
|
||||
if(!payload) { console.error("Could not generate ODS number format for |" + nf + "|"); return ""; }
|
||||
return writextag("number:" + type + "-style", payload, nopts);
|
||||
}
|
||||
|
||||
function write_names_ods(Names, SheetNames, idx) {
|
||||
var scoped = Names.filter(function(name) { return name.Sheet == (idx == -1 ? null : idx); });
|
||||
if(!scoped.length) return "";
|
||||
@ -22351,7 +22716,7 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function
|
||||
|
||||
var null_cell_xml = ' <table:table-cell />\n';
|
||||
var covered_cell_xml = ' <table:covered-table-cell/>\n';
|
||||
var write_ws = function(ws, wb/*:Workbook*/, i/*:number*//*::, opts*/)/*:string*/ {
|
||||
var write_ws = function(ws, wb/*:Workbook*/, i/*:number*/, opts, nfs)/*:string*/ {
|
||||
/* Section 9 Tables */
|
||||
var o/*:Array<string>*/ = [];
|
||||
o.push(' <table:table table:name="' + escapexml(wb.SheetNames[i]) + '" table:style-name="ta1">\n');
|
||||
@ -22427,6 +22792,7 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function
|
||||
if(_tgt.charAt(0) != "#" && !_tgt.match(/^\w+:/)) _tgt = '../' + _tgt;
|
||||
text_p = writextag('text:a', text_p, {'xlink:href': _tgt.replace(/&/g, "&")});
|
||||
}
|
||||
if(nfs[cell.z]) ct["table:style-name"] = "ce" + nfs[cell.z].slice(1);
|
||||
o.push(' ' + writextag('table:table-cell', writextag('text:p', text_p, {}), ct) + '\n');
|
||||
}
|
||||
o.push(' </table:table-row>\n');
|
||||
@ -22439,14 +22805,6 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function
|
||||
var write_automatic_styles_ods = function(o/*:Array<string>*/, wb) {
|
||||
o.push(' <office:automatic-styles>\n');
|
||||
|
||||
o.push(' <number:date-style style:name="N37" number:automatic-order="true">\n');
|
||||
o.push(' <number:month number:style="long"/>\n');
|
||||
o.push(' <number:text>/</number:text>\n');
|
||||
o.push(' <number:day number:style="long"/>\n');
|
||||
o.push(' <number:text>/</number:text>\n');
|
||||
o.push(' <number:year/>\n');
|
||||
o.push(' </number:date-style>\n');
|
||||
|
||||
/* column styles */
|
||||
var cidx = 0;
|
||||
wb.SheetNames.map(function(n) { return wb.Sheets[n]; }).forEach(function(ws) {
|
||||
@ -22487,12 +22845,38 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function
|
||||
o.push(' <style:table-properties table:display="true" style:writing-mode="lr-tb"/>\n');
|
||||
o.push(' </style:style>\n');
|
||||
|
||||
/* table cells, text */
|
||||
o.push(' <number:date-style style:name="N37" number:automatic-order="true">\n');
|
||||
o.push(' <number:month number:style="long"/>\n');
|
||||
o.push(' <number:text>/</number:text>\n');
|
||||
o.push(' <number:day number:style="long"/>\n');
|
||||
o.push(' <number:text>/</number:text>\n');
|
||||
o.push(' <number:year/>\n');
|
||||
o.push(' </number:date-style>\n');
|
||||
|
||||
/* number formats, table cells, text */
|
||||
var nfs = {};
|
||||
var nfi = 69;
|
||||
wb.SheetNames.map(function(n) { return wb.Sheets[n]; }).forEach(function(ws) {
|
||||
if(!ws) return;
|
||||
var range = decode_range(ws["!ref"]);
|
||||
for(var R = 0; R <= range.e.r; ++R) for(var C = 0; C <= range.e.c; ++C) {
|
||||
var c = Array.isArray(ws) ? (ws[R]||[])[C] : ws[encode_cell({r:R,c:C})];
|
||||
if(!c || !c.z || c.z.toLowerCase() == "general") continue;
|
||||
if(!nfs[c.z]) {
|
||||
var out = write_number_format_ods(c.z, "N" + nfi);
|
||||
if(out) { nfs[c.z] = "N" + nfi; ++nfi; o.push(out + "\n"); }
|
||||
}
|
||||
}
|
||||
});
|
||||
o.push(' <style:style style:name="ce1" style:family="table-cell" style:parent-style-name="Default" style:data-style-name="N37"/>\n');
|
||||
keys(nfs).forEach(function(nf) {
|
||||
o.push('<style:style style:name="ce' + nfs[nf].slice(1) + '" style:family="table-cell" style:parent-style-name="Default" style:data-style-name="' + nfs[nf] + '"/>\n');
|
||||
});
|
||||
|
||||
/* page-layout */
|
||||
|
||||
o.push(' </office:automatic-styles>\n');
|
||||
return nfs;
|
||||
};
|
||||
|
||||
return function wcx(wb, opts) {
|
||||
@ -22545,15 +22929,15 @@ var write_content_ods/*:{(wb:any, opts:any):string}*/ = /* @__PURE__ */(function
|
||||
|
||||
if(opts.bookType == "fods") {
|
||||
o.push('<office:document' + attr + fods + '>\n');
|
||||
o.push(write_meta_ods().replace(/office:document-meta/g, "office:meta"));
|
||||
o.push(write_meta_ods().replace(/<office:document-meta.*?>/, "").replace(/<\/office:document-meta>/, "") + "\n");
|
||||
// TODO: settings (equiv of settings.xml for ODS)
|
||||
} else o.push('<office:document-content' + attr + '>\n');
|
||||
// o.push(' <office:scripts/>\n');
|
||||
write_automatic_styles_ods(o, wb);
|
||||
var nfs = write_automatic_styles_ods(o, wb);
|
||||
o.push(' <office:body>\n');
|
||||
o.push(' <office:spreadsheet>\n');
|
||||
if(((wb.Workbook||{}).WBProps||{}).date1904) o.push(' <table:calculation-settings table:case-sensitive="false" table:search-criteria-must-apply-to-whole-cell="true" table:use-wildcards="true" table:use-regular-expressions="false" table:automatic-find-labels="false">\n <table:null-date table:date-value="1904-01-01"/>\n </table:calculation-settings>\n');
|
||||
for(var i = 0; i != wb.SheetNames.length; ++i) o.push(write_ws(wb.Sheets[wb.SheetNames[i]], wb, i, opts));
|
||||
for(var i = 0; i != wb.SheetNames.length; ++i) o.push(write_ws(wb.Sheets[wb.SheetNames[i]], wb, i, opts, nfs));
|
||||
if((wb.Workbook||{}).Names) o.push(write_names_ods(wb.Workbook.Names, wb.SheetNames, -1));
|
||||
o.push(' </office:spreadsheet>\n');
|
||||
o.push(' </office:body>\n');
|
||||
|
Loading…
Reference in New Issue
Block a user