odf.js 1.2.0 → 1.3.0

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/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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;");
450
450
  }
451
+ const XML_ENTITY_PATTERN = /&(?:amp|lt|gt|quot|apos);/g;
452
+ const XML_ENTITY_DECODE = {
453
+ "&amp;": "&",
454
+ "&lt;": "<",
455
+ "&gt;": ">",
456
+ "&quot;": "\"",
457
+ "&apos;": "'"
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$1(element, name) {
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$1(root, "manifest:version");
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$1(child, "manifest:full-path");
491
- const mediaType = attrValue$1(child, "manifest:media-type");
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$1(child, "manifest:version");
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$1(child, "manifest:full-path");
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 LENGTH_PATTERN = /^(-?(?:\d+(?:\.\d+)?|\.\d+))(cm|mm|in|pt|pc|px)$/;
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 `${valuePt}pt`;
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 COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/;
780
- function parseColor(value) {
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 = sumLength(paragraph.children);
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 = getSpaceCount(node);
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 = measureLength(node);
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
- export { AttributeSchema, BinaryPartSchema, MANIFEST_PART, MIMETYPE_PART, ManifestEntrySchema, ManifestProblemSchema, ManifestSchema, ODF_MEDIA_TYPES, ODF_NAMESPACES, PackageSchema, PartSchema, STYLE_FAMILIES, StylePropertiesSchema, StyleRegistry, XmlCdataSchema, XmlCommentSchema, XmlDeclarationSchema, XmlElementSchema, XmlNodeSchema, XmlPartSchema, XmlPiSchema, XmlTextSchema, base64ToBytes, buildManifest, buildStylePropertyElements, buildXml, bytesToBase64, canonicalPropertiesString, decodePackage, el, encodePackage, encodeXmlText, ensureSpan, formatPercentageMultiplier, formatPt, isXmlNode, mediaTypeForExtension, packageCodec, paragraphPropertiesToAttributes, parseLength, parsePackage, parseParagraphProperties, parseStyleElementProperties, parseTextProperties, parseXml, readManifest, readMimetype, serializePackage, setDocumentMediaType, sniffImageFormat, syncManifest, textPropertiesToAttributes, txt, unzipPackage, validateManifest, writeManifest, writeMimetype, xmlCodec, xmlnsAttributes, zipPackage };
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.2.0",
3
+ "version": "1.3.0",
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": {