@particle-academy/holy-sheet 2.10.2 → 2.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/README.md +27 -0
- package/dist/chunk-NQRZFV32.js +6839 -0
- package/dist/chunk-NQRZFV32.js.map +1 -0
- package/dist/cli.cjs +6920 -0
- package/dist/cli.cjs.map +1 -0
- package/dist/cli.d.cts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +82 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.cjs +565 -177
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +55 -4
- package/dist/index.d.ts +55 -4
- package/dist/index.js +9 -6455
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
package/dist/index.cjs
CHANGED
|
@@ -521,9 +521,10 @@ var ChartRange = class _ChartRange {
|
|
|
521
521
|
|
|
522
522
|
// src/workbook/workbook.ts
|
|
523
523
|
var Workbook = class {
|
|
524
|
-
constructor(sheets, meta = {}) {
|
|
524
|
+
constructor(sheets, meta = {}, sourceKind2 = "document") {
|
|
525
525
|
this.sheets = sheets;
|
|
526
526
|
this.meta = meta;
|
|
527
|
+
this.sourceKind = sourceKind2;
|
|
527
528
|
}
|
|
528
529
|
};
|
|
529
530
|
|
|
@@ -568,6 +569,23 @@ var ArchiveRefused = class _ArchiveRefused extends Error {
|
|
|
568
569
|
Object.setPrototypeOf(this, _ArchiveRefused.prototype);
|
|
569
570
|
}
|
|
570
571
|
};
|
|
572
|
+
var TEMPLATE_MESSAGES = {
|
|
573
|
+
macro_enabled: "Macro-enabled templates (.xltm) are refused: re-save it as an .xltx in Excel.",
|
|
574
|
+
family_mismatch: "This package declares a template of a different kind than its contents.",
|
|
575
|
+
already_template: "This package is already a template.",
|
|
576
|
+
already_document: "This package is already a document.",
|
|
577
|
+
not_convertible: "This package has no single content-type declaration that can be rewritten.",
|
|
578
|
+
odf_mimetype: "An OpenDocument package must store its mimetype entry first and uncompressed.",
|
|
579
|
+
bad_target: 'The conversion target must be "document" or "template".'
|
|
580
|
+
};
|
|
581
|
+
var OfficeTemplateRefused = class _OfficeTemplateRefused extends Error {
|
|
582
|
+
constructor(reason) {
|
|
583
|
+
super(`Template refused (${reason}): ${TEMPLATE_MESSAGES[reason] ?? reason}`);
|
|
584
|
+
this.reason = reason;
|
|
585
|
+
this.name = "OfficeTemplateRefused";
|
|
586
|
+
Object.setPrototypeOf(this, _OfficeTemplateRefused.prototype);
|
|
587
|
+
}
|
|
588
|
+
};
|
|
571
589
|
var SchemaException = class _SchemaException extends Error {
|
|
572
590
|
constructor(errors, message) {
|
|
573
591
|
super(message ?? summarize(errors));
|
|
@@ -769,7 +787,7 @@ var Normalizer = class {
|
|
|
769
787
|
address,
|
|
770
788
|
new Cell(
|
|
771
789
|
address,
|
|
772
|
-
this.coerceValue(rawValue, format, address, sheetName2),
|
|
790
|
+
this.coerceValue(rawValue, format, address, sheetName2, true),
|
|
773
791
|
cellData?.formula ?? null,
|
|
774
792
|
format,
|
|
775
793
|
comment,
|
|
@@ -862,7 +880,7 @@ var Normalizer = class {
|
|
|
862
880
|
const rawValue = value.value ?? null;
|
|
863
881
|
return new Cell(
|
|
864
882
|
address,
|
|
865
|
-
this.coerceValue(rawValue, merged, address, sheetName2),
|
|
883
|
+
this.coerceValue(rawValue, merged, address, sheetName2, true),
|
|
866
884
|
value.formula ?? null,
|
|
867
885
|
merged,
|
|
868
886
|
comment,
|
|
@@ -877,7 +895,15 @@ var Normalizer = class {
|
|
|
877
895
|
columnFormat
|
|
878
896
|
);
|
|
879
897
|
}
|
|
880
|
-
|
|
898
|
+
/**
|
|
899
|
+
* `explicit`: the value came from an object cell's `value` key. An explicit
|
|
900
|
+
* string is the caller saying "this text, exactly", so it is never coerced
|
|
901
|
+
* to a number: `{value: "+1"}` stays "+1" and `{value: "007"}` stays "007".
|
|
902
|
+
* Only a date/datetime display format still turns a string into a serial,
|
|
903
|
+
* because that conversion is what the format asks for. Bare values keep
|
|
904
|
+
* numeric-string coercion. Mirrors PHP `Normalizer::coerceValue`.
|
|
905
|
+
*/
|
|
906
|
+
coerceValue(value, format, address = "?", sheetName2, explicit = false) {
|
|
881
907
|
if (value === null || value === void 0) return null;
|
|
882
908
|
const df = format?.displayFormat;
|
|
883
909
|
if (df === "date" || df === "datetime") {
|
|
@@ -885,6 +911,9 @@ var Normalizer = class {
|
|
|
885
911
|
return DateConverter.toSerial(value, df === "datetime");
|
|
886
912
|
}
|
|
887
913
|
}
|
|
914
|
+
if (explicit && typeof value === "string") {
|
|
915
|
+
return value;
|
|
916
|
+
}
|
|
888
917
|
if (typeof value === "string" && isNumericString(value)) {
|
|
889
918
|
return numericStringToNumber(value);
|
|
890
919
|
}
|
|
@@ -2624,9 +2653,11 @@ var corrupt = () => {
|
|
|
2624
2653
|
throw new ArchiveRefused("archive_corrupt");
|
|
2625
2654
|
};
|
|
2626
2655
|
var SourceArchive = class _SourceArchive {
|
|
2627
|
-
constructor(original, parts) {
|
|
2656
|
+
constructor(original, parts, records = [], archiveComment = new Uint8Array()) {
|
|
2628
2657
|
this.original = original;
|
|
2629
2658
|
this.parts = parts;
|
|
2659
|
+
this.records = records;
|
|
2660
|
+
this.archiveComment = archiveComment;
|
|
2630
2661
|
}
|
|
2631
2662
|
static open(input, options = {}) {
|
|
2632
2663
|
for (const [key, value] of Object.entries(options)) {
|
|
@@ -2635,7 +2666,7 @@ var SourceArchive = class _SourceArchive {
|
|
|
2635
2666
|
const limits = { ...DEFAULT_ARCHIVE_LIMITS, ...options };
|
|
2636
2667
|
if (input.length > limits.maxArchiveBytes) throw new ArchiveRefused("archive_limit");
|
|
2637
2668
|
const bytes = input.slice();
|
|
2638
|
-
const records = index(bytes, limits);
|
|
2669
|
+
const [records, comment] = index(bytes, limits);
|
|
2639
2670
|
const parts = /* @__PURE__ */ new Map();
|
|
2640
2671
|
for (const record of records) {
|
|
2641
2672
|
const compressed = bytes.subarray(record.start, record.start + record.compressed);
|
|
@@ -2648,7 +2679,8 @@ var SourceArchive = class _SourceArchive {
|
|
|
2648
2679
|
if (payload.length !== record.size || crc32(payload) !== record.crc) corrupt();
|
|
2649
2680
|
parts.set(record.name, payload);
|
|
2650
2681
|
}
|
|
2651
|
-
|
|
2682
|
+
const entries = records.map(({ name, size, crc, local, finish, central, centralLength, flags, method, localExtra }) => ({ name, size, crc, local, finish, central, centralLength, flags, method, localExtra }));
|
|
2683
|
+
return new _SourceArchive(bytes, parts, entries, comment);
|
|
2652
2684
|
}
|
|
2653
2685
|
names() {
|
|
2654
2686
|
return [...this.parts.keys()];
|
|
@@ -2659,6 +2691,14 @@ var SourceArchive = class _SourceArchive {
|
|
|
2659
2691
|
toBytes() {
|
|
2660
2692
|
return this.original.slice();
|
|
2661
2693
|
}
|
|
2694
|
+
/** Verified member records in central-directory order. */
|
|
2695
|
+
entries() {
|
|
2696
|
+
return this.records.map((entry) => ({ ...entry }));
|
|
2697
|
+
}
|
|
2698
|
+
/** The archive comment, verbatim. */
|
|
2699
|
+
comment() {
|
|
2700
|
+
return this.archiveComment.slice();
|
|
2701
|
+
}
|
|
2662
2702
|
};
|
|
2663
2703
|
function index(bytes, limits) {
|
|
2664
2704
|
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
@@ -2737,7 +2777,20 @@ function index(bytes, limits) {
|
|
|
2737
2777
|
}
|
|
2738
2778
|
if (!found) corrupt();
|
|
2739
2779
|
}
|
|
2740
|
-
records.push({
|
|
2780
|
+
records.push({
|
|
2781
|
+
name,
|
|
2782
|
+
method,
|
|
2783
|
+
crc,
|
|
2784
|
+
size,
|
|
2785
|
+
compressed,
|
|
2786
|
+
start,
|
|
2787
|
+
local,
|
|
2788
|
+
finish,
|
|
2789
|
+
central: at2,
|
|
2790
|
+
centralLength: 46 + nameLength + extraLength + commentLength,
|
|
2791
|
+
flags,
|
|
2792
|
+
localExtra
|
|
2793
|
+
});
|
|
2741
2794
|
regions.push([local, finish]);
|
|
2742
2795
|
at2 += 46 + nameLength + extraLength + commentLength;
|
|
2743
2796
|
}
|
|
@@ -2748,7 +2801,7 @@ function index(bytes, limits) {
|
|
|
2748
2801
|
if (start < previous) corrupt();
|
|
2749
2802
|
previous = finish;
|
|
2750
2803
|
}
|
|
2751
|
-
return records;
|
|
2804
|
+
return [records, bytes.slice(end + 22, end + 22 + u16(end + 20))];
|
|
2752
2805
|
}
|
|
2753
2806
|
function partName(bytes, flags) {
|
|
2754
2807
|
let name;
|
|
@@ -2837,6 +2890,472 @@ var FormatSniffer = {
|
|
|
2837
2890
|
}
|
|
2838
2891
|
};
|
|
2839
2892
|
|
|
2893
|
+
// src/reader/xml.ts
|
|
2894
|
+
var XML_NS = "http://www.w3.org/XML/1998/namespace";
|
|
2895
|
+
function localName(name) {
|
|
2896
|
+
const idx = name.indexOf(":");
|
|
2897
|
+
return idx >= 0 ? name.slice(idx + 1) : name;
|
|
2898
|
+
}
|
|
2899
|
+
function unescapeXml(s) {
|
|
2900
|
+
if (s.indexOf("&") < 0) return s;
|
|
2901
|
+
return s.replace(/&(#x?[0-9a-fA-F]+|\w+);/g, (m, ent) => {
|
|
2902
|
+
switch (ent) {
|
|
2903
|
+
case "amp":
|
|
2904
|
+
return "&";
|
|
2905
|
+
case "lt":
|
|
2906
|
+
return "<";
|
|
2907
|
+
case "gt":
|
|
2908
|
+
return ">";
|
|
2909
|
+
case "quot":
|
|
2910
|
+
return '"';
|
|
2911
|
+
case "apos":
|
|
2912
|
+
return "'";
|
|
2913
|
+
default:
|
|
2914
|
+
if (ent[0] === "#") {
|
|
2915
|
+
const code = ent[1] === "x" || ent[1] === "X" ? parseInt(ent.slice(2), 16) : parseInt(ent.slice(1), 10);
|
|
2916
|
+
return Number.isFinite(code) ? String.fromCodePoint(code) : m;
|
|
2917
|
+
}
|
|
2918
|
+
return m;
|
|
2919
|
+
}
|
|
2920
|
+
});
|
|
2921
|
+
}
|
|
2922
|
+
function findTagEnd(src, from) {
|
|
2923
|
+
let quote2 = "";
|
|
2924
|
+
for (let i = from; i < src.length; i++) {
|
|
2925
|
+
const ch = src[i];
|
|
2926
|
+
if (quote2) {
|
|
2927
|
+
if (ch === quote2) quote2 = "";
|
|
2928
|
+
} else if (ch === '"' || ch === "'") {
|
|
2929
|
+
quote2 = ch;
|
|
2930
|
+
} else if (ch === ">") {
|
|
2931
|
+
return i;
|
|
2932
|
+
}
|
|
2933
|
+
}
|
|
2934
|
+
return src.length;
|
|
2935
|
+
}
|
|
2936
|
+
var ATTR_RE = /([^\s=/]+)\s*=\s*("([^"]*)"|'([^']*)')/g;
|
|
2937
|
+
function parseTag(inner) {
|
|
2938
|
+
const trimmed = inner.trim();
|
|
2939
|
+
let i = 0;
|
|
2940
|
+
while (i < trimmed.length && !/\s/.test(trimmed[i])) i++;
|
|
2941
|
+
const name = trimmed.slice(0, i);
|
|
2942
|
+
const attrs = {};
|
|
2943
|
+
const raw = [];
|
|
2944
|
+
const rest = trimmed.slice(i);
|
|
2945
|
+
for (const m of rest.matchAll(ATTR_RE)) {
|
|
2946
|
+
const rawValue = m[3] ?? m[4] ?? "";
|
|
2947
|
+
attrs[localName(m[1])] = unescapeXml(rawValue);
|
|
2948
|
+
raw.push([m[1], rawValue]);
|
|
2949
|
+
}
|
|
2950
|
+
return { name, attrs, raw };
|
|
2951
|
+
}
|
|
2952
|
+
function parseXml(src, options = {}) {
|
|
2953
|
+
const namespaces = options.namespaces === true;
|
|
2954
|
+
let i = 0;
|
|
2955
|
+
const n = src.length;
|
|
2956
|
+
const root = { name: "#root", attrs: {}, children: [], text: "" };
|
|
2957
|
+
if (namespaces) root.content = [];
|
|
2958
|
+
const stack = [root];
|
|
2959
|
+
const scopes = [{ xml: XML_NS }];
|
|
2960
|
+
const appendText = (text2) => {
|
|
2961
|
+
const top = stack[stack.length - 1];
|
|
2962
|
+
top.text += text2;
|
|
2963
|
+
if (namespaces && text2 !== "") top.content.push(text2);
|
|
2964
|
+
};
|
|
2965
|
+
while (i < n) {
|
|
2966
|
+
if (src[i] === "<") {
|
|
2967
|
+
if (src.startsWith("<!--", i)) {
|
|
2968
|
+
const end2 = src.indexOf("-->", i);
|
|
2969
|
+
i = end2 < 0 ? n : end2 + 3;
|
|
2970
|
+
continue;
|
|
2971
|
+
}
|
|
2972
|
+
if (src.startsWith("<![CDATA[", i)) {
|
|
2973
|
+
const end2 = src.indexOf("]]>", i);
|
|
2974
|
+
appendText(src.slice(i + 9, end2 < 0 ? n : end2));
|
|
2975
|
+
i = end2 < 0 ? n : end2 + 3;
|
|
2976
|
+
continue;
|
|
2977
|
+
}
|
|
2978
|
+
if (src.startsWith("<?", i)) {
|
|
2979
|
+
const end2 = src.indexOf("?>", i);
|
|
2980
|
+
i = end2 < 0 ? n : end2 + 2;
|
|
2981
|
+
continue;
|
|
2982
|
+
}
|
|
2983
|
+
if (src.startsWith("<!", i)) {
|
|
2984
|
+
const end2 = src.indexOf(">", i);
|
|
2985
|
+
i = end2 < 0 ? n : end2 + 1;
|
|
2986
|
+
continue;
|
|
2987
|
+
}
|
|
2988
|
+
if (src[i + 1] === "/") {
|
|
2989
|
+
const end2 = src.indexOf(">", i);
|
|
2990
|
+
if (stack.length > 1) {
|
|
2991
|
+
stack.pop();
|
|
2992
|
+
if (namespaces) scopes.pop();
|
|
2993
|
+
}
|
|
2994
|
+
i = end2 < 0 ? n : end2 + 1;
|
|
2995
|
+
continue;
|
|
2996
|
+
}
|
|
2997
|
+
const end = findTagEnd(src, i + 1);
|
|
2998
|
+
const tagContent = src.slice(i + 1, end);
|
|
2999
|
+
const selfClosing = tagContent.endsWith("/");
|
|
3000
|
+
const { name, attrs, raw } = parseTag(selfClosing ? tagContent.slice(0, -1) : tagContent);
|
|
3001
|
+
const node = { name: localName(name), attrs, children: [], text: "" };
|
|
3002
|
+
const parent = stack[stack.length - 1];
|
|
3003
|
+
parent.children.push(node);
|
|
3004
|
+
let scope;
|
|
3005
|
+
if (namespaces) {
|
|
3006
|
+
scope = resolveNamespaces(node, name, raw, scopes[scopes.length - 1]);
|
|
3007
|
+
parent.content.push(node);
|
|
3008
|
+
}
|
|
3009
|
+
if (!selfClosing) {
|
|
3010
|
+
stack.push(node);
|
|
3011
|
+
if (scope) scopes.push(scope);
|
|
3012
|
+
}
|
|
3013
|
+
i = end + 1;
|
|
3014
|
+
} else {
|
|
3015
|
+
const next = src.indexOf("<", i);
|
|
3016
|
+
const stop = next < 0 ? n : next;
|
|
3017
|
+
appendText(unescapeXml(src.slice(i, stop)));
|
|
3018
|
+
i = stop;
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
return root.children[0] ?? null;
|
|
3022
|
+
}
|
|
3023
|
+
function resolveNamespaces(node, qname, raw, parentScope) {
|
|
3024
|
+
let scope = parentScope;
|
|
3025
|
+
for (const [key, value] of raw) {
|
|
3026
|
+
if (key === "xmlns" || key.startsWith("xmlns:")) {
|
|
3027
|
+
if (scope === parentScope) scope = { ...parentScope };
|
|
3028
|
+
scope[key === "xmlns" ? "" : key.slice(6)] = unescapeXml(value);
|
|
3029
|
+
}
|
|
3030
|
+
}
|
|
3031
|
+
const colon = qname.indexOf(":");
|
|
3032
|
+
node.ns = scope[colon >= 0 ? qname.slice(0, colon) : ""] ?? "";
|
|
3033
|
+
node.content = [];
|
|
3034
|
+
const nsAttrs = {};
|
|
3035
|
+
for (const [key, value] of raw) {
|
|
3036
|
+
if (key === "xmlns" || key.startsWith("xmlns:")) continue;
|
|
3037
|
+
const idx = key.indexOf(":");
|
|
3038
|
+
const normalised = unescapeXml(value.replace(/[\t\n\r]/g, " "));
|
|
3039
|
+
if (idx < 0) {
|
|
3040
|
+
nsAttrs[key] = normalised;
|
|
3041
|
+
} else {
|
|
3042
|
+
nsAttrs[`{${scope[key.slice(0, idx)] ?? ""}}${key.slice(idx + 1)}`] = normalised;
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
3045
|
+
node.nsAttrs = nsAttrs;
|
|
3046
|
+
return scope;
|
|
3047
|
+
}
|
|
3048
|
+
function el(node, name) {
|
|
3049
|
+
return node?.children.find((c) => c.name === name);
|
|
3050
|
+
}
|
|
3051
|
+
function els(node, name) {
|
|
3052
|
+
return node ? node.children.filter((c) => c.name === name) : [];
|
|
3053
|
+
}
|
|
3054
|
+
function at(node, name) {
|
|
3055
|
+
return node?.attrs[name];
|
|
3056
|
+
}
|
|
3057
|
+
|
|
3058
|
+
// src/zip/office-template.ts
|
|
3059
|
+
var DOCUMENT = "document";
|
|
3060
|
+
var TEMPLATE = "template";
|
|
3061
|
+
var FAMILY = "spreadsheet";
|
|
3062
|
+
var VBA = "application/vnd.ms-office.vbaProject";
|
|
3063
|
+
var FAMILIES = {
|
|
3064
|
+
word: {
|
|
3065
|
+
part: "/word/document.xml",
|
|
3066
|
+
document: "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml",
|
|
3067
|
+
template: "application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml",
|
|
3068
|
+
macroDocument: "application/vnd.ms-word.document.macroEnabled.main+xml",
|
|
3069
|
+
macroTemplate: "application/vnd.ms-word.template.macroEnabledTemplate.main+xml",
|
|
3070
|
+
odfDocument: "application/vnd.oasis.opendocument.text",
|
|
3071
|
+
odfTemplate: "application/vnd.oasis.opendocument.text-template",
|
|
3072
|
+
odfBody: "text"
|
|
3073
|
+
},
|
|
3074
|
+
presentation: {
|
|
3075
|
+
part: "/ppt/presentation.xml",
|
|
3076
|
+
document: "application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml",
|
|
3077
|
+
template: "application/vnd.openxmlformats-officedocument.presentationml.template.main+xml",
|
|
3078
|
+
macroDocument: "application/vnd.ms-powerpoint.presentation.macroEnabled.main+xml",
|
|
3079
|
+
macroTemplate: "application/vnd.ms-powerpoint.template.macroEnabled.main+xml",
|
|
3080
|
+
odfDocument: "application/vnd.oasis.opendocument.presentation",
|
|
3081
|
+
odfTemplate: "application/vnd.oasis.opendocument.presentation-template",
|
|
3082
|
+
odfBody: "presentation"
|
|
3083
|
+
},
|
|
3084
|
+
spreadsheet: {
|
|
3085
|
+
part: "/xl/workbook.xml",
|
|
3086
|
+
document: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml",
|
|
3087
|
+
template: "application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml",
|
|
3088
|
+
macroDocument: "application/vnd.ms-excel.sheet.macroEnabled.main+xml",
|
|
3089
|
+
macroTemplate: "application/vnd.ms-excel.template.macroEnabled.main+xml",
|
|
3090
|
+
odfDocument: "application/vnd.oasis.opendocument.spreadsheet",
|
|
3091
|
+
odfTemplate: "application/vnd.oasis.opendocument.spreadsheet-template",
|
|
3092
|
+
odfBody: "spreadsheet"
|
|
3093
|
+
}
|
|
3094
|
+
};
|
|
3095
|
+
var OWN = FAMILIES[FAMILY];
|
|
3096
|
+
var UTF8 = new TextDecoder("utf-8");
|
|
3097
|
+
function filesView(files) {
|
|
3098
|
+
return {
|
|
3099
|
+
read: (name) => Object.prototype.hasOwnProperty.call(files, name) ? files[name] : null,
|
|
3100
|
+
names: () => Object.keys(files)
|
|
3101
|
+
};
|
|
3102
|
+
}
|
|
3103
|
+
function sourceKind(zip) {
|
|
3104
|
+
const types = zip.read("[Content_Types].xml");
|
|
3105
|
+
if (types !== null) return ooxmlKind(zip, types);
|
|
3106
|
+
const mimetype = zip.read("mimetype");
|
|
3107
|
+
if (mimetype !== null) return odfKind(zip, latin1(mimetype));
|
|
3108
|
+
return DOCUMENT;
|
|
3109
|
+
}
|
|
3110
|
+
function convert(bytes, to = null) {
|
|
3111
|
+
if (to !== null && to !== DOCUMENT && to !== TEMPLATE) throw new OfficeTemplateRefused("bad_target");
|
|
3112
|
+
const zip = SourceArchive.open(new Uint8Array(bytes));
|
|
3113
|
+
const kind = sourceKind(zip);
|
|
3114
|
+
const target = to ?? (kind === TEMPLATE ? DOCUMENT : TEMPLATE);
|
|
3115
|
+
if (target === kind) throw new OfficeTemplateRefused(`already_${kind}`);
|
|
3116
|
+
const replacements = /* @__PURE__ */ new Map();
|
|
3117
|
+
const types = zip.read("[Content_Types].xml");
|
|
3118
|
+
const mimetypeBytes = zip.read("mimetype");
|
|
3119
|
+
if (types !== null) {
|
|
3120
|
+
const declared = declarations(types);
|
|
3121
|
+
if (declared === null) throw new OfficeTemplateRefused("not_convertible");
|
|
3122
|
+
if (hasVba(zip, declared) || typesOf(declared, OWN.part).includes(OWN.macroDocument)) {
|
|
3123
|
+
throw new OfficeTemplateRefused("macro_enabled");
|
|
3124
|
+
}
|
|
3125
|
+
const [from, value] = target === TEMPLATE ? [OWN.document, OWN.template] : [OWN.template, OWN.document];
|
|
3126
|
+
replacements.set("[Content_Types].xml", fromLatin1(rewriteOverride(latin1(types), OWN.part, from, value)));
|
|
3127
|
+
} else if (mimetypeBytes !== null) {
|
|
3128
|
+
const entries = zip.entries();
|
|
3129
|
+
const first2 = entries[0];
|
|
3130
|
+
const byLocal = [...entries].sort((a, b) => a.local - b.local);
|
|
3131
|
+
if (first2 === void 0 || first2.name !== "mimetype" || byLocal[0].name !== "mimetype" || first2.method !== 0 || first2.localExtra !== 0) {
|
|
3132
|
+
throw new OfficeTemplateRefused("odf_mimetype");
|
|
3133
|
+
}
|
|
3134
|
+
const [from, value] = target === TEMPLATE ? [OWN.odfDocument, OWN.odfTemplate] : [OWN.odfTemplate, OWN.odfDocument];
|
|
3135
|
+
if (latin1(mimetypeBytes) !== from) throw new OfficeTemplateRefused("not_convertible");
|
|
3136
|
+
replacements.set("mimetype", fromLatin1(value));
|
|
3137
|
+
const manifest = zip.read("META-INF/manifest.xml");
|
|
3138
|
+
if (manifest !== null) {
|
|
3139
|
+
const rewritten = rewriteManifestRoot(latin1(manifest), from, value);
|
|
3140
|
+
if (rewritten !== null) replacements.set("META-INF/manifest.xml", fromLatin1(rewritten));
|
|
3141
|
+
}
|
|
3142
|
+
} else {
|
|
3143
|
+
throw new OfficeTemplateRefused("not_convertible");
|
|
3144
|
+
}
|
|
3145
|
+
const out = rebuild(zip, replacements);
|
|
3146
|
+
if (sourceKind(SourceArchive.open(out)) !== target) {
|
|
3147
|
+
throw new Error("Template conversion did not produce the requested kind.");
|
|
3148
|
+
}
|
|
3149
|
+
return out;
|
|
3150
|
+
}
|
|
3151
|
+
function ooxmlKind(zip, types) {
|
|
3152
|
+
const declared = declarations(types);
|
|
3153
|
+
if (declared === null) return DOCUMENT;
|
|
3154
|
+
let kind = DOCUMENT;
|
|
3155
|
+
for (const [part2, type] of declared) {
|
|
3156
|
+
for (const [name, family] of Object.entries(FAMILIES)) {
|
|
3157
|
+
if (type !== family.template && type !== family.macroTemplate) continue;
|
|
3158
|
+
if (name !== FAMILY || part2 !== asciiLower2(OWN.part)) throw new OfficeTemplateRefused("family_mismatch");
|
|
3159
|
+
if (type === family.macroTemplate) throw new OfficeTemplateRefused("macro_enabled");
|
|
3160
|
+
kind = TEMPLATE;
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
if (kind === TEMPLATE && hasVba(zip, declared)) throw new OfficeTemplateRefused("macro_enabled");
|
|
3164
|
+
return kind;
|
|
3165
|
+
}
|
|
3166
|
+
function odfKind(zip, mimetype) {
|
|
3167
|
+
if (mimetype !== OWN.odfTemplate) {
|
|
3168
|
+
if (mimetype.startsWith("application/vnd.oasis.opendocument.") && mimetype.endsWith("-template")) {
|
|
3169
|
+
throw new OfficeTemplateRefused("family_mismatch");
|
|
3170
|
+
}
|
|
3171
|
+
return DOCUMENT;
|
|
3172
|
+
}
|
|
3173
|
+
const content = dom(zip.read("content.xml"));
|
|
3174
|
+
const body = content !== null ? firstByLocalName(content, "body") : null;
|
|
3175
|
+
const first2 = body?.children[0] ?? null;
|
|
3176
|
+
if (first2 === null || first2.name !== OWN.odfBody) throw new OfficeTemplateRefused("family_mismatch");
|
|
3177
|
+
const root = manifestRootType(dom(zip.read("META-INF/manifest.xml")));
|
|
3178
|
+
if (root !== null && root !== OWN.odfTemplate && root !== OWN.odfDocument) {
|
|
3179
|
+
throw new OfficeTemplateRefused("family_mismatch");
|
|
3180
|
+
}
|
|
3181
|
+
return TEMPLATE;
|
|
3182
|
+
}
|
|
3183
|
+
function declarations(types) {
|
|
3184
|
+
const root = dom(types);
|
|
3185
|
+
if (root === null) return null;
|
|
3186
|
+
const out = [];
|
|
3187
|
+
for (const el2 of elements(root)) {
|
|
3188
|
+
if (el2.name === "Override") out.push([asciiLower2(el2.attrs["PartName"] ?? ""), el2.attrs["ContentType"] ?? ""]);
|
|
3189
|
+
else if (el2.name === "Default") out.push([`*.${asciiLower2(el2.attrs["Extension"] ?? "")}`, el2.attrs["ContentType"] ?? ""]);
|
|
3190
|
+
}
|
|
3191
|
+
return out;
|
|
3192
|
+
}
|
|
3193
|
+
function typesOf(declared, part2) {
|
|
3194
|
+
return declared.filter(([name]) => name === asciiLower2(part2)).map(([, type]) => type);
|
|
3195
|
+
}
|
|
3196
|
+
function hasVba(zip, declared) {
|
|
3197
|
+
if (declared.some(([, type]) => asciiLower2(type) === asciiLower2(VBA))) return true;
|
|
3198
|
+
return zip.names().some((name) => /(^|\/)vbaProject\.bin$/i.test(name));
|
|
3199
|
+
}
|
|
3200
|
+
function manifestRootType(root) {
|
|
3201
|
+
if (root === null) return null;
|
|
3202
|
+
for (const el2 of elements(root)) {
|
|
3203
|
+
if (el2.name === "file-entry" && el2.attrs["full-path"] === "/") return el2.attrs["media-type"] ?? null;
|
|
3204
|
+
}
|
|
3205
|
+
return null;
|
|
3206
|
+
}
|
|
3207
|
+
function rewriteOverride(xml, part2, from, to) {
|
|
3208
|
+
const hits = [];
|
|
3209
|
+
for (const tag of xml.matchAll(/<(?:[A-Za-z_][\w.\-]*:)?Override\b[^>]*>/g)) {
|
|
3210
|
+
const name = /\sPartName\s*=\s*(["'])(.*?)\1/s.exec(tag[0]);
|
|
3211
|
+
if (name === null || asciiLower2(name[2]) !== asciiLower2(part2)) continue;
|
|
3212
|
+
const type = /\sContentType\s*=\s*(["'])(.*?)\1/s.exec(tag[0]);
|
|
3213
|
+
if (type !== null && type[2] === from) hits.push(tag.index + type.index + type[0].indexOf(type[1]) + 1);
|
|
3214
|
+
}
|
|
3215
|
+
if (hits.length !== 1) throw new OfficeTemplateRefused("not_convertible");
|
|
3216
|
+
const out = xml.slice(0, hits[0]) + to + xml.slice(hits[0] + from.length);
|
|
3217
|
+
const declared = declarations(out);
|
|
3218
|
+
const after = declared === null ? [] : typesOf(declared, part2);
|
|
3219
|
+
if (after.length !== 1 || after[0] !== to) throw new OfficeTemplateRefused("not_convertible");
|
|
3220
|
+
return out;
|
|
3221
|
+
}
|
|
3222
|
+
function rewriteManifestRoot(xml, from, to) {
|
|
3223
|
+
const current = manifestRootType(dom(xml));
|
|
3224
|
+
if (current === null) return null;
|
|
3225
|
+
if (current !== from) throw new OfficeTemplateRefused("not_convertible");
|
|
3226
|
+
const hits = [];
|
|
3227
|
+
for (const tag of xml.matchAll(/<(?:[A-Za-z_][\w.\-]*:)?file-entry\b[^>]*>/g)) {
|
|
3228
|
+
if (!/\s(?:[A-Za-z_][\w.\-]*:)?full-path\s*=\s*(["'])\/\1/.test(tag[0])) continue;
|
|
3229
|
+
const type = /\s(?:[A-Za-z_][\w.\-]*:)?media-type\s*=\s*(["'])(.*?)\1/s.exec(tag[0]);
|
|
3230
|
+
if (type !== null && type[2] === from) hits.push(tag.index + type.index + type[0].indexOf(type[1]) + 1);
|
|
3231
|
+
}
|
|
3232
|
+
if (hits.length !== 1) throw new OfficeTemplateRefused("not_convertible");
|
|
3233
|
+
const out = xml.slice(0, hits[0]) + to + xml.slice(hits[0] + from.length);
|
|
3234
|
+
if (manifestRootType(dom(out)) !== to) throw new OfficeTemplateRefused("not_convertible");
|
|
3235
|
+
return out;
|
|
3236
|
+
}
|
|
3237
|
+
function rebuild(zip, replacements) {
|
|
3238
|
+
const original = zip.toBytes();
|
|
3239
|
+
const view = new DataView(original.buffer, original.byteOffset, original.byteLength);
|
|
3240
|
+
const u16 = (at2) => view.getUint16(at2, true);
|
|
3241
|
+
const u32 = (at2) => view.getUint32(at2, true);
|
|
3242
|
+
const entries = zip.entries();
|
|
3243
|
+
const byLocal = [...entries].sort((a, b) => a.local - b.local);
|
|
3244
|
+
const nameBytes = (entry) => original.subarray(entry.central + 46, entry.central + 46 + u16(entry.central + 28));
|
|
3245
|
+
const chunks = [];
|
|
3246
|
+
const offsets = /* @__PURE__ */ new Map();
|
|
3247
|
+
let length = 0;
|
|
3248
|
+
const push = (chunk) => {
|
|
3249
|
+
chunks.push(chunk);
|
|
3250
|
+
length += chunk.length;
|
|
3251
|
+
};
|
|
3252
|
+
for (const entry of byLocal) {
|
|
3253
|
+
offsets.set(entry.name, length);
|
|
3254
|
+
const data = replacements.get(entry.name);
|
|
3255
|
+
if (data === void 0) {
|
|
3256
|
+
push(original.subarray(entry.local, entry.finish));
|
|
3257
|
+
continue;
|
|
3258
|
+
}
|
|
3259
|
+
const name = nameBytes(entry);
|
|
3260
|
+
const header = new Uint8Array(30 + name.length + data.length);
|
|
3261
|
+
const hv = new DataView(header.buffer);
|
|
3262
|
+
hv.setUint32(0, 67324752, true);
|
|
3263
|
+
hv.setUint16(4, 10, true);
|
|
3264
|
+
hv.setUint16(6, entry.flags & 2048, true);
|
|
3265
|
+
hv.setUint16(8, 0, true);
|
|
3266
|
+
hv.setUint16(10, u16(entry.local + 10), true);
|
|
3267
|
+
hv.setUint16(12, u16(entry.local + 12), true);
|
|
3268
|
+
hv.setUint32(14, crc32(data) >>> 0, true);
|
|
3269
|
+
hv.setUint32(18, data.length, true);
|
|
3270
|
+
hv.setUint32(22, data.length, true);
|
|
3271
|
+
hv.setUint16(26, name.length, true);
|
|
3272
|
+
hv.setUint16(28, 0, true);
|
|
3273
|
+
header.set(name, 30);
|
|
3274
|
+
header.set(data, 30 + name.length);
|
|
3275
|
+
push(header);
|
|
3276
|
+
}
|
|
3277
|
+
const localsLength = length;
|
|
3278
|
+
for (const entry of entries) {
|
|
3279
|
+
const data = replacements.get(entry.name);
|
|
3280
|
+
if (data === void 0) {
|
|
3281
|
+
const record2 = new Uint8Array(entry.centralLength);
|
|
3282
|
+
record2.set(original.subarray(entry.central, entry.central + entry.centralLength));
|
|
3283
|
+
new DataView(record2.buffer, record2.byteOffset, record2.byteLength).setUint32(42, offsets.get(entry.name), true);
|
|
3284
|
+
push(record2);
|
|
3285
|
+
continue;
|
|
3286
|
+
}
|
|
3287
|
+
const name = nameBytes(entry);
|
|
3288
|
+
const at2 = entry.central;
|
|
3289
|
+
const record = new Uint8Array(46 + name.length);
|
|
3290
|
+
const rv = new DataView(record.buffer);
|
|
3291
|
+
rv.setUint32(0, 33639248, true);
|
|
3292
|
+
rv.setUint16(4, u16(at2 + 4), true);
|
|
3293
|
+
rv.setUint16(6, 10, true);
|
|
3294
|
+
rv.setUint16(8, entry.flags & 2048, true);
|
|
3295
|
+
rv.setUint16(10, 0, true);
|
|
3296
|
+
rv.setUint16(12, u16(at2 + 12), true);
|
|
3297
|
+
rv.setUint16(14, u16(at2 + 14), true);
|
|
3298
|
+
rv.setUint32(16, crc32(data) >>> 0, true);
|
|
3299
|
+
rv.setUint32(20, data.length, true);
|
|
3300
|
+
rv.setUint32(24, data.length, true);
|
|
3301
|
+
rv.setUint16(28, name.length, true);
|
|
3302
|
+
rv.setUint16(36, u16(at2 + 36), true);
|
|
3303
|
+
rv.setUint32(38, u32(at2 + 38), true);
|
|
3304
|
+
rv.setUint32(42, offsets.get(entry.name), true);
|
|
3305
|
+
record.set(name, 46);
|
|
3306
|
+
push(record);
|
|
3307
|
+
}
|
|
3308
|
+
const comment = zip.comment();
|
|
3309
|
+
const end = new Uint8Array(22 + comment.length);
|
|
3310
|
+
const ev = new DataView(end.buffer);
|
|
3311
|
+
ev.setUint32(0, 101010256, true);
|
|
3312
|
+
ev.setUint16(8, entries.length, true);
|
|
3313
|
+
ev.setUint16(10, entries.length, true);
|
|
3314
|
+
ev.setUint32(12, length - localsLength, true);
|
|
3315
|
+
ev.setUint32(16, localsLength, true);
|
|
3316
|
+
ev.setUint16(20, comment.length, true);
|
|
3317
|
+
end.set(comment, 22);
|
|
3318
|
+
push(end);
|
|
3319
|
+
const out = new Uint8Array(length);
|
|
3320
|
+
let pos = 0;
|
|
3321
|
+
for (const chunk of chunks) {
|
|
3322
|
+
out.set(chunk, pos);
|
|
3323
|
+
pos += chunk.length;
|
|
3324
|
+
}
|
|
3325
|
+
return out;
|
|
3326
|
+
}
|
|
3327
|
+
function dom(xml) {
|
|
3328
|
+
if (xml === null) return null;
|
|
3329
|
+
const text2 = typeof xml === "string" ? UTF8.decode(fromLatin1(xml)) : UTF8.decode(xml);
|
|
3330
|
+
if (text2 === "" || /<!DOCTYPE/i.test(text2)) return null;
|
|
3331
|
+
return parseXml(text2);
|
|
3332
|
+
}
|
|
3333
|
+
function* elements(root) {
|
|
3334
|
+
const stack = [root];
|
|
3335
|
+
while (stack.length > 0) {
|
|
3336
|
+
const el2 = stack.pop();
|
|
3337
|
+
yield el2;
|
|
3338
|
+
for (let i = el2.children.length - 1; i >= 0; i--) stack.push(el2.children[i]);
|
|
3339
|
+
}
|
|
3340
|
+
}
|
|
3341
|
+
function firstByLocalName(root, local) {
|
|
3342
|
+
for (const el2 of elements(root)) if (el2.name === local) return el2;
|
|
3343
|
+
return null;
|
|
3344
|
+
}
|
|
3345
|
+
function asciiLower2(s) {
|
|
3346
|
+
return s.replace(/[A-Z]/g, (c) => String.fromCharCode(c.charCodeAt(0) + 32));
|
|
3347
|
+
}
|
|
3348
|
+
function latin1(bytes) {
|
|
3349
|
+
let out = "";
|
|
3350
|
+
for (let i = 0; i < bytes.length; i += 32768) out += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
3351
|
+
return out;
|
|
3352
|
+
}
|
|
3353
|
+
function fromLatin1(s) {
|
|
3354
|
+
const out = new Uint8Array(s.length);
|
|
3355
|
+
for (let i = 0; i < s.length; i++) out[i] = s.charCodeAt(i) & 255;
|
|
3356
|
+
return out;
|
|
3357
|
+
}
|
|
3358
|
+
|
|
2840
3359
|
// src/reader/file-input.ts
|
|
2841
3360
|
async function readFileBounded(path) {
|
|
2842
3361
|
const fs = await import('fs');
|
|
@@ -3377,6 +3896,7 @@ var WorkbookSchema = {
|
|
|
3377
3896
|
fromWorkbook(workbook) {
|
|
3378
3897
|
const schema = { sheets: workbook.sheets.map(sheetToSchema) };
|
|
3379
3898
|
if (Object.keys(workbook.meta).length > 0) schema["meta"] = workbook.meta;
|
|
3899
|
+
if (workbook.sourceKind === "template") schema["sourceKind"] = "template";
|
|
3380
3900
|
return schema;
|
|
3381
3901
|
}
|
|
3382
3902
|
};
|
|
@@ -3479,171 +3999,6 @@ function commentToObject(c) {
|
|
|
3479
3999
|
return out;
|
|
3480
4000
|
}
|
|
3481
4001
|
|
|
3482
|
-
// src/reader/xml.ts
|
|
3483
|
-
var XML_NS = "http://www.w3.org/XML/1998/namespace";
|
|
3484
|
-
function localName(name) {
|
|
3485
|
-
const idx = name.indexOf(":");
|
|
3486
|
-
return idx >= 0 ? name.slice(idx + 1) : name;
|
|
3487
|
-
}
|
|
3488
|
-
function unescapeXml(s) {
|
|
3489
|
-
if (s.indexOf("&") < 0) return s;
|
|
3490
|
-
return s.replace(/&(#x?[0-9a-fA-F]+|\w+);/g, (m, ent) => {
|
|
3491
|
-
switch (ent) {
|
|
3492
|
-
case "amp":
|
|
3493
|
-
return "&";
|
|
3494
|
-
case "lt":
|
|
3495
|
-
return "<";
|
|
3496
|
-
case "gt":
|
|
3497
|
-
return ">";
|
|
3498
|
-
case "quot":
|
|
3499
|
-
return '"';
|
|
3500
|
-
case "apos":
|
|
3501
|
-
return "'";
|
|
3502
|
-
default:
|
|
3503
|
-
if (ent[0] === "#") {
|
|
3504
|
-
const code = ent[1] === "x" || ent[1] === "X" ? parseInt(ent.slice(2), 16) : parseInt(ent.slice(1), 10);
|
|
3505
|
-
return Number.isFinite(code) ? String.fromCodePoint(code) : m;
|
|
3506
|
-
}
|
|
3507
|
-
return m;
|
|
3508
|
-
}
|
|
3509
|
-
});
|
|
3510
|
-
}
|
|
3511
|
-
function findTagEnd(src, from) {
|
|
3512
|
-
let quote2 = "";
|
|
3513
|
-
for (let i = from; i < src.length; i++) {
|
|
3514
|
-
const ch = src[i];
|
|
3515
|
-
if (quote2) {
|
|
3516
|
-
if (ch === quote2) quote2 = "";
|
|
3517
|
-
} else if (ch === '"' || ch === "'") {
|
|
3518
|
-
quote2 = ch;
|
|
3519
|
-
} else if (ch === ">") {
|
|
3520
|
-
return i;
|
|
3521
|
-
}
|
|
3522
|
-
}
|
|
3523
|
-
return src.length;
|
|
3524
|
-
}
|
|
3525
|
-
var ATTR_RE = /([^\s=/]+)\s*=\s*("([^"]*)"|'([^']*)')/g;
|
|
3526
|
-
function parseTag(inner) {
|
|
3527
|
-
const trimmed = inner.trim();
|
|
3528
|
-
let i = 0;
|
|
3529
|
-
while (i < trimmed.length && !/\s/.test(trimmed[i])) i++;
|
|
3530
|
-
const name = trimmed.slice(0, i);
|
|
3531
|
-
const attrs = {};
|
|
3532
|
-
const raw = [];
|
|
3533
|
-
const rest = trimmed.slice(i);
|
|
3534
|
-
for (const m of rest.matchAll(ATTR_RE)) {
|
|
3535
|
-
const rawValue = m[3] ?? m[4] ?? "";
|
|
3536
|
-
attrs[localName(m[1])] = unescapeXml(rawValue);
|
|
3537
|
-
raw.push([m[1], rawValue]);
|
|
3538
|
-
}
|
|
3539
|
-
return { name, attrs, raw };
|
|
3540
|
-
}
|
|
3541
|
-
function parseXml(src, options = {}) {
|
|
3542
|
-
const namespaces = options.namespaces === true;
|
|
3543
|
-
let i = 0;
|
|
3544
|
-
const n = src.length;
|
|
3545
|
-
const root = { name: "#root", attrs: {}, children: [], text: "" };
|
|
3546
|
-
if (namespaces) root.content = [];
|
|
3547
|
-
const stack = [root];
|
|
3548
|
-
const scopes = [{ xml: XML_NS }];
|
|
3549
|
-
const appendText = (text2) => {
|
|
3550
|
-
const top = stack[stack.length - 1];
|
|
3551
|
-
top.text += text2;
|
|
3552
|
-
if (namespaces && text2 !== "") top.content.push(text2);
|
|
3553
|
-
};
|
|
3554
|
-
while (i < n) {
|
|
3555
|
-
if (src[i] === "<") {
|
|
3556
|
-
if (src.startsWith("<!--", i)) {
|
|
3557
|
-
const end2 = src.indexOf("-->", i);
|
|
3558
|
-
i = end2 < 0 ? n : end2 + 3;
|
|
3559
|
-
continue;
|
|
3560
|
-
}
|
|
3561
|
-
if (src.startsWith("<![CDATA[", i)) {
|
|
3562
|
-
const end2 = src.indexOf("]]>", i);
|
|
3563
|
-
appendText(src.slice(i + 9, end2 < 0 ? n : end2));
|
|
3564
|
-
i = end2 < 0 ? n : end2 + 3;
|
|
3565
|
-
continue;
|
|
3566
|
-
}
|
|
3567
|
-
if (src.startsWith("<?", i)) {
|
|
3568
|
-
const end2 = src.indexOf("?>", i);
|
|
3569
|
-
i = end2 < 0 ? n : end2 + 2;
|
|
3570
|
-
continue;
|
|
3571
|
-
}
|
|
3572
|
-
if (src.startsWith("<!", i)) {
|
|
3573
|
-
const end2 = src.indexOf(">", i);
|
|
3574
|
-
i = end2 < 0 ? n : end2 + 1;
|
|
3575
|
-
continue;
|
|
3576
|
-
}
|
|
3577
|
-
if (src[i + 1] === "/") {
|
|
3578
|
-
const end2 = src.indexOf(">", i);
|
|
3579
|
-
if (stack.length > 1) {
|
|
3580
|
-
stack.pop();
|
|
3581
|
-
if (namespaces) scopes.pop();
|
|
3582
|
-
}
|
|
3583
|
-
i = end2 < 0 ? n : end2 + 1;
|
|
3584
|
-
continue;
|
|
3585
|
-
}
|
|
3586
|
-
const end = findTagEnd(src, i + 1);
|
|
3587
|
-
const tagContent = src.slice(i + 1, end);
|
|
3588
|
-
const selfClosing = tagContent.endsWith("/");
|
|
3589
|
-
const { name, attrs, raw } = parseTag(selfClosing ? tagContent.slice(0, -1) : tagContent);
|
|
3590
|
-
const node = { name: localName(name), attrs, children: [], text: "" };
|
|
3591
|
-
const parent = stack[stack.length - 1];
|
|
3592
|
-
parent.children.push(node);
|
|
3593
|
-
let scope;
|
|
3594
|
-
if (namespaces) {
|
|
3595
|
-
scope = resolveNamespaces(node, name, raw, scopes[scopes.length - 1]);
|
|
3596
|
-
parent.content.push(node);
|
|
3597
|
-
}
|
|
3598
|
-
if (!selfClosing) {
|
|
3599
|
-
stack.push(node);
|
|
3600
|
-
if (scope) scopes.push(scope);
|
|
3601
|
-
}
|
|
3602
|
-
i = end + 1;
|
|
3603
|
-
} else {
|
|
3604
|
-
const next = src.indexOf("<", i);
|
|
3605
|
-
const stop = next < 0 ? n : next;
|
|
3606
|
-
appendText(unescapeXml(src.slice(i, stop)));
|
|
3607
|
-
i = stop;
|
|
3608
|
-
}
|
|
3609
|
-
}
|
|
3610
|
-
return root.children[0] ?? null;
|
|
3611
|
-
}
|
|
3612
|
-
function resolveNamespaces(node, qname, raw, parentScope) {
|
|
3613
|
-
let scope = parentScope;
|
|
3614
|
-
for (const [key, value] of raw) {
|
|
3615
|
-
if (key === "xmlns" || key.startsWith("xmlns:")) {
|
|
3616
|
-
if (scope === parentScope) scope = { ...parentScope };
|
|
3617
|
-
scope[key === "xmlns" ? "" : key.slice(6)] = unescapeXml(value);
|
|
3618
|
-
}
|
|
3619
|
-
}
|
|
3620
|
-
const colon = qname.indexOf(":");
|
|
3621
|
-
node.ns = scope[colon >= 0 ? qname.slice(0, colon) : ""] ?? "";
|
|
3622
|
-
node.content = [];
|
|
3623
|
-
const nsAttrs = {};
|
|
3624
|
-
for (const [key, value] of raw) {
|
|
3625
|
-
if (key === "xmlns" || key.startsWith("xmlns:")) continue;
|
|
3626
|
-
const idx = key.indexOf(":");
|
|
3627
|
-
const normalised = unescapeXml(value.replace(/[\t\n\r]/g, " "));
|
|
3628
|
-
if (idx < 0) {
|
|
3629
|
-
nsAttrs[key] = normalised;
|
|
3630
|
-
} else {
|
|
3631
|
-
nsAttrs[`{${scope[key.slice(0, idx)] ?? ""}}${key.slice(idx + 1)}`] = normalised;
|
|
3632
|
-
}
|
|
3633
|
-
}
|
|
3634
|
-
node.nsAttrs = nsAttrs;
|
|
3635
|
-
return scope;
|
|
3636
|
-
}
|
|
3637
|
-
function el(node, name) {
|
|
3638
|
-
return node?.children.find((c) => c.name === name);
|
|
3639
|
-
}
|
|
3640
|
-
function els(node, name) {
|
|
3641
|
-
return node ? node.children.filter((c) => c.name === name) : [];
|
|
3642
|
-
}
|
|
3643
|
-
function at(node, name) {
|
|
3644
|
-
return node?.attrs[name];
|
|
3645
|
-
}
|
|
3646
|
-
|
|
3647
4002
|
// src/reader/ods-reader.ts
|
|
3648
4003
|
var MAX_ROWS = 1048576;
|
|
3649
4004
|
var MAX_COLUMNS = 16384;
|
|
@@ -3658,6 +4013,7 @@ var OdsReader = class {
|
|
|
3658
4013
|
}
|
|
3659
4014
|
/** Read an already-unzipped package (name -> bytes). */
|
|
3660
4015
|
readFiles(files) {
|
|
4016
|
+
const kind = sourceKind(filesView(files));
|
|
3661
4017
|
const getText = (name) => files[name] ? decoder2.decode(files[name]) : null;
|
|
3662
4018
|
const content = getText("content.xml");
|
|
3663
4019
|
if (content === null) throw new Error("[holy-sheet] missing content.xml");
|
|
@@ -3672,7 +4028,7 @@ var OdsReader = class {
|
|
|
3672
4028
|
if (table.ns === NS.TABLE && table.name === "table") sheets.push(this.readSheet(table, styles));
|
|
3673
4029
|
}
|
|
3674
4030
|
const meta = getText("meta.xml");
|
|
3675
|
-
return new Workbook(sheets, meta === null ? {} : readMeta(meta));
|
|
4031
|
+
return new Workbook(sheets, meta === null ? {} : readMeta(meta), kind);
|
|
3676
4032
|
}
|
|
3677
4033
|
readSheet(table, styles) {
|
|
3678
4034
|
const columns = [];
|
|
@@ -4338,6 +4694,7 @@ var XlsxReader = class {
|
|
|
4338
4694
|
}
|
|
4339
4695
|
/** Read an already-unzipped package (name -> bytes), so a caller that sniffed it does not unzip twice. */
|
|
4340
4696
|
readFiles(files) {
|
|
4697
|
+
const kind = sourceKind(filesView(files));
|
|
4341
4698
|
const getText = (name) => files[name] ? decoder3.decode(files[name]) : null;
|
|
4342
4699
|
const stylesXml = getText("xl/styles.xml");
|
|
4343
4700
|
const stylesIndex = stylesXml ? StylesParser.parse(stylesXml) : [];
|
|
@@ -4393,7 +4750,7 @@ var XlsxReader = class {
|
|
|
4393
4750
|
i++;
|
|
4394
4751
|
}
|
|
4395
4752
|
const meta = this.parseDocProps(getText("docProps/core.xml"));
|
|
4396
|
-
return new Workbook(sheets, meta);
|
|
4753
|
+
return new Workbook(sheets, meta, kind);
|
|
4397
4754
|
}
|
|
4398
4755
|
parseDocProps(coreXml) {
|
|
4399
4756
|
if (coreXml === null) return {};
|
|
@@ -6331,7 +6688,7 @@ var holy_sheet_schema_default = {
|
|
|
6331
6688
|
};
|
|
6332
6689
|
|
|
6333
6690
|
// src/agent.ts
|
|
6334
|
-
var VERSION = "2.
|
|
6691
|
+
var VERSION = "2.11.1";
|
|
6335
6692
|
function toU8(input) {
|
|
6336
6693
|
return input instanceof Uint8Array ? input : new Uint8Array(input);
|
|
6337
6694
|
}
|
|
@@ -6372,6 +6729,32 @@ var Agent = {
|
|
|
6372
6729
|
read(input) {
|
|
6373
6730
|
return readSpreadsheet(toU8(input), null);
|
|
6374
6731
|
},
|
|
6732
|
+
/**
|
|
6733
|
+
* Turn an Excel `.xltx` / LibreOffice `.ots` template into its `.xlsx` /
|
|
6734
|
+
* `.ods` twin, or back. `to` is `"document"`, `"template"`, or null to flip.
|
|
6735
|
+
* Universal and synchronous: bytes in, bytes out. Mirrors PHP
|
|
6736
|
+
* `Agent::convertTemplate()`.
|
|
6737
|
+
*
|
|
6738
|
+
* Only the declaration moves: the workbook part's content type in
|
|
6739
|
+
* `[Content_Types].xml`, or the ODF `mimetype` entry together with the
|
|
6740
|
+
* manifest's root media-type (ODF 1.3 requires them to agree). Every other
|
|
6741
|
+
* member is copied as its original zip record, byte for byte.
|
|
6742
|
+
*
|
|
6743
|
+
* Throws `OfficeTemplateRefused` (macro-enabled, mismatched, already that
|
|
6744
|
+
* kind), `ArchiveRefused` (archive guard), or `UnsupportedFormatException`
|
|
6745
|
+
* for a package that is not a spreadsheet.
|
|
6746
|
+
*/
|
|
6747
|
+
convertTemplate(input, to = null) {
|
|
6748
|
+
const bytes = toU8(input);
|
|
6749
|
+
const zip = SourceArchive.open(bytes);
|
|
6750
|
+
const raw = zip.read("mimetype");
|
|
6751
|
+
const mimetype = raw === null ? null : new TextDecoder().decode(raw).trim();
|
|
6752
|
+
const odf = mimetype !== null && mimetype.startsWith("application/vnd.oasis.opendocument.spreadsheet");
|
|
6753
|
+
if (!odf && (mimetype !== null || zip.read("xl/workbook.xml") === null)) {
|
|
6754
|
+
throw UnsupportedFormatException.unknownPackage(null, mimetype);
|
|
6755
|
+
}
|
|
6756
|
+
return convert(bytes, to);
|
|
6757
|
+
},
|
|
6375
6758
|
/** Round-trip an xlsx or ods file on disk back to a schema (Node only). */
|
|
6376
6759
|
async describe(path) {
|
|
6377
6760
|
const fs = await import('fs');
|
|
@@ -6473,6 +6856,10 @@ var HolySheet = class {
|
|
|
6473
6856
|
describe(path) {
|
|
6474
6857
|
return Agent.describe(path);
|
|
6475
6858
|
}
|
|
6859
|
+
/** {@link Agent.convertTemplate}: .xltx/.ots to .xlsx/.ods and back. */
|
|
6860
|
+
convertTemplate(input, to = null) {
|
|
6861
|
+
return Agent.convertTemplate(input, to);
|
|
6862
|
+
}
|
|
6476
6863
|
validateAndRepair(schema) {
|
|
6477
6864
|
return Agent.validateAndRepair(schema);
|
|
6478
6865
|
}
|
|
@@ -6505,6 +6892,7 @@ exports.HolySheet = HolySheet;
|
|
|
6505
6892
|
exports.Inference = Inference;
|
|
6506
6893
|
exports.Normalizer = Normalizer;
|
|
6507
6894
|
exports.OdsReader = OdsReader;
|
|
6895
|
+
exports.OfficeTemplateRefused = OfficeTemplateRefused;
|
|
6508
6896
|
exports.Repairer = Repairer;
|
|
6509
6897
|
exports.SchemaException = SchemaException;
|
|
6510
6898
|
exports.SheetDiff = SheetDiff;
|