odf.js 1.1.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.cjs +974 -9
- package/dist/index.d.cts +139 -1
- package/dist/index.d.ts +139 -1
- package/dist/index.js +932 -10
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -2,6 +2,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
2
2
|
let zod = require("zod");
|
|
3
3
|
let fast_xml_parser = require("fast-xml-parser");
|
|
4
4
|
let fflate = require("fflate");
|
|
5
|
+
let document_content_model = require("document-content-model");
|
|
5
6
|
//#region src/model/node.ts
|
|
6
7
|
const AttributeSchema = zod.z.object({
|
|
7
8
|
name: zod.z.string(),
|
|
@@ -448,6 +449,17 @@ function txt(value) {
|
|
|
448
449
|
function encodeXmlText(value) {
|
|
449
450
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
450
451
|
}
|
|
452
|
+
const XML_ENTITY_PATTERN = /&(?:amp|lt|gt|quot|apos);/g;
|
|
453
|
+
const XML_ENTITY_DECODE = {
|
|
454
|
+
"&": "&",
|
|
455
|
+
"<": "<",
|
|
456
|
+
">": ">",
|
|
457
|
+
""": "\"",
|
|
458
|
+
"'": "'"
|
|
459
|
+
};
|
|
460
|
+
function decodeXmlText(value) {
|
|
461
|
+
return value.replace(XML_ENTITY_PATTERN, (entity) => XML_ENTITY_DECODE[entity]);
|
|
462
|
+
}
|
|
451
463
|
//#endregion
|
|
452
464
|
//#region src/manifest.ts
|
|
453
465
|
const ManifestEntrySchema = zod.z.object({
|
|
@@ -471,26 +483,26 @@ const STANDARD_XML_PART_NAMES = /* @__PURE__ */ new Set([
|
|
|
471
483
|
"meta.xml",
|
|
472
484
|
"settings.xml"
|
|
473
485
|
]);
|
|
474
|
-
function findChildElement(nodes, tag) {
|
|
486
|
+
function findChildElement$1(nodes, tag) {
|
|
475
487
|
for (const node of nodes) if (node.type === "element" && node.tag === tag) return node;
|
|
476
488
|
}
|
|
477
|
-
function attrValue(element, name) {
|
|
489
|
+
function attrValue$2(element, name) {
|
|
478
490
|
return element.attributes.find((attribute) => attribute.name === name)?.value;
|
|
479
491
|
}
|
|
480
492
|
function readManifest(pkg) {
|
|
481
493
|
const part = pkg.parts[MANIFEST_PART];
|
|
482
494
|
if (part?.kind !== "xml") throw new Error(`package has no ${MANIFEST_PART} XML part to read`);
|
|
483
|
-
const root = findChildElement(part.nodes, "manifest:manifest");
|
|
495
|
+
const root = findChildElement$1(part.nodes, "manifest:manifest");
|
|
484
496
|
if (root === void 0) throw new Error(`${MANIFEST_PART} has no manifest:manifest root element`);
|
|
485
|
-
const version = attrValue(root, "manifest:version");
|
|
497
|
+
const version = attrValue$2(root, "manifest:version");
|
|
486
498
|
if (version === void 0) throw new Error(`${MANIFEST_PART}'s manifest:manifest root is missing the required manifest:version attribute`);
|
|
487
499
|
const entries = [];
|
|
488
500
|
for (const child of root.children) {
|
|
489
501
|
if (child.type !== "element" || child.tag !== "manifest:file-entry") continue;
|
|
490
|
-
const fullPath = attrValue(child, "manifest:full-path");
|
|
491
|
-
const mediaType = attrValue(child, "manifest:media-type");
|
|
502
|
+
const fullPath = attrValue$2(child, "manifest:full-path");
|
|
503
|
+
const mediaType = attrValue$2(child, "manifest:media-type");
|
|
492
504
|
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(child, "manifest:version");
|
|
505
|
+
const entryVersion = attrValue$2(child, "manifest:version");
|
|
494
506
|
entries.push(entryVersion === void 0 ? {
|
|
495
507
|
fullPath,
|
|
496
508
|
mediaType
|
|
@@ -600,7 +612,7 @@ function validateManifest(pkg) {
|
|
|
600
612
|
});
|
|
601
613
|
return problems;
|
|
602
614
|
}
|
|
603
|
-
const root = findChildElement(manifestPart.nodes, "manifest:manifest");
|
|
615
|
+
const root = findChildElement$1(manifestPart.nodes, "manifest:manifest");
|
|
604
616
|
if (root === void 0) {
|
|
605
617
|
problems.push({
|
|
606
618
|
severity: "error",
|
|
@@ -649,7 +661,7 @@ function validateManifest(pkg) {
|
|
|
649
661
|
for (const child of root.children) {
|
|
650
662
|
if (child.type !== "element" || child.tag !== "manifest:file-entry") continue;
|
|
651
663
|
if (!child.children.some((grandchild) => grandchild.type === "element" && grandchild.tag === "manifest:encryption-data")) continue;
|
|
652
|
-
const fullPath = attrValue(child, "manifest:full-path");
|
|
664
|
+
const fullPath = attrValue$2(child, "manifest:full-path");
|
|
653
665
|
if (fullPath === void 0) continue;
|
|
654
666
|
problems.push({
|
|
655
667
|
severity: "warning",
|
|
@@ -681,9 +693,926 @@ function setDocumentMediaType(pkg, mediaType, version = DEFAULT_MANIFEST_VERSION
|
|
|
681
693
|
});
|
|
682
694
|
}
|
|
683
695
|
//#endregion
|
|
696
|
+
//#region src/typed/shared/units.ts
|
|
697
|
+
const LENGTH_PATTERN = /^(-?(?:\d+(?:\.\d+)?|\.\d+))(cm|mm|in|pt|pc|px)$/;
|
|
698
|
+
const POINTS_PER_INCH = 72;
|
|
699
|
+
const POINTS_PER_PICA = 12;
|
|
700
|
+
const CM_PER_INCH = 2.54;
|
|
701
|
+
const MM_PER_INCH = 25.4;
|
|
702
|
+
const CSS_REFERENCE_PIXELS_PER_INCH = 96;
|
|
703
|
+
function unitToPtFactor(unit) {
|
|
704
|
+
switch (unit) {
|
|
705
|
+
case "pt": return 1;
|
|
706
|
+
case "in": return POINTS_PER_INCH;
|
|
707
|
+
case "cm": return POINTS_PER_INCH / CM_PER_INCH;
|
|
708
|
+
case "mm": return POINTS_PER_INCH / MM_PER_INCH;
|
|
709
|
+
case "pc": return POINTS_PER_PICA;
|
|
710
|
+
case "px": return POINTS_PER_INCH / CSS_REFERENCE_PIXELS_PER_INCH;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
function isLengthUnit(value) {
|
|
714
|
+
return value === "cm" || value === "mm" || value === "in" || value === "pt" || value === "pc" || value === "px";
|
|
715
|
+
}
|
|
716
|
+
function parseOdfLength(value) {
|
|
717
|
+
const match = LENGTH_PATTERN.exec(value);
|
|
718
|
+
if (match === null) return;
|
|
719
|
+
const numeric = match[1];
|
|
720
|
+
const unit = match[2];
|
|
721
|
+
if (numeric === void 0 || unit === void 0 || !isLengthUnit(unit)) return;
|
|
722
|
+
return Number(numeric) * unitToPtFactor(unit);
|
|
723
|
+
}
|
|
724
|
+
function formatOdfLength(pt, unit = "pt") {
|
|
725
|
+
return `${pt / unitToPtFactor(unit)}${unit}`;
|
|
726
|
+
}
|
|
727
|
+
//#endregion
|
|
728
|
+
//#region src/typed/shared/color.ts
|
|
729
|
+
const ODF_COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/;
|
|
730
|
+
function parseOdfColor(value) {
|
|
731
|
+
if (!ODF_COLOR_PATTERN.test(value)) return;
|
|
732
|
+
return (0, document_content_model.rgbHexToColor)(value);
|
|
733
|
+
}
|
|
734
|
+
function formatOdfColor(color) {
|
|
735
|
+
return `#${(0, document_content_model.colorToRgbHex)(color)}`;
|
|
736
|
+
}
|
|
737
|
+
//#endregion
|
|
738
|
+
//#region src/styles/properties.ts
|
|
739
|
+
const StylePropertiesSchema = zod.z.object({
|
|
740
|
+
bold: zod.z.boolean().optional(),
|
|
741
|
+
italic: zod.z.boolean().optional(),
|
|
742
|
+
underline: zod.z.boolean().optional(),
|
|
743
|
+
strike: zod.z.boolean().optional(),
|
|
744
|
+
fontFamily: zod.z.string().optional(),
|
|
745
|
+
sizePt: zod.z.number().optional(),
|
|
746
|
+
color: document_content_model.ColorSchema.optional(),
|
|
747
|
+
alignment: document_content_model.AlignmentSchema.optional(),
|
|
748
|
+
spacingBeforePt: zod.z.number().optional(),
|
|
749
|
+
spacingAfterPt: zod.z.number().optional(),
|
|
750
|
+
lineSpacing: zod.z.number().optional(),
|
|
751
|
+
indentLeftPt: zod.z.number().optional(),
|
|
752
|
+
indentFirstLinePt: zod.z.number().optional()
|
|
753
|
+
});
|
|
754
|
+
const ATTR = {
|
|
755
|
+
fontWeight: "fo:font-weight",
|
|
756
|
+
fontStyle: "fo:font-style",
|
|
757
|
+
underlineStyle: "style:text-underline-style",
|
|
758
|
+
underlineWidth: "style:text-underline-width",
|
|
759
|
+
underlineColor: "style:text-underline-color",
|
|
760
|
+
lineThroughStyle: "style:text-line-through-style",
|
|
761
|
+
lineThroughType: "style:text-line-through-type",
|
|
762
|
+
fontFamily: "fo:font-family",
|
|
763
|
+
fontSize: "fo:font-size",
|
|
764
|
+
color: "fo:color",
|
|
765
|
+
textAlign: "fo:text-align",
|
|
766
|
+
marginTop: "fo:margin-top",
|
|
767
|
+
marginBottom: "fo:margin-bottom",
|
|
768
|
+
lineHeight: "fo:line-height",
|
|
769
|
+
marginLeft: "fo:margin-left",
|
|
770
|
+
textIndent: "fo:text-indent"
|
|
771
|
+
};
|
|
772
|
+
const TEXT_ATTR_NAMES = /* @__PURE__ */ new Set([
|
|
773
|
+
ATTR.fontWeight,
|
|
774
|
+
ATTR.fontStyle,
|
|
775
|
+
ATTR.underlineStyle,
|
|
776
|
+
ATTR.underlineWidth,
|
|
777
|
+
ATTR.underlineColor,
|
|
778
|
+
ATTR.lineThroughStyle,
|
|
779
|
+
ATTR.lineThroughType,
|
|
780
|
+
ATTR.fontFamily,
|
|
781
|
+
ATTR.fontSize,
|
|
782
|
+
ATTR.color
|
|
783
|
+
]);
|
|
784
|
+
const PARAGRAPH_ATTR_NAMES = /* @__PURE__ */ new Set([
|
|
785
|
+
ATTR.textAlign,
|
|
786
|
+
ATTR.marginTop,
|
|
787
|
+
ATTR.marginBottom,
|
|
788
|
+
ATTR.lineHeight,
|
|
789
|
+
ATTR.marginLeft,
|
|
790
|
+
ATTR.textIndent
|
|
791
|
+
]);
|
|
792
|
+
function attributeMap(element) {
|
|
793
|
+
const map = /* @__PURE__ */ new Map();
|
|
794
|
+
for (const attribute of element.attributes) map.set(attribute.name, attribute.value);
|
|
795
|
+
return map;
|
|
796
|
+
}
|
|
797
|
+
const parseLength = parseOdfLength;
|
|
798
|
+
function formatPt(valuePt) {
|
|
799
|
+
return formatOdfLength(valuePt, "pt");
|
|
800
|
+
}
|
|
801
|
+
const PERCENTAGE_PATTERN = /^(-?(?:\d+(?:\.\d+)?|\.\d+))%$/;
|
|
802
|
+
function parsePercentageMultiplier(value) {
|
|
803
|
+
const match = PERCENTAGE_PATTERN.exec(value);
|
|
804
|
+
if (match === null) return;
|
|
805
|
+
const numeric = match[1];
|
|
806
|
+
if (numeric === void 0) return;
|
|
807
|
+
return Number(numeric) / 100;
|
|
808
|
+
}
|
|
809
|
+
function formatPercentageMultiplier(multiplier) {
|
|
810
|
+
return `${multiplier * 100}%`;
|
|
811
|
+
}
|
|
812
|
+
const parseColor = parseOdfColor;
|
|
813
|
+
const formatColor = formatOdfColor;
|
|
814
|
+
function parseLineDecoration(style, companionA, companionAOnValue, companionB, companionBOnValue) {
|
|
815
|
+
if (style === void 0 && companionA === void 0 && companionB === void 0) return;
|
|
816
|
+
if (style === "solid" && (companionA === void 0 || companionA === companionAOnValue) && (companionB === void 0 || companionB === companionBOnValue)) return true;
|
|
817
|
+
if (style === "none" && companionA === void 0 && companionB === void 0) return false;
|
|
818
|
+
return "unknown";
|
|
819
|
+
}
|
|
820
|
+
const RISKY_STYLE_ELEMENT_ATTRS = /* @__PURE__ */ new Set(["style:master-page-name", "style:next-style-name"]);
|
|
821
|
+
function parseTextProperties(element) {
|
|
822
|
+
const attrs = attributeMap(element);
|
|
823
|
+
const properties = {};
|
|
824
|
+
let hasUnknown = false;
|
|
825
|
+
for (const name of attrs.keys()) if (!TEXT_ATTR_NAMES.has(name)) hasUnknown = true;
|
|
826
|
+
const fontWeight = attrs.get(ATTR.fontWeight);
|
|
827
|
+
if (fontWeight === "bold") properties.bold = true;
|
|
828
|
+
else if (fontWeight === "normal") properties.bold = false;
|
|
829
|
+
else if (fontWeight !== void 0) hasUnknown = true;
|
|
830
|
+
const fontStyle = attrs.get(ATTR.fontStyle);
|
|
831
|
+
if (fontStyle === "italic") properties.italic = true;
|
|
832
|
+
else if (fontStyle === "normal") properties.italic = false;
|
|
833
|
+
else if (fontStyle !== void 0) hasUnknown = true;
|
|
834
|
+
const underline = parseLineDecoration(attrs.get(ATTR.underlineStyle), attrs.get(ATTR.underlineWidth), "auto", attrs.get(ATTR.underlineColor), "font-color");
|
|
835
|
+
if (underline === "unknown") hasUnknown = true;
|
|
836
|
+
else if (underline !== void 0) properties.underline = underline;
|
|
837
|
+
const strike = parseLineDecoration(attrs.get(ATTR.lineThroughStyle), attrs.get(ATTR.lineThroughType), "single", void 0, "");
|
|
838
|
+
if (strike === "unknown") hasUnknown = true;
|
|
839
|
+
else if (strike !== void 0) properties.strike = strike;
|
|
840
|
+
const fontFamily = attrs.get(ATTR.fontFamily);
|
|
841
|
+
if (fontFamily !== void 0) properties.fontFamily = fontFamily;
|
|
842
|
+
const fontSize = attrs.get(ATTR.fontSize);
|
|
843
|
+
if (fontSize !== void 0) {
|
|
844
|
+
const pt = parseLength(fontSize);
|
|
845
|
+
if (pt === void 0) hasUnknown = true;
|
|
846
|
+
else properties.sizePt = pt;
|
|
847
|
+
}
|
|
848
|
+
const color = attrs.get(ATTR.color);
|
|
849
|
+
if (color !== void 0) {
|
|
850
|
+
const parsed = parseColor(color);
|
|
851
|
+
if (parsed === void 0) hasUnknown = true;
|
|
852
|
+
else properties.color = parsed;
|
|
853
|
+
}
|
|
854
|
+
return {
|
|
855
|
+
properties,
|
|
856
|
+
hasUnknown
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
function parseParagraphProperties(element) {
|
|
860
|
+
const attrs = attributeMap(element);
|
|
861
|
+
const properties = {};
|
|
862
|
+
let hasUnknown = false;
|
|
863
|
+
for (const name of attrs.keys()) if (!PARAGRAPH_ATTR_NAMES.has(name)) hasUnknown = true;
|
|
864
|
+
const textAlign = attrs.get(ATTR.textAlign);
|
|
865
|
+
if (textAlign === "left" || textAlign === "center" || textAlign === "right" || textAlign === "justify") properties.alignment = textAlign;
|
|
866
|
+
else if (textAlign !== void 0) hasUnknown = true;
|
|
867
|
+
const marginTop = attrs.get(ATTR.marginTop);
|
|
868
|
+
if (marginTop !== void 0) {
|
|
869
|
+
const pt = parseLength(marginTop);
|
|
870
|
+
if (pt === void 0) hasUnknown = true;
|
|
871
|
+
else properties.spacingBeforePt = pt;
|
|
872
|
+
}
|
|
873
|
+
const marginBottom = attrs.get(ATTR.marginBottom);
|
|
874
|
+
if (marginBottom !== void 0) {
|
|
875
|
+
const pt = parseLength(marginBottom);
|
|
876
|
+
if (pt === void 0) hasUnknown = true;
|
|
877
|
+
else properties.spacingAfterPt = pt;
|
|
878
|
+
}
|
|
879
|
+
const marginLeft = attrs.get(ATTR.marginLeft);
|
|
880
|
+
if (marginLeft !== void 0) {
|
|
881
|
+
const pt = parseLength(marginLeft);
|
|
882
|
+
if (pt === void 0) hasUnknown = true;
|
|
883
|
+
else properties.indentLeftPt = pt;
|
|
884
|
+
}
|
|
885
|
+
const textIndent = attrs.get(ATTR.textIndent);
|
|
886
|
+
if (textIndent !== void 0) {
|
|
887
|
+
const pt = parseLength(textIndent);
|
|
888
|
+
if (pt === void 0) hasUnknown = true;
|
|
889
|
+
else properties.indentFirstLinePt = pt;
|
|
890
|
+
}
|
|
891
|
+
const lineHeight = attrs.get(ATTR.lineHeight);
|
|
892
|
+
if (lineHeight !== void 0) {
|
|
893
|
+
const multiplier = parsePercentageMultiplier(lineHeight);
|
|
894
|
+
if (multiplier === void 0) hasUnknown = true;
|
|
895
|
+
else properties.lineSpacing = multiplier;
|
|
896
|
+
}
|
|
897
|
+
return {
|
|
898
|
+
properties,
|
|
899
|
+
hasUnknown
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
function parseStyleElementProperties(styleElement) {
|
|
903
|
+
let properties = {};
|
|
904
|
+
let hasUnknown = false;
|
|
905
|
+
for (const attribute of styleElement.attributes) if (RISKY_STYLE_ELEMENT_ATTRS.has(attribute.name)) hasUnknown = true;
|
|
906
|
+
for (const child of styleElement.children) {
|
|
907
|
+
if (child.type !== "element") continue;
|
|
908
|
+
if (child.tag === "style:text-properties") {
|
|
909
|
+
const result = parseTextProperties(child);
|
|
910
|
+
properties = {
|
|
911
|
+
...properties,
|
|
912
|
+
...result.properties
|
|
913
|
+
};
|
|
914
|
+
if (result.hasUnknown) hasUnknown = true;
|
|
915
|
+
} else if (child.tag === "style:paragraph-properties") {
|
|
916
|
+
const result = parseParagraphProperties(child);
|
|
917
|
+
properties = {
|
|
918
|
+
...properties,
|
|
919
|
+
...result.properties
|
|
920
|
+
};
|
|
921
|
+
if (result.hasUnknown) hasUnknown = true;
|
|
922
|
+
} else hasUnknown = true;
|
|
923
|
+
}
|
|
924
|
+
return {
|
|
925
|
+
properties,
|
|
926
|
+
hasUnknown
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
function textPropertiesToAttributes(properties) {
|
|
930
|
+
const attributes = [];
|
|
931
|
+
if (properties.bold !== void 0) attributes.push({
|
|
932
|
+
name: ATTR.fontWeight,
|
|
933
|
+
value: properties.bold ? "bold" : "normal"
|
|
934
|
+
});
|
|
935
|
+
if (properties.italic !== void 0) attributes.push({
|
|
936
|
+
name: ATTR.fontStyle,
|
|
937
|
+
value: properties.italic ? "italic" : "normal"
|
|
938
|
+
});
|
|
939
|
+
if (properties.underline !== void 0) if (properties.underline) {
|
|
940
|
+
attributes.push({
|
|
941
|
+
name: ATTR.underlineStyle,
|
|
942
|
+
value: "solid"
|
|
943
|
+
});
|
|
944
|
+
attributes.push({
|
|
945
|
+
name: ATTR.underlineWidth,
|
|
946
|
+
value: "auto"
|
|
947
|
+
});
|
|
948
|
+
attributes.push({
|
|
949
|
+
name: ATTR.underlineColor,
|
|
950
|
+
value: "font-color"
|
|
951
|
+
});
|
|
952
|
+
} else attributes.push({
|
|
953
|
+
name: ATTR.underlineStyle,
|
|
954
|
+
value: "none"
|
|
955
|
+
});
|
|
956
|
+
if (properties.strike !== void 0) if (properties.strike) {
|
|
957
|
+
attributes.push({
|
|
958
|
+
name: ATTR.lineThroughStyle,
|
|
959
|
+
value: "solid"
|
|
960
|
+
});
|
|
961
|
+
attributes.push({
|
|
962
|
+
name: ATTR.lineThroughType,
|
|
963
|
+
value: "single"
|
|
964
|
+
});
|
|
965
|
+
} else attributes.push({
|
|
966
|
+
name: ATTR.lineThroughStyle,
|
|
967
|
+
value: "none"
|
|
968
|
+
});
|
|
969
|
+
if (properties.fontFamily !== void 0) attributes.push({
|
|
970
|
+
name: ATTR.fontFamily,
|
|
971
|
+
value: encodeXmlText(properties.fontFamily)
|
|
972
|
+
});
|
|
973
|
+
if (properties.sizePt !== void 0) attributes.push({
|
|
974
|
+
name: ATTR.fontSize,
|
|
975
|
+
value: formatPt(properties.sizePt)
|
|
976
|
+
});
|
|
977
|
+
if (properties.color !== void 0) attributes.push({
|
|
978
|
+
name: ATTR.color,
|
|
979
|
+
value: formatColor(properties.color)
|
|
980
|
+
});
|
|
981
|
+
return attributes;
|
|
982
|
+
}
|
|
983
|
+
function paragraphPropertiesToAttributes(properties) {
|
|
984
|
+
const attributes = [];
|
|
985
|
+
if (properties.alignment !== void 0) attributes.push({
|
|
986
|
+
name: ATTR.textAlign,
|
|
987
|
+
value: properties.alignment
|
|
988
|
+
});
|
|
989
|
+
if (properties.spacingBeforePt !== void 0) attributes.push({
|
|
990
|
+
name: ATTR.marginTop,
|
|
991
|
+
value: formatPt(properties.spacingBeforePt)
|
|
992
|
+
});
|
|
993
|
+
if (properties.spacingAfterPt !== void 0) attributes.push({
|
|
994
|
+
name: ATTR.marginBottom,
|
|
995
|
+
value: formatPt(properties.spacingAfterPt)
|
|
996
|
+
});
|
|
997
|
+
if (properties.lineSpacing !== void 0) attributes.push({
|
|
998
|
+
name: ATTR.lineHeight,
|
|
999
|
+
value: formatPercentageMultiplier(properties.lineSpacing)
|
|
1000
|
+
});
|
|
1001
|
+
if (properties.indentLeftPt !== void 0) attributes.push({
|
|
1002
|
+
name: ATTR.marginLeft,
|
|
1003
|
+
value: formatPt(properties.indentLeftPt)
|
|
1004
|
+
});
|
|
1005
|
+
if (properties.indentFirstLinePt !== void 0) attributes.push({
|
|
1006
|
+
name: ATTR.textIndent,
|
|
1007
|
+
value: formatPt(properties.indentFirstLinePt)
|
|
1008
|
+
});
|
|
1009
|
+
return attributes;
|
|
1010
|
+
}
|
|
1011
|
+
//#endregion
|
|
1012
|
+
//#region src/styles/serialize.ts
|
|
1013
|
+
function attributesToRecord(attributes) {
|
|
1014
|
+
const record = {};
|
|
1015
|
+
for (const attribute of attributes) record[attribute.name] = attribute.value;
|
|
1016
|
+
return record;
|
|
1017
|
+
}
|
|
1018
|
+
function buildStylePropertyElements(properties) {
|
|
1019
|
+
const elements = [];
|
|
1020
|
+
const paragraphAttributes = paragraphPropertiesToAttributes(properties);
|
|
1021
|
+
if (paragraphAttributes.length > 0) elements.push(el("style:paragraph-properties", attributesToRecord(paragraphAttributes)));
|
|
1022
|
+
const textAttributes = textPropertiesToAttributes(properties);
|
|
1023
|
+
if (textAttributes.length > 0) elements.push(el("style:text-properties", attributesToRecord(textAttributes)));
|
|
1024
|
+
return elements;
|
|
1025
|
+
}
|
|
1026
|
+
function canonicalPropertiesString(properties) {
|
|
1027
|
+
return [...paragraphPropertiesToAttributes(properties), ...textPropertiesToAttributes(properties)].map((attribute) => `${attribute.name}=${attribute.value}`).join("|");
|
|
1028
|
+
}
|
|
1029
|
+
//#endregion
|
|
1030
|
+
//#region src/styles/registry.ts
|
|
1031
|
+
const STYLE_FAMILIES = [
|
|
1032
|
+
"paragraph",
|
|
1033
|
+
"text",
|
|
1034
|
+
"table",
|
|
1035
|
+
"table-column",
|
|
1036
|
+
"table-row",
|
|
1037
|
+
"table-cell",
|
|
1038
|
+
"graphic"
|
|
1039
|
+
];
|
|
1040
|
+
function isStyleFamily(value) {
|
|
1041
|
+
return value === "paragraph" || value === "text" || value === "table" || value === "table-column" || value === "table-row" || value === "table-cell" || value === "graphic";
|
|
1042
|
+
}
|
|
1043
|
+
const CONTENT_PREFIXES = {
|
|
1044
|
+
paragraph: "P",
|
|
1045
|
+
text: "T",
|
|
1046
|
+
table: "ta",
|
|
1047
|
+
"table-column": "co",
|
|
1048
|
+
"table-row": "ro",
|
|
1049
|
+
"table-cell": "ce",
|
|
1050
|
+
graphic: "fr"
|
|
1051
|
+
};
|
|
1052
|
+
const STYLES_PREFIXES = {
|
|
1053
|
+
paragraph: "PS",
|
|
1054
|
+
text: "TS",
|
|
1055
|
+
table: "taS",
|
|
1056
|
+
"table-column": "coS",
|
|
1057
|
+
"table-row": "roS",
|
|
1058
|
+
"table-cell": "ceS",
|
|
1059
|
+
graphic: "frS"
|
|
1060
|
+
};
|
|
1061
|
+
function prefixesForPart(partPath) {
|
|
1062
|
+
const baseName = partPath.slice(partPath.lastIndexOf("/") + 1);
|
|
1063
|
+
if (baseName === "content.xml") return CONTENT_PREFIXES;
|
|
1064
|
+
if (baseName === "styles.xml") return STYLES_PREFIXES;
|
|
1065
|
+
throw new Error(`StyleRegistry.forPart: expected a part named "content.xml" or "styles.xml" (by base name), got "${partPath}"`);
|
|
1066
|
+
}
|
|
1067
|
+
function emptyFamilySets() {
|
|
1068
|
+
return {
|
|
1069
|
+
paragraph: /* @__PURE__ */ new Set(),
|
|
1070
|
+
text: /* @__PURE__ */ new Set(),
|
|
1071
|
+
table: /* @__PURE__ */ new Set(),
|
|
1072
|
+
"table-column": /* @__PURE__ */ new Set(),
|
|
1073
|
+
"table-row": /* @__PURE__ */ new Set(),
|
|
1074
|
+
"table-cell": /* @__PURE__ */ new Set(),
|
|
1075
|
+
graphic: /* @__PURE__ */ new Set()
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
function attrValue$1(element, name) {
|
|
1079
|
+
return element.attributes.find((attribute) => attribute.name === name)?.value;
|
|
1080
|
+
}
|
|
1081
|
+
function findDirectChild(nodes, tag) {
|
|
1082
|
+
for (const node of nodes) if (node.type === "element" && node.tag === tag) return node;
|
|
1083
|
+
}
|
|
1084
|
+
function findRootElement(nodes) {
|
|
1085
|
+
const root = nodes.find((node) => node.type === "element");
|
|
1086
|
+
if (root === void 0) throw new Error("StyleRegistry: part has no root XML element -- construct the part's minimal root (office:document-content/office:document-styles) before building a StyleRegistry for it");
|
|
1087
|
+
return root;
|
|
1088
|
+
}
|
|
1089
|
+
function ensureAutomaticStyles(root) {
|
|
1090
|
+
const existing = findDirectChild(root.children, "office:automatic-styles");
|
|
1091
|
+
if (existing !== void 0) return existing;
|
|
1092
|
+
const created = el("office:automatic-styles");
|
|
1093
|
+
const insertBeforeTags = /* @__PURE__ */ new Set([
|
|
1094
|
+
"office:body",
|
|
1095
|
+
"office:master-styles",
|
|
1096
|
+
"office:settings"
|
|
1097
|
+
]);
|
|
1098
|
+
const insertIndex = root.children.findIndex((node) => node.type === "element" && insertBeforeTags.has(node.tag));
|
|
1099
|
+
if (insertIndex === -1) root.children.push(created);
|
|
1100
|
+
else root.children.splice(insertIndex, 0, created);
|
|
1101
|
+
return created;
|
|
1102
|
+
}
|
|
1103
|
+
function reserveStyleNames(container, reserved) {
|
|
1104
|
+
for (const child of container.children) {
|
|
1105
|
+
if (child.type !== "element" || child.tag !== "style:style") continue;
|
|
1106
|
+
const name = attrValue$1(child, "style:name");
|
|
1107
|
+
const family = attrValue$1(child, "style:family");
|
|
1108
|
+
if (name === void 0 || family === void 0 || !isStyleFamily(family)) continue;
|
|
1109
|
+
reserved[family].add(name);
|
|
1110
|
+
}
|
|
1111
|
+
}
|
|
1112
|
+
const FINGERPRINT_SEPARATOR = "\0";
|
|
1113
|
+
const NO_PARENT_SENTINEL = "";
|
|
1114
|
+
function computeFingerprint(family, properties, parentStyleName) {
|
|
1115
|
+
const parentComponent = parentStyleName ?? NO_PARENT_SENTINEL;
|
|
1116
|
+
return [
|
|
1117
|
+
family,
|
|
1118
|
+
canonicalPropertiesString(properties),
|
|
1119
|
+
parentComponent
|
|
1120
|
+
].join(FINGERPRINT_SEPARATOR);
|
|
1121
|
+
}
|
|
1122
|
+
var StyleRegistry = class StyleRegistry {
|
|
1123
|
+
automaticStyles;
|
|
1124
|
+
prefixes;
|
|
1125
|
+
reservedByFamily;
|
|
1126
|
+
knownStyles = /* @__PURE__ */ new Map();
|
|
1127
|
+
fingerprintToName = /* @__PURE__ */ new Map();
|
|
1128
|
+
nameToFingerprint = /* @__PURE__ */ new Map();
|
|
1129
|
+
familyCounters = {
|
|
1130
|
+
paragraph: 1,
|
|
1131
|
+
text: 1,
|
|
1132
|
+
table: 1,
|
|
1133
|
+
"table-column": 1,
|
|
1134
|
+
"table-row": 1,
|
|
1135
|
+
"table-cell": 1,
|
|
1136
|
+
graphic: 1
|
|
1137
|
+
};
|
|
1138
|
+
constructor(automaticStyles, prefixes, reservedByFamily) {
|
|
1139
|
+
this.automaticStyles = automaticStyles;
|
|
1140
|
+
this.prefixes = prefixes;
|
|
1141
|
+
this.reservedByFamily = reservedByFamily;
|
|
1142
|
+
}
|
|
1143
|
+
static forPart(pkg, partPath, options = {}) {
|
|
1144
|
+
const part = pkg.parts[partPath];
|
|
1145
|
+
if (part?.kind !== "xml") throw new Error(`StyleRegistry.forPart: "${partPath}" is not an XML part of the given package`);
|
|
1146
|
+
const prefixes = prefixesForPart(partPath);
|
|
1147
|
+
const root = findRootElement(part.nodes);
|
|
1148
|
+
const automaticStyles = ensureAutomaticStyles(root);
|
|
1149
|
+
const reservedByFamily = emptyFamilySets();
|
|
1150
|
+
const registry = new StyleRegistry(automaticStyles, prefixes, reservedByFamily);
|
|
1151
|
+
for (const child of automaticStyles.children) {
|
|
1152
|
+
if (child.type !== "element" || child.tag !== "style:style") continue;
|
|
1153
|
+
const name = attrValue$1(child, "style:name");
|
|
1154
|
+
const family = attrValue$1(child, "style:family");
|
|
1155
|
+
if (name === void 0 || family === void 0 || !isStyleFamily(family)) continue;
|
|
1156
|
+
registry.knownStyles.set(name, child);
|
|
1157
|
+
reservedByFamily[family].add(name);
|
|
1158
|
+
const parsed = parseStyleElementProperties(child);
|
|
1159
|
+
if (!parsed.hasUnknown) {
|
|
1160
|
+
const parentStyleName = attrValue$1(child, "style:parent-style-name");
|
|
1161
|
+
const fingerprint = computeFingerprint(family, parsed.properties, parentStyleName);
|
|
1162
|
+
if (!registry.fingerprintToName.has(fingerprint)) {
|
|
1163
|
+
registry.fingerprintToName.set(fingerprint, name);
|
|
1164
|
+
registry.nameToFingerprint.set(name, fingerprint);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
const ownStyles = findDirectChild(root.children, "office:styles");
|
|
1169
|
+
if (ownStyles !== void 0) reserveStyleNames(ownStyles, reservedByFamily);
|
|
1170
|
+
if (options.otherPart !== void 0) {
|
|
1171
|
+
const otherPart = options.otherPart.pkg.parts[options.otherPart.partPath];
|
|
1172
|
+
if (otherPart?.kind === "xml") {
|
|
1173
|
+
const otherRoot = findRootElement(otherPart.nodes);
|
|
1174
|
+
const otherAutomatic = findDirectChild(otherRoot.children, "office:automatic-styles");
|
|
1175
|
+
if (otherAutomatic !== void 0) reserveStyleNames(otherAutomatic, reservedByFamily);
|
|
1176
|
+
const otherStyles = findDirectChild(otherRoot.children, "office:styles");
|
|
1177
|
+
if (otherStyles !== void 0) reserveStyleNames(otherStyles, reservedByFamily);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
if (options.additionalReservedNames !== void 0) for (const family of STYLE_FAMILIES) for (const name of options.additionalReservedNames) reservedByFamily[family].add(name);
|
|
1181
|
+
return registry;
|
|
1182
|
+
}
|
|
1183
|
+
fingerprint(request) {
|
|
1184
|
+
return computeFingerprint(request.family, request.properties, request.parentStyleName);
|
|
1185
|
+
}
|
|
1186
|
+
intern(request) {
|
|
1187
|
+
const fingerprint = this.fingerprint(request);
|
|
1188
|
+
const existingName = this.fingerprintToName.get(fingerprint);
|
|
1189
|
+
if (existingName !== void 0) return existingName;
|
|
1190
|
+
const name = this.mintName(request.family);
|
|
1191
|
+
const attributes = {
|
|
1192
|
+
"style:name": name,
|
|
1193
|
+
"style:family": request.family
|
|
1194
|
+
};
|
|
1195
|
+
if (request.parentStyleName !== void 0) attributes["style:parent-style-name"] = encodeXmlText(request.parentStyleName);
|
|
1196
|
+
const styleElement = el("style:style", attributes, buildStylePropertyElements(request.properties));
|
|
1197
|
+
this.automaticStyles.children.push(styleElement);
|
|
1198
|
+
this.knownStyles.set(name, styleElement);
|
|
1199
|
+
this.reservedByFamily[request.family].add(name);
|
|
1200
|
+
this.fingerprintToName.set(fingerprint, name);
|
|
1201
|
+
this.nameToFingerprint.set(name, fingerprint);
|
|
1202
|
+
return name;
|
|
1203
|
+
}
|
|
1204
|
+
mintName(family) {
|
|
1205
|
+
const prefix = this.prefixes[family];
|
|
1206
|
+
const reserved = this.reservedByFamily[family];
|
|
1207
|
+
let counter = this.familyCounters[family];
|
|
1208
|
+
while (reserved.has(`${prefix}${counter}`)) counter += 1;
|
|
1209
|
+
const name = `${prefix}${counter}`;
|
|
1210
|
+
this.familyCounters[family] = counter + 1;
|
|
1211
|
+
return name;
|
|
1212
|
+
}
|
|
1213
|
+
names() {
|
|
1214
|
+
return [...this.knownStyles.keys()];
|
|
1215
|
+
}
|
|
1216
|
+
gc(referenced) {
|
|
1217
|
+
let removed = 0;
|
|
1218
|
+
for (const [name, element] of [...this.knownStyles]) {
|
|
1219
|
+
if (referenced.has(name)) continue;
|
|
1220
|
+
const index = this.automaticStyles.children.indexOf(element);
|
|
1221
|
+
if (index !== -1) this.automaticStyles.children.splice(index, 1);
|
|
1222
|
+
this.knownStyles.delete(name);
|
|
1223
|
+
const fingerprint = this.nameToFingerprint.get(name);
|
|
1224
|
+
if (fingerprint !== void 0) {
|
|
1225
|
+
this.fingerprintToName.delete(fingerprint);
|
|
1226
|
+
this.nameToFingerprint.delete(name);
|
|
1227
|
+
}
|
|
1228
|
+
removed += 1;
|
|
1229
|
+
}
|
|
1230
|
+
return removed;
|
|
1231
|
+
}
|
|
1232
|
+
};
|
|
1233
|
+
//#endregion
|
|
1234
|
+
//#region src/xml/query.ts
|
|
1235
|
+
function rootElement(nodes) {
|
|
1236
|
+
for (const node of nodes) if (node.type === "element") return node;
|
|
1237
|
+
}
|
|
1238
|
+
function findChildElement(nodes, tag) {
|
|
1239
|
+
for (const node of nodes) if (node.type === "element" && node.tag === tag) return node;
|
|
1240
|
+
}
|
|
1241
|
+
function childrenWithTag(element, tag) {
|
|
1242
|
+
const out = [];
|
|
1243
|
+
for (const child of element.children) if (child.type === "element" && child.tag === tag) out.push(child);
|
|
1244
|
+
return out;
|
|
1245
|
+
}
|
|
1246
|
+
function attrValue(element, name) {
|
|
1247
|
+
return element.attributes.find((attribute) => attribute.name === name)?.value;
|
|
1248
|
+
}
|
|
1249
|
+
//#endregion
|
|
1250
|
+
//#region src/typed/shared/text.ts
|
|
1251
|
+
function getOdfSpaceCount(element) {
|
|
1252
|
+
const raw = attrValue(element, "text:c");
|
|
1253
|
+
if (raw === void 0) return 1;
|
|
1254
|
+
const parsed = Number.parseInt(raw, 10);
|
|
1255
|
+
if (!Number.isInteger(parsed) || parsed < 0 || String(parsed) !== raw) throw new Error(`getOdfSpaceCount: text:s has a malformed text:c attribute: "${raw}"`);
|
|
1256
|
+
return parsed;
|
|
1257
|
+
}
|
|
1258
|
+
function measureOdfNodeLength(node) {
|
|
1259
|
+
if (node.type === "text") return node.value.length;
|
|
1260
|
+
if (node.type !== "element") return 0;
|
|
1261
|
+
if (node.tag === "text:s") return getOdfSpaceCount(node);
|
|
1262
|
+
if (node.tag === "text:tab" || node.tag === "text:line-break") return 1;
|
|
1263
|
+
if (node.tag === "text:span") return sumOdfNodeLength(node.children);
|
|
1264
|
+
return 0;
|
|
1265
|
+
}
|
|
1266
|
+
function sumOdfNodeLength(nodes) {
|
|
1267
|
+
let total = 0;
|
|
1268
|
+
for (const node of nodes) total += measureOdfNodeLength(node);
|
|
1269
|
+
return total;
|
|
1270
|
+
}
|
|
1271
|
+
function decodeOdfNode(node) {
|
|
1272
|
+
if (node.type === "text") return decodeXmlText(node.value);
|
|
1273
|
+
if (node.type !== "element") return "";
|
|
1274
|
+
if (node.tag === "text:s") return " ".repeat(getOdfSpaceCount(node));
|
|
1275
|
+
if (node.tag === "text:tab") return " ";
|
|
1276
|
+
if (node.tag === "text:line-break") return "\n";
|
|
1277
|
+
if (node.tag === "text:span") return decodeOdfText(node);
|
|
1278
|
+
return "";
|
|
1279
|
+
}
|
|
1280
|
+
function decodeOdfText(container) {
|
|
1281
|
+
let text = "";
|
|
1282
|
+
for (const child of container.children) text += decodeOdfNode(child);
|
|
1283
|
+
return text;
|
|
1284
|
+
}
|
|
1285
|
+
//#endregion
|
|
1286
|
+
//#region src/styles/span.ts
|
|
1287
|
+
function ensureSpan(paragraph, start, end, styleName) {
|
|
1288
|
+
if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end < start) throw new Error(`ensureSpan: invalid range [${start}, ${end})`);
|
|
1289
|
+
const total = sumOdfNodeLength(paragraph.children);
|
|
1290
|
+
if (end > total) throw new Error(`ensureSpan: range end ${end} exceeds the container's total character length ${total}`);
|
|
1291
|
+
const { before, after: rest } = splitChildrenAt(paragraph.children, start);
|
|
1292
|
+
const { before: middle, after } = splitChildrenAt(rest, end - start);
|
|
1293
|
+
let span;
|
|
1294
|
+
const soleChild = middle.length === 1 ? middle[0] : void 0;
|
|
1295
|
+
if (soleChild?.type === "element" && soleChild.tag === "text:span") {
|
|
1296
|
+
span = soleChild;
|
|
1297
|
+
setStyleName(span, styleName);
|
|
1298
|
+
} else span = el("text:span", { "text:style-name": encodeXmlText(styleName) }, middle);
|
|
1299
|
+
paragraph.children = [
|
|
1300
|
+
...before,
|
|
1301
|
+
span,
|
|
1302
|
+
...after
|
|
1303
|
+
];
|
|
1304
|
+
return span;
|
|
1305
|
+
}
|
|
1306
|
+
function cloneAttributes(attributes) {
|
|
1307
|
+
return attributes.map((attribute) => ({ ...attribute }));
|
|
1308
|
+
}
|
|
1309
|
+
function setStyleName(span, styleName) {
|
|
1310
|
+
const encoded = encodeXmlText(styleName);
|
|
1311
|
+
const existing = span.attributes.find((attribute) => attribute.name === "text:style-name");
|
|
1312
|
+
if (existing !== void 0) {
|
|
1313
|
+
existing.value = encoded;
|
|
1314
|
+
return;
|
|
1315
|
+
}
|
|
1316
|
+
span.attributes.push({
|
|
1317
|
+
name: "text:style-name",
|
|
1318
|
+
value: encoded
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
function buildSpaceRun(count) {
|
|
1322
|
+
return count === 1 ? el("text:s") : el("text:s", { "text:c": String(count) });
|
|
1323
|
+
}
|
|
1324
|
+
function splitNode(node, offset) {
|
|
1325
|
+
if (node.type === "text") return {
|
|
1326
|
+
left: {
|
|
1327
|
+
type: "text",
|
|
1328
|
+
value: node.value.slice(0, offset)
|
|
1329
|
+
},
|
|
1330
|
+
right: {
|
|
1331
|
+
type: "text",
|
|
1332
|
+
value: node.value.slice(offset)
|
|
1333
|
+
}
|
|
1334
|
+
};
|
|
1335
|
+
if (node.type === "element" && node.tag === "text:s") {
|
|
1336
|
+
const count = getOdfSpaceCount(node);
|
|
1337
|
+
return {
|
|
1338
|
+
left: buildSpaceRun(offset),
|
|
1339
|
+
right: buildSpaceRun(count - offset)
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
if (node.type === "element" && node.tag === "text:span") {
|
|
1343
|
+
const inner = splitChildrenAt(node.children, offset);
|
|
1344
|
+
return {
|
|
1345
|
+
left: inner.before.length === 0 ? void 0 : {
|
|
1346
|
+
...node,
|
|
1347
|
+
attributes: cloneAttributes(node.attributes),
|
|
1348
|
+
children: inner.before
|
|
1349
|
+
},
|
|
1350
|
+
right: inner.after.length === 0 ? void 0 : {
|
|
1351
|
+
...node,
|
|
1352
|
+
attributes: cloneAttributes(node.attributes),
|
|
1353
|
+
children: inner.after
|
|
1354
|
+
}
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
const label = node.type === "element" ? node.tag : node.type;
|
|
1358
|
+
throw new Error(`ensureSpan: cannot split "${label}" at a fractional offset -- this indicates a character-length computation bug, since every node type with length 1 or 0 should never reach this branch`);
|
|
1359
|
+
}
|
|
1360
|
+
function splitChildrenAt(children, offset) {
|
|
1361
|
+
if (offset <= 0) return {
|
|
1362
|
+
before: [],
|
|
1363
|
+
after: [...children]
|
|
1364
|
+
};
|
|
1365
|
+
const before = [];
|
|
1366
|
+
let remaining = offset;
|
|
1367
|
+
for (let index = 0; index < children.length; index += 1) {
|
|
1368
|
+
if (remaining === 0) return {
|
|
1369
|
+
before,
|
|
1370
|
+
after: children.slice(index)
|
|
1371
|
+
};
|
|
1372
|
+
const node = children[index];
|
|
1373
|
+
const length = measureOdfNodeLength(node);
|
|
1374
|
+
if (remaining >= length) {
|
|
1375
|
+
before.push(node);
|
|
1376
|
+
remaining -= length;
|
|
1377
|
+
continue;
|
|
1378
|
+
}
|
|
1379
|
+
const { left, right } = splitNode(node, remaining);
|
|
1380
|
+
const after = [];
|
|
1381
|
+
if (left !== void 0) before.push(left);
|
|
1382
|
+
if (right !== void 0) after.push(right);
|
|
1383
|
+
after.push(...children.slice(index + 1));
|
|
1384
|
+
return {
|
|
1385
|
+
before,
|
|
1386
|
+
after
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
return {
|
|
1390
|
+
before,
|
|
1391
|
+
after: []
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
//#endregion
|
|
1395
|
+
//#region src/typed/shared/a1.ts
|
|
1396
|
+
const ALPHABET_SIZE = 26;
|
|
1397
|
+
const ALPHABET_START_CODE = "A".charCodeAt(0);
|
|
1398
|
+
function columnIndexToLetters(index) {
|
|
1399
|
+
if (!Number.isInteger(index) || index < 0) throw new Error(`columnIndexToLetters: index must be a non-negative integer, got ${index}`);
|
|
1400
|
+
let remaining = index + 1;
|
|
1401
|
+
let letters = "";
|
|
1402
|
+
while (remaining > 0) {
|
|
1403
|
+
const digit = (remaining - 1) % ALPHABET_SIZE;
|
|
1404
|
+
letters = String.fromCharCode(ALPHABET_START_CODE + digit) + letters;
|
|
1405
|
+
remaining = Math.floor((remaining - 1) / ALPHABET_SIZE);
|
|
1406
|
+
}
|
|
1407
|
+
return letters;
|
|
1408
|
+
}
|
|
1409
|
+
function cellReference(columnIndex, rowIndex) {
|
|
1410
|
+
if (!Number.isInteger(rowIndex) || rowIndex < 0) throw new Error(`cellReference: rowIndex must be a non-negative integer, got ${rowIndex}`);
|
|
1411
|
+
return `${columnIndexToLetters(columnIndex)}${rowIndex + 1}`;
|
|
1412
|
+
}
|
|
1413
|
+
function validateRepeatCount(repeatCount, caller) {
|
|
1414
|
+
if (!Number.isInteger(repeatCount) || repeatCount < 1) throw new Error(`${caller}: repeatCount must be a positive integer, got ${repeatCount}`);
|
|
1415
|
+
}
|
|
1416
|
+
var TableCursor = class {
|
|
1417
|
+
columnCursor = 0;
|
|
1418
|
+
rowCursor = 0;
|
|
1419
|
+
get columnIndex() {
|
|
1420
|
+
return this.columnCursor;
|
|
1421
|
+
}
|
|
1422
|
+
get rowIndex() {
|
|
1423
|
+
return this.rowCursor;
|
|
1424
|
+
}
|
|
1425
|
+
nextCell(repeatCount = 1) {
|
|
1426
|
+
validateRepeatCount(repeatCount, "TableCursor.nextCell");
|
|
1427
|
+
const reference = cellReference(this.columnCursor, this.rowCursor);
|
|
1428
|
+
this.columnCursor += repeatCount;
|
|
1429
|
+
return reference;
|
|
1430
|
+
}
|
|
1431
|
+
nextRow(repeatCount = 1) {
|
|
1432
|
+
validateRepeatCount(repeatCount, "TableCursor.nextRow");
|
|
1433
|
+
this.rowCursor += repeatCount;
|
|
1434
|
+
this.columnCursor = 0;
|
|
1435
|
+
}
|
|
1436
|
+
};
|
|
1437
|
+
//#endregion
|
|
1438
|
+
//#region src/typed/shared/geometry.ts
|
|
1439
|
+
function parsePageSize(pageLayoutProperties) {
|
|
1440
|
+
const widthValue = attrValue(pageLayoutProperties, "fo:page-width");
|
|
1441
|
+
const heightValue = attrValue(pageLayoutProperties, "fo:page-height");
|
|
1442
|
+
if (widthValue === void 0 || heightValue === void 0) return;
|
|
1443
|
+
const widthPt = parseOdfLength(widthValue);
|
|
1444
|
+
const heightPt = parseOdfLength(heightValue);
|
|
1445
|
+
if (widthPt === void 0 || heightPt === void 0) return;
|
|
1446
|
+
return {
|
|
1447
|
+
widthPt,
|
|
1448
|
+
heightPt
|
|
1449
|
+
};
|
|
1450
|
+
}
|
|
1451
|
+
function parseMargins(pageLayoutProperties) {
|
|
1452
|
+
const topValue = attrValue(pageLayoutProperties, "fo:margin-top");
|
|
1453
|
+
const rightValue = attrValue(pageLayoutProperties, "fo:margin-right");
|
|
1454
|
+
const bottomValue = attrValue(pageLayoutProperties, "fo:margin-bottom");
|
|
1455
|
+
const leftValue = attrValue(pageLayoutProperties, "fo:margin-left");
|
|
1456
|
+
if (topValue === void 0 || rightValue === void 0 || bottomValue === void 0 || leftValue === void 0) return;
|
|
1457
|
+
const topPt = parseOdfLength(topValue);
|
|
1458
|
+
const rightPt = parseOdfLength(rightValue);
|
|
1459
|
+
const bottomPt = parseOdfLength(bottomValue);
|
|
1460
|
+
const leftPt = parseOdfLength(leftValue);
|
|
1461
|
+
if (topPt === void 0 || rightPt === void 0 || bottomPt === void 0 || leftPt === void 0) return;
|
|
1462
|
+
return {
|
|
1463
|
+
topPt,
|
|
1464
|
+
rightPt,
|
|
1465
|
+
bottomPt,
|
|
1466
|
+
leftPt
|
|
1467
|
+
};
|
|
1468
|
+
}
|
|
1469
|
+
function parseBox(element) {
|
|
1470
|
+
const xValue = attrValue(element, "svg:x");
|
|
1471
|
+
const yValue = attrValue(element, "svg:y");
|
|
1472
|
+
const widthValue = attrValue(element, "svg:width");
|
|
1473
|
+
const heightValue = attrValue(element, "svg:height");
|
|
1474
|
+
if (xValue === void 0 || yValue === void 0 || widthValue === void 0 || heightValue === void 0) return;
|
|
1475
|
+
const xPt = parseOdfLength(xValue);
|
|
1476
|
+
const yPt = parseOdfLength(yValue);
|
|
1477
|
+
const widthPt = parseOdfLength(widthValue);
|
|
1478
|
+
const heightPt = parseOdfLength(heightValue);
|
|
1479
|
+
if (xPt === void 0 || yPt === void 0 || widthPt === void 0 || heightPt === void 0) return;
|
|
1480
|
+
return {
|
|
1481
|
+
xPt,
|
|
1482
|
+
yPt,
|
|
1483
|
+
widthPt,
|
|
1484
|
+
heightPt
|
|
1485
|
+
};
|
|
1486
|
+
}
|
|
1487
|
+
//#endregion
|
|
1488
|
+
//#region src/typed/shared/cascade.ts
|
|
1489
|
+
const STYLE_PARTS = ["content.xml", "styles.xml"];
|
|
1490
|
+
const NAME_KEY_SEPARATOR = "\0";
|
|
1491
|
+
function nameKey(family, name) {
|
|
1492
|
+
return `${family}${NAME_KEY_SEPARATOR}${name}`;
|
|
1493
|
+
}
|
|
1494
|
+
function collectStyles(pkg) {
|
|
1495
|
+
const byName = /* @__PURE__ */ new Map();
|
|
1496
|
+
const defaultByFamily = /* @__PURE__ */ new Map();
|
|
1497
|
+
for (const partPath of STYLE_PARTS) {
|
|
1498
|
+
const part = pkg.parts[partPath];
|
|
1499
|
+
if (part?.kind !== "xml") continue;
|
|
1500
|
+
const root = rootElement(part.nodes);
|
|
1501
|
+
if (root === void 0) continue;
|
|
1502
|
+
for (const containerTag of ["office:automatic-styles", "office:styles"]) {
|
|
1503
|
+
const container = findChildElement(root.children, containerTag);
|
|
1504
|
+
if (container === void 0) continue;
|
|
1505
|
+
for (const child of container.children) {
|
|
1506
|
+
if (child.type !== "element") continue;
|
|
1507
|
+
if (child.tag === "style:style") {
|
|
1508
|
+
const name = attrValue(child, "style:name");
|
|
1509
|
+
const family = attrValue(child, "style:family");
|
|
1510
|
+
if (name === void 0 || family === void 0 || !isStyleFamily(family)) continue;
|
|
1511
|
+
byName.set(nameKey(family, name), child);
|
|
1512
|
+
} else if (child.tag === "style:default-style") {
|
|
1513
|
+
const family = attrValue(child, "style:family");
|
|
1514
|
+
if (family === void 0 || !isStyleFamily(family)) continue;
|
|
1515
|
+
if (!defaultByFamily.has(family)) defaultByFamily.set(family, child);
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
return {
|
|
1521
|
+
byName,
|
|
1522
|
+
defaultByFamily
|
|
1523
|
+
};
|
|
1524
|
+
}
|
|
1525
|
+
function resolveStyle(styleName, family, pkg) {
|
|
1526
|
+
const { byName, defaultByFamily } = collectStyles(pkg);
|
|
1527
|
+
const diagnostics = [];
|
|
1528
|
+
const defaultElement = defaultByFamily.get(family);
|
|
1529
|
+
let properties = defaultElement === void 0 ? {} : parseStyleElementProperties(defaultElement).properties;
|
|
1530
|
+
if (styleName === void 0) return {
|
|
1531
|
+
properties,
|
|
1532
|
+
diagnostics
|
|
1533
|
+
};
|
|
1534
|
+
const chain = [];
|
|
1535
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1536
|
+
let currentName = styleName;
|
|
1537
|
+
while (currentName !== void 0) {
|
|
1538
|
+
const key = nameKey(family, currentName);
|
|
1539
|
+
if (visited.has(key)) {
|
|
1540
|
+
diagnostics.push({
|
|
1541
|
+
severity: "warning",
|
|
1542
|
+
message: `cyclic style:parent-style-name chain detected at style "${currentName}" (family "${family}") -- breaking the cycle and stopping cascade resolution at this point`
|
|
1543
|
+
});
|
|
1544
|
+
break;
|
|
1545
|
+
}
|
|
1546
|
+
visited.add(key);
|
|
1547
|
+
const element = byName.get(key);
|
|
1548
|
+
if (element === void 0) {
|
|
1549
|
+
diagnostics.push({
|
|
1550
|
+
severity: "warning",
|
|
1551
|
+
message: `style "${currentName}" (family "${family}") was not found in content.xml or styles.xml -- stopping cascade resolution at this point`
|
|
1552
|
+
});
|
|
1553
|
+
break;
|
|
1554
|
+
}
|
|
1555
|
+
chain.push(element);
|
|
1556
|
+
currentName = attrValue(element, "style:parent-style-name");
|
|
1557
|
+
}
|
|
1558
|
+
chain.reverse();
|
|
1559
|
+
for (const element of chain) properties = {
|
|
1560
|
+
...properties,
|
|
1561
|
+
...parseStyleElementProperties(element).properties
|
|
1562
|
+
};
|
|
1563
|
+
return {
|
|
1564
|
+
properties,
|
|
1565
|
+
diagnostics
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
//#endregion
|
|
1569
|
+
//#region src/typed/shared/metadata.ts
|
|
1570
|
+
const META_PART = "meta.xml";
|
|
1571
|
+
function elementText(element) {
|
|
1572
|
+
let text = "";
|
|
1573
|
+
for (const child of element.children) if (child.type === "text") text += decodeXmlText(child.value);
|
|
1574
|
+
return text;
|
|
1575
|
+
}
|
|
1576
|
+
function firstElementText(container, tag) {
|
|
1577
|
+
const element = childrenWithTag(container, tag)[0];
|
|
1578
|
+
if (element === void 0) return;
|
|
1579
|
+
const text = elementText(element);
|
|
1580
|
+
return text.length > 0 ? text : void 0;
|
|
1581
|
+
}
|
|
1582
|
+
function readOdfMetadata(pkg) {
|
|
1583
|
+
const part = pkg.parts[META_PART];
|
|
1584
|
+
if (part?.kind !== "xml") return {};
|
|
1585
|
+
const root = rootElement(part.nodes);
|
|
1586
|
+
const meta = root === void 0 ? void 0 : findChildElement(root.children, "office:meta");
|
|
1587
|
+
if (meta === void 0) return {};
|
|
1588
|
+
const metadata = {};
|
|
1589
|
+
const title = firstElementText(meta, "dc:title");
|
|
1590
|
+
if (title !== void 0) metadata.title = title;
|
|
1591
|
+
const author = firstElementText(meta, "meta:initial-creator");
|
|
1592
|
+
if (author !== void 0) metadata.author = author;
|
|
1593
|
+
const subject = firstElementText(meta, "dc:subject");
|
|
1594
|
+
if (subject !== void 0) metadata.subject = subject;
|
|
1595
|
+
const keywords = childrenWithTag(meta, "meta:keyword").map(elementText).filter((keyword) => keyword.length > 0);
|
|
1596
|
+
if (keywords.length > 0) metadata.keywords = keywords;
|
|
1597
|
+
const creator = firstElementText(meta, "meta:generator");
|
|
1598
|
+
if (creator !== void 0) metadata.creator = creator;
|
|
1599
|
+
const createdIso = firstElementText(meta, "meta:creation-date");
|
|
1600
|
+
if (createdIso !== void 0) metadata.createdIso = createdIso;
|
|
1601
|
+
const modifiedIso = firstElementText(meta, "dc:date");
|
|
1602
|
+
if (modifiedIso !== void 0) metadata.modifiedIso = modifiedIso;
|
|
1603
|
+
return metadata;
|
|
1604
|
+
}
|
|
1605
|
+
//#endregion
|
|
1606
|
+
Object.defineProperty(exports, "AlignmentSchema", {
|
|
1607
|
+
enumerable: true,
|
|
1608
|
+
get: function() {
|
|
1609
|
+
return document_content_model.AlignmentSchema;
|
|
1610
|
+
}
|
|
1611
|
+
});
|
|
684
1612
|
exports.AttributeSchema = AttributeSchema;
|
|
685
1613
|
exports.BinaryPartSchema = BinaryPartSchema;
|
|
686
1614
|
exports.MANIFEST_PART = MANIFEST_PART;
|
|
1615
|
+
exports.META_PART = META_PART;
|
|
687
1616
|
exports.MIMETYPE_PART = MIMETYPE_PART;
|
|
688
1617
|
exports.ManifestEntrySchema = ManifestEntrySchema;
|
|
689
1618
|
exports.ManifestProblemSchema = ManifestProblemSchema;
|
|
@@ -692,6 +1621,10 @@ exports.ODF_MEDIA_TYPES = ODF_MEDIA_TYPES;
|
|
|
692
1621
|
exports.ODF_NAMESPACES = ODF_NAMESPACES;
|
|
693
1622
|
exports.PackageSchema = PackageSchema;
|
|
694
1623
|
exports.PartSchema = PartSchema;
|
|
1624
|
+
exports.STYLE_FAMILIES = STYLE_FAMILIES;
|
|
1625
|
+
exports.StylePropertiesSchema = StylePropertiesSchema;
|
|
1626
|
+
exports.StyleRegistry = StyleRegistry;
|
|
1627
|
+
exports.TableCursor = TableCursor;
|
|
695
1628
|
exports.XmlCdataSchema = XmlCdataSchema;
|
|
696
1629
|
exports.XmlCommentSchema = XmlCommentSchema;
|
|
697
1630
|
exports.XmlDeclarationSchema = XmlDeclarationSchema;
|
|
@@ -700,25 +1633,57 @@ exports.XmlNodeSchema = XmlNodeSchema;
|
|
|
700
1633
|
exports.XmlPartSchema = XmlPartSchema;
|
|
701
1634
|
exports.XmlPiSchema = XmlPiSchema;
|
|
702
1635
|
exports.XmlTextSchema = XmlTextSchema;
|
|
1636
|
+
exports.attrValue = attrValue;
|
|
703
1637
|
exports.base64ToBytes = base64ToBytes;
|
|
704
1638
|
exports.buildManifest = buildManifest;
|
|
1639
|
+
exports.buildStylePropertyElements = buildStylePropertyElements;
|
|
705
1640
|
exports.buildXml = buildXml;
|
|
706
1641
|
exports.bytesToBase64 = bytesToBase64;
|
|
1642
|
+
exports.canonicalPropertiesString = canonicalPropertiesString;
|
|
1643
|
+
exports.cellReference = cellReference;
|
|
1644
|
+
exports.childrenWithTag = childrenWithTag;
|
|
1645
|
+
exports.columnIndexToLetters = columnIndexToLetters;
|
|
1646
|
+
exports.decodeOdfText = decodeOdfText;
|
|
707
1647
|
exports.decodePackage = decodePackage;
|
|
1648
|
+
exports.decodeXmlText = decodeXmlText;
|
|
708
1649
|
exports.el = el;
|
|
709
1650
|
exports.encodePackage = encodePackage;
|
|
710
1651
|
exports.encodeXmlText = encodeXmlText;
|
|
1652
|
+
exports.ensureSpan = ensureSpan;
|
|
1653
|
+
exports.findChildElement = findChildElement;
|
|
1654
|
+
exports.formatOdfColor = formatOdfColor;
|
|
1655
|
+
exports.formatOdfLength = formatOdfLength;
|
|
1656
|
+
exports.formatPercentageMultiplier = formatPercentageMultiplier;
|
|
1657
|
+
exports.formatPt = formatPt;
|
|
1658
|
+
exports.getOdfSpaceCount = getOdfSpaceCount;
|
|
1659
|
+
exports.isStyleFamily = isStyleFamily;
|
|
711
1660
|
exports.isXmlNode = isXmlNode;
|
|
1661
|
+
exports.measureOdfNodeLength = measureOdfNodeLength;
|
|
712
1662
|
exports.mediaTypeForExtension = mediaTypeForExtension;
|
|
713
1663
|
exports.packageCodec = packageCodec;
|
|
1664
|
+
exports.paragraphPropertiesToAttributes = paragraphPropertiesToAttributes;
|
|
1665
|
+
exports.parseBox = parseBox;
|
|
1666
|
+
exports.parseLength = parseLength;
|
|
1667
|
+
exports.parseMargins = parseMargins;
|
|
1668
|
+
exports.parseOdfColor = parseOdfColor;
|
|
1669
|
+
exports.parseOdfLength = parseOdfLength;
|
|
714
1670
|
exports.parsePackage = parsePackage;
|
|
1671
|
+
exports.parsePageSize = parsePageSize;
|
|
1672
|
+
exports.parseParagraphProperties = parseParagraphProperties;
|
|
1673
|
+
exports.parseStyleElementProperties = parseStyleElementProperties;
|
|
1674
|
+
exports.parseTextProperties = parseTextProperties;
|
|
715
1675
|
exports.parseXml = parseXml;
|
|
716
1676
|
exports.readManifest = readManifest;
|
|
717
1677
|
exports.readMimetype = readMimetype;
|
|
1678
|
+
exports.readOdfMetadata = readOdfMetadata;
|
|
1679
|
+
exports.resolveStyle = resolveStyle;
|
|
1680
|
+
exports.rootElement = rootElement;
|
|
718
1681
|
exports.serializePackage = serializePackage;
|
|
719
1682
|
exports.setDocumentMediaType = setDocumentMediaType;
|
|
720
1683
|
exports.sniffImageFormat = sniffImageFormat;
|
|
1684
|
+
exports.sumOdfNodeLength = sumOdfNodeLength;
|
|
721
1685
|
exports.syncManifest = syncManifest;
|
|
1686
|
+
exports.textPropertiesToAttributes = textPropertiesToAttributes;
|
|
722
1687
|
exports.txt = txt;
|
|
723
1688
|
exports.unzipPackage = unzipPackage;
|
|
724
1689
|
exports.validateManifest = validateManifest;
|