odf.js 1.2.0 → 1.3.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/README.md +116 -0
- package/dist/index.cjs +367 -69
- package/dist/index.d.cts +58 -2
- package/dist/index.d.ts +58 -2
- package/dist/index.js +341 -72
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { XMLBuilder, XMLParser } from "fast-xml-parser";
|
|
3
3
|
import { unzipSync, zipSync } from "fflate";
|
|
4
|
-
import { AlignmentSchema, ColorSchema, colorToRgbHex, rgbHexToColor } from "document-content-model";
|
|
4
|
+
import { AlignmentSchema, AlignmentSchema as AlignmentSchema$1, ColorSchema, colorToRgbHex, rgbHexToColor } from "document-content-model";
|
|
5
5
|
//#region src/model/node.ts
|
|
6
6
|
const AttributeSchema = z.object({
|
|
7
7
|
name: z.string(),
|
|
@@ -448,6 +448,17 @@ function txt(value) {
|
|
|
448
448
|
function encodeXmlText(value) {
|
|
449
449
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
450
450
|
}
|
|
451
|
+
const XML_ENTITY_PATTERN = /&(?:amp|lt|gt|quot|apos);/g;
|
|
452
|
+
const XML_ENTITY_DECODE = {
|
|
453
|
+
"&": "&",
|
|
454
|
+
"<": "<",
|
|
455
|
+
">": ">",
|
|
456
|
+
""": "\"",
|
|
457
|
+
"'": "'"
|
|
458
|
+
};
|
|
459
|
+
function decodeXmlText(value) {
|
|
460
|
+
return value.replace(XML_ENTITY_PATTERN, (entity) => XML_ENTITY_DECODE[entity]);
|
|
461
|
+
}
|
|
451
462
|
//#endregion
|
|
452
463
|
//#region src/manifest.ts
|
|
453
464
|
const ManifestEntrySchema = z.object({
|
|
@@ -471,26 +482,26 @@ const STANDARD_XML_PART_NAMES = /* @__PURE__ */ new Set([
|
|
|
471
482
|
"meta.xml",
|
|
472
483
|
"settings.xml"
|
|
473
484
|
]);
|
|
474
|
-
function findChildElement(nodes, tag) {
|
|
485
|
+
function findChildElement$1(nodes, tag) {
|
|
475
486
|
for (const node of nodes) if (node.type === "element" && node.tag === tag) return node;
|
|
476
487
|
}
|
|
477
|
-
function attrValue$
|
|
488
|
+
function attrValue$2(element, name) {
|
|
478
489
|
return element.attributes.find((attribute) => attribute.name === name)?.value;
|
|
479
490
|
}
|
|
480
491
|
function readManifest(pkg) {
|
|
481
492
|
const part = pkg.parts[MANIFEST_PART];
|
|
482
493
|
if (part?.kind !== "xml") throw new Error(`package has no ${MANIFEST_PART} XML part to read`);
|
|
483
|
-
const root = findChildElement(part.nodes, "manifest:manifest");
|
|
494
|
+
const root = findChildElement$1(part.nodes, "manifest:manifest");
|
|
484
495
|
if (root === void 0) throw new Error(`${MANIFEST_PART} has no manifest:manifest root element`);
|
|
485
|
-
const version = attrValue$
|
|
496
|
+
const version = attrValue$2(root, "manifest:version");
|
|
486
497
|
if (version === void 0) throw new Error(`${MANIFEST_PART}'s manifest:manifest root is missing the required manifest:version attribute`);
|
|
487
498
|
const entries = [];
|
|
488
499
|
for (const child of root.children) {
|
|
489
500
|
if (child.type !== "element" || child.tag !== "manifest:file-entry") continue;
|
|
490
|
-
const fullPath = attrValue$
|
|
491
|
-
const mediaType = attrValue$
|
|
501
|
+
const fullPath = attrValue$2(child, "manifest:full-path");
|
|
502
|
+
const mediaType = attrValue$2(child, "manifest:media-type");
|
|
492
503
|
if (fullPath === void 0 || mediaType === void 0) throw new Error(`${MANIFEST_PART} has a manifest:file-entry missing manifest:full-path or manifest:media-type`);
|
|
493
|
-
const entryVersion = attrValue$
|
|
504
|
+
const entryVersion = attrValue$2(child, "manifest:version");
|
|
494
505
|
entries.push(entryVersion === void 0 ? {
|
|
495
506
|
fullPath,
|
|
496
507
|
mediaType
|
|
@@ -600,7 +611,7 @@ function validateManifest(pkg) {
|
|
|
600
611
|
});
|
|
601
612
|
return problems;
|
|
602
613
|
}
|
|
603
|
-
const root = findChildElement(manifestPart.nodes, "manifest:manifest");
|
|
614
|
+
const root = findChildElement$1(manifestPart.nodes, "manifest:manifest");
|
|
604
615
|
if (root === void 0) {
|
|
605
616
|
problems.push({
|
|
606
617
|
severity: "error",
|
|
@@ -649,7 +660,7 @@ function validateManifest(pkg) {
|
|
|
649
660
|
for (const child of root.children) {
|
|
650
661
|
if (child.type !== "element" || child.tag !== "manifest:file-entry") continue;
|
|
651
662
|
if (!child.children.some((grandchild) => grandchild.type === "element" && grandchild.tag === "manifest:encryption-data")) continue;
|
|
652
|
-
const fullPath = attrValue$
|
|
663
|
+
const fullPath = attrValue$2(child, "manifest:full-path");
|
|
653
664
|
if (fullPath === void 0) continue;
|
|
654
665
|
problems.push({
|
|
655
666
|
severity: "warning",
|
|
@@ -681,6 +692,48 @@ function setDocumentMediaType(pkg, mediaType, version = DEFAULT_MANIFEST_VERSION
|
|
|
681
692
|
});
|
|
682
693
|
}
|
|
683
694
|
//#endregion
|
|
695
|
+
//#region src/typed/shared/units.ts
|
|
696
|
+
const LENGTH_PATTERN = /^(-?(?:\d+(?:\.\d+)?|\.\d+))(cm|mm|in|pt|pc|px)$/;
|
|
697
|
+
const POINTS_PER_INCH = 72;
|
|
698
|
+
const POINTS_PER_PICA = 12;
|
|
699
|
+
const CM_PER_INCH = 2.54;
|
|
700
|
+
const MM_PER_INCH = 25.4;
|
|
701
|
+
const CSS_REFERENCE_PIXELS_PER_INCH = 96;
|
|
702
|
+
function unitToPtFactor(unit) {
|
|
703
|
+
switch (unit) {
|
|
704
|
+
case "pt": return 1;
|
|
705
|
+
case "in": return POINTS_PER_INCH;
|
|
706
|
+
case "cm": return POINTS_PER_INCH / CM_PER_INCH;
|
|
707
|
+
case "mm": return POINTS_PER_INCH / MM_PER_INCH;
|
|
708
|
+
case "pc": return POINTS_PER_PICA;
|
|
709
|
+
case "px": return POINTS_PER_INCH / CSS_REFERENCE_PIXELS_PER_INCH;
|
|
710
|
+
}
|
|
711
|
+
}
|
|
712
|
+
function isLengthUnit(value) {
|
|
713
|
+
return value === "cm" || value === "mm" || value === "in" || value === "pt" || value === "pc" || value === "px";
|
|
714
|
+
}
|
|
715
|
+
function parseOdfLength(value) {
|
|
716
|
+
const match = LENGTH_PATTERN.exec(value);
|
|
717
|
+
if (match === null) return;
|
|
718
|
+
const numeric = match[1];
|
|
719
|
+
const unit = match[2];
|
|
720
|
+
if (numeric === void 0 || unit === void 0 || !isLengthUnit(unit)) return;
|
|
721
|
+
return Number(numeric) * unitToPtFactor(unit);
|
|
722
|
+
}
|
|
723
|
+
function formatOdfLength(pt, unit = "pt") {
|
|
724
|
+
return `${pt / unitToPtFactor(unit)}${unit}`;
|
|
725
|
+
}
|
|
726
|
+
//#endregion
|
|
727
|
+
//#region src/typed/shared/color.ts
|
|
728
|
+
const ODF_COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/;
|
|
729
|
+
function parseOdfColor(value) {
|
|
730
|
+
if (!ODF_COLOR_PATTERN.test(value)) return;
|
|
731
|
+
return rgbHexToColor(value);
|
|
732
|
+
}
|
|
733
|
+
function formatOdfColor(color) {
|
|
734
|
+
return `#${colorToRgbHex(color)}`;
|
|
735
|
+
}
|
|
736
|
+
//#endregion
|
|
684
737
|
//#region src/styles/properties.ts
|
|
685
738
|
const StylePropertiesSchema = z.object({
|
|
686
739
|
bold: z.boolean().optional(),
|
|
@@ -690,7 +743,7 @@ const StylePropertiesSchema = z.object({
|
|
|
690
743
|
fontFamily: z.string().optional(),
|
|
691
744
|
sizePt: z.number().optional(),
|
|
692
745
|
color: ColorSchema.optional(),
|
|
693
|
-
alignment: AlignmentSchema.optional(),
|
|
746
|
+
alignment: AlignmentSchema$1.optional(),
|
|
694
747
|
spacingBeforePt: z.number().optional(),
|
|
695
748
|
spacingAfterPt: z.number().optional(),
|
|
696
749
|
lineSpacing: z.number().optional(),
|
|
@@ -740,30 +793,9 @@ function attributeMap(element) {
|
|
|
740
793
|
for (const attribute of element.attributes) map.set(attribute.name, attribute.value);
|
|
741
794
|
return map;
|
|
742
795
|
}
|
|
743
|
-
const
|
|
744
|
-
function unitToPtFactor(unit) {
|
|
745
|
-
switch (unit) {
|
|
746
|
-
case "pt": return 1;
|
|
747
|
-
case "in": return 72;
|
|
748
|
-
case "cm": return 72 / 2.54;
|
|
749
|
-
case "mm": return 72 / 25.4;
|
|
750
|
-
case "pc": return 12;
|
|
751
|
-
case "px": return .75;
|
|
752
|
-
}
|
|
753
|
-
}
|
|
754
|
-
function isLengthUnit(value) {
|
|
755
|
-
return value === "cm" || value === "mm" || value === "in" || value === "pt" || value === "pc" || value === "px";
|
|
756
|
-
}
|
|
757
|
-
function parseLength(value) {
|
|
758
|
-
const match = LENGTH_PATTERN.exec(value);
|
|
759
|
-
if (match === null) return;
|
|
760
|
-
const numeric = match[1];
|
|
761
|
-
const unit = match[2];
|
|
762
|
-
if (numeric === void 0 || unit === void 0 || !isLengthUnit(unit)) return;
|
|
763
|
-
return Number(numeric) * unitToPtFactor(unit);
|
|
764
|
-
}
|
|
796
|
+
const parseLength = parseOdfLength;
|
|
765
797
|
function formatPt(valuePt) {
|
|
766
|
-
return
|
|
798
|
+
return formatOdfLength(valuePt, "pt");
|
|
767
799
|
}
|
|
768
800
|
const PERCENTAGE_PATTERN = /^(-?(?:\d+(?:\.\d+)?|\.\d+))%$/;
|
|
769
801
|
function parsePercentageMultiplier(value) {
|
|
@@ -776,14 +808,8 @@ function parsePercentageMultiplier(value) {
|
|
|
776
808
|
function formatPercentageMultiplier(multiplier) {
|
|
777
809
|
return `${multiplier * 100}%`;
|
|
778
810
|
}
|
|
779
|
-
const
|
|
780
|
-
|
|
781
|
-
if (!COLOR_PATTERN.test(value)) return;
|
|
782
|
-
return rgbHexToColor(value);
|
|
783
|
-
}
|
|
784
|
-
function formatColor(color) {
|
|
785
|
-
return `#${colorToRgbHex(color)}`;
|
|
786
|
-
}
|
|
811
|
+
const parseColor = parseOdfColor;
|
|
812
|
+
const formatColor = formatOdfColor;
|
|
787
813
|
function parseLineDecoration(style, companionA, companionAOnValue, companionB, companionBOnValue) {
|
|
788
814
|
if (style === void 0 && companionA === void 0 && companionB === void 0) return;
|
|
789
815
|
if (style === "solid" && (companionA === void 0 || companionA === companionAOnValue) && (companionB === void 0 || companionB === companionBOnValue)) return true;
|
|
@@ -1048,7 +1074,7 @@ function emptyFamilySets() {
|
|
|
1048
1074
|
graphic: /* @__PURE__ */ new Set()
|
|
1049
1075
|
};
|
|
1050
1076
|
}
|
|
1051
|
-
function attrValue(element, name) {
|
|
1077
|
+
function attrValue$1(element, name) {
|
|
1052
1078
|
return element.attributes.find((attribute) => attribute.name === name)?.value;
|
|
1053
1079
|
}
|
|
1054
1080
|
function findDirectChild(nodes, tag) {
|
|
@@ -1076,8 +1102,8 @@ function ensureAutomaticStyles(root) {
|
|
|
1076
1102
|
function reserveStyleNames(container, reserved) {
|
|
1077
1103
|
for (const child of container.children) {
|
|
1078
1104
|
if (child.type !== "element" || child.tag !== "style:style") continue;
|
|
1079
|
-
const name = attrValue(child, "style:name");
|
|
1080
|
-
const family = attrValue(child, "style:family");
|
|
1105
|
+
const name = attrValue$1(child, "style:name");
|
|
1106
|
+
const family = attrValue$1(child, "style:family");
|
|
1081
1107
|
if (name === void 0 || family === void 0 || !isStyleFamily(family)) continue;
|
|
1082
1108
|
reserved[family].add(name);
|
|
1083
1109
|
}
|
|
@@ -1123,14 +1149,14 @@ var StyleRegistry = class StyleRegistry {
|
|
|
1123
1149
|
const registry = new StyleRegistry(automaticStyles, prefixes, reservedByFamily);
|
|
1124
1150
|
for (const child of automaticStyles.children) {
|
|
1125
1151
|
if (child.type !== "element" || child.tag !== "style:style") continue;
|
|
1126
|
-
const name = attrValue(child, "style:name");
|
|
1127
|
-
const family = attrValue(child, "style:family");
|
|
1152
|
+
const name = attrValue$1(child, "style:name");
|
|
1153
|
+
const family = attrValue$1(child, "style:family");
|
|
1128
1154
|
if (name === void 0 || family === void 0 || !isStyleFamily(family)) continue;
|
|
1129
1155
|
registry.knownStyles.set(name, child);
|
|
1130
1156
|
reservedByFamily[family].add(name);
|
|
1131
1157
|
const parsed = parseStyleElementProperties(child);
|
|
1132
1158
|
if (!parsed.hasUnknown) {
|
|
1133
|
-
const parentStyleName = attrValue(child, "style:parent-style-name");
|
|
1159
|
+
const parentStyleName = attrValue$1(child, "style:parent-style-name");
|
|
1134
1160
|
const fingerprint = computeFingerprint(family, parsed.properties, parentStyleName);
|
|
1135
1161
|
if (!registry.fingerprintToName.has(fingerprint)) {
|
|
1136
1162
|
registry.fingerprintToName.set(fingerprint, name);
|
|
@@ -1204,10 +1230,62 @@ var StyleRegistry = class StyleRegistry {
|
|
|
1204
1230
|
}
|
|
1205
1231
|
};
|
|
1206
1232
|
//#endregion
|
|
1233
|
+
//#region src/xml/query.ts
|
|
1234
|
+
function rootElement(nodes) {
|
|
1235
|
+
for (const node of nodes) if (node.type === "element") return node;
|
|
1236
|
+
}
|
|
1237
|
+
function findChildElement(nodes, tag) {
|
|
1238
|
+
for (const node of nodes) if (node.type === "element" && node.tag === tag) return node;
|
|
1239
|
+
}
|
|
1240
|
+
function childrenWithTag(element, tag) {
|
|
1241
|
+
const out = [];
|
|
1242
|
+
for (const child of element.children) if (child.type === "element" && child.tag === tag) out.push(child);
|
|
1243
|
+
return out;
|
|
1244
|
+
}
|
|
1245
|
+
function attrValue(element, name) {
|
|
1246
|
+
return element.attributes.find((attribute) => attribute.name === name)?.value;
|
|
1247
|
+
}
|
|
1248
|
+
//#endregion
|
|
1249
|
+
//#region src/typed/shared/text.ts
|
|
1250
|
+
function getOdfSpaceCount(element) {
|
|
1251
|
+
const raw = attrValue(element, "text:c");
|
|
1252
|
+
if (raw === void 0) return 1;
|
|
1253
|
+
const parsed = Number.parseInt(raw, 10);
|
|
1254
|
+
if (!Number.isInteger(parsed) || parsed < 0 || String(parsed) !== raw) throw new Error(`getOdfSpaceCount: text:s has a malformed text:c attribute: "${raw}"`);
|
|
1255
|
+
return parsed;
|
|
1256
|
+
}
|
|
1257
|
+
function measureOdfNodeLength(node) {
|
|
1258
|
+
if (node.type === "text") return node.value.length;
|
|
1259
|
+
if (node.type !== "element") return 0;
|
|
1260
|
+
if (node.tag === "text:s") return getOdfSpaceCount(node);
|
|
1261
|
+
if (node.tag === "text:tab" || node.tag === "text:line-break") return 1;
|
|
1262
|
+
if (node.tag === "text:span") return sumOdfNodeLength(node.children);
|
|
1263
|
+
return 0;
|
|
1264
|
+
}
|
|
1265
|
+
function sumOdfNodeLength(nodes) {
|
|
1266
|
+
let total = 0;
|
|
1267
|
+
for (const node of nodes) total += measureOdfNodeLength(node);
|
|
1268
|
+
return total;
|
|
1269
|
+
}
|
|
1270
|
+
function decodeOdfNode(node) {
|
|
1271
|
+
if (node.type === "text") return decodeXmlText(node.value);
|
|
1272
|
+
if (node.type !== "element") return "";
|
|
1273
|
+
if (node.tag === "text:s") return " ".repeat(getOdfSpaceCount(node));
|
|
1274
|
+
if (node.tag === "text:tab") return " ";
|
|
1275
|
+
if (node.tag === "text:line-break") return "\n";
|
|
1276
|
+
if (node.tag === "text:span") return decodeOdfText(node);
|
|
1277
|
+
return "";
|
|
1278
|
+
}
|
|
1279
|
+
function decodeOdfText(container) {
|
|
1280
|
+
let text = "";
|
|
1281
|
+
for (const child of container.children) text += decodeOdfNode(child);
|
|
1282
|
+
return text;
|
|
1283
|
+
}
|
|
1284
|
+
//#endregion
|
|
1207
1285
|
//#region src/styles/span.ts
|
|
1208
1286
|
function ensureSpan(paragraph, start, end, styleName) {
|
|
1209
1287
|
if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end < start) throw new Error(`ensureSpan: invalid range [${start}, ${end})`);
|
|
1210
|
-
const total =
|
|
1288
|
+
const total = sumOdfNodeLength(paragraph.children);
|
|
1211
1289
|
if (end > total) throw new Error(`ensureSpan: range end ${end} exceeds the container's total character length ${total}`);
|
|
1212
1290
|
const { before, after: rest } = splitChildrenAt(paragraph.children, start);
|
|
1213
1291
|
const { before: middle, after } = splitChildrenAt(rest, end - start);
|
|
@@ -1239,29 +1317,9 @@ function setStyleName(span, styleName) {
|
|
|
1239
1317
|
value: encoded
|
|
1240
1318
|
});
|
|
1241
1319
|
}
|
|
1242
|
-
function getSpaceCount(spaceElement) {
|
|
1243
|
-
const raw = spaceElement.attributes.find((attribute) => attribute.name === "text:c")?.value;
|
|
1244
|
-
if (raw === void 0) return 1;
|
|
1245
|
-
const parsed = Number.parseInt(raw, 10);
|
|
1246
|
-
if (!Number.isInteger(parsed) || parsed < 0 || String(parsed) !== raw) throw new Error(`ensureSpan: text:s has a malformed text:c attribute: "${raw}"`);
|
|
1247
|
-
return parsed;
|
|
1248
|
-
}
|
|
1249
1320
|
function buildSpaceRun(count) {
|
|
1250
1321
|
return count === 1 ? el("text:s") : el("text:s", { "text:c": String(count) });
|
|
1251
1322
|
}
|
|
1252
|
-
function measureLength(node) {
|
|
1253
|
-
if (node.type === "text") return node.value.length;
|
|
1254
|
-
if (node.type !== "element") return 0;
|
|
1255
|
-
if (node.tag === "text:s") return getSpaceCount(node);
|
|
1256
|
-
if (node.tag === "text:tab" || node.tag === "text:line-break") return 1;
|
|
1257
|
-
if (node.tag === "text:span") return sumLength(node.children);
|
|
1258
|
-
return 0;
|
|
1259
|
-
}
|
|
1260
|
-
function sumLength(nodes) {
|
|
1261
|
-
let total = 0;
|
|
1262
|
-
for (const node of nodes) total += measureLength(node);
|
|
1263
|
-
return total;
|
|
1264
|
-
}
|
|
1265
1323
|
function splitNode(node, offset) {
|
|
1266
1324
|
if (node.type === "text") return {
|
|
1267
1325
|
left: {
|
|
@@ -1274,7 +1332,7 @@ function splitNode(node, offset) {
|
|
|
1274
1332
|
}
|
|
1275
1333
|
};
|
|
1276
1334
|
if (node.type === "element" && node.tag === "text:s") {
|
|
1277
|
-
const count =
|
|
1335
|
+
const count = getOdfSpaceCount(node);
|
|
1278
1336
|
return {
|
|
1279
1337
|
left: buildSpaceRun(offset),
|
|
1280
1338
|
right: buildSpaceRun(count - offset)
|
|
@@ -1311,7 +1369,7 @@ function splitChildrenAt(children, offset) {
|
|
|
1311
1369
|
after: children.slice(index)
|
|
1312
1370
|
};
|
|
1313
1371
|
const node = children[index];
|
|
1314
|
-
const length =
|
|
1372
|
+
const length = measureOdfNodeLength(node);
|
|
1315
1373
|
if (remaining >= length) {
|
|
1316
1374
|
before.push(node);
|
|
1317
1375
|
remaining -= length;
|
|
@@ -1333,4 +1391,215 @@ function splitChildrenAt(children, offset) {
|
|
|
1333
1391
|
};
|
|
1334
1392
|
}
|
|
1335
1393
|
//#endregion
|
|
1336
|
-
|
|
1394
|
+
//#region src/typed/shared/a1.ts
|
|
1395
|
+
const ALPHABET_SIZE = 26;
|
|
1396
|
+
const ALPHABET_START_CODE = "A".charCodeAt(0);
|
|
1397
|
+
function columnIndexToLetters(index) {
|
|
1398
|
+
if (!Number.isInteger(index) || index < 0) throw new Error(`columnIndexToLetters: index must be a non-negative integer, got ${index}`);
|
|
1399
|
+
let remaining = index + 1;
|
|
1400
|
+
let letters = "";
|
|
1401
|
+
while (remaining > 0) {
|
|
1402
|
+
const digit = (remaining - 1) % ALPHABET_SIZE;
|
|
1403
|
+
letters = String.fromCharCode(ALPHABET_START_CODE + digit) + letters;
|
|
1404
|
+
remaining = Math.floor((remaining - 1) / ALPHABET_SIZE);
|
|
1405
|
+
}
|
|
1406
|
+
return letters;
|
|
1407
|
+
}
|
|
1408
|
+
function cellReference(columnIndex, rowIndex) {
|
|
1409
|
+
if (!Number.isInteger(rowIndex) || rowIndex < 0) throw new Error(`cellReference: rowIndex must be a non-negative integer, got ${rowIndex}`);
|
|
1410
|
+
return `${columnIndexToLetters(columnIndex)}${rowIndex + 1}`;
|
|
1411
|
+
}
|
|
1412
|
+
function validateRepeatCount(repeatCount, caller) {
|
|
1413
|
+
if (!Number.isInteger(repeatCount) || repeatCount < 1) throw new Error(`${caller}: repeatCount must be a positive integer, got ${repeatCount}`);
|
|
1414
|
+
}
|
|
1415
|
+
var TableCursor = class {
|
|
1416
|
+
columnCursor = 0;
|
|
1417
|
+
rowCursor = 0;
|
|
1418
|
+
get columnIndex() {
|
|
1419
|
+
return this.columnCursor;
|
|
1420
|
+
}
|
|
1421
|
+
get rowIndex() {
|
|
1422
|
+
return this.rowCursor;
|
|
1423
|
+
}
|
|
1424
|
+
nextCell(repeatCount = 1) {
|
|
1425
|
+
validateRepeatCount(repeatCount, "TableCursor.nextCell");
|
|
1426
|
+
const reference = cellReference(this.columnCursor, this.rowCursor);
|
|
1427
|
+
this.columnCursor += repeatCount;
|
|
1428
|
+
return reference;
|
|
1429
|
+
}
|
|
1430
|
+
nextRow(repeatCount = 1) {
|
|
1431
|
+
validateRepeatCount(repeatCount, "TableCursor.nextRow");
|
|
1432
|
+
this.rowCursor += repeatCount;
|
|
1433
|
+
this.columnCursor = 0;
|
|
1434
|
+
}
|
|
1435
|
+
};
|
|
1436
|
+
//#endregion
|
|
1437
|
+
//#region src/typed/shared/geometry.ts
|
|
1438
|
+
function parsePageSize(pageLayoutProperties) {
|
|
1439
|
+
const widthValue = attrValue(pageLayoutProperties, "fo:page-width");
|
|
1440
|
+
const heightValue = attrValue(pageLayoutProperties, "fo:page-height");
|
|
1441
|
+
if (widthValue === void 0 || heightValue === void 0) return;
|
|
1442
|
+
const widthPt = parseOdfLength(widthValue);
|
|
1443
|
+
const heightPt = parseOdfLength(heightValue);
|
|
1444
|
+
if (widthPt === void 0 || heightPt === void 0) return;
|
|
1445
|
+
return {
|
|
1446
|
+
widthPt,
|
|
1447
|
+
heightPt
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
function parseMargins(pageLayoutProperties) {
|
|
1451
|
+
const topValue = attrValue(pageLayoutProperties, "fo:margin-top");
|
|
1452
|
+
const rightValue = attrValue(pageLayoutProperties, "fo:margin-right");
|
|
1453
|
+
const bottomValue = attrValue(pageLayoutProperties, "fo:margin-bottom");
|
|
1454
|
+
const leftValue = attrValue(pageLayoutProperties, "fo:margin-left");
|
|
1455
|
+
if (topValue === void 0 || rightValue === void 0 || bottomValue === void 0 || leftValue === void 0) return;
|
|
1456
|
+
const topPt = parseOdfLength(topValue);
|
|
1457
|
+
const rightPt = parseOdfLength(rightValue);
|
|
1458
|
+
const bottomPt = parseOdfLength(bottomValue);
|
|
1459
|
+
const leftPt = parseOdfLength(leftValue);
|
|
1460
|
+
if (topPt === void 0 || rightPt === void 0 || bottomPt === void 0 || leftPt === void 0) return;
|
|
1461
|
+
return {
|
|
1462
|
+
topPt,
|
|
1463
|
+
rightPt,
|
|
1464
|
+
bottomPt,
|
|
1465
|
+
leftPt
|
|
1466
|
+
};
|
|
1467
|
+
}
|
|
1468
|
+
function parseBox(element) {
|
|
1469
|
+
const xValue = attrValue(element, "svg:x");
|
|
1470
|
+
const yValue = attrValue(element, "svg:y");
|
|
1471
|
+
const widthValue = attrValue(element, "svg:width");
|
|
1472
|
+
const heightValue = attrValue(element, "svg:height");
|
|
1473
|
+
if (xValue === void 0 || yValue === void 0 || widthValue === void 0 || heightValue === void 0) return;
|
|
1474
|
+
const xPt = parseOdfLength(xValue);
|
|
1475
|
+
const yPt = parseOdfLength(yValue);
|
|
1476
|
+
const widthPt = parseOdfLength(widthValue);
|
|
1477
|
+
const heightPt = parseOdfLength(heightValue);
|
|
1478
|
+
if (xPt === void 0 || yPt === void 0 || widthPt === void 0 || heightPt === void 0) return;
|
|
1479
|
+
return {
|
|
1480
|
+
xPt,
|
|
1481
|
+
yPt,
|
|
1482
|
+
widthPt,
|
|
1483
|
+
heightPt
|
|
1484
|
+
};
|
|
1485
|
+
}
|
|
1486
|
+
//#endregion
|
|
1487
|
+
//#region src/typed/shared/cascade.ts
|
|
1488
|
+
const STYLE_PARTS = ["content.xml", "styles.xml"];
|
|
1489
|
+
const NAME_KEY_SEPARATOR = "\0";
|
|
1490
|
+
function nameKey(family, name) {
|
|
1491
|
+
return `${family}${NAME_KEY_SEPARATOR}${name}`;
|
|
1492
|
+
}
|
|
1493
|
+
function collectStyles(pkg) {
|
|
1494
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1495
|
+
const defaultByFamily = /* @__PURE__ */ new Map();
|
|
1496
|
+
for (const partPath of STYLE_PARTS) {
|
|
1497
|
+
const part = pkg.parts[partPath];
|
|
1498
|
+
if (part?.kind !== "xml") continue;
|
|
1499
|
+
const root = rootElement(part.nodes);
|
|
1500
|
+
if (root === void 0) continue;
|
|
1501
|
+
for (const containerTag of ["office:automatic-styles", "office:styles"]) {
|
|
1502
|
+
const container = findChildElement(root.children, containerTag);
|
|
1503
|
+
if (container === void 0) continue;
|
|
1504
|
+
for (const child of container.children) {
|
|
1505
|
+
if (child.type !== "element") continue;
|
|
1506
|
+
if (child.tag === "style:style") {
|
|
1507
|
+
const name = attrValue(child, "style:name");
|
|
1508
|
+
const family = attrValue(child, "style:family");
|
|
1509
|
+
if (name === void 0 || family === void 0 || !isStyleFamily(family)) continue;
|
|
1510
|
+
byName.set(nameKey(family, name), child);
|
|
1511
|
+
} else if (child.tag === "style:default-style") {
|
|
1512
|
+
const family = attrValue(child, "style:family");
|
|
1513
|
+
if (family === void 0 || !isStyleFamily(family)) continue;
|
|
1514
|
+
if (!defaultByFamily.has(family)) defaultByFamily.set(family, child);
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
return {
|
|
1520
|
+
byName,
|
|
1521
|
+
defaultByFamily
|
|
1522
|
+
};
|
|
1523
|
+
}
|
|
1524
|
+
function resolveStyle(styleName, family, pkg) {
|
|
1525
|
+
const { byName, defaultByFamily } = collectStyles(pkg);
|
|
1526
|
+
const diagnostics = [];
|
|
1527
|
+
const defaultElement = defaultByFamily.get(family);
|
|
1528
|
+
let properties = defaultElement === void 0 ? {} : parseStyleElementProperties(defaultElement).properties;
|
|
1529
|
+
if (styleName === void 0) return {
|
|
1530
|
+
properties,
|
|
1531
|
+
diagnostics
|
|
1532
|
+
};
|
|
1533
|
+
const chain = [];
|
|
1534
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1535
|
+
let currentName = styleName;
|
|
1536
|
+
while (currentName !== void 0) {
|
|
1537
|
+
const key = nameKey(family, currentName);
|
|
1538
|
+
if (visited.has(key)) {
|
|
1539
|
+
diagnostics.push({
|
|
1540
|
+
severity: "warning",
|
|
1541
|
+
message: `cyclic style:parent-style-name chain detected at style "${currentName}" (family "${family}") -- breaking the cycle and stopping cascade resolution at this point`
|
|
1542
|
+
});
|
|
1543
|
+
break;
|
|
1544
|
+
}
|
|
1545
|
+
visited.add(key);
|
|
1546
|
+
const element = byName.get(key);
|
|
1547
|
+
if (element === void 0) {
|
|
1548
|
+
diagnostics.push({
|
|
1549
|
+
severity: "warning",
|
|
1550
|
+
message: `style "${currentName}" (family "${family}") was not found in content.xml or styles.xml -- stopping cascade resolution at this point`
|
|
1551
|
+
});
|
|
1552
|
+
break;
|
|
1553
|
+
}
|
|
1554
|
+
chain.push(element);
|
|
1555
|
+
currentName = attrValue(element, "style:parent-style-name");
|
|
1556
|
+
}
|
|
1557
|
+
chain.reverse();
|
|
1558
|
+
for (const element of chain) properties = {
|
|
1559
|
+
...properties,
|
|
1560
|
+
...parseStyleElementProperties(element).properties
|
|
1561
|
+
};
|
|
1562
|
+
return {
|
|
1563
|
+
properties,
|
|
1564
|
+
diagnostics
|
|
1565
|
+
};
|
|
1566
|
+
}
|
|
1567
|
+
//#endregion
|
|
1568
|
+
//#region src/typed/shared/metadata.ts
|
|
1569
|
+
const META_PART = "meta.xml";
|
|
1570
|
+
function elementText(element) {
|
|
1571
|
+
let text = "";
|
|
1572
|
+
for (const child of element.children) if (child.type === "text") text += decodeXmlText(child.value);
|
|
1573
|
+
return text;
|
|
1574
|
+
}
|
|
1575
|
+
function firstElementText(container, tag) {
|
|
1576
|
+
const element = childrenWithTag(container, tag)[0];
|
|
1577
|
+
if (element === void 0) return;
|
|
1578
|
+
const text = elementText(element);
|
|
1579
|
+
return text.length > 0 ? text : void 0;
|
|
1580
|
+
}
|
|
1581
|
+
function readOdfMetadata(pkg) {
|
|
1582
|
+
const part = pkg.parts[META_PART];
|
|
1583
|
+
if (part?.kind !== "xml") return {};
|
|
1584
|
+
const root = rootElement(part.nodes);
|
|
1585
|
+
const meta = root === void 0 ? void 0 : findChildElement(root.children, "office:meta");
|
|
1586
|
+
if (meta === void 0) return {};
|
|
1587
|
+
const metadata = {};
|
|
1588
|
+
const title = firstElementText(meta, "dc:title");
|
|
1589
|
+
if (title !== void 0) metadata.title = title;
|
|
1590
|
+
const author = firstElementText(meta, "meta:initial-creator");
|
|
1591
|
+
if (author !== void 0) metadata.author = author;
|
|
1592
|
+
const subject = firstElementText(meta, "dc:subject");
|
|
1593
|
+
if (subject !== void 0) metadata.subject = subject;
|
|
1594
|
+
const keywords = childrenWithTag(meta, "meta:keyword").map(elementText).filter((keyword) => keyword.length > 0);
|
|
1595
|
+
if (keywords.length > 0) metadata.keywords = keywords;
|
|
1596
|
+
const creator = firstElementText(meta, "meta:generator");
|
|
1597
|
+
if (creator !== void 0) metadata.creator = creator;
|
|
1598
|
+
const createdIso = firstElementText(meta, "meta:creation-date");
|
|
1599
|
+
if (createdIso !== void 0) metadata.createdIso = createdIso;
|
|
1600
|
+
const modifiedIso = firstElementText(meta, "dc:date");
|
|
1601
|
+
if (modifiedIso !== void 0) metadata.modifiedIso = modifiedIso;
|
|
1602
|
+
return metadata;
|
|
1603
|
+
}
|
|
1604
|
+
//#endregion
|
|
1605
|
+
export { AlignmentSchema, AttributeSchema, BinaryPartSchema, MANIFEST_PART, META_PART, MIMETYPE_PART, ManifestEntrySchema, ManifestProblemSchema, ManifestSchema, ODF_MEDIA_TYPES, ODF_NAMESPACES, PackageSchema, PartSchema, STYLE_FAMILIES, StylePropertiesSchema, StyleRegistry, TableCursor, XmlCdataSchema, XmlCommentSchema, XmlDeclarationSchema, XmlElementSchema, XmlNodeSchema, XmlPartSchema, XmlPiSchema, XmlTextSchema, attrValue, base64ToBytes, buildManifest, buildStylePropertyElements, buildXml, bytesToBase64, canonicalPropertiesString, cellReference, childrenWithTag, columnIndexToLetters, decodeOdfText, decodePackage, decodeXmlText, el, encodePackage, encodeXmlText, ensureSpan, findChildElement, formatOdfColor, formatOdfLength, formatPercentageMultiplier, formatPt, getOdfSpaceCount, isStyleFamily, isXmlNode, measureOdfNodeLength, mediaTypeForExtension, packageCodec, paragraphPropertiesToAttributes, parseBox, parseLength, parseMargins, parseOdfColor, parseOdfLength, parsePackage, parsePageSize, parseParagraphProperties, parseStyleElementProperties, parseTextProperties, parseXml, readManifest, readMimetype, readOdfMetadata, resolveStyle, rootElement, serializePackage, setDocumentMediaType, sniffImageFormat, sumOdfNodeLength, syncManifest, textPropertiesToAttributes, txt, unzipPackage, validateManifest, writeManifest, writeMimetype, xmlCodec, xmlnsAttributes, zipPackage };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "odf.js",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "Type-safe, lossless round-trip conversion between OpenDocument Format packages (odt, ods, odp) and JSON, hand-written and dependency-minimal, built on Zod 4 codecs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|