@stll/folio-core 0.44.0 → 0.46.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/apply.js +59 -19
- package/dist/ai-edits/headless.js +2 -1
- package/dist/compare/compare.js +2 -5
- package/dist/compare/content-alignment.js +78 -53
- package/dist/compare/inline-atoms.js +34 -20
- package/dist/compare/scenario.js +2 -5
- package/dist/compare/types.d.ts +9 -1
- package/dist/compare/types.js +12 -1
- package/dist/compat/eigenpal.d.ts +2 -1
- package/dist/compat/eigenpal.js +2 -1
- package/dist/content-controls/checkboxDisplay.d.ts +11 -0
- package/dist/content-controls/checkboxDisplay.js +40 -0
- package/dist/content-controls/findContentControls.js +3 -1
- package/dist/content-controls/mutateContentControls.js +21 -4
- package/dist/controller/contentControlWidgetController.d.ts +1 -0
- package/dist/controller/contentControlWidgetController.js +3 -0
- package/dist/controller/hiddenEditorApi.js +3 -1
- package/dist/controller/hiddenEditorManager.d.ts +1 -1
- package/dist/controller/hiddenEditorManager.js +6 -3
- package/dist/display-list/build/buildDisplayList.js +20 -1
- package/dist/display-list/build/imagePrimitives.d.ts +1 -1
- package/dist/display-list/build/imagePrimitives.js +3 -1
- package/dist/display-list/build/paragraphPrimitives.js +21 -2
- package/dist/display-list/dom/renderDisplayListToDom.js +18 -10
- package/dist/display-list/types.d.ts +17 -0
- package/dist/document-operations.js +14 -3
- package/dist/docx/attributeRemainder.d.ts +43 -0
- package/dist/docx/attributeRemainder.js +65 -0
- package/dist/docx/blockContentParser.d.ts +1 -1
- package/dist/docx/blockContentParser.js +87 -63
- package/dist/docx/blockPlainText.d.ts +3 -4
- package/dist/docx/blockPlainText.js +1 -0
- package/dist/docx/bookmarkPlacement.js +2 -0
- package/dist/docx/borderParser.js +5 -5
- package/dist/docx/commentAnchorIndex.d.ts +30 -0
- package/dist/docx/commentAnchorIndex.js +50 -0
- package/dist/docx/commentParser.d.ts +1 -1
- package/dist/docx/commentParser.js +78 -50
- package/dist/docx/commentRangeIntegrity.d.ts +16 -1
- package/dist/docx/commentRangeIntegrity.js +42 -1
- package/dist/docx/commentRangeJoin.d.ts +9 -0
- package/dist/docx/commentRangeJoin.js +33 -0
- package/dist/docx/commentReferenceCompletion.d.ts +9 -0
- package/dist/docx/commentReferenceCompletion.js +47 -0
- package/dist/docx/commentReferenceNormalization.js +1 -0
- package/dist/docx/commentReplyMarkers.js +11 -19
- package/dist/docx/commentThreadKey.d.ts +18 -0
- package/dist/docx/commentThreadKey.js +22 -0
- package/dist/docx/compatibility.js +1 -0
- package/dist/docx/containerChildren.d.ts +111 -0
- package/dist/docx/containerChildren.gen.d.ts +28 -0
- package/dist/docx/containerChildren.gen.js +246 -0
- package/dist/docx/containerChildren.js +103 -0
- package/dist/docx/diagramPreview.js +87 -27
- package/dist/docx/documentParser.d.ts +1 -1
- package/dist/docx/documentParser.js +3 -3
- package/dist/docx/ensureParaIds.js +17 -7
- package/dist/docx/fieldParser.d.ts +1 -1
- package/dist/docx/fieldParser.js +4 -4
- package/dist/docx/fieldState.d.ts +26 -0
- package/dist/docx/fieldState.js +53 -0
- package/dist/docx/footnoteParser.d.ts +1 -1
- package/dist/docx/graphicFrameLocks.d.ts +16 -4
- package/dist/docx/graphicFrameLocks.js +19 -6
- package/dist/docx/groupDrawingParser.js +3 -3
- package/dist/docx/headerFooterParser.js +1 -1
- package/dist/docx/headerFooterReferenceNormalization.js +1 -0
- package/dist/docx/hyperlinkParser.d.ts +27 -8
- package/dist/docx/hyperlinkParser.js +78 -20
- package/dist/docx/imageParser.d.ts +9 -1
- package/dist/docx/imageParser.js +91 -20
- package/dist/docx/imageRawXml.d.ts +14 -1
- package/dist/docx/imageRawXml.js +30 -6
- package/dist/docx/inlineWrapperContent.d.ts +85 -0
- package/dist/docx/inlineWrapperContent.js +84 -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/normalizeBaseDirection.js +10 -1
- package/dist/docx/paraIdAttribute.d.ts +21 -0
- package/dist/docx/paraIdAttribute.js +64 -0
- package/dist/docx/paragraphParser.d.ts +1 -1
- package/dist/docx/paragraphParser.js +277 -168
- package/dist/docx/paragraphPropertySource.js +5 -1
- package/dist/docx/paragraphTextBoxEnrichment.d.ts +1 -1
- package/dist/docx/paragraphTextBoxEnrichment.js +9 -52
- package/dist/docx/paragraphTraversal.js +7 -4
- package/dist/docx/parseWarningMessage.js +2 -1
- package/dist/docx/parser.js +2 -2
- package/dist/docx/preservedRunContent.d.ts +32 -0
- package/dist/docx/preservedRunContent.js +86 -0
- package/dist/docx/previewBudget.d.ts +64 -0
- package/dist/docx/previewBudget.js +88 -0
- package/dist/docx/renderedPageBreakNormalization.js +2 -4
- package/dist/docx/revisionIdNormalization.js +17 -5
- package/dist/docx/rezip.js +91 -36
- package/dist/docx/runParser.d.ts +1 -1
- package/dist/docx/runParser.js +16 -12
- package/dist/docx/sdtPropertiesPatch.js +24 -18
- package/dist/docx/sectionParser.js +8 -1
- package/dist/docx/sectionReferenceHistory.js +2 -2
- package/dist/docx/selectiveSave.js +6 -6
- package/dist/docx/selectiveXmlPatch.d.ts +46 -2
- package/dist/docx/selectiveXmlPatch.js +86 -39
- package/dist/docx/serializer/blockSdtSerializer.js +38 -26
- package/dist/docx/serializer/borderSerializer.d.ts +1 -1
- package/dist/docx/serializer/borderSerializer.js +15 -14
- package/dist/docx/serializer/commentSerializer.d.ts +41 -16
- package/dist/docx/serializer/commentSerializer.js +87 -92
- package/dist/docx/serializer/documentSerializer.js +8 -11
- package/dist/docx/serializer/fontTableSerializer.js +6 -6
- package/dist/docx/serializer/headerFooterSerializer.js +12 -14
- package/dist/docx/serializer/markupRangeAttributes.js +2 -2
- package/dist/docx/serializer/noteSerializer.js +8 -8
- package/dist/docx/serializer/numberingSerializer.js +7 -6
- package/dist/docx/serializer/paragraphSerializer.js +93 -72
- package/dist/docx/serializer/partNamespaces.js +2 -2
- package/dist/docx/serializer/runSerializer.js +73 -44
- package/dist/docx/serializer/sectionPropertiesSerializer.js +19 -14
- package/dist/docx/serializer/settingsSerializer.js +4 -3
- package/dist/docx/serializer/stylesSerializer.js +6 -6
- package/dist/docx/serializer/tableSerializer.js +28 -17
- 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/createBilingualDocument.js +3 -1
- package/dist/docx/server/materializeYjsDocx.d.ts +1 -1
- package/dist/docx/server/materializeYjsDocx.js +9 -1
- package/dist/docx/server/migrateYjsAttrSchema.d.ts +55 -0
- package/dist/docx/server/migrateYjsAttrSchema.js +95 -0
- package/dist/docx/server/validateDocxConformance.js +22 -1
- package/dist/docx/shapeParser.js +12 -10
- package/dist/docx/tableParser.d.ts +1 -1
- package/dist/docx/tableParser.js +206 -85
- package/dist/docx/textBoxParser.d.ts +25 -2
- package/dist/docx/textBoxParser.js +59 -4
- package/dist/docx/unzip.d.ts +23 -0
- package/dist/docx/unzip.js +37 -26
- package/dist/docx/verbatimCapture.js +1 -1
- package/dist/docx/vmlImageParser.d.ts +17 -1
- package/dist/docx/vmlImageParser.js +20 -3
- package/dist/docx/vmlPreview.d.ts +1 -3
- package/dist/docx/vmlPreview.js +2 -30
- package/dist/docx/xmlEncoding.d.ts +5 -0
- package/dist/docx/xmlEncoding.js +12 -0
- package/dist/docx/xmlParser.d.ts +26 -2
- package/dist/docx/xmlParser.js +70 -27
- package/dist/docx/xmlResourceLimits.d.ts +89 -9
- package/dist/docx/xmlResourceLimits.js +105 -24
- package/dist/headless-layout.js +10 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/internal/pageBreakRunSourceDescendantIndex.js +5 -2
- package/dist/internal/paragraphFormattingSerialization.js +3 -2
- package/dist/layout-bridge/convert/toFlowBlocks.js +102 -12
- package/dist/layout-engine/measure/tableCellFloating.d.ts +2 -0
- package/dist/layout-engine/measure/tableCellFloating.js +2 -0
- package/dist/layout-engine/types.d.ts +23 -1
- package/dist/layout-painter/renderImage.d.ts +5 -1
- package/dist/layout-painter/renderImage.js +14 -6
- package/dist/layout-painter/renderPage.d.ts +2 -0
- package/dist/layout-painter/renderPage.js +4 -0
- package/dist/layout-painter/renderParagraph.d.ts +13 -1
- package/dist/layout-painter/renderParagraph.js +26 -6
- package/dist/managers/autoSaveCodec.d.ts +9 -1
- package/dist/managers/autoSaveCodec.js +14 -12
- package/dist/markdown/images.js +1 -4
- package/dist/markdown/renderBlock.js +1 -0
- package/dist/markdown/renderRuns.d.ts +1 -1
- package/dist/markdown/renderRuns.js +28 -5
- package/dist/markdown/renderTable.js +22 -5
- package/dist/markdown/trailers.js +4 -1
- package/dist/pdf/images.js +142 -6
- package/dist/prosemirror/attrs/index.d.ts +14 -3
- package/dist/prosemirror/attrs/index.js +224 -2
- package/dist/prosemirror/authoredTransformAttrs.d.ts +28 -0
- package/dist/prosemirror/authoredTransformAttrs.js +64 -0
- package/dist/prosemirror/commands/contentControls.js +10 -2
- package/dist/prosemirror/commands/image.d.ts +9 -2
- package/dist/prosemirror/commands/image.js +43 -28
- package/dist/prosemirror/commands/pageBreak.js +1 -1
- package/dist/prosemirror/commentReferenceAttrs.d.ts +8 -0
- package/dist/prosemirror/commentReferenceAttrs.js +33 -0
- package/dist/prosemirror/commentReferenceIntegrity.d.ts +47 -0
- package/dist/prosemirror/commentReferenceIntegrity.js +43 -0
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +2 -13
- package/dist/prosemirror/conversion/fromProseDoc.js +338 -99
- package/dist/prosemirror/conversion/index.d.ts +2 -2
- package/dist/prosemirror/conversion/toProseDoc.d.ts +7 -9
- package/dist/prosemirror/conversion/toProseDoc.js +551 -327
- package/dist/prosemirror/extensions/StarterKit.js +8 -58
- package/dist/prosemirror/extensions/core/DocExtension.js +1 -1
- package/dist/prosemirror/extensions/core/ParagraphExtension.js +2 -1
- package/dist/prosemirror/extensions/features/BaseKeymapExtension.js +45 -27
- package/dist/prosemirror/extensions/features/EmptyParagraphFormatExtension.js +3 -1
- package/dist/prosemirror/extensions/features/ImagePasteExtension.js +5 -3
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +1 -0
- package/dist/prosemirror/extensions/markRegistry.d.ts +41 -0
- package/dist/prosemirror/extensions/markRegistry.js +75 -0
- package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +2 -3
- package/dist/prosemirror/extensions/marks/InlineWrapperExtension.d.ts +18 -0
- package/dist/prosemirror/extensions/marks/InlineWrapperExtension.js +63 -0
- package/dist/prosemirror/extensions/marks/markUtils.d.ts +2 -1
- package/dist/prosemirror/extensions/marks/markUtils.js +47 -4
- package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.d.ts +11 -0
- package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +87 -0
- package/dist/prosemirror/extensions/nodes/FieldExtension.js +9 -7
- package/dist/prosemirror/extensions/nodes/ImageExtension.js +20 -0
- package/dist/prosemirror/extensions/nodes/PreservedBlockExtension.d.ts +32 -0
- package/dist/prosemirror/extensions/nodes/PreservedBlockExtension.js +67 -0
- package/dist/prosemirror/extensions/nodes/PreservedXmlExtension.d.ts +16 -0
- package/dist/prosemirror/extensions/nodes/PreservedXmlExtension.js +60 -0
- package/dist/prosemirror/extensions/nodes/ShapeExtension.js +10 -2
- package/dist/prosemirror/extensions/nodes/TableExtension.js +3 -2
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +13 -5
- package/dist/prosemirror/inlineWrapperStack.d.ts +39 -0
- package/dist/prosemirror/inlineWrapperStack.js +75 -0
- package/dist/prosemirror/pageBreakRunProjection.d.ts +17 -7
- package/dist/prosemirror/pageBreakRunProjection.js +19 -9
- package/dist/prosemirror/paragraphFormattingProvenance.d.ts +6 -3
- package/dist/prosemirror/paragraphFormattingProvenance.js +12 -3
- package/dist/prosemirror/replacedAnnotations.d.ts +59 -0
- package/dist/prosemirror/replacedAnnotations.js +165 -0
- package/dist/prosemirror/runFormattingInlineCarriers.d.ts +3 -1
- package/dist/prosemirror/runFormattingInlineCarriers.js +5 -1
- package/dist/prosemirror/schema/index.d.ts +3 -3
- package/dist/prosemirror/schema/marks.d.ts +25 -1
- package/dist/prosemirror/schema/nodes.d.ts +139 -3
- package/dist/prosemirror/schema/nodes.js +16 -1
- package/dist/prosemirror/trackedRunInlineAtoms.d.ts +2 -0
- package/dist/prosemirror/trackedRunInlineAtoms.js +2 -0
- package/dist/prosemirror/validation.js +16 -2
- package/dist/prosemirror/yjsDocumentMetadata.d.ts +73 -0
- package/dist/prosemirror/yjsDocumentMetadata.js +171 -0
- package/dist/prosemirror/zeroWidthAnchors.js +2 -0
- package/dist/render-dom/commentAnchorAttributes.d.ts +37 -0
- package/dist/render-dom/commentAnchorAttributes.js +54 -0
- package/dist/server.d.ts +3 -1
- package/dist/server.js +3 -1
- 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.d.ts +9 -1
- package/dist/utils/clipboard.js +29 -2
- package/dist/utils/findReplace.js +1 -1
- package/dist/utils/imageLuminance.d.ts +15 -0
- package/dist/utils/imageLuminance.js +32 -0
- package/dist/utils/mergeDocumentContent.js +7 -17
- package/dist/utils/replaceText.js +1 -0
- 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/blockRangeMarkers.d.ts +0 -36
- package/dist/docx/blockRangeMarkers.js +0 -59
- package/dist/prosemirror/yjsParagraphSourceContract.d.ts +0 -9
- package/dist/prosemirror/yjsParagraphSourceContract.js +0 -26
package/dist/docx/rezip.js
CHANGED
|
@@ -19,7 +19,7 @@ import { RELATIONSHIP_TYPES, parseRelationships, resolveRelativePath } from "./r
|
|
|
19
19
|
import { removeResolvedHeaderFooterParts } from "./removeHeaderFooterParts.js";
|
|
20
20
|
import { normalizeRevisionIdsInXmlParts } from "./revisionIdNormalization.js";
|
|
21
21
|
import { buildPatchedNotePartXml, collectChangedNoteParaIds, collectParaIds, patchNumberingDefinitions } from "./selectiveXmlPatch.js";
|
|
22
|
-
import {
|
|
22
|
+
import { planCommentParts, serializeComments, serializeCommentsExtended } from "./serializer/commentSerializer.js";
|
|
23
23
|
import { serializeDocument } from "./serializer/documentSerializer.js";
|
|
24
24
|
import { serializeFontTableXml } from "./serializer/fontTableSerializer.js";
|
|
25
25
|
import { serializeHeaderFooter } from "./serializer/headerFooterSerializer.js";
|
|
@@ -29,11 +29,10 @@ import { readRootNamespaceBindings } from "./serializer/partNamespaces.js";
|
|
|
29
29
|
import { serializeSettingsXml } from "./serializer/settingsSerializer.js";
|
|
30
30
|
import { serializeStyle, serializeStylesXml } from "./serializer/stylesSerializer.js";
|
|
31
31
|
import { serializeThemeXml } from "./serializer/themeSerializer.js";
|
|
32
|
-
import {
|
|
33
|
-
import { OFFICE_RELATIONSHIP_NAMESPACE_URIS, WORDPROCESSINGML_NAMESPACE_URIS, findChild, getAttribute, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespaceUri, matchesName, parseXml, parseXmlDocument } from "./xmlParser.js";
|
|
32
|
+
import { OFFICE_RELATIONSHIP_NAMESPACE_URIS, WORDPROCESSINGML_NAMESPACE_URIS, findChild, findChildByNamespaceUri, findChildrenByNamespaceUri, getAttribute, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespaceUri, matchesName, parseXml, parseXmlDocument } from "./xmlParser.js";
|
|
34
33
|
import { assertXmlResourceLimits } from "./xmlResourceLimits.js";
|
|
35
34
|
import { panic } from "better-result";
|
|
36
|
-
import { validateDocxPackage } from "@stll/docx-core";
|
|
35
|
+
import { escapeXmlAttribute, escapeXmlText, validateDocxPackage } from "@stll/docx-core";
|
|
37
36
|
import JSZip from "jszip";
|
|
38
37
|
//#region src/docx/rezip.ts
|
|
39
38
|
/**
|
|
@@ -85,7 +84,7 @@ function findMaxRId(relsXml) {
|
|
|
85
84
|
}
|
|
86
85
|
const isWordprocessingElement = (element, localName) => getLocalName(element.name) === localName && WORDPROCESSINGML_NAMESPACE_URIS.has(getNamespaceUri(element) ?? "");
|
|
87
86
|
const countDocumentSections = (xml) => {
|
|
88
|
-
assertXmlResourceLimits(xml);
|
|
87
|
+
assertXmlResourceLimits({ xml });
|
|
89
88
|
let count = 0;
|
|
90
89
|
const pending = [{
|
|
91
90
|
element: parseXml(xml),
|
|
@@ -106,7 +105,7 @@ const countDocumentSections = (xml) => {
|
|
|
106
105
|
};
|
|
107
106
|
const extractHeaderFooterReferences = (xml) => {
|
|
108
107
|
const references = [];
|
|
109
|
-
assertXmlResourceLimits(xml);
|
|
108
|
+
assertXmlResourceLimits({ xml });
|
|
110
109
|
const pending = [parseXml(xml)];
|
|
111
110
|
while (pending.length > 0) {
|
|
112
111
|
const node = pending.pop();
|
|
@@ -166,15 +165,15 @@ async function serializeCommentsToZip(doc, zip, compressionLevel) {
|
|
|
166
165
|
if (comments.length === 0) {
|
|
167
166
|
if (sourceCommentsXml === void 0 || !hasCommentEntries(sourceCommentsXml)) return;
|
|
168
167
|
}
|
|
169
|
-
|
|
170
|
-
const commentsXml = serializeComments(
|
|
168
|
+
const plan = planCommentParts(comments);
|
|
169
|
+
const commentsXml = serializeComments(plan, sourceCommentsXml === void 0 ? void 0 : readRootNamespaceBindings(sourceCommentsXml));
|
|
171
170
|
zip.file(sourceCommentsFile?.name ?? "word/comments.xml", commentsXml, {
|
|
172
171
|
compression: "DEFLATE",
|
|
173
172
|
compressionOptions: { level: compressionLevel }
|
|
174
173
|
});
|
|
175
174
|
await ensureCommentsContentType(zip, compressionLevel);
|
|
176
175
|
await ensureCommentsRelationship(zip, compressionLevel);
|
|
177
|
-
await syncCommentsExtendedPart(
|
|
176
|
+
await syncCommentsExtendedPart(plan, zip, compressionLevel);
|
|
178
177
|
}
|
|
179
178
|
const hasCommentEntries = (xml) => {
|
|
180
179
|
const root = parseXml(xml);
|
|
@@ -189,8 +188,8 @@ const hasCommentEntries = (xml) => {
|
|
|
189
188
|
* removed part triggers Word's repair prompt). The reply-thread markers in
|
|
190
189
|
* `document.xml` are synthesized separately (see {@link applyReplyThreadMarkers}).
|
|
191
190
|
*/
|
|
192
|
-
async function syncCommentsExtendedPart(
|
|
193
|
-
const xml = serializeCommentsExtended(
|
|
191
|
+
async function syncCommentsExtendedPart(plan, zip, compressionLevel) {
|
|
192
|
+
const xml = serializeCommentsExtended(plan);
|
|
194
193
|
const existing = findZipEntryCaseInsensitive(zip, COMMENTS_EXTENDED_PART_LOWER);
|
|
195
194
|
if (!xml) {
|
|
196
195
|
if (!existing) return;
|
|
@@ -396,7 +395,7 @@ async function processNewImages(parts, zip, compressionLevel) {
|
|
|
396
395
|
compression: "DEFLATE",
|
|
397
396
|
compressionOptions: { level: compressionLevel }
|
|
398
397
|
});
|
|
399
|
-
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))}"/>`);
|
|
400
399
|
extensionsAdded.add(extension);
|
|
401
400
|
if (drawing.rawXml) drawing.rawXml = rebindDrawingImageRelationship({
|
|
402
401
|
xml: drawing.rawXml,
|
|
@@ -444,6 +443,7 @@ function collectHyperlinksWithoutRId(blocks) {
|
|
|
444
443
|
break;
|
|
445
444
|
case "simpleField":
|
|
446
445
|
case "inlineSdt":
|
|
446
|
+
case "inlineWrapper":
|
|
447
447
|
case "insertion":
|
|
448
448
|
case "deletion":
|
|
449
449
|
case "moveFrom":
|
|
@@ -455,7 +455,7 @@ function collectHyperlinksWithoutRId(blocks) {
|
|
|
455
455
|
};
|
|
456
456
|
for (const block of blocks) if (block.type === "paragraph") collectInlineHyperlinks(block.content);
|
|
457
457
|
else if (block.type === "table") for (const row of block.rows) for (const cell of row.cells) hyperlinks.push(...collectHyperlinksWithoutRId(cell.content));
|
|
458
|
-
else hyperlinks.push(...collectHyperlinksWithoutRId(block.content));
|
|
458
|
+
else if (block.type === "blockSdt") hyperlinks.push(...collectHyperlinksWithoutRId(block.content));
|
|
459
459
|
return hyperlinks;
|
|
460
460
|
}
|
|
461
461
|
/** The selective save boundary must use the same resource census as full repack. */
|
|
@@ -476,7 +476,7 @@ async function processNewHyperlinks(parts, zip, compressionLevel) {
|
|
|
476
476
|
if (!hyperlink.href) continue;
|
|
477
477
|
maxId++;
|
|
478
478
|
const newRId = `rId${maxId}`;
|
|
479
|
-
relEntries.push(`<Relationship Id="${newRId}" Type="${RELATIONSHIP_TYPES.hyperlink}" Target="${
|
|
479
|
+
relEntries.push(`<Relationship Id="${newRId}" Type="${RELATIONSHIP_TYPES.hyperlink}" Target="${escapeXmlAttribute(hyperlink.href)}" TargetMode="External"/>`);
|
|
480
480
|
hyperlink.rId = newRId;
|
|
481
481
|
}
|
|
482
482
|
zip.file(relsPath, relsXml.replace("</Relationships>", `${relEntries.join("")}</Relationships>`), {
|
|
@@ -952,7 +952,7 @@ async function addRelationship(originalBuffer, relationship) {
|
|
|
952
952
|
const relsXml = await relsFile.async("text");
|
|
953
953
|
const newRId = `rId${findMaxRId(relsXml) + 1}`;
|
|
954
954
|
const targetModeAttr = relationship.targetMode === "External" ? " TargetMode=\"External\"" : "";
|
|
955
|
-
const newRelElement = `<Relationship Id="${newRId}" Type="${relationship.type}" Target="${
|
|
955
|
+
const newRelElement = `<Relationship Id="${newRId}" Type="${relationship.type}" Target="${escapeXmlAttribute(relationship.target)}"${targetModeAttr}/>`;
|
|
956
956
|
const updatedRelsXml = relsXml.replace("</Relationships>", `${newRelElement}</Relationships>`);
|
|
957
957
|
zip.file(relsPath, updatedRelsXml);
|
|
958
958
|
return {
|
|
@@ -1101,7 +1101,7 @@ async function materializeNewHeaderFooterParts(doc, zip, compressionLevel) {
|
|
|
1101
1101
|
type: relType,
|
|
1102
1102
|
target: filename
|
|
1103
1103
|
});
|
|
1104
|
-
relEntries.push(`<Relationship Id="${
|
|
1104
|
+
relEntries.push(`<Relationship Id="${escapeXmlAttribute(effectiveRId)}" Type="${relType}" Target="${filename}"/>`);
|
|
1105
1105
|
overrides.push(`<Override PartName="/word/${filename}" ContentType="${contentType}"/>`);
|
|
1106
1106
|
}
|
|
1107
1107
|
};
|
|
@@ -1218,7 +1218,7 @@ async function rebindWatermarkRelIds(doc, zip, compressionLevel) {
|
|
|
1218
1218
|
}
|
|
1219
1219
|
if (!resolvedRId) {
|
|
1220
1220
|
resolvedRId = `rId${findMaxRId(relsXml) + 1}`;
|
|
1221
|
-
const relXml = canonical.mode === "external" ? `<Relationship Id="${resolvedRId}" Type="${RELATIONSHIP_TYPES.image}" Target="${
|
|
1221
|
+
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))}"/>`;
|
|
1222
1222
|
relsXmlByPath.set(relsPath, relsXml.replace("</Relationships>", `${relXml}</Relationships>`));
|
|
1223
1223
|
changedPaths.add(relsPath);
|
|
1224
1224
|
}
|
|
@@ -1426,7 +1426,26 @@ const seedStylesXmlWith = (missing) => {
|
|
|
1426
1426
|
const rootClose = SEED_STYLES_XML.lastIndexOf(STYLES_CLOSE_ROOT);
|
|
1427
1427
|
return SEED_STYLES_XML.slice(0, rootClose) + missing.map(serializeStyle).join("") + SEED_STYLES_XML.slice(rootClose);
|
|
1428
1428
|
};
|
|
1429
|
-
|
|
1429
|
+
/**
|
|
1430
|
+
* The style ids `word/styles.xml` already defines.
|
|
1431
|
+
*
|
|
1432
|
+
* Read by parsing the part, because the model holds the *decoded* id and the
|
|
1433
|
+
* part holds its XML spelling: `w:styleId='Body A'` is single-quoted,
|
|
1434
|
+
* `w:styleId="Header & Footer"` is escaped, and either may sit behind an
|
|
1435
|
+
* attribute value containing `>`. A reader that misses one of those reports a
|
|
1436
|
+
* style the part already defines as missing, and
|
|
1437
|
+
* {@link serializeAddedStylesIntoZip} appends it — on every save, for ever.
|
|
1438
|
+
*/
|
|
1439
|
+
const definedStyleIds = (stylesXml) => {
|
|
1440
|
+
const root = parseXml(stylesXml);
|
|
1441
|
+
const styles = findChildByNamespaceUri(root, WORDPROCESSINGML_NAMESPACE_URIS, "styles") ?? root;
|
|
1442
|
+
const ids = /* @__PURE__ */ new Set();
|
|
1443
|
+
for (const style of findChildrenByNamespaceUri(styles, WORDPROCESSINGML_NAMESPACE_URIS, "style")) {
|
|
1444
|
+
const id = getAttributeByNamespaceUri(style, WORDPROCESSINGML_NAMESPACE_URIS, "styleId") ?? style.attributes?.["w:styleId"];
|
|
1445
|
+
if (id !== void 0 && id !== null && id !== "") ids.add(String(id));
|
|
1446
|
+
}
|
|
1447
|
+
return ids;
|
|
1448
|
+
};
|
|
1430
1449
|
/**
|
|
1431
1450
|
* Append styles the model defines but the original `word/styles.xml` lacks.
|
|
1432
1451
|
* Existing styles stay byte-exact (edits to them are not written here, the
|
|
@@ -1472,9 +1491,13 @@ async function serializeAddedStylesIntoZip(doc, originalZip, newZip, compression
|
|
|
1472
1491
|
});
|
|
1473
1492
|
return;
|
|
1474
1493
|
}
|
|
1475
|
-
const existing =
|
|
1476
|
-
|
|
1477
|
-
const
|
|
1494
|
+
const existing = new Set(definedStyleIds(originalXml));
|
|
1495
|
+
const added = [];
|
|
1496
|
+
for (const style of styles.styles) {
|
|
1497
|
+
if (existing.has(style.styleId)) continue;
|
|
1498
|
+
existing.add(style.styleId);
|
|
1499
|
+
added.push(style);
|
|
1500
|
+
}
|
|
1478
1501
|
if (added.length === 0) return;
|
|
1479
1502
|
const patched = originalXml.slice(0, rootClose) + added.map(serializeStyle).join("") + originalXml.slice(rootClose);
|
|
1480
1503
|
newZip.file(file.name, patched, {
|
|
@@ -1482,28 +1505,60 @@ async function serializeAddedStylesIntoZip(doc, originalZip, newZip, compression
|
|
|
1482
1505
|
compressionOptions: { level: compressionLevel }
|
|
1483
1506
|
});
|
|
1484
1507
|
}
|
|
1508
|
+
/**
|
|
1509
|
+
* The XML to write for a patched note part, or null to keep the original.
|
|
1510
|
+
*
|
|
1511
|
+
* A refusal over comment-range balance is not a failure to serialize: the
|
|
1512
|
+
* splice was too narrow to keep a comment's range whole, so the part is
|
|
1513
|
+
* written from the model, which is balanced with itself and with the
|
|
1514
|
+
* `document.xml` this repack rewrites beside it. Word's required separator
|
|
1515
|
+
* notes come back synthesized rather than byte-exact, the price of keeping the
|
|
1516
|
+
* comment anchored to the text it was written about. Any other refusal means a
|
|
1517
|
+
* changed paragraph could not be located in the part at all, which no wider
|
|
1518
|
+
* rewrite fixes.
|
|
1519
|
+
*/
|
|
1520
|
+
const notePartXmlFor = ({ patch, originalXml, replacementXml, hasDirtyParagraph, elementName, partName }) => {
|
|
1521
|
+
const keepOriginal = () => {
|
|
1522
|
+
if (hasDirtyParagraph) throw new DocxPackageFidelityError(`Cannot serialize changed ${elementName} paragraphs into ${partName}`);
|
|
1523
|
+
return null;
|
|
1524
|
+
};
|
|
1525
|
+
switch (patch.type) {
|
|
1526
|
+
case "patched": return patch.xml === originalXml ? keepOriginal() : patch.xml;
|
|
1527
|
+
case "refused": switch (patch.reason) {
|
|
1528
|
+
case "comment-range-balance": return replacementXml;
|
|
1529
|
+
case "unroutable-paragraph": return keepOriginal();
|
|
1530
|
+
default: {
|
|
1531
|
+
const unreachable = patch.reason;
|
|
1532
|
+
return panic("Unhandled note-part patch refusal", { reason: unreachable });
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
default: return panic("Unhandled note-part patch", { patch });
|
|
1536
|
+
}
|
|
1537
|
+
};
|
|
1485
1538
|
async function patchNotePartIntoZip({ conventionalLowerPath, currentXml, replacementXml, baselineFrom, elementName, changedNoteParaIds, originalZip, newZip, compressionLevel }) {
|
|
1486
1539
|
const file = findNotePartEntry(originalZip, conventionalLowerPath);
|
|
1487
1540
|
if (!file) return;
|
|
1488
1541
|
const originalXml = await file.async("text");
|
|
1489
1542
|
const baselineXml = baselineFrom(originalXml);
|
|
1490
1543
|
const effectiveChangedNoteParaIds = changedNoteParaIds ?? collectChangedNoteParaIds(baselineXml, currentXml);
|
|
1491
|
-
const
|
|
1544
|
+
const currentParaIds = collectParaIds(currentXml);
|
|
1545
|
+
const patchedXml = notePartXmlFor({
|
|
1546
|
+
patch: buildPatchedNotePartXml({
|
|
1547
|
+
originalXml,
|
|
1548
|
+
baselineXml,
|
|
1549
|
+
serializedXml: currentXml,
|
|
1550
|
+
replacementXml,
|
|
1551
|
+
elementName,
|
|
1552
|
+
changedParaIds: effectiveChangedNoteParaIds
|
|
1553
|
+
}),
|
|
1492
1554
|
originalXml,
|
|
1493
|
-
baselineXml,
|
|
1494
|
-
serializedXml: currentXml,
|
|
1495
1555
|
replacementXml,
|
|
1556
|
+
hasDirtyParagraph: [...effectiveChangedNoteParaIds].some((paraId) => currentParaIds.has(paraId)),
|
|
1496
1557
|
elementName,
|
|
1497
|
-
|
|
1558
|
+
partName: file.name
|
|
1498
1559
|
});
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
if (patched === null || hasDirtyParagraph && patched === originalXml) {
|
|
1502
|
-
if (hasDirtyParagraph) throw new DocxPackageFidelityError(`Cannot serialize changed ${elementName} paragraphs into ${file.name}`);
|
|
1503
|
-
return;
|
|
1504
|
-
}
|
|
1505
|
-
if (patched === originalXml) return;
|
|
1506
|
-
newZip.file(file.name, patched, {
|
|
1560
|
+
if (patchedXml === null || patchedXml === originalXml) return;
|
|
1561
|
+
newZip.file(file.name, patchedXml, {
|
|
1507
1562
|
compression: "DEFLATE",
|
|
1508
1563
|
compressionOptions: { level: compressionLevel }
|
|
1509
1564
|
});
|
|
@@ -1531,7 +1586,7 @@ function updateCoreProperties(corePropsXml, { updateModifiedDate, modifiedBy })
|
|
|
1531
1586
|
if (result.includes("<dcterms:modified")) result = result.replace(/<dcterms:modified[^<>]*>[^<]*<\/dcterms:modified>/u, `<dcterms:modified xsi:type="dcterms:W3CDTF">${now}</dcterms:modified>`);
|
|
1532
1587
|
}
|
|
1533
1588
|
if (modifiedBy) {
|
|
1534
|
-
if (result.includes("<cp:lastModifiedBy")) result = result.replace(/<cp:lastModifiedBy>[^<]*<\/cp:lastModifiedBy>/u, `<cp:lastModifiedBy>${
|
|
1589
|
+
if (result.includes("<cp:lastModifiedBy")) result = result.replace(/<cp:lastModifiedBy>[^<]*<\/cp:lastModifiedBy>/u, `<cp:lastModifiedBy>${escapeXmlText(modifiedBy)}</cp:lastModifiedBy>`);
|
|
1535
1590
|
}
|
|
1536
1591
|
return result;
|
|
1537
1592
|
}
|
|
@@ -1638,13 +1693,13 @@ const createEmptyDocxZip = ({ creator, application }) => {
|
|
|
1638
1693
|
</w:document>`);
|
|
1639
1694
|
zip.file(STYLES_PART_PATH, SEED_STYLES_XML);
|
|
1640
1695
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1641
|
-
const creatorElement = creator === void 0 ? "" : `\n <dc:creator>${
|
|
1696
|
+
const creatorElement = creator === void 0 ? "" : `\n <dc:creator>${escapeXmlText(creator)}</dc:creator>`;
|
|
1642
1697
|
zip.file("docProps/core.xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
1643
1698
|
<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}
|
|
1644
1699
|
<dcterms:created xsi:type="dcterms:W3CDTF">${now}</dcterms:created>
|
|
1645
1700
|
<dcterms:modified xsi:type="dcterms:W3CDTF">${now}</dcterms:modified>
|
|
1646
1701
|
</cp:coreProperties>`);
|
|
1647
|
-
const applicationElements = application === void 0 ? "" : `\n <Application>${
|
|
1702
|
+
const applicationElements = application === void 0 ? "" : `\n <Application>${escapeXmlText(application)}</Application>\n <AppVersion>${CREATED_APP_VERSION}</AppVersion>`;
|
|
1648
1703
|
zip.file("docProps/app.xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
1649
1704
|
<Properties xmlns="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">${applicationElements}
|
|
1650
1705
|
</Properties>`);
|
package/dist/docx/runParser.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { document_d_exports } from "../types/document.js";
|
|
2
|
-
import { StyleMap } from "./styleParser.js";
|
|
3
2
|
import { XmlElement } from "./xmlParser.js";
|
|
3
|
+
import { StyleMap } from "./styleParser.js";
|
|
4
4
|
//#region src/docx/runParser.d.ts
|
|
5
5
|
/**
|
|
6
6
|
* `w:vertAlign` `baseline` is the reserved value that means "no vertical
|
package/dist/docx/runParser.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { parseHorizontalScalePercent } from "../utils/horizontalScale.js";
|
|
2
|
+
import { NO_MODELLED_ATTRIBUTES, attributeRemainder } from "./attributeRemainder.js";
|
|
2
3
|
import { parseDiagramPreview } from "./diagramPreview.js";
|
|
4
|
+
import { parseFieldState } from "./fieldState.js";
|
|
3
5
|
import { isGroupDrawing, parseGroupDrawing } from "./groupDrawingParser.js";
|
|
4
6
|
import { parseImage } from "./imageParser.js";
|
|
5
7
|
import { imageRawXmlFingerprint } from "./imageRawXml.js";
|
|
6
8
|
import { EmphasisMarkSchema, FontHintSchema, FontThemeSchema, HighlightColorSchema, PositionalTabAlignmentSchema, PositionalTabLeaderSchema, PositionalTabRelativeToSchema, TextEffectSchema, ThemeColorSlotSchema, UnderlineStyleSchema, narrowEnum } from "./parserEnums.js";
|
|
9
|
+
import { preserveRunChild } from "./preservedRunContent.js";
|
|
7
10
|
import { parseShading } from "./shadingParser.js";
|
|
8
11
|
import { parseShapeFromDrawing, shouldPreserveRawShapeDrawing } from "./shapeParser.js";
|
|
9
12
|
import { isTextBoxDrawing } from "./textBoxParser.js";
|
|
@@ -11,7 +14,7 @@ import { resolveThemeFontRef } from "./themeParser.js";
|
|
|
11
14
|
import { parsePropertyChangeInfo } from "./trackedChangeInfo.js";
|
|
12
15
|
import { captureVerbatimXml } from "./verbatimCapture.js";
|
|
13
16
|
import { parseVmlImageContent, shouldPreserveRawVmlPict } from "./vmlImageParser.js";
|
|
14
|
-
import { cloneWithXmlnsDeclarations, findAllDeep, findChild, findChildren, getAttribute, getChildElements, getLocalName, getTextContent, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute,
|
|
17
|
+
import { cloneWithXmlnsDeclarations, findAllDeep, findChild, findChildren, getAttribute, getChildElements, getLocalName, getTextContent, mergeXmlnsDeclarations, parseBooleanElement, parseNumericAttribute, selectAlternateContentBranch } from "./xmlParser.js";
|
|
15
18
|
import { DRAWING_RAW_XML_MODES } from "@stll/docx-core/model";
|
|
16
19
|
//#region src/docx/runParser.ts
|
|
17
20
|
/**
|
|
@@ -419,17 +422,14 @@ function parseEndnoteReference(element) {
|
|
|
419
422
|
*/
|
|
420
423
|
function parseFieldChar(element) {
|
|
421
424
|
const fldCharType = getAttribute(element, "w", "fldCharType");
|
|
422
|
-
const fldLock = parseOnOffAttribute(element, "w", "fldLock") === true;
|
|
423
|
-
const dirty = parseOnOffAttribute(element, "w", "dirty") === true;
|
|
424
425
|
let charType = "begin";
|
|
425
426
|
if (fldCharType === "separate") charType = "separate";
|
|
426
427
|
else if (fldCharType === "end") charType = "end";
|
|
427
428
|
const content = {
|
|
428
429
|
type: "fieldChar",
|
|
429
|
-
charType
|
|
430
|
+
charType,
|
|
431
|
+
...parseFieldState(element)
|
|
430
432
|
};
|
|
431
|
-
if (fldLock) content.fldLock = true;
|
|
432
|
-
if (dirty) content.dirty = true;
|
|
433
433
|
const numberingChange = findChild(element, "w", "numberingChange");
|
|
434
434
|
if (numberingChange) {
|
|
435
435
|
const original = getAttribute(numberingChange, "w", "original");
|
|
@@ -576,10 +576,11 @@ function parseRunContents(runElement, rels, media, rootXmlns = {}) {
|
|
|
576
576
|
}
|
|
577
577
|
case "object": {
|
|
578
578
|
const objectPreview = parseVmlImageContent(child, rels, media, rootXmlns);
|
|
579
|
-
|
|
579
|
+
contents.push(objectPreview ?? preserveRunChild(child));
|
|
580
580
|
break;
|
|
581
581
|
}
|
|
582
582
|
case "rPr": break;
|
|
583
|
+
case "commentReference": break;
|
|
583
584
|
case "lastRenderedPageBreak":
|
|
584
585
|
contents.push({ type: "renderedPageBreak" });
|
|
585
586
|
break;
|
|
@@ -637,11 +638,9 @@ function parseRunContents(runElement, rels, media, rootXmlns = {}) {
|
|
|
637
638
|
if (contents.length === contentsBeforeAlternate && !hasTextBoxBranch) contents.push(preserveOnlyDrawing(captureVerbatimXml(cloneWithXmlnsDeclarations(child, rootXmlns))));
|
|
638
639
|
break;
|
|
639
640
|
}
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
case "continuationSeparator": break;
|
|
644
|
-
default: break;
|
|
641
|
+
default:
|
|
642
|
+
contents.push(preserveRunChild(child));
|
|
643
|
+
break;
|
|
645
644
|
}
|
|
646
645
|
return contents;
|
|
647
646
|
}
|
|
@@ -668,6 +667,11 @@ function parseRun(node, styles, theme, rels = null, media = null, rootXmlns = {}
|
|
|
668
667
|
if (propertyChangesResult) run.propertyChanges = propertyChangesResult;
|
|
669
668
|
}
|
|
670
669
|
run.content = parseRunContents(node, rels, media, mergeXmlnsDeclarations(rootXmlns, node));
|
|
670
|
+
const remainder = attributeRemainder({
|
|
671
|
+
element: node,
|
|
672
|
+
modelled: NO_MODELLED_ATTRIBUTES
|
|
673
|
+
});
|
|
674
|
+
if (remainder) run.preservedAttributes = remainder;
|
|
671
675
|
return run;
|
|
672
676
|
}
|
|
673
677
|
/**
|
|
@@ -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) => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { NO_MODELLED_ATTRIBUTES, attributeRemainder } from "./attributeRemainder.js";
|
|
1
2
|
import { parseBorderSpec } from "./borderParser.js";
|
|
2
3
|
import { parseFooterReference, parseHeaderReference } from "./headerFooterRefParser.js";
|
|
3
4
|
import { parseEndnoteProperties, parseFootnoteProperties } from "./notePropertiesParser.js";
|
|
@@ -159,7 +160,8 @@ function parseSectionProperties(sectPr, context) {
|
|
|
159
160
|
if (space !== void 0) props.columnSpace = space;
|
|
160
161
|
const equalWidth = parseOnOffAttribute(cols, "w", "equalWidth");
|
|
161
162
|
if (equalWidth !== void 0) props.equalWidth = equalWidth;
|
|
162
|
-
|
|
163
|
+
const separator = parseOnOffAttribute(cols, "w", "sep");
|
|
164
|
+
if (separator !== void 0) props.separator = separator;
|
|
163
165
|
const colElements = findChildren(cols, "w", "col");
|
|
164
166
|
if (colElements.length > 0) {
|
|
165
167
|
props.columns = [];
|
|
@@ -310,6 +312,11 @@ function parseSectionProperties(sectPr, context) {
|
|
|
310
312
|
return change;
|
|
311
313
|
});
|
|
312
314
|
if (propertyChanges.length > 0) props.propertyChanges = propertyChanges;
|
|
315
|
+
const remainder = attributeRemainder({
|
|
316
|
+
element: sectPr,
|
|
317
|
+
modelled: NO_MODELLED_ATTRIBUTES
|
|
318
|
+
});
|
|
319
|
+
if (remainder) props.preservedAttributes = remainder;
|
|
313
320
|
Object.defineProperty(props, unserializedSectionPropertyChildNames, {
|
|
314
321
|
enumerable: true,
|
|
315
322
|
value: unhandledChildNames
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { canonicalJson } from "../utils/canonicalJson.js";
|
|
2
|
-
import { escapeXml } from "./serializer/xmlUtils.js";
|
|
3
2
|
import { OFFICE_RELATIONSHIP_NAMESPACE_URIS, WORDPROCESSINGML_NAMESPACE_URIS, findChildrenByNamespaceUri, getAttributeByNamespaceUri, getChildElements, getLocalName, getNamespaceUri } from "./xmlParser.js";
|
|
4
3
|
import { TaggedError } from "better-result";
|
|
4
|
+
import { escapeXmlAttribute } from "@stll/docx-core";
|
|
5
5
|
//#region src/docx/sectionReferenceHistory.ts
|
|
6
6
|
const SECTION_REFERENCE_HISTORY_NAMESPACE = "urn:stella:folio:section-reference-history:1";
|
|
7
7
|
const HISTORY_NAMESPACES = /* @__PURE__ */ new Set([SECTION_REFERENCE_HISTORY_NAMESPACE]);
|
|
@@ -41,7 +41,7 @@ const serializeSectionReferenceHistory = (references) => {
|
|
|
41
41
|
if (new Set(values.map(({ type }) => type)).size !== values.length) return invalidHistory();
|
|
42
42
|
return values.map(({ type, rId }) => {
|
|
43
43
|
if (!rId || rId.trim() !== rId) return invalidHistory();
|
|
44
|
-
return `<w:${kind}Reference w:type="${type}" r:id="${
|
|
44
|
+
return `<w:${kind}Reference w:type="${type}" r:id="${escapeXmlAttribute(rId)}"/>`;
|
|
45
45
|
}).join("");
|
|
46
46
|
};
|
|
47
47
|
const content = serializeReferences("header", references.headerReferences ?? []) + serializeReferences("footer", references.footerReferences ?? []);
|
|
@@ -8,7 +8,7 @@ import { RELATIONSHIP_TYPES } from "./relsParser.js";
|
|
|
8
8
|
import { COMMENTS_CONTENT_TYPE, COMMENTS_EXTENDED_PART_LOWER, addCommentsExtendedOverride, addCommentsExtendedRelationship, applyUpdatesToZip, collectHeaderFooterUpdates, findMaxRId, hasModelDrivenPictureWatermark, hasUnmaterializedHeaderFooter, hasUnmaterializedInlineResources, updateCoreProperties, withoutAttachedTemplate } from "./rezip.js";
|
|
9
9
|
import "./selectiveSaveFlags.js";
|
|
10
10
|
import { buildPatchedDocumentXml, buildPatchedNoteXml, collectParaIds, patchNumberingDefinitions } from "./selectiveXmlPatch.js";
|
|
11
|
-
import {
|
|
11
|
+
import { planCommentParts, serializeComments, serializeCommentsExtended } from "./serializer/commentSerializer.js";
|
|
12
12
|
import { serializeDocument } from "./serializer/documentSerializer.js";
|
|
13
13
|
import { serializeEndnotes, serializeFootnotes } from "./serializer/noteSerializer.js";
|
|
14
14
|
import { serializeNumberingXml } from "./serializer/numberingSerializer.js";
|
|
@@ -84,8 +84,8 @@ const commentsExtendedInfoEqual = (a, b) => {
|
|
|
84
84
|
* - Threading changed: write the part and ensure its override + relationship,
|
|
85
85
|
* bailing if the packaging files needed to wire it are absent.
|
|
86
86
|
*/
|
|
87
|
-
async function patchCommentsExtended(zip,
|
|
88
|
-
const desiredXml = serializeCommentsExtended(
|
|
87
|
+
async function patchCommentsExtended(zip, plan, updates) {
|
|
88
|
+
const desiredXml = serializeCommentsExtended(plan);
|
|
89
89
|
const existing = findZipEntryCaseInsensitive(zip, COMMENTS_EXTENDED_PART_LOWER);
|
|
90
90
|
if (!desiredXml) return existing === null;
|
|
91
91
|
const baselineInfo = existing ? parseCommentsExtended(await existing.async("text")) : /* @__PURE__ */ new Map();
|
|
@@ -190,6 +190,7 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
|
|
|
190
190
|
if (!validateFolioDocumentModel(doc).valid) return null;
|
|
191
191
|
const comments = doc.package.document.comments ?? [];
|
|
192
192
|
const hasComments = comments.length > 0;
|
|
193
|
+
const commentPlan = planCommentParts(comments);
|
|
193
194
|
try {
|
|
194
195
|
const zip = await (await import("jszip")).default.loadAsync(originalBuffer);
|
|
195
196
|
for (const [path, file] of Object.entries(zip.files)) if (!file.dir && isUnsafePackagePath(path)) return null;
|
|
@@ -215,9 +216,8 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
|
|
|
215
216
|
}
|
|
216
217
|
const sourceCommentsFile = zip.file("word/comments.xml");
|
|
217
218
|
if (hasComments || sourceCommentsFile) {
|
|
218
|
-
ensureThreadedCommentParaIds(comments);
|
|
219
219
|
const sourceBindings = sourceCommentsFile ? readRootNamespaceBindings(await sourceCommentsFile.async("text")) : void 0;
|
|
220
|
-
updates.set("word/comments.xml", serializeComments(
|
|
220
|
+
updates.set("word/comments.xml", serializeComments(commentPlan, sourceBindings));
|
|
221
221
|
}
|
|
222
222
|
if (hasComments) {
|
|
223
223
|
const ctFile = zip.file("[Content_Types].xml");
|
|
@@ -235,7 +235,7 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
|
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
|
-
if (!await patchCommentsExtended(zip,
|
|
238
|
+
if (!await patchCommentsExtended(zip, commentPlan, updates)) return null;
|
|
239
239
|
await patchNumberingPart(zip, doc, updates);
|
|
240
240
|
for (const [path, xml] of await collectHeaderFooterUpdates(doc, zip)) updates.set(path, xml);
|
|
241
241
|
await queueSettingsUpdates(zip, updates);
|
|
@@ -109,6 +109,24 @@ declare function buildPatchedDocumentXml(originalXml: string, serializedXml: str
|
|
|
109
109
|
* the caller can fall back to preserving the original part verbatim.
|
|
110
110
|
*/
|
|
111
111
|
declare function buildPatchedNoteXml(originalXml: string, serializedXml: string, changedIds: Set<string>): string | null;
|
|
112
|
+
/** One region of a part replaced by re-serialized XML. */
|
|
113
|
+
type XmlSplice = {
|
|
114
|
+
start: number;
|
|
115
|
+
end: number;
|
|
116
|
+
newXml: string;
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* Apply `splices` to `xml`, end-to-start so earlier offsets stay valid.
|
|
120
|
+
*
|
|
121
|
+
* Every selective patch is a splice, and every splice goes through here, so
|
|
122
|
+
* the refusal below is a property of the operation rather than a check each
|
|
123
|
+
* site has to remember. A patch rewrites the regions an edit touched and keeps
|
|
124
|
+
* the rest of the part byte-for-byte, so it can write half a comment range:
|
|
125
|
+
* invalid OOXML that anchors the comment to nothing. Answers null when it
|
|
126
|
+
* would, leaving the caller to rewrite a wider region — ultimately the whole
|
|
127
|
+
* part from the model, which is balanced with itself.
|
|
128
|
+
*/
|
|
129
|
+
declare const spliceXml: (xml: string, splices: readonly XmlSplice[]) => string | null;
|
|
112
130
|
type NoteElementName = "footnote" | "endnote";
|
|
113
131
|
declare const collectChangedNoteParaIds: (baselineXml: string, currentXml: string) => Set<string>;
|
|
114
132
|
type BuildPatchedNotePartXmlOptions = {
|
|
@@ -119,6 +137,25 @@ type BuildPatchedNotePartXmlOptions = {
|
|
|
119
137
|
elementName: NoteElementName;
|
|
120
138
|
changedParaIds?: ReadonlySet<string>;
|
|
121
139
|
};
|
|
140
|
+
/** Why a note part could not be patched; see {@link NotePartPatch}. */
|
|
141
|
+
type NotePartPatchRefusal =
|
|
142
|
+
/** A note element, or a changed paragraph, was missing or ambiguous. */
|
|
143
|
+
"unroutable-paragraph" |
|
|
144
|
+
/** Even rewriting whole notes would leave a comment range with one half. */
|
|
145
|
+
"comment-range-balance";
|
|
146
|
+
/**
|
|
147
|
+
* What patching a note part produced. `refused` is not a failure to serialize:
|
|
148
|
+
* the caller writes the part from the model instead, the note-part reading of
|
|
149
|
+
* the fall back to a full repack the document story takes when its own splice
|
|
150
|
+
* is refused.
|
|
151
|
+
*/
|
|
152
|
+
type NotePartPatch = {
|
|
153
|
+
type: "patched";
|
|
154
|
+
xml: string;
|
|
155
|
+
} | {
|
|
156
|
+
type: "refused";
|
|
157
|
+
reason: NotePartPatchRefusal;
|
|
158
|
+
};
|
|
122
159
|
/**
|
|
123
160
|
* Patch an existing note part from its model serialization.
|
|
124
161
|
*
|
|
@@ -130,8 +167,15 @@ type BuildPatchedNotePartXmlOptions = {
|
|
|
130
167
|
* notes, and unaffected equal-shape paragraphs remain byte-exact.
|
|
131
168
|
* `replacementXml` also supplies synthesized automatic note-reference marks,
|
|
132
169
|
* which the parsed model intentionally omits.
|
|
170
|
+
*
|
|
171
|
+
* A comment can be anchored on a note's own text, so its range spans that
|
|
172
|
+
* note's paragraphs and an edit inside the span moves a range half from one
|
|
173
|
+
* paragraph to another. Replacing only the dirty paragraph then drops the half
|
|
174
|
+
* it held and leaves the other standing, so {@link spliceXml} refuses the
|
|
175
|
+
* result and the changed notes are rewritten whole instead — model content on
|
|
176
|
+
* both sides of the range, with every other note still byte-exact.
|
|
133
177
|
*/
|
|
134
|
-
declare function buildPatchedNotePartXml({ originalXml, baselineXml, serializedXml, replacementXml, elementName, changedParaIds }: BuildPatchedNotePartXmlOptions):
|
|
178
|
+
declare function buildPatchedNotePartXml({ originalXml, baselineXml, serializedXml, replacementXml, elementName, changedParaIds }: BuildPatchedNotePartXmlOptions): NotePartPatch;
|
|
135
179
|
type ChangedNumberingDefs = {
|
|
136
180
|
abstractNums: Set<string>;
|
|
137
181
|
nums: Set<string>;
|
|
@@ -178,4 +222,4 @@ declare const patchNumberingDefinitions: ({ originalXml, baselineXml, currentXml
|
|
|
178
222
|
*/
|
|
179
223
|
declare function appendNumberingDefs(xml: string, currentXml: string, added: ChangedNumberingDefs): string | null;
|
|
180
224
|
//#endregion
|
|
181
|
-
export { ChangedNumberingDefs, ParagraphOffsets, PatchSafetyOptions, PatchValidationResult, appendNumberingDefs, buildParagraphOffsetIndex, buildPatchedDocumentXml, buildPatchedNotePartXml, buildPatchedNoteXml, buildPatchedNumberingXml, collectAddedNumberingDefs, collectChangedNoteParaIds, collectChangedNumberingDefs, collectParaIds, countParagraphElements, extractParagraphXml, findParagraphOffsets, isXmlNameBoundary, patchNumberingDefinitions, validatePatchSafety };
|
|
225
|
+
export { ChangedNumberingDefs, NotePartPatch, NotePartPatchRefusal, ParagraphOffsets, PatchSafetyOptions, PatchValidationResult, XmlSplice, appendNumberingDefs, buildParagraphOffsetIndex, buildPatchedDocumentXml, buildPatchedNotePartXml, buildPatchedNoteXml, buildPatchedNumberingXml, collectAddedNumberingDefs, collectChangedNoteParaIds, collectChangedNumberingDefs, collectParaIds, countParagraphElements, extractParagraphXml, findParagraphOffsets, isXmlNameBoundary, patchNumberingDefinitions, spliceXml, validatePatchSafety };
|