@stll/folio-core 0.43.0 → 0.45.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/ai-edits/__fixtures__/paragraphs.js +2 -2
- package/dist/ai-edits/headless.js +7 -5
- package/dist/ai-edits/index.d.ts +2 -2
- package/dist/ai-edits/index.js +2 -2
- package/dist/ai-edits/snapshot.js +13 -9
- package/dist/compare/content-alignment.js +94 -54
- package/dist/compare/inline-atoms.js +34 -20
- package/dist/compare/style-resources.js +6 -0
- package/dist/content-controls/mutateContentControls.js +4 -2
- package/dist/display-list/dom/renderDisplayListToDom.js +8 -8
- package/dist/document-operations.js +14 -3
- package/dist/docx/appVersionNormalization.d.ts +0 -18
- package/dist/docx/blockContentParser.js +8 -0
- package/dist/docx/blockRangeMarkers.d.ts +36 -0
- package/dist/docx/blockRangeMarkers.js +59 -0
- package/dist/docx/bookmarkParser.d.ts +2 -20
- package/dist/docx/bookmarkParser.js +6 -30
- package/dist/docx/borderParser.d.ts +13 -0
- package/dist/docx/borderParser.js +71 -0
- package/dist/docx/builtInStyles.d.ts +165 -0
- package/dist/docx/builtInStyles.js +239 -0
- package/dist/docx/commentIdNormalization.d.ts +3 -1
- package/dist/docx/commentIdNormalization.js +18 -1
- package/dist/docx/commentParser.d.ts +2 -1
- package/dist/docx/commentParser.js +80 -42
- package/dist/docx/commentReferenceNormalization.d.ts +4 -1
- package/dist/docx/commentReferenceNormalization.js +23 -14
- package/dist/docx/commentThreadKey.d.ts +18 -0
- package/dist/docx/commentThreadKey.js +22 -0
- package/dist/docx/danglingRelationshipReferences.d.ts +15 -0
- package/dist/docx/danglingRelationshipReferences.js +30 -0
- package/dist/docx/defaultParagraphStyle.d.ts +18 -1
- package/dist/docx/defaultParagraphStyle.js +23 -1
- package/dist/docx/diagramPreview.js +87 -27
- package/dist/docx/documentParser.d.ts +2 -1
- package/dist/docx/documentParser.js +2 -2
- package/dist/docx/drawingUtils.d.ts +8 -1
- package/dist/docx/drawingUtils.js +12 -3
- package/dist/docx/fieldParser.js +3 -5
- package/dist/docx/footnoteParser.d.ts +3 -2
- package/dist/docx/footnoteParser.js +19 -4
- package/dist/docx/groupDrawingParser.js +4 -4
- package/dist/docx/headerFooterRefParser.d.ts +4 -3
- package/dist/docx/headerFooterRefParser.js +42 -12
- package/dist/docx/headerFooterReferenceNormalization.d.ts +4 -1
- package/dist/docx/headerFooterReferenceNormalization.js +5 -1
- package/dist/docx/hyperlinkParser.js +13 -17
- package/dist/docx/imageParser.d.ts +10 -2
- package/dist/docx/imageParser.js +80 -30
- package/dist/docx/imageRawXml.d.ts +14 -1
- package/dist/docx/imageRawXml.js +35 -11
- package/dist/docx/markupRangeMarker.d.ts +15 -0
- package/dist/docx/markupRangeMarker.js +44 -0
- package/dist/docx/mathToMathml.js +12 -14
- package/dist/docx/nonVisualDrawingProps.d.ts +34 -0
- package/dist/docx/nonVisualDrawingProps.js +46 -0
- package/dist/docx/noteReferenceStyles.d.ts +29 -0
- package/dist/docx/noteReferenceStyles.js +70 -0
- package/dist/docx/numberingReferenceNormalization.d.ts +4 -1
- package/dist/docx/numberingReferenceNormalization.js +20 -1
- package/dist/docx/paraIdRangeNormalization.d.ts +0 -19
- package/dist/docx/paragraphParser.js +66 -99
- package/dist/docx/paragraphPropertySource.js +1 -0
- package/dist/docx/paragraphTextBoxEnrichment.js +3 -0
- package/dist/docx/paragraphTraversal.d.ts +37 -1
- package/dist/docx/paragraphTraversal.js +84 -1
- package/dist/docx/parseContext.d.ts +37 -0
- package/dist/docx/parseContext.js +67 -0
- package/dist/docx/parseWarningMessage.d.ts +6 -0
- package/dist/docx/parseWarningMessage.js +44 -0
- package/dist/docx/parser.js +83 -29
- package/dist/docx/previewBudget.d.ts +64 -0
- package/dist/docx/previewBudget.js +88 -0
- package/dist/docx/relsParser.d.ts +28 -11
- package/dist/docx/relsParser.js +26 -13
- package/dist/docx/revisionIdNormalization.js +96 -10
- package/dist/docx/rezip.js +80 -40
- package/dist/docx/runConsolidator.js +1 -2
- package/dist/docx/runParser.d.ts +8 -1
- package/dist/docx/runParser.js +30 -48
- package/dist/docx/sdtPropertiesPatch.js +24 -18
- package/dist/docx/sectionParser.d.ts +2 -1
- package/dist/docx/sectionParser.js +21 -65
- package/dist/docx/sectionReferenceHistory.js +2 -2
- package/dist/docx/selectiveSave.js +6 -6
- package/dist/docx/serializer/blockSdtSerializer.js +38 -26
- package/dist/docx/serializer/borderSerializer.d.ts +2 -3
- package/dist/docx/serializer/borderSerializer.js +13 -12
- package/dist/docx/serializer/commentSerializer.d.ts +41 -16
- package/dist/docx/serializer/commentSerializer.js +82 -72
- package/dist/docx/serializer/documentSerializer.d.ts +1 -5
- package/dist/docx/serializer/documentSerializer.js +6 -16
- package/dist/docx/serializer/fontTableSerializer.js +6 -6
- package/dist/docx/serializer/headerFooterSerializer.js +10 -5
- package/dist/docx/serializer/markupRangeAttributes.d.ts +8 -0
- package/dist/docx/serializer/markupRangeAttributes.js +24 -0
- package/dist/docx/serializer/noteSerializer.js +5 -0
- package/dist/docx/serializer/numberingSerializer.js +7 -6
- package/dist/docx/serializer/paragraphSerializer.d.ts +1 -5
- package/dist/docx/serializer/paragraphSerializer.js +47 -52
- package/dist/docx/serializer/partNamespaces.js +2 -2
- package/dist/docx/serializer/runSerializer.js +57 -31
- package/dist/docx/serializer/sectionPropertiesSerializer.js +11 -10
- package/dist/docx/serializer/settingsSerializer.js +4 -3
- package/dist/docx/serializer/stylesSerializer.js +6 -6
- package/dist/docx/serializer/tableSerializer.js +37 -21
- package/dist/docx/serializer/textFormattingSerializer.d.ts +2 -3
- package/dist/docx/serializer/textFormattingSerializer.js +29 -28
- package/dist/docx/serializer/themeSerializer.js +6 -6
- package/dist/docx/serializer/trackedChangeAttributes.js +2 -2
- package/dist/docx/serializer/xmlUtils.d.ts +1 -2
- package/dist/docx/serializer/xmlUtils.js +1 -13
- package/dist/docx/server/boundedArchive.d.ts +12 -0
- package/dist/docx/server/boundedArchive.js +20 -1
- package/dist/docx/server/build.js +8 -1
- package/dist/docx/server/createBilingualDocument.js +10 -18
- package/dist/docx/server/extractDocxText.js +3 -4
- package/dist/docx/server/validateDocxConformance.js +22 -1
- package/dist/docx/shadingParser.d.ts +6 -0
- package/dist/docx/shadingParser.js +32 -0
- package/dist/docx/shapeParser.js +10 -8
- package/dist/docx/styleParser.js +13 -87
- package/dist/docx/styleReferenceResolution.d.ts +36 -0
- package/dist/docx/styleReferenceResolution.js +51 -0
- package/dist/docx/tableLook.d.ts +57 -0
- package/dist/docx/tableLook.js +63 -0
- package/dist/docx/tableParser.d.ts +7 -9
- package/dist/docx/tableParser.js +64 -110
- package/dist/docx/textBoxParser.js +11 -6
- package/dist/docx/trackedMoveRangeNormalization.d.ts +3 -1
- package/dist/docx/trackedMoveRangeNormalization.js +11 -21
- package/dist/docx/transitionalSpelling.d.ts +13 -2
- package/dist/docx/transitionalSpelling.js +23 -1
- package/dist/docx/unzip.d.ts +23 -0
- package/dist/docx/unzip.js +32 -22
- package/dist/docx/verbatimCapture.js +5 -12
- package/dist/docx/vmlImageParser.js +5 -4
- package/dist/docx/vmlPreview.d.ts +1 -3
- package/dist/docx/vmlPreview.js +2 -30
- package/dist/docx/watermarkParser.js +2 -2
- package/dist/docx/xmlParser.d.ts +38 -33
- package/dist/docx/xmlParser.js +92 -47
- package/dist/docx/xmlResourceLimits.d.ts +89 -9
- package/dist/docx/xmlResourceLimits.js +105 -24
- package/dist/internal/pageBreakRunSourceDescendantIndex.js +2 -1
- package/dist/internal/paragraphFormattingSerialization.d.ts +2 -3
- package/dist/internal/paragraphFormattingSerialization.js +29 -8
- package/dist/layout-bridge/convert/footnoteLayout.js +2 -7
- package/dist/layout-engine/index.d.ts +2 -2
- package/dist/layout-engine/index.js +2 -2
- package/dist/layout-engine/measure/measureBlocks.js +1 -6
- package/dist/layout-engine/types.d.ts +8 -2
- package/dist/layout-engine/types.js +35 -2
- package/dist/layout-painter/renderImage.js +4 -3
- package/dist/layout-painter/renderParagraph.js +4 -3
- package/dist/managers/autoSaveCodec.js +2 -8
- package/dist/markdown/images.js +1 -4
- package/dist/markdown/index.js +1 -1
- package/dist/markdown/internals.d.ts +6 -1
- package/dist/markdown/internals.js +14 -1
- package/dist/markdown/renderBlock.js +35 -21
- package/dist/markdown/renderParagraph.js +14 -5
- package/dist/markdown/renderRuns.js +4 -3
- package/dist/markdown/renderTable.js +4 -3
- package/dist/markdown/trailers.js +41 -7
- package/dist/markdown/types.d.ts +3 -7
- package/dist/prosemirror/attrs/index.js +71 -5
- package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +11 -1
- package/dist/prosemirror/bookmarkBoundaryAttrs.js +18 -3
- package/dist/prosemirror/commands/image.js +1 -0
- package/dist/prosemirror/commands/index.d.ts +3 -3
- package/dist/prosemirror/commands/index.js +2 -2
- package/dist/prosemirror/commands/paragraph.d.ts +3 -3
- package/dist/prosemirror/commands/paragraph.js +2 -2
- package/dist/prosemirror/commentIdAllocator.js +2 -7
- package/dist/prosemirror/conversion/fromProseDoc.js +197 -68
- package/dist/prosemirror/conversion/toProseDoc.d.ts +1 -14
- package/dist/prosemirror/conversion/toProseDoc.js +458 -335
- package/dist/prosemirror/extensions/core/ParagraphExtension.d.ts +14 -1
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +11 -6
- package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +3 -3
- package/dist/prosemirror/extensions/features/PasteCleanupExtension.d.ts +4 -1
- package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +6 -2
- package/dist/prosemirror/extensions/features/pastedHeadingStyles.d.ts +7 -0
- package/dist/prosemirror/extensions/features/pastedHeadingStyles.js +74 -0
- package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +2 -3
- package/dist/prosemirror/extensions/marks/markUtils.d.ts +11 -3
- package/dist/prosemirror/extensions/marks/markUtils.js +98 -19
- package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +7 -3
- package/dist/prosemirror/extensions/nodes/ImageExtension.js +6 -1
- package/dist/prosemirror/extensions/nodes/ShapeExtension.js +8 -2
- package/dist/prosemirror/extensions/nodes/TableExtension.js +15 -1
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +8 -4
- package/dist/prosemirror/extensions/types.d.ts +2 -2
- package/dist/prosemirror/index.d.ts +3 -3
- package/dist/prosemirror/index.js +3 -3
- package/dist/prosemirror/insertOperations.d.ts +9 -2
- package/dist/prosemirror/insertOperations.js +9 -4
- package/dist/prosemirror/paragraphFormattingProvenance.d.ts +162 -0
- package/dist/prosemirror/paragraphFormattingProvenance.js +115 -0
- package/dist/prosemirror/plugins/documentStyles.d.ts +9 -1
- package/dist/prosemirror/plugins/documentStyles.js +11 -1
- package/dist/prosemirror/plugins/index.d.ts +2 -2
- package/dist/prosemirror/plugins/index.js +2 -2
- package/dist/prosemirror/plugins/revisionIds.d.ts +11 -2
- package/dist/prosemirror/plugins/revisionIds.js +21 -6
- package/dist/prosemirror/runFormattingReconciliation.js +3 -2
- package/dist/prosemirror/runStyleFormatting.d.ts +1 -1
- package/dist/prosemirror/schema/nodes.d.ts +81 -1
- package/dist/prosemirror/styles/resolvedStyleAttrs.js +2 -0
- package/dist/prosemirror/styles/styleResolver.d.ts +9 -0
- package/dist/prosemirror/styles/styleResolver.js +12 -0
- package/dist/style-engine/styleEngine.d.ts +3 -0
- package/dist/style-engine/styleEngine.js +3 -0
- package/dist/style-sets/extract.js +1 -23
- package/dist/style-sets/stellaStyle.js +46 -39
- package/dist/style-sets/styleSetNormalization.d.ts +19 -0
- package/dist/style-sets/styleSetNormalization.js +99 -0
- package/dist/types/content.d.ts +2 -2
- package/dist/utils/base64.d.ts +36 -0
- package/dist/utils/base64.js +40 -0
- package/dist/utils/clipboard.js +2 -1
- package/dist/utils/createDocument.js +145 -20
- package/dist/utils/headingCollector.d.ts +8 -5
- package/dist/utils/headingCollector.js +23 -25
- package/dist/utils/tableOfContentsStyle.js +9 -2
- package/dist/utils/units.d.ts +10 -1
- package/dist/utils/units.js +12 -1
- package/dist/utils/urlSecurity.d.ts +8 -2
- package/dist/utils/urlSecurity.js +21 -3
- package/package.json +2 -2
- package/dist/docx/textWhitespace.d.ts +0 -4
- package/dist/docx/textWhitespace.js +0 -4
- package/dist/layout-bridge/engine/tableWidthUtils.d.ts +0 -6
- package/dist/layout-bridge/engine/tableWidthUtils.js +0 -25
- package/dist/markdown/headings.d.ts +0 -13
- package/dist/markdown/headings.js +0 -20
|
@@ -31,16 +31,79 @@ const REVISION_ELEMENT_NAMES = /* @__PURE__ */ new Set([
|
|
|
31
31
|
"trPrChange"
|
|
32
32
|
]);
|
|
33
33
|
const REVISION_ELEMENT_CANDIDATE = new RegExp(`<(?:[^\\s<>/:]+:)?(?:${[...REVISION_ELEMENT_NAMES].join("|")})(?:[\\s/>])`, "u");
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
/**
|
|
35
|
+
* The rest of the annotation id space.
|
|
36
|
+
*
|
|
37
|
+
* A comment, a bookmark, a protected range and a tracked change all draw their
|
|
38
|
+
* `w:id` from one space: Word allocates from a single counter, which is why a
|
|
39
|
+
* package carrying several kinds almost never repeats a value across them. So
|
|
40
|
+
* an id this pass mints must avoid these as well, or a renumbered `w:ins`
|
|
41
|
+
* lands on a live comment.
|
|
42
|
+
*
|
|
43
|
+
* They are only ever reserved, never claimed. A comment id legitimately
|
|
44
|
+
* appears four times (`w:comment`, both range markers and the reference) and a
|
|
45
|
+
* bookmark id twice, so feeding them to the uniqueness machinery would reject
|
|
46
|
+
* a package Word wrote. Their pairing is also why they are not revision
|
|
47
|
+
* elements: renumbering one end of a range would unpair it.
|
|
48
|
+
*/
|
|
49
|
+
const ANNOTATION_ELEMENT_NAMES = /* @__PURE__ */ new Set([
|
|
50
|
+
"bookmarkEnd",
|
|
51
|
+
"bookmarkStart",
|
|
52
|
+
"comment",
|
|
53
|
+
"commentRangeEnd",
|
|
54
|
+
"commentRangeStart",
|
|
55
|
+
"commentReference",
|
|
56
|
+
"customXmlDelRangeEnd",
|
|
57
|
+
"customXmlDelRangeStart",
|
|
58
|
+
"customXmlInsRangeEnd",
|
|
59
|
+
"customXmlInsRangeStart",
|
|
60
|
+
"customXmlMoveFromRangeEnd",
|
|
61
|
+
"customXmlMoveFromRangeStart",
|
|
62
|
+
"customXmlMoveToRangeEnd",
|
|
63
|
+
"customXmlMoveToRangeStart",
|
|
64
|
+
"moveFromRangeEnd",
|
|
65
|
+
"moveFromRangeStart",
|
|
66
|
+
"moveToRangeEnd",
|
|
67
|
+
"moveToRangeStart",
|
|
68
|
+
"permEnd",
|
|
69
|
+
"permStart"
|
|
70
|
+
]);
|
|
71
|
+
const ANNOTATION_ELEMENT_CANDIDATE = new RegExp(`<(?:[^\\s<>/:]+:)?(?:${[...ANNOTATION_ELEMENT_NAMES].join("|")})(?:[\\s/>])`, "u");
|
|
72
|
+
const ID_KINDS = {
|
|
73
|
+
revision: "revision",
|
|
74
|
+
annotation: "annotation"
|
|
75
|
+
};
|
|
76
|
+
/** One lookup for both halves of the space, so an element is classified once. */
|
|
77
|
+
const ID_KIND_BY_ELEMENT_NAME = new Map([...[...REVISION_ELEMENT_NAMES].map((name) => [name, ID_KINDS.revision]), ...[...ANNOTATION_ELEMENT_NAMES].map((name) => [name, ID_KINDS.annotation])]);
|
|
78
|
+
/**
|
|
79
|
+
* An element's `w:id` and which half of the annotation space it belongs to.
|
|
80
|
+
*
|
|
81
|
+
* One classifier rather than two, because it runs on every element of every
|
|
82
|
+
* scanned part: resolving the namespace and the local name twice to ask two
|
|
83
|
+
* questions measured 18% on a 17.6 MiB package.
|
|
84
|
+
*
|
|
85
|
+
* `w:permStart` types its id as a string, so a protected range named
|
|
86
|
+
* `everyone` yields nothing. That is correct: a value the allocator can never
|
|
87
|
+
* mint is not one it has to avoid.
|
|
88
|
+
*/
|
|
89
|
+
const identifiedElement = (element) => {
|
|
90
|
+
if (!element.name || !WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(element) ?? "")) return null;
|
|
91
|
+
const localName = getLocalName(element.name);
|
|
92
|
+
const kind = ID_KIND_BY_ELEMENT_NAME.get(localName);
|
|
93
|
+
if (kind === void 0) return null;
|
|
36
94
|
const attribute = findAttributeByNamespaceUri(element, WORDPROCESSINGML_NAMESPACE_URIS, "id");
|
|
37
95
|
if (!attribute) return null;
|
|
38
96
|
const id = Number(attribute.value);
|
|
39
97
|
return Number.isSafeInteger(id) && id >= 0 ? {
|
|
98
|
+
kind,
|
|
40
99
|
name: attribute.name,
|
|
41
100
|
id
|
|
42
101
|
} : null;
|
|
43
102
|
};
|
|
103
|
+
const revisionAttribute = (element) => {
|
|
104
|
+
const identified = identifiedElement(element);
|
|
105
|
+
return identified?.kind === ID_KINDS.revision ? identified : null;
|
|
106
|
+
};
|
|
44
107
|
/**
|
|
45
108
|
* Keep physical tracked-change element ids unique across a package.
|
|
46
109
|
*
|
|
@@ -54,18 +117,22 @@ const normalizeRevisionIdsInXmlParts = (parts) => {
|
|
|
54
117
|
const occurrencesByPath = /* @__PURE__ */ new Map();
|
|
55
118
|
const reserved = /* @__PURE__ */ new Set();
|
|
56
119
|
for (const [path, xml] of candidates) {
|
|
57
|
-
assertXmlResourceLimits(
|
|
120
|
+
assertXmlResourceLimits({
|
|
121
|
+
xml,
|
|
122
|
+
partPath: path
|
|
123
|
+
});
|
|
58
124
|
const ids = [];
|
|
59
125
|
if (rewriteStreamingXmlDecimalAttributes(xml, (element) => {
|
|
60
|
-
const
|
|
61
|
-
if (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
126
|
+
const identified = identifiedElement(element);
|
|
127
|
+
if (identified === null) return null;
|
|
128
|
+
if (identified.kind === ID_KINDS.revision) ids.push(identified.id);
|
|
129
|
+
reserved.add(identified.id);
|
|
65
130
|
return null;
|
|
66
131
|
}).status === "unsupported") throw new XmlResourceLimitError({
|
|
67
132
|
message: `Revision-id normalization could not safely scan ${path}`,
|
|
68
|
-
limit: "syntax"
|
|
133
|
+
limit: "syntax",
|
|
134
|
+
observed: 0,
|
|
135
|
+
allowed: 0
|
|
69
136
|
});
|
|
70
137
|
occurrencesByPath.set(path, ids);
|
|
71
138
|
}
|
|
@@ -73,6 +140,23 @@ const normalizeRevisionIdsInXmlParts = (parts) => {
|
|
|
73
140
|
const firstSeen = /* @__PURE__ */ new Set();
|
|
74
141
|
for (const [path, ids] of occurrencesByPath) for (const id of ids) if (firstSeen.has(id)) repeatedPaths.add(path);
|
|
75
142
|
else firstSeen.add(id);
|
|
143
|
+
if (repeatedPaths.size > 0) for (const [path, xml] of parts) {
|
|
144
|
+
if (occurrencesByPath.has(path) || !ANNOTATION_ELEMENT_CANDIDATE.test(xml)) continue;
|
|
145
|
+
assertXmlResourceLimits({
|
|
146
|
+
xml,
|
|
147
|
+
partPath: path
|
|
148
|
+
});
|
|
149
|
+
if (rewriteStreamingXmlDecimalAttributes(xml, (element) => {
|
|
150
|
+
const identified = identifiedElement(element);
|
|
151
|
+
if (identified !== null) reserved.add(identified.id);
|
|
152
|
+
return null;
|
|
153
|
+
}).status === "unsupported") throw new XmlResourceLimitError({
|
|
154
|
+
message: `Revision-id normalization could not safely scan ${path}`,
|
|
155
|
+
limit: "syntax",
|
|
156
|
+
observed: 0,
|
|
157
|
+
allowed: 0
|
|
158
|
+
});
|
|
159
|
+
}
|
|
76
160
|
let nextId = 0;
|
|
77
161
|
const allocate = () => {
|
|
78
162
|
while (reserved.has(nextId)) nextId += 1;
|
|
@@ -111,7 +195,9 @@ const normalizeRevisionIdsInXmlParts = (parts) => {
|
|
|
111
195
|
});
|
|
112
196
|
if (rewritten.status === "unsupported") throw new XmlResourceLimitError({
|
|
113
197
|
message: `Revision-id normalization could not safely rewrite ${path}`,
|
|
114
|
-
limit: "syntax"
|
|
198
|
+
limit: "syntax",
|
|
199
|
+
observed: 0,
|
|
200
|
+
allowed: 0
|
|
115
201
|
});
|
|
116
202
|
normalized.set(path, rewritten.value);
|
|
117
203
|
}
|
package/dist/docx/rezip.js
CHANGED
|
@@ -10,6 +10,7 @@ import { parseEndnotes, parseFootnotes } from "./footnoteParser.js";
|
|
|
10
10
|
import { parseHeaderFooterType } from "./headerFooterRefParser.js";
|
|
11
11
|
import { assertValidFolioDocumentModel } from "./modelValidation.js";
|
|
12
12
|
import { isNewDataUrlDrawing } from "./newImage.js";
|
|
13
|
+
import { missingNoteReferenceStyles, noteReferenceNeeds } from "./noteReferenceStyles.js";
|
|
13
14
|
import { parseNumbering } from "./numberingParser.js";
|
|
14
15
|
import { isNumberingReference } from "./numberingReference.js";
|
|
15
16
|
import { isUnsafePackagePath, reconcilePackageReferences, removeUnsafeEntries } from "./packageParts.js";
|
|
@@ -18,7 +19,7 @@ import { RELATIONSHIP_TYPES, parseRelationships, resolveRelativePath } from "./r
|
|
|
18
19
|
import { removeResolvedHeaderFooterParts } from "./removeHeaderFooterParts.js";
|
|
19
20
|
import { normalizeRevisionIdsInXmlParts } from "./revisionIdNormalization.js";
|
|
20
21
|
import { buildPatchedNotePartXml, collectChangedNoteParaIds, collectParaIds, patchNumberingDefinitions } from "./selectiveXmlPatch.js";
|
|
21
|
-
import {
|
|
22
|
+
import { planCommentParts, serializeComments, serializeCommentsExtended } from "./serializer/commentSerializer.js";
|
|
22
23
|
import { serializeDocument } from "./serializer/documentSerializer.js";
|
|
23
24
|
import { serializeFontTableXml } from "./serializer/fontTableSerializer.js";
|
|
24
25
|
import { serializeHeaderFooter } from "./serializer/headerFooterSerializer.js";
|
|
@@ -28,11 +29,10 @@ import { readRootNamespaceBindings } from "./serializer/partNamespaces.js";
|
|
|
28
29
|
import { serializeSettingsXml } from "./serializer/settingsSerializer.js";
|
|
29
30
|
import { serializeStyle, serializeStylesXml } from "./serializer/stylesSerializer.js";
|
|
30
31
|
import { serializeThemeXml } from "./serializer/themeSerializer.js";
|
|
31
|
-
import { escapeXml } from "./serializer/xmlUtils.js";
|
|
32
32
|
import { OFFICE_RELATIONSHIP_NAMESPACE_URIS, WORDPROCESSINGML_NAMESPACE_URIS, findChild, getAttribute, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespaceUri, matchesName, parseXml, parseXmlDocument } from "./xmlParser.js";
|
|
33
33
|
import { assertXmlResourceLimits } from "./xmlResourceLimits.js";
|
|
34
34
|
import { panic } from "better-result";
|
|
35
|
-
import { validateDocxPackage } from "@stll/docx-core";
|
|
35
|
+
import { escapeXmlAttribute, escapeXmlText, validateDocxPackage } from "@stll/docx-core";
|
|
36
36
|
import JSZip from "jszip";
|
|
37
37
|
//#region src/docx/rezip.ts
|
|
38
38
|
/**
|
|
@@ -84,7 +84,7 @@ function findMaxRId(relsXml) {
|
|
|
84
84
|
}
|
|
85
85
|
const isWordprocessingElement = (element, localName) => getLocalName(element.name) === localName && WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(element) ?? "");
|
|
86
86
|
const countDocumentSections = (xml) => {
|
|
87
|
-
assertXmlResourceLimits(xml);
|
|
87
|
+
assertXmlResourceLimits({ xml });
|
|
88
88
|
let count = 0;
|
|
89
89
|
const pending = [{
|
|
90
90
|
element: parseXml(xml),
|
|
@@ -105,7 +105,7 @@ const countDocumentSections = (xml) => {
|
|
|
105
105
|
};
|
|
106
106
|
const extractHeaderFooterReferences = (xml) => {
|
|
107
107
|
const references = [];
|
|
108
|
-
assertXmlResourceLimits(xml);
|
|
108
|
+
assertXmlResourceLimits({ xml });
|
|
109
109
|
const pending = [parseXml(xml)];
|
|
110
110
|
while (pending.length > 0) {
|
|
111
111
|
const node = pending.pop();
|
|
@@ -165,15 +165,15 @@ async function serializeCommentsToZip(doc, zip, compressionLevel) {
|
|
|
165
165
|
if (comments.length === 0) {
|
|
166
166
|
if (sourceCommentsXml === void 0 || !hasCommentEntries(sourceCommentsXml)) return;
|
|
167
167
|
}
|
|
168
|
-
|
|
169
|
-
const commentsXml = serializeComments(
|
|
168
|
+
const plan = planCommentParts(comments);
|
|
169
|
+
const commentsXml = serializeComments(plan, sourceCommentsXml === void 0 ? void 0 : readRootNamespaceBindings(sourceCommentsXml));
|
|
170
170
|
zip.file(sourceCommentsFile?.name ?? "word/comments.xml", commentsXml, {
|
|
171
171
|
compression: "DEFLATE",
|
|
172
172
|
compressionOptions: { level: compressionLevel }
|
|
173
173
|
});
|
|
174
174
|
await ensureCommentsContentType(zip, compressionLevel);
|
|
175
175
|
await ensureCommentsRelationship(zip, compressionLevel);
|
|
176
|
-
await syncCommentsExtendedPart(
|
|
176
|
+
await syncCommentsExtendedPart(plan, zip, compressionLevel);
|
|
177
177
|
}
|
|
178
178
|
const hasCommentEntries = (xml) => {
|
|
179
179
|
const root = parseXml(xml);
|
|
@@ -188,8 +188,8 @@ const hasCommentEntries = (xml) => {
|
|
|
188
188
|
* removed part triggers Word's repair prompt). The reply-thread markers in
|
|
189
189
|
* `document.xml` are synthesized separately (see {@link applyReplyThreadMarkers}).
|
|
190
190
|
*/
|
|
191
|
-
async function syncCommentsExtendedPart(
|
|
192
|
-
const xml = serializeCommentsExtended(
|
|
191
|
+
async function syncCommentsExtendedPart(plan, zip, compressionLevel) {
|
|
192
|
+
const xml = serializeCommentsExtended(plan);
|
|
193
193
|
const existing = findZipEntryCaseInsensitive(zip, COMMENTS_EXTENDED_PART_LOWER);
|
|
194
194
|
if (!xml) {
|
|
195
195
|
if (!existing) return;
|
|
@@ -395,7 +395,7 @@ async function processNewImages(parts, zip, compressionLevel) {
|
|
|
395
395
|
compression: "DEFLATE",
|
|
396
396
|
compressionOptions: { level: compressionLevel }
|
|
397
397
|
});
|
|
398
|
-
relEntries.push(`<Relationship Id="${newRId}" Type="${RELATIONSHIP_TYPES.image}" Target="${
|
|
398
|
+
relEntries.push(`<Relationship Id="${newRId}" Type="${RELATIONSHIP_TYPES.image}" Target="${escapeXmlAttribute(relativeTargetForPart(partPath, mediaPath))}"/>`);
|
|
399
399
|
extensionsAdded.add(extension);
|
|
400
400
|
if (drawing.rawXml) drawing.rawXml = rebindDrawingImageRelationship({
|
|
401
401
|
xml: drawing.rawXml,
|
|
@@ -475,7 +475,7 @@ async function processNewHyperlinks(parts, zip, compressionLevel) {
|
|
|
475
475
|
if (!hyperlink.href) continue;
|
|
476
476
|
maxId++;
|
|
477
477
|
const newRId = `rId${maxId}`;
|
|
478
|
-
relEntries.push(`<Relationship Id="${newRId}" Type="${RELATIONSHIP_TYPES.hyperlink}" Target="${
|
|
478
|
+
relEntries.push(`<Relationship Id="${newRId}" Type="${RELATIONSHIP_TYPES.hyperlink}" Target="${escapeXmlAttribute(hyperlink.href)}" TargetMode="External"/>`);
|
|
479
479
|
hyperlink.rId = newRId;
|
|
480
480
|
}
|
|
481
481
|
zip.file(relsPath, relsXml.replace("</Relationships>", `${relEntries.join("")}</Relationships>`), {
|
|
@@ -951,7 +951,7 @@ async function addRelationship(originalBuffer, relationship) {
|
|
|
951
951
|
const relsXml = await relsFile.async("text");
|
|
952
952
|
const newRId = `rId${findMaxRId(relsXml) + 1}`;
|
|
953
953
|
const targetModeAttr = relationship.targetMode === "External" ? " TargetMode=\"External\"" : "";
|
|
954
|
-
const newRelElement = `<Relationship Id="${newRId}" Type="${relationship.type}" Target="${
|
|
954
|
+
const newRelElement = `<Relationship Id="${newRId}" Type="${relationship.type}" Target="${escapeXmlAttribute(relationship.target)}"${targetModeAttr}/>`;
|
|
955
955
|
const updatedRelsXml = relsXml.replace("</Relationships>", `${newRelElement}</Relationships>`);
|
|
956
956
|
zip.file(relsPath, updatedRelsXml);
|
|
957
957
|
return {
|
|
@@ -1100,7 +1100,7 @@ async function materializeNewHeaderFooterParts(doc, zip, compressionLevel) {
|
|
|
1100
1100
|
type: relType,
|
|
1101
1101
|
target: filename
|
|
1102
1102
|
});
|
|
1103
|
-
relEntries.push(`<Relationship Id="${
|
|
1103
|
+
relEntries.push(`<Relationship Id="${escapeXmlAttribute(effectiveRId)}" Type="${relType}" Target="${filename}"/>`);
|
|
1104
1104
|
overrides.push(`<Override PartName="/word/${filename}" ContentType="${contentType}"/>`);
|
|
1105
1105
|
}
|
|
1106
1106
|
};
|
|
@@ -1217,7 +1217,7 @@ async function rebindWatermarkRelIds(doc, zip, compressionLevel) {
|
|
|
1217
1217
|
}
|
|
1218
1218
|
if (!resolvedRId) {
|
|
1219
1219
|
resolvedRId = `rId${findMaxRId(relsXml) + 1}`;
|
|
1220
|
-
const relXml = canonical.mode === "external" ? `<Relationship Id="${resolvedRId}" Type="${RELATIONSHIP_TYPES.image}" Target="${
|
|
1220
|
+
const relXml = canonical.mode === "external" ? `<Relationship Id="${resolvedRId}" Type="${RELATIONSHIP_TYPES.image}" Target="${escapeXmlAttribute(canonical.url)}" TargetMode="External"/>` : `<Relationship Id="${resolvedRId}" Type="${RELATIONSHIP_TYPES.image}" Target="${escapeXmlAttribute(relativeTargetForPart(partPath, canonical.absolute))}"/>`;
|
|
1221
1221
|
relsXmlByPath.set(relsPath, relsXml.replace("</Relationships>", `${relXml}</Relationships>`));
|
|
1222
1222
|
changedPaths.add(relsPath);
|
|
1223
1223
|
}
|
|
@@ -1389,6 +1389,42 @@ async function serializeNumberingIntoZip(doc, originalZip, newZip, compressionLe
|
|
|
1389
1389
|
}
|
|
1390
1390
|
const STYLES_PART_PATH = "word/styles.xml";
|
|
1391
1391
|
const STYLES_CLOSE_ROOT = "</w:styles>";
|
|
1392
|
+
/**
|
|
1393
|
+
* The style table a package folio creates starts from: `docDefaults` and
|
|
1394
|
+
* `Normal`, and nothing else. A document that carries its own style table
|
|
1395
|
+
* replaces this part wholesale.
|
|
1396
|
+
*/
|
|
1397
|
+
const SEED_STYLES_XML = `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
1398
|
+
<w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
|
1399
|
+
<w:docDefaults>
|
|
1400
|
+
<w:rPrDefault>
|
|
1401
|
+
<w:rPr>
|
|
1402
|
+
<w:rFonts w:ascii="Calibri" w:hAnsi="Calibri"/>
|
|
1403
|
+
<w:sz w:val="22"/>
|
|
1404
|
+
</w:rPr>
|
|
1405
|
+
</w:rPrDefault>
|
|
1406
|
+
<w:pPrDefault>
|
|
1407
|
+
<w:pPr>
|
|
1408
|
+
<w:spacing w:after="200" w:line="276" w:lineRule="auto"/>
|
|
1409
|
+
</w:pPr>
|
|
1410
|
+
</w:pPrDefault>
|
|
1411
|
+
</w:docDefaults>
|
|
1412
|
+
<w:style w:type="paragraph" w:default="1" w:styleId="Normal">
|
|
1413
|
+
<w:name w:val="Normal"/>
|
|
1414
|
+
</w:style>
|
|
1415
|
+
</w:styles>`;
|
|
1416
|
+
/**
|
|
1417
|
+
* The seed part plus the reference styles this package's comments and notes
|
|
1418
|
+
* need. A document with no style table of its own never reaches
|
|
1419
|
+
* {@link styleDefinitionsToSerialize}, so without this its reference marks
|
|
1420
|
+
* would carry a `w:rStyle` naming nothing — the defect
|
|
1421
|
+
* `noteReferenceStyles.ts` exists to remove, on the one path that skips it.
|
|
1422
|
+
*/
|
|
1423
|
+
const seedStylesXmlWith = (missing) => {
|
|
1424
|
+
if (missing.length === 0) return SEED_STYLES_XML;
|
|
1425
|
+
const rootClose = SEED_STYLES_XML.lastIndexOf(STYLES_CLOSE_ROOT);
|
|
1426
|
+
return SEED_STYLES_XML.slice(0, rootClose) + missing.map(serializeStyle).join("") + SEED_STYLES_XML.slice(rootClose);
|
|
1427
|
+
};
|
|
1392
1428
|
const STYLE_ID_PATTERN = /<w:style\b[^>]*?\bw:styleId="(?<id>[^"]+)"/gu;
|
|
1393
1429
|
/**
|
|
1394
1430
|
* Append styles the model defines but the original `word/styles.xml` lacks.
|
|
@@ -1397,6 +1433,27 @@ const STYLE_ID_PATTERN = /<w:style\b[^>]*?\bw:styleId="(?<id>[^"]+)"/gu;
|
|
|
1397
1433
|
* per-language clones a bilingual transform adds, are emitted before the root
|
|
1398
1434
|
* close so paragraphs referencing them resolve on reopen.
|
|
1399
1435
|
*/
|
|
1436
|
+
/**
|
|
1437
|
+
* The styles to write into a package folio is authoring: the model's, plus any
|
|
1438
|
+
* reference character style the serializers are about to emit for this
|
|
1439
|
+
* package's comments and notes but the style table does not define. See
|
|
1440
|
+
* `noteReferenceStyles.ts` — the reference mark would otherwise carry a
|
|
1441
|
+
* `w:rStyle` pointing at nothing.
|
|
1442
|
+
*
|
|
1443
|
+
* Only for a package folio writes from scratch. Repacking a document someone
|
|
1444
|
+
* else authored preserves `word/styles.xml` byte for byte, and adding a
|
|
1445
|
+
* definition there would rewrite a part the user never edited: their document,
|
|
1446
|
+
* their style table, missing reference style included.
|
|
1447
|
+
*/
|
|
1448
|
+
const styleDefinitionsToSerialize = (doc) => {
|
|
1449
|
+
const styles = doc.package.styles;
|
|
1450
|
+
if (!styles) return;
|
|
1451
|
+
const missing = missingNoteReferenceStyles(styles, noteReferenceNeeds(doc.package));
|
|
1452
|
+
return missing.length === 0 ? styles : {
|
|
1453
|
+
...styles,
|
|
1454
|
+
styles: [...styles.styles, ...missing]
|
|
1455
|
+
};
|
|
1456
|
+
};
|
|
1400
1457
|
async function serializeAddedStylesIntoZip(doc, originalZip, newZip, compressionLevel) {
|
|
1401
1458
|
const styles = doc.package.styles;
|
|
1402
1459
|
if (!styles || styles.styles.length === 0) return;
|
|
@@ -1473,7 +1530,7 @@ function updateCoreProperties(corePropsXml, { updateModifiedDate, modifiedBy })
|
|
|
1473
1530
|
if (result.includes("<dcterms:modified")) result = result.replace(/<dcterms:modified[^<>]*>[^<]*<\/dcterms:modified>/u, `<dcterms:modified xsi:type="dcterms:W3CDTF">${now}</dcterms:modified>`);
|
|
1474
1531
|
}
|
|
1475
1532
|
if (modifiedBy) {
|
|
1476
|
-
if (result.includes("<cp:lastModifiedBy")) result = result.replace(/<cp:lastModifiedBy>[^<]*<\/cp:lastModifiedBy>/u, `<cp:lastModifiedBy>${
|
|
1533
|
+
if (result.includes("<cp:lastModifiedBy")) result = result.replace(/<cp:lastModifiedBy>[^<]*<\/cp:lastModifiedBy>/u, `<cp:lastModifiedBy>${escapeXmlText(modifiedBy)}</cp:lastModifiedBy>`);
|
|
1477
1534
|
}
|
|
1478
1535
|
return result;
|
|
1479
1536
|
}
|
|
@@ -1578,33 +1635,15 @@ const createEmptyDocxZip = ({ creator, application }) => {
|
|
|
1578
1635
|
</w:sectPr>
|
|
1579
1636
|
</w:body>
|
|
1580
1637
|
</w:document>`);
|
|
1581
|
-
zip.file(
|
|
1582
|
-
<w:styles xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main">
|
|
1583
|
-
<w:docDefaults>
|
|
1584
|
-
<w:rPrDefault>
|
|
1585
|
-
<w:rPr>
|
|
1586
|
-
<w:rFonts w:ascii="Calibri" w:hAnsi="Calibri"/>
|
|
1587
|
-
<w:sz w:val="22"/>
|
|
1588
|
-
</w:rPr>
|
|
1589
|
-
</w:rPrDefault>
|
|
1590
|
-
<w:pPrDefault>
|
|
1591
|
-
<w:pPr>
|
|
1592
|
-
<w:spacing w:after="200" w:line="276" w:lineRule="auto"/>
|
|
1593
|
-
</w:pPr>
|
|
1594
|
-
</w:pPrDefault>
|
|
1595
|
-
</w:docDefaults>
|
|
1596
|
-
<w:style w:type="paragraph" w:default="1" w:styleId="Normal">
|
|
1597
|
-
<w:name w:val="Normal"/>
|
|
1598
|
-
</w:style>
|
|
1599
|
-
</w:styles>`);
|
|
1638
|
+
zip.file(STYLES_PART_PATH, SEED_STYLES_XML);
|
|
1600
1639
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1601
|
-
const creatorElement = creator === void 0 ? "" : `\n <dc:creator>${
|
|
1640
|
+
const creatorElement = creator === void 0 ? "" : `\n <dc:creator>${escapeXmlText(creator)}</dc:creator>`;
|
|
1602
1641
|
zip.file("docProps/core.xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
1603
1642
|
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">${creatorElement}
|
|
1604
1643
|
<dcterms:created xsi:type="dcterms:W3CDTF">${now}</dcterms:created>
|
|
1605
1644
|
<dcterms:modified xsi:type="dcterms:W3CDTF">${now}</dcterms:modified>
|
|
1606
1645
|
</cp:coreProperties>`);
|
|
1607
|
-
const applicationElements = application === void 0 ? "" : `\n <Application>${
|
|
1646
|
+
const applicationElements = application === void 0 ? "" : `\n <Application>${escapeXmlText(application)}</Application>\n <AppVersion>${CREATED_APP_VERSION}</AppVersion>`;
|
|
1608
1647
|
zip.file("docProps/app.xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
1609
1648
|
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">${applicationElements}
|
|
1610
1649
|
</Properties>`);
|
|
@@ -1647,10 +1686,11 @@ const createDocumentSeedZip = async (doc, properties) => {
|
|
|
1647
1686
|
const relationships = ["<Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles\" Target=\"styles.xml\"/>"];
|
|
1648
1687
|
const overrides = [];
|
|
1649
1688
|
let nextRelationshipId = 2;
|
|
1650
|
-
|
|
1689
|
+
const styleDefinitions = styleDefinitionsToSerialize(doc);
|
|
1690
|
+
if (styleDefinitions) {
|
|
1651
1691
|
assertStyleNumberingReferences(doc);
|
|
1652
|
-
zip.file(
|
|
1653
|
-
}
|
|
1692
|
+
zip.file(STYLES_PART_PATH, serializeStylesXml(styleDefinitions));
|
|
1693
|
+
} else zip.file(STYLES_PART_PATH, seedStylesXmlWith(missingNoteReferenceStyles(void 0, noteReferenceNeeds(doc.package))));
|
|
1654
1694
|
const numbering = doc.package.numbering;
|
|
1655
1695
|
if (numbering && (numbering.abstractNums.length > 0 || numbering.nums.length > 0)) {
|
|
1656
1696
|
zip.file("word/numbering.xml", serializeNumberingXml(numbering));
|
|
@@ -114,8 +114,7 @@ function mergeRunContent(content1, content2) {
|
|
|
114
114
|
if (lastText?.type === "text" && firstText?.type === "text") {
|
|
115
115
|
result[result.length - 1] = {
|
|
116
116
|
type: "text",
|
|
117
|
-
text: lastText.text + firstText.text
|
|
118
|
-
...lastText.preserveSpace || firstText.preserveSpace ? { preserveSpace: true } : {}
|
|
117
|
+
text: lastText.text + firstText.text
|
|
119
118
|
};
|
|
120
119
|
for (let i = 1; i < content2.length; i++) result.push(content2[i]);
|
|
121
120
|
} else for (const c of content2) result.push(c);
|
package/dist/docx/runParser.d.ts
CHANGED
|
@@ -2,6 +2,13 @@ import { document_d_exports } from "../types/document.js";
|
|
|
2
2
|
import { StyleMap } from "./styleParser.js";
|
|
3
3
|
import { XmlElement } from "./xmlParser.js";
|
|
4
4
|
//#region src/docx/runParser.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* `w:vertAlign` `baseline` is the reserved value that means "no vertical
|
|
7
|
+
* offset", not a third offset beside `superscript` and `subscript`. It is
|
|
8
|
+
* answered here because this module reads the slot, so a consumer deciding
|
|
9
|
+
* whether a run sits on the baseline cannot drift from how it was parsed.
|
|
10
|
+
*/
|
|
11
|
+
declare const isBaselineVertAlign: (vertAlign: document_d_exports.TextFormatting["vertAlign"]) => boolean;
|
|
5
12
|
/**
|
|
6
13
|
* Parse run formatting properties (w:rPr)
|
|
7
14
|
*
|
|
@@ -74,4 +81,4 @@ declare function hasFieldChar(run: document_d_exports.Run): boolean;
|
|
|
74
81
|
*/
|
|
75
82
|
declare function getFieldCharType(run: document_d_exports.Run): "begin" | "separate" | "end" | null;
|
|
76
83
|
//#endregion
|
|
77
|
-
export { getFieldCharType, getImages, getRunText, hasContent, hasFieldChar, hasImage, parseRun, parseRunProperties };
|
|
84
|
+
export { getFieldCharType, getImages, getRunText, hasContent, hasFieldChar, hasImage, isBaselineVertAlign, parseRun, parseRunProperties };
|
package/dist/docx/runParser.js
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
import { isValidHexColor } from "../utils/colorResolver.js";
|
|
2
1
|
import { parseHorizontalScalePercent } from "../utils/horizontalScale.js";
|
|
3
2
|
import { parseDiagramPreview } from "./diagramPreview.js";
|
|
4
3
|
import { isGroupDrawing, parseGroupDrawing } from "./groupDrawingParser.js";
|
|
5
4
|
import { parseImage } from "./imageParser.js";
|
|
6
5
|
import { imageRawXmlFingerprint } from "./imageRawXml.js";
|
|
7
|
-
import { EmphasisMarkSchema, FontHintSchema, FontThemeSchema, HighlightColorSchema, PositionalTabAlignmentSchema, PositionalTabLeaderSchema, PositionalTabRelativeToSchema,
|
|
6
|
+
import { EmphasisMarkSchema, FontHintSchema, FontThemeSchema, HighlightColorSchema, PositionalTabAlignmentSchema, PositionalTabLeaderSchema, PositionalTabRelativeToSchema, TextEffectSchema, ThemeColorSlotSchema, UnderlineStyleSchema, narrowEnum } from "./parserEnums.js";
|
|
7
|
+
import { parseShading } from "./shadingParser.js";
|
|
8
8
|
import { parseShapeFromDrawing, shouldPreserveRawShapeDrawing } from "./shapeParser.js";
|
|
9
9
|
import { isTextBoxDrawing } from "./textBoxParser.js";
|
|
10
|
-
import { requiresXmlSpacePreserve } from "./textWhitespace.js";
|
|
11
10
|
import { resolveThemeFontRef } from "./themeParser.js";
|
|
12
11
|
import { parsePropertyChangeInfo } from "./trackedChangeInfo.js";
|
|
13
12
|
import { captureVerbatimXml } from "./verbatimCapture.js";
|
|
14
13
|
import { parseVmlImageContent, shouldPreserveRawVmlPict } from "./vmlImageParser.js";
|
|
15
|
-
import { cloneWithXmlnsDeclarations, findAllDeep, findChild, findChildren, getAttribute, getChildElements, getLocalName, getTextContent, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute, selectAlternateContentBranch } from "./xmlParser.js";
|
|
14
|
+
import { cloneWithXmlnsDeclarations, findAllDeep, findChild, findChildren, getAttribute, getChildElements, getLocalName, getTextContent, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute, parseOnOffAttribute, selectAlternateContentBranch } from "./xmlParser.js";
|
|
16
15
|
import { DRAWING_RAW_XML_MODES } from "@stll/docx-core/model";
|
|
17
16
|
//#region src/docx/runParser.ts
|
|
18
17
|
/**
|
|
@@ -36,31 +35,6 @@ function parseColorValue(rgb, themeColor, themeTint, themeShade) {
|
|
|
36
35
|
if (themeShade) color.themeShade = themeShade;
|
|
37
36
|
return color;
|
|
38
37
|
}
|
|
39
|
-
/**
|
|
40
|
-
* Parse shading properties (w:shd)
|
|
41
|
-
*/
|
|
42
|
-
function parseShadingProperties(shd) {
|
|
43
|
-
if (!shd) return;
|
|
44
|
-
const props = {};
|
|
45
|
-
const color = getAttribute(shd, "w", "color");
|
|
46
|
-
if (color === "auto") props.color = { auto: true };
|
|
47
|
-
else if (color && isValidHexColor(color)) props.color = { rgb: color };
|
|
48
|
-
const fill = getAttribute(shd, "w", "fill");
|
|
49
|
-
if (fill === "auto") props.fill = { auto: true };
|
|
50
|
-
else if (fill && isValidHexColor(fill)) props.fill = { rgb: fill };
|
|
51
|
-
const validatedThemeFill = narrowEnum(getAttribute(shd, "w", "themeFill"), ThemeColorSlotSchema);
|
|
52
|
-
if (validatedThemeFill) {
|
|
53
|
-
if (!props.fill) props.fill = {};
|
|
54
|
-
props.fill.themeColor = validatedThemeFill;
|
|
55
|
-
}
|
|
56
|
-
const themeFillTint = getAttribute(shd, "w", "themeFillTint");
|
|
57
|
-
if (themeFillTint && props.fill) props.fill.themeTint = themeFillTint;
|
|
58
|
-
const themeFillShade = getAttribute(shd, "w", "themeFillShade");
|
|
59
|
-
if (themeFillShade && props.fill) props.fill.themeShade = themeFillShade;
|
|
60
|
-
const pattern = narrowEnum(getAttribute(shd, "w", "val"), ShadingPatternSchema);
|
|
61
|
-
if (pattern) props.pattern = pattern;
|
|
62
|
-
return Object.keys(props).length > 0 ? props : void 0;
|
|
63
|
-
}
|
|
64
38
|
function collectFirstRunPropertyChildren(rPr) {
|
|
65
39
|
const children = {};
|
|
66
40
|
for (const child of rPr.elements ?? []) {
|
|
@@ -167,6 +141,13 @@ function collectFirstRunPropertyChildren(rPr) {
|
|
|
167
141
|
return children;
|
|
168
142
|
}
|
|
169
143
|
/**
|
|
144
|
+
* `w:vertAlign` `baseline` is the reserved value that means "no vertical
|
|
145
|
+
* offset", not a third offset beside `superscript` and `subscript`. It is
|
|
146
|
+
* answered here because this module reads the slot, so a consumer deciding
|
|
147
|
+
* whether a run sits on the baseline cannot drift from how it was parsed.
|
|
148
|
+
*/
|
|
149
|
+
const isBaselineVertAlign = (vertAlign) => vertAlign === "baseline";
|
|
150
|
+
/**
|
|
170
151
|
* Parse run formatting properties (w:rPr)
|
|
171
152
|
*
|
|
172
153
|
* Handles ALL rPr properties:
|
|
@@ -231,7 +212,7 @@ function parseRunProperties(rPr, theme, _styles) {
|
|
|
231
212
|
}
|
|
232
213
|
const shd = propertyChildren.shd;
|
|
233
214
|
if (shd) {
|
|
234
|
-
const shadingResult =
|
|
215
|
+
const shadingResult = parseShading(shd);
|
|
235
216
|
if (shadingResult) formatting.shading = shadingResult;
|
|
236
217
|
}
|
|
237
218
|
const sz = propertyChildren.sz;
|
|
@@ -370,14 +351,10 @@ function parseRunPropertyChanges(rPr, theme, styles, currentFormatting) {
|
|
|
370
351
|
* Parse text content (w:t)
|
|
371
352
|
*/
|
|
372
353
|
function parseTextContent(element) {
|
|
373
|
-
|
|
374
|
-
const preserveSpace = getAttribute(element, "xml", "space") === "preserve" || requiresXmlSpacePreserve(text);
|
|
375
|
-
const content = {
|
|
354
|
+
return {
|
|
376
355
|
type: "text",
|
|
377
|
-
text
|
|
356
|
+
text: getTextContent(element)
|
|
378
357
|
};
|
|
379
|
-
if (preserveSpace) content.preserveSpace = true;
|
|
380
|
-
return content;
|
|
381
358
|
}
|
|
382
359
|
/**
|
|
383
360
|
* Parse tab element (w:tab)
|
|
@@ -442,8 +419,8 @@ function parseEndnoteReference(element) {
|
|
|
442
419
|
*/
|
|
443
420
|
function parseFieldChar(element) {
|
|
444
421
|
const fldCharType = getAttribute(element, "w", "fldCharType");
|
|
445
|
-
const fldLock =
|
|
446
|
-
const dirty =
|
|
422
|
+
const fldLock = parseOnOffAttribute(element, "w", "fldLock") === true;
|
|
423
|
+
const dirty = parseOnOffAttribute(element, "w", "dirty") === true;
|
|
447
424
|
let charType = "begin";
|
|
448
425
|
if (fldCharType === "separate") charType = "separate";
|
|
449
426
|
else if (fldCharType === "end") charType = "end";
|
|
@@ -473,15 +450,14 @@ function parseInstrText(element) {
|
|
|
473
450
|
* Wrap raw XML the model cannot project at all.
|
|
474
451
|
*
|
|
475
452
|
* `DrawingContent` always carries an `Image`, so preservation-only content
|
|
476
|
-
* gets a placeholder one
|
|
477
|
-
*
|
|
478
|
-
*
|
|
453
|
+
* gets a placeholder one. It names no relationship, which keeps
|
|
454
|
+
* `classifyDrawingSafety` and the serializer on the replay path instead of
|
|
455
|
+
* regenerating DrawingML from the placeholder.
|
|
479
456
|
*/
|
|
480
457
|
const preserveOnlyDrawing = (rawXml) => ({
|
|
481
458
|
type: "drawing",
|
|
482
459
|
image: {
|
|
483
460
|
type: "image",
|
|
484
|
-
rId: "",
|
|
485
461
|
size: {
|
|
486
462
|
width: 0,
|
|
487
463
|
height: 0
|
|
@@ -530,13 +506,19 @@ function parseDrawingContent(element, rels, media) {
|
|
|
530
506
|
};
|
|
531
507
|
const image = parseImage(element, rels ?? void 0, media ?? void 0);
|
|
532
508
|
if (!image) return null;
|
|
533
|
-
const
|
|
509
|
+
const rawXml = captureVerbatimXml(element);
|
|
510
|
+
if (image.rId === void 0) return {
|
|
511
|
+
type: "drawing",
|
|
512
|
+
image,
|
|
513
|
+
rawXml,
|
|
514
|
+
rawXmlMode: DRAWING_RAW_XML_MODES.PRESERVE_ONLY
|
|
515
|
+
};
|
|
516
|
+
return {
|
|
534
517
|
type: "drawing",
|
|
535
|
-
image
|
|
518
|
+
image,
|
|
519
|
+
rawXml,
|
|
520
|
+
rawImageFingerprint: imageRawXmlFingerprint(image)
|
|
536
521
|
};
|
|
537
|
-
drawing.rawXml = captureVerbatimXml(element);
|
|
538
|
-
drawing.rawImageFingerprint = imageRawXmlFingerprint(image);
|
|
539
|
-
return drawing;
|
|
540
522
|
}
|
|
541
523
|
/**
|
|
542
524
|
* Parse all content within a run element
|
|
@@ -750,4 +732,4 @@ function getFieldCharType(run) {
|
|
|
750
732
|
return run.content.find((c) => c.type === "fieldChar")?.charType ?? null;
|
|
751
733
|
}
|
|
752
734
|
//#endregion
|
|
753
|
-
export { getFieldCharType, getImages, getRunText, hasContent, hasFieldChar, hasImage, parseRun, parseRunProperties };
|
|
735
|
+
export { getFieldCharType, getImages, getRunText, hasContent, hasFieldChar, hasImage, isBaselineVertAlign, parseRun, parseRunProperties };
|
|
@@ -1,21 +1,27 @@
|
|
|
1
|
+
import { escapeXmlAttribute } from "@stll/docx-core";
|
|
1
2
|
//#region src/docx/sdtPropertiesPatch.ts
|
|
2
|
-
const XML_ATTR_ESCAPES = {
|
|
3
|
-
"&": "&",
|
|
4
|
-
"<": "<",
|
|
5
|
-
">": ">",
|
|
6
|
-
"\"": """,
|
|
7
|
-
"'": "'"
|
|
8
|
-
};
|
|
9
3
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
4
|
+
* Surgical patches for the captured `<w:sdtPr>` XML string.
|
|
5
|
+
*
|
|
6
|
+
* Background. The block-SDT serializer (commit 3) replays
|
|
7
|
+
* `properties.rawPropertiesXml` verbatim so unmodeled OOXML markers
|
|
8
|
+
* (`w:dataBinding`, `w15:repeatingSection`, custom XML mappings) survive
|
|
9
|
+
* a round trip. That replay is correct for unchanged controls but goes
|
|
10
|
+
* stale the moment the editor mutates a modeled property: a user
|
|
11
|
+
* toggling a checkbox, picking a date, or choosing a dropdown value
|
|
12
|
+
* updates `properties.checked` / `dateFormat` / etc., but the raw
|
|
13
|
+
* `w14:checked w14:val="0"` already encoded by the source DOCX stays
|
|
14
|
+
* in `rawPropertiesXml` and gets written back on save — Word reopens
|
|
15
|
+
* the document with the user's interactive change discarded.
|
|
16
|
+
*
|
|
17
|
+
* `reconcileRawSdtPr` walks every modeled field that has an OOXML
|
|
18
|
+
* representation inside `<w:sdtPr>` and, if the field is set on the
|
|
19
|
+
* model, rewrites the matching element in the raw string. Unmodeled
|
|
20
|
+
* markers are untouched, so dataBinding / repeatingSection round-trip
|
|
21
|
+
* stays lossless even after the user has mutated the control.
|
|
22
|
+
*
|
|
23
|
+
* Picked up from upstream eigenpal/docx-editor#661.
|
|
15
24
|
*/
|
|
16
|
-
function escapeXmlAttr(value) {
|
|
17
|
-
return value.replace(/[&<>"']/gu, (ch) => XML_ATTR_ESCAPES[ch] ?? ch);
|
|
18
|
-
}
|
|
19
25
|
/**
|
|
20
26
|
* Drop any `*:lastValue="…"` attribute (any namespace prefix, or
|
|
21
27
|
* unprefixed) from an attribute-list string. We avoid a single greedy
|
|
@@ -116,8 +122,8 @@ function reconcileRawSdtPr(raw, props, options = {}) {
|
|
|
116
122
|
if (fullDate !== void 0 || dateFormat !== void 0) {
|
|
117
123
|
const wDate = /<(?<prefix>\w+):date\b(?<attrs>[^>]*)>(?<inner>[\s\S]*?)<\/\w+:date>/iu;
|
|
118
124
|
const wDateSelf = /<(?<prefix>\w+):date\b(?<attrs>[^/>]*)\/>/iu;
|
|
119
|
-
const fullDateAttr = fullDate !== void 0 ? ` w:fullDate="${
|
|
120
|
-
const formatChild = dateFormat !== void 0 ? `<w:dateFormat w:val="${
|
|
125
|
+
const fullDateAttr = fullDate !== void 0 ? ` w:fullDate="${escapeXmlAttribute(fullDate)}"` : "";
|
|
126
|
+
const formatChild = dateFormat !== void 0 ? `<w:dateFormat w:val="${escapeXmlAttribute(dateFormat)}"/>` : "";
|
|
121
127
|
if (wDate.test(next)) next = next.replace(wDate, (_match, prefix, matchedAttrs, inner) => {
|
|
122
128
|
let body = inner.replaceAll(/<\w+:dateFormat\b[^>]*(?:\/>|>[\s\S]*?<\/\w+:dateFormat>)/giu, "");
|
|
123
129
|
if (formatChild) body = `${formatChild}${body}`;
|
|
@@ -128,7 +134,7 @@ function reconcileRawSdtPr(raw, props, options = {}) {
|
|
|
128
134
|
}
|
|
129
135
|
}
|
|
130
136
|
if ((props.sdtType === "dropdown" || props.sdtType === "comboBox") && options.dropdownLastValue !== void 0) {
|
|
131
|
-
const escapedValue =
|
|
137
|
+
const escapedValue = escapeXmlAttribute(options.dropdownLastValue);
|
|
132
138
|
const opened = /<(?<prefix>\w+):(?<name>dropDownList|comboBox)\b(?<attrs>[^>]*)>(?<inner>[\s\S]*?)<\/\w+:(?:dropDownList|comboBox)>/iu;
|
|
133
139
|
const selfClosing = /<(?<prefix>\w+):(?<name>dropDownList|comboBox)\b(?<attrs>[^/>]*)\/>/iu;
|
|
134
140
|
if (opened.test(next)) next = next.replace(opened, (_match, prefix, name, attrs, inner) => {
|