@stll/folio-core 0.32.2 → 0.33.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-edits/__fixtures__/paragraphs.d.ts +17 -0
- package/dist/ai-edits/__fixtures__/paragraphs.js +33 -0
- package/dist/ai-edits/apply.d.ts +49 -3
- package/dist/ai-edits/apply.js +652 -86
- package/dist/ai-edits/blockRange.d.ts +7 -3
- package/dist/ai-edits/blockRange.js +14 -6
- package/dist/ai-edits/headless.d.ts +39 -5
- package/dist/ai-edits/headless.js +76 -17
- package/dist/ai-edits/index.d.ts +6 -6
- package/dist/ai-edits/index.js +3 -3
- package/dist/ai-edits/read.d.ts +4 -0
- package/dist/ai-edits/read.js +30 -6
- package/dist/ai-edits/scoped-reading.js +4 -3
- package/dist/ai-edits/snapshot.d.ts +42 -2
- package/dist/ai-edits/snapshot.js +127 -62
- package/dist/ai-edits/table-row-column-mutations.d.ts +24 -1
- package/dist/ai-edits/table-row-column-mutations.js +98 -6
- package/dist/ai-edits/table-targets.d.ts +12 -3
- package/dist/ai-edits/table-targets.js +15 -5
- package/dist/ai-edits/types.d.ts +219 -15
- package/dist/ai-edits/word-diff.d.ts +54 -11
- package/dist/ai-edits/word-diff.js +221 -61
- package/dist/compare/__fixtures__/body-sequence.d.ts +56 -0
- package/dist/compare/__fixtures__/body-sequence.js +136 -0
- package/dist/compare/__fixtures__/nested-table.d.ts +16 -0
- package/dist/compare/__fixtures__/nested-table.js +50 -0
- package/dist/compare/__fixtures__/numbered-list.d.ts +34 -0
- package/dist/compare/__fixtures__/numbered-list.js +85 -0
- package/dist/compare/column-alignment.d.ts +27 -0
- package/dist/compare/column-alignment.js +162 -0
- package/dist/compare/compare.d.ts +107 -0
- package/dist/compare/compare.js +412 -0
- package/dist/compare/formatting.d.ts +29 -0
- package/dist/compare/formatting.js +84 -0
- package/dist/compare/plan.d.ts +22 -0
- package/dist/compare/plan.js +1141 -0
- package/dist/compare/reproducible-package.d.ts +21 -0
- package/dist/compare/reproducible-package.js +47 -0
- package/dist/compare/scenario.d.ts +77 -0
- package/dist/compare/scenario.js +263 -0
- package/dist/compare/types.d.ts +297 -0
- package/dist/compare/types.js +42 -0
- package/dist/compare/verification.d.ts +83 -0
- package/dist/compare/verification.js +189 -0
- package/dist/compat/eigenpal.d.ts +9 -6
- package/dist/compat/eigenpal.js +6 -3
- package/dist/controller/layoutPipeline.d.ts +13 -0
- package/dist/controller/layoutPipeline.js +15 -1
- package/dist/display-list/build/buildContext.d.ts +39 -0
- package/dist/display-list/build/buildContext.js +31 -0
- package/dist/display-list/build/buildDisplayList.d.ts +79 -0
- package/dist/display-list/build/buildDisplayList.js +283 -0
- package/dist/display-list/build/colors.d.ts +20 -0
- package/dist/display-list/build/colors.js +136 -0
- package/dist/display-list/build/floatingImages.d.ts +24 -0
- package/dist/display-list/build/floatingImages.js +41 -0
- package/dist/display-list/build/fontTable.d.ts +31 -0
- package/dist/display-list/build/fontTable.js +97 -0
- package/dist/display-list/build/furniture.d.ts +86 -0
- package/dist/display-list/build/furniture.js +98 -0
- package/dist/display-list/build/glyphs.d.ts +55 -0
- package/dist/display-list/build/glyphs.js +103 -0
- package/dist/display-list/build/headerFooterPrimitives.d.ts +17 -0
- package/dist/display-list/build/headerFooterPrimitives.js +40 -0
- package/dist/display-list/build/imagePrimitives.d.ts +35 -0
- package/dist/display-list/build/imagePrimitives.js +264 -0
- package/dist/display-list/build/pageBorderPrimitives.d.ts +23 -0
- package/dist/display-list/build/pageBorderPrimitives.js +111 -0
- package/dist/display-list/build/pageFurniture.d.ts +35 -0
- package/dist/display-list/build/pageFurniture.js +141 -0
- package/dist/display-list/build/paragraphPrimitives.d.ts +19 -0
- package/dist/display-list/build/paragraphPrimitives.js +819 -0
- package/dist/display-list/build/regions.d.ts +46 -0
- package/dist/display-list/build/regions.js +56 -0
- package/dist/display-list/build/storyPrimitives.d.ts +29 -0
- package/dist/display-list/build/storyPrimitives.js +279 -0
- package/dist/display-list/build/strokes.d.ts +19 -0
- package/dist/display-list/build/strokes.js +78 -0
- package/dist/display-list/build/tablePrimitives.d.ts +38 -0
- package/dist/display-list/build/tablePrimitives.js +409 -0
- package/dist/display-list/build/textBoxPrimitives.d.ts +14 -0
- package/dist/display-list/build/textBoxPrimitives.js +129 -0
- package/dist/display-list/build/textDecorations.d.ts +29 -0
- package/dist/display-list/build/textDecorations.js +29 -0
- package/dist/display-list/build/unsupported.d.ts +57 -0
- package/dist/display-list/build/unsupported.js +0 -0
- package/dist/display-list/build/watermarkPrimitives.d.ts +15 -0
- package/dist/display-list/build/watermarkPrimitives.js +128 -0
- package/dist/display-list/dom/renderDisplayListToDom.d.ts +26 -0
- package/dist/display-list/dom/renderDisplayListToDom.js +620 -0
- package/dist/display-list/editor/displayListPagePainter.d.ts +22 -0
- package/dist/display-list/editor/displayListPagePainter.js +63 -0
- package/dist/display-list/editor/pageRenderer.d.ts +24 -0
- package/dist/display-list/editor/pageRenderer.js +36 -0
- package/dist/display-list/primitives.d.ts +95 -0
- package/dist/display-list/primitives.js +127 -0
- package/dist/display-list/types.d.ts +472 -0
- package/dist/display-list/types.js +0 -0
- package/dist/document-operations.d.ts +39 -8
- package/dist/document-operations.js +169 -16
- package/dist/document-stories.d.ts +16 -0
- package/dist/document-stories.js +48 -4
- package/dist/docx/hyperlinkParser.d.ts +9 -1
- package/dist/docx/hyperlinkParser.js +19 -13
- package/dist/docx/packageParts.d.ts +35 -0
- package/dist/docx/packageParts.js +120 -0
- package/dist/docx/paraIdRangeNormalization.d.ts +40 -0
- package/dist/docx/paraIdRangeNormalization.js +64 -0
- package/dist/docx/paragraphParser.js +101 -26
- package/dist/docx/paragraphTraversal.js +1 -1
- package/dist/docx/renderedPageBreakNormalization.js +3 -1
- package/dist/docx/revisionIdNormalization.d.ts +27 -0
- package/dist/docx/revisionIdNormalization.js +121 -0
- package/dist/docx/rezip.d.ts +6 -0
- package/dist/docx/rezip.js +51 -19
- package/dist/docx/selectiveSave.js +2 -2
- package/dist/docx/serializer/commentSerializer.js +6 -1
- package/dist/docx/serializer/paragraphSerializer.js +73 -20
- package/dist/docx/serializer/runSerializer.js +11 -10
- package/dist/docx/serializer/tableSerializer.js +26 -10
- package/dist/docx/server/applyDocxXmlPatchProposal.js +1 -1
- package/dist/docx/server/createBilingualDocx.js +3 -2
- package/dist/docx/streamingXmlParser.d.ts +13 -1
- package/dist/docx/streamingXmlParser.js +49 -11
- package/dist/docx/unzip.d.ts +1 -2
- package/dist/docx/unzip.js +25 -6
- package/dist/docx/xmlParser.d.ts +3 -1
- package/dist/docx/xmlParser.js +33 -27
- package/dist/export-pdf.d.ts +43 -0
- package/dist/export-pdf.js +98 -0
- package/dist/fonts/headlessMeasure.d.ts +76 -0
- package/dist/fonts/headlessMeasure.js +0 -0
- package/dist/fonts/sfnt/parse.d.ts +51 -0
- package/dist/fonts/sfnt/parse.js +525 -0
- package/dist/fonts/sfnt/subset.d.ts +20 -0
- package/dist/fonts/sfnt/subset.js +350 -0
- package/dist/fonts/sfnt/tables.d.ts +88 -0
- package/dist/fonts/sfnt/tables.js +131 -0
- package/dist/fonts/sfnt/woff.d.ts +14 -0
- package/dist/fonts/sfnt/woff.js +163 -0
- package/dist/generated/text_shaper.js +324 -0
- package/dist/generated/text_shaper_bg.wasm +0 -0
- package/dist/headless-layout.d.ts +75 -0
- package/dist/headless-layout.js +350 -0
- package/dist/index.d.ts +9 -6
- package/dist/index.js +6 -3
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +2 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +3 -1
- package/dist/layout-bridge/convert/toFlowBlocks.js +2 -0
- package/dist/layout-engine/measure/advanceComposition.d.ts +62 -0
- package/dist/layout-engine/measure/advanceComposition.js +148 -0
- package/dist/layout-engine/measure/cache.d.ts +2 -0
- package/dist/layout-engine/measure/measureContainer.js +13 -0
- package/dist/layout-engine/measure/measureParagraph.js +1 -2
- package/dist/layout-engine/measure/measureTypes.d.ts +12 -0
- package/dist/layout-engine/measure/tableInlinePlacement.d.ts +3 -2
- package/dist/layout-engine/measure/tableInlinePlacement.js +14 -1
- package/dist/layout-engine/tableIndentCompatibility.d.ts +5 -0
- package/dist/layout-engine/tableIndentCompatibility.js +15 -0
- package/dist/layout-engine/types.d.ts +8 -0
- package/dist/layout-painter/renderPage.d.ts +29 -1
- package/dist/layout-painter/renderPage.js +47 -19
- package/dist/layout-painter/renderParagraph.d.ts +21 -2
- package/dist/layout-painter/renderParagraph.js +1 -1
- package/dist/layout-painter/renderTable.js +37 -4
- package/dist/managers/editorShortcuts.d.ts +33 -1
- package/dist/managers/editorShortcuts.js +20 -1
- package/dist/markdown/renderRuns.js +7 -2
- package/dist/markdown/renderTable.js +5 -1
- package/dist/model.d.ts +3 -3
- package/dist/model.js +2 -2
- package/dist/pdf/contentStream.d.ts +73 -0
- package/dist/pdf/contentStream.js +143 -0
- package/dist/pdf/fonts.d.ts +96 -0
- package/dist/pdf/fonts.js +593 -0
- package/dist/pdf/images.d.ts +28 -0
- package/dist/pdf/images.js +439 -0
- package/dist/pdf/objects.d.ts +93 -0
- package/dist/pdf/objects.js +237 -0
- package/dist/pdf/pageSpace.d.ts +43 -0
- package/dist/pdf/pageSpace.js +57 -0
- package/dist/pdf/paint.d.ts +32 -0
- package/dist/pdf/paint.js +442 -0
- package/dist/pdf/writePdf.d.ts +56 -0
- package/dist/pdf/writePdf.js +345 -0
- package/dist/prosemirror/commands/comments.js +96 -6
- package/dist/prosemirror/conversion/fromProseDoc.js +41 -7
- package/dist/prosemirror/conversion/toProseDoc.js +41 -4
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.d.ts +15 -2
- package/dist/prosemirror/extensions/features/ParaIdAllocatorExtension.js +35 -6
- package/dist/prosemirror/extensions/features/ParagraphChangeTrackerExtension.js +2 -2
- package/dist/prosemirror/extensions/features/pasteCleanup.js +4 -3
- package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.d.ts +3 -1
- package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +5 -3
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.d.ts +7 -1
- package/dist/prosemirror/extensions/nodes/RenderedPageBreakExtension.js +9 -3
- package/dist/prosemirror/extensions/nodes/TableExtension.js +56 -39
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +3 -1
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +5 -3
- package/dist/prosemirror/plugins/suggestionMode.js +1 -0
- package/dist/prosemirror/revisionCarriers.js +19 -2
- package/dist/prosemirror/utils/extractTrackedChanges.d.ts +2 -2
- package/dist/prosemirror/utils/extractTrackedChanges.js +59 -23
- package/dist/prosemirror/validation.js +55 -27
- package/dist/prosemirror/zeroWidthAnchors.d.ts +10 -0
- package/dist/prosemirror/zeroWidthAnchors.js +23 -0
- package/dist/redline.js +12 -79
- package/dist/server.d.ts +3 -3
- package/dist/server.js +2 -2
- package/dist/shaping/placeRun.d.ts +51 -0
- package/dist/shaping/placeRun.js +84 -0
- package/dist/shaping/shaper.d.ts +92 -0
- package/dist/shaping/shaper.js +102 -0
- package/dist/types/block-id.d.ts +18 -2
- package/dist/types/block-id.js +49 -5
- package/dist/types/content.d.ts +2 -2
- package/dist/utils/clipboard.js +4 -3
- package/dist/utils/fontResolver.js +183 -0
- package/dist/utils/htmlComments.d.ts +17 -0
- package/dist/utils/htmlComments.js +22 -0
- package/dist/utils/scriptSegments.d.ts +12 -1
- package/dist/utils/scriptSegments.js +17 -1
- package/dist/version-comparison.d.ts +1 -1
- package/package.json +7 -3
|
@@ -210,10 +210,8 @@ function serializeParagraphPropertyChange(change) {
|
|
|
210
210
|
const normalizedPreviousPPr = previousPPrInner.length > 0 ? `<w:pPr>${previousPPrInner}</w:pPr>` : "<w:pPr/>";
|
|
211
211
|
return `<w:pPrChange ${attrs.join(" ")}>${normalizedPreviousPPr}</w:pPrChange>`;
|
|
212
212
|
}
|
|
213
|
-
/**
|
|
214
|
-
|
|
215
|
-
*/
|
|
216
|
-
function serializeHyperlink(hyperlink) {
|
|
213
|
+
/** The attribute list of a `w:hyperlink`, without its children. */
|
|
214
|
+
function hyperlinkAttributes(hyperlink) {
|
|
217
215
|
const attrs = [];
|
|
218
216
|
if (hyperlink.rId) attrs.push(`r:id="${escapeXml(hyperlink.rId)}"`);
|
|
219
217
|
if (hyperlink.anchor) attrs.push(`w:anchor="${escapeXml(hyperlink.anchor)}"`);
|
|
@@ -222,12 +220,19 @@ function serializeHyperlink(hyperlink) {
|
|
|
222
220
|
if (hyperlink.history === true) attrs.push("w:history=\"1\"");
|
|
223
221
|
else if (hyperlink.history === false) attrs.push("w:history=\"0\"");
|
|
224
222
|
if (hyperlink.docLocation) attrs.push(`w:docLocation="${escapeXml(hyperlink.docLocation)}"`);
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
return
|
|
223
|
+
return attrs.length > 0 ? ` ${attrs.join(" ")}` : "";
|
|
224
|
+
}
|
|
225
|
+
/** One `w:hyperlink` child, with the caller deciding how a run is written. */
|
|
226
|
+
function serializeHyperlinkChild(child, serializeChildRun) {
|
|
227
|
+
if (child.type === "run") return serializeChildRun(child);
|
|
228
|
+
return child.type === "bookmarkStart" ? serializeBookmarkStart(child) : serializeBookmarkEnd(child);
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Serialize a hyperlink (w:hyperlink)
|
|
232
|
+
*/
|
|
233
|
+
function serializeHyperlink(hyperlink) {
|
|
234
|
+
const childrenXml = hyperlink.children.map((child) => serializeHyperlinkChild(child, serializeRun)).join("");
|
|
235
|
+
return `<w:hyperlink${hyperlinkAttributes(hyperlink)}>${childrenXml}</w:hyperlink>`;
|
|
231
236
|
}
|
|
232
237
|
/**
|
|
233
238
|
* Serialize bookmark start (w:bookmarkStart)
|
|
@@ -368,6 +373,14 @@ function serializeMoveRangeStart(tag, marker) {
|
|
|
368
373
|
function rewriteRunTextAsDeleted(xml) {
|
|
369
374
|
return xml.replace(/<w:t\b/gu, "<w:delText").replace(/<\/w:t>/gu, "</w:delText>").replace(/<w:instrText\b/gu, "<w:delInstrText").replace(/<\/w:instrText>/gu, "</w:delInstrText>");
|
|
370
375
|
}
|
|
376
|
+
function trackedChangeTag(change) {
|
|
377
|
+
switch (change.type) {
|
|
378
|
+
case "insertion": return "ins";
|
|
379
|
+
case "deletion": return "del";
|
|
380
|
+
case "moveFrom": return "moveFrom";
|
|
381
|
+
case "moveTo": return "moveTo";
|
|
382
|
+
}
|
|
383
|
+
}
|
|
371
384
|
function serializeTrackedChange(tag, change) {
|
|
372
385
|
const info = change.info;
|
|
373
386
|
const normalizedId = normalizeRevisionId(info.id);
|
|
@@ -389,14 +402,39 @@ function serializeTrackedChange(tag, change) {
|
|
|
389
402
|
return rewriteRunTextAsDeleted(contentXml);
|
|
390
403
|
}).join("");
|
|
391
404
|
};
|
|
392
|
-
const
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
405
|
+
const serializeContentRun = (run) => tag === "del" || tag === "moveFrom" ? serializeDeletedRun(run) : serializeRun(run);
|
|
406
|
+
const serializeWrappedItem = (item) => {
|
|
407
|
+
if (item.type === "run") return serializeContentRun(item);
|
|
408
|
+
if (item.type === "simpleField" || item.type === "complexField") {
|
|
409
|
+
const xml = item.type === "simpleField" ? serializeSimpleField(item) : serializeComplexField(item);
|
|
410
|
+
return tag === "del" || tag === "moveFrom" ? rewriteRunTextAsDeleted(xml) : xml;
|
|
396
411
|
}
|
|
397
|
-
return
|
|
398
|
-
|
|
399
|
-
|
|
412
|
+
if (item.type === "insertion" || item.type === "deletion" || item.type === "moveFrom" || item.type === "moveTo") return serializeTrackedChange(trackedChangeTag(item), item);
|
|
413
|
+
return item.type === "bookmarkStart" ? serializeBookmarkStart(item) : serializeBookmarkEnd(item);
|
|
414
|
+
};
|
|
415
|
+
const open = `<w:${tag} ${attrs.join(" ")}>`;
|
|
416
|
+
const close = `</w:${tag}>`;
|
|
417
|
+
const wrap = (inner) => inner.length === 0 ? "" : `${open}${inner}${close}`;
|
|
418
|
+
if (change.content.length === 0) return `${open}${close}`;
|
|
419
|
+
const segments = [];
|
|
420
|
+
const pending = [];
|
|
421
|
+
const flushPending = () => {
|
|
422
|
+
if (pending.length > 0) {
|
|
423
|
+
segments.push(wrap(pending.join("")));
|
|
424
|
+
pending.length = 0;
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
for (const item of change.content) {
|
|
428
|
+
if (item.type === "hyperlink") {
|
|
429
|
+
flushPending();
|
|
430
|
+
const childrenXml = item.children.map((child) => serializeHyperlinkChild(child, serializeContentRun)).join("");
|
|
431
|
+
segments.push(`<w:hyperlink${hyperlinkAttributes(item)}>${open}${childrenXml}${close}</w:hyperlink>`);
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
pending.push(serializeWrappedItem(item));
|
|
435
|
+
}
|
|
436
|
+
flushPending();
|
|
437
|
+
return segments.join("");
|
|
400
438
|
}
|
|
401
439
|
/** Emit the `<w:commentReference>` run Word places after a comment range end. */
|
|
402
440
|
function serializeCommentReferenceRun(id) {
|
|
@@ -470,12 +508,27 @@ function serializeParagraph(paragraph) {
|
|
|
470
508
|
return `<w:p${attrsStr}>${parts.join("")}</w:p>`;
|
|
471
509
|
}
|
|
472
510
|
function injectRenderedPageBreakIntoFirstRun(xml) {
|
|
473
|
-
const
|
|
474
|
-
const openingTag = runOpeningTag.exec(xml);
|
|
511
|
+
const openingTag = /<w:r(?=[\s>/])[^>]*>/u.exec(xml);
|
|
475
512
|
if (!openingTag) return null;
|
|
476
513
|
const runEnd = xml.indexOf("</w:r>", openingTag.index + openingTag[0].length);
|
|
477
514
|
if (runEnd !== -1 && xml.slice(openingTag.index + openingTag[0].length, runEnd).includes("<w:lastRenderedPageBreak/>")) return xml;
|
|
478
|
-
|
|
515
|
+
const contentStart = openingTag.index + openingTag[0].length;
|
|
516
|
+
let insertionOffset = contentStart;
|
|
517
|
+
if (xml.startsWith("<w:rPr", contentStart)) {
|
|
518
|
+
const propertyTag = /<\/?w:rPr(?=[\s>/])[^>]*>/gu;
|
|
519
|
+
propertyTag.lastIndex = contentStart;
|
|
520
|
+
let depth = 0;
|
|
521
|
+
for (const match of xml.matchAll(propertyTag)) {
|
|
522
|
+
if (match.index !== contentStart && depth === 0) break;
|
|
523
|
+
if (match[0].startsWith("</")) depth--;
|
|
524
|
+
else if (!match[0].endsWith("/>")) depth++;
|
|
525
|
+
if (depth === 0) {
|
|
526
|
+
insertionOffset = match.index + match[0].length;
|
|
527
|
+
break;
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
return `${xml.slice(0, insertionOffset)}<w:lastRenderedPageBreak/>${xml.slice(insertionOffset)}`;
|
|
479
532
|
}
|
|
480
533
|
/**
|
|
481
534
|
* Serialize multiple paragraphs to OOXML XML
|
|
@@ -92,13 +92,6 @@ function serializeTextFormatting(formatting) {
|
|
|
92
92
|
if (formatting.fontFamily.csTheme) fontAttrs.push(`w:cstheme="${escapeXml(formatting.fontFamily.csTheme)}"`);
|
|
93
93
|
if (fontAttrs.length > 0) parts.push(`<w:rFonts ${fontAttrs.join(" ")}/>`);
|
|
94
94
|
}
|
|
95
|
-
if (formatting.language) {
|
|
96
|
-
const languageAttrs = [];
|
|
97
|
-
if (formatting.language.val) languageAttrs.push(`w:val="${escapeXml(formatting.language.val)}"`);
|
|
98
|
-
if (formatting.language.eastAsia) languageAttrs.push(`w:eastAsia="${escapeXml(formatting.language.eastAsia)}"`);
|
|
99
|
-
if (formatting.language.bidi) languageAttrs.push(`w:bidi="${escapeXml(formatting.language.bidi)}"`);
|
|
100
|
-
if (languageAttrs.length > 0) parts.push(`<w:lang ${languageAttrs.join(" ")}/>`);
|
|
101
|
-
}
|
|
102
95
|
if (formatting.bold === true) parts.push("<w:b/>");
|
|
103
96
|
else if (formatting.bold === false) parts.push("<w:b w:val=\"0\"/>");
|
|
104
97
|
if (formatting.boldCs === true) parts.push("<w:bCs/>");
|
|
@@ -134,11 +127,12 @@ function serializeTextFormatting(formatting) {
|
|
|
134
127
|
if (formatting.position !== void 0) parts.push(`<w:position w:val="${intAttr(formatting.position)}"/>`);
|
|
135
128
|
if (formatting.fontSize !== void 0) parts.push(`<w:sz w:val="${intAttr(formatting.fontSize)}"/>`);
|
|
136
129
|
if (formatting.fontSizeCs !== void 0) parts.push(`<w:szCs w:val="${intAttr(formatting.fontSizeCs)}"/>`);
|
|
130
|
+
let customHighlightShadingXml = "";
|
|
137
131
|
if (formatting.highlight) {
|
|
138
132
|
if (VALID_HIGHLIGHT_COLORS.has(formatting.highlight)) parts.push(`<w:highlight w:val="${formatting.highlight}"/>`);
|
|
139
133
|
else if (!formatting.shading) {
|
|
140
134
|
const hex = formatting.highlight.replace(/^#/u, "");
|
|
141
|
-
if (/^[0-9a-fA-F]{6}$/u.test(hex))
|
|
135
|
+
if (/^[0-9a-fA-F]{6}$/u.test(hex)) customHighlightShadingXml = `<w:shd w:val="clear" w:color="auto" w:fill="${hex}"/>`;
|
|
142
136
|
}
|
|
143
137
|
}
|
|
144
138
|
if (formatting.underline) {
|
|
@@ -152,14 +146,21 @@ function serializeTextFormatting(formatting) {
|
|
|
152
146
|
parts.push(`<w:u ${uAttrs.join(" ")}/>`);
|
|
153
147
|
}
|
|
154
148
|
if (formatting.effect && formatting.effect !== "none") parts.push(`<w:effect w:val="${formatting.effect}"/>`);
|
|
155
|
-
|
|
156
|
-
const shadingXml = serializeShading(formatting.shading);
|
|
149
|
+
const shadingXml = serializeShading(formatting.shading) || customHighlightShadingXml;
|
|
157
150
|
if (shadingXml) parts.push(shadingXml);
|
|
158
151
|
if (formatting.vertAlign) parts.push(`<w:vertAlign w:val="${formatting.vertAlign}"/>`);
|
|
159
152
|
if (formatting.rtl === true) parts.push("<w:rtl/>");
|
|
160
153
|
else if (formatting.rtl === false) parts.push("<w:rtl w:val=\"0\"/>");
|
|
161
154
|
if (formatting.cs === true) parts.push("<w:cs/>");
|
|
162
155
|
else if (formatting.cs === false) parts.push("<w:cs w:val=\"0\"/>");
|
|
156
|
+
if (formatting.emphasisMark) parts.push(`<w:em w:val="${formatting.emphasisMark}"/>`);
|
|
157
|
+
if (formatting.language) {
|
|
158
|
+
const languageAttrs = [];
|
|
159
|
+
if (formatting.language.val) languageAttrs.push(`w:val="${escapeXml(formatting.language.val)}"`);
|
|
160
|
+
if (formatting.language.eastAsia) languageAttrs.push(`w:eastAsia="${escapeXml(formatting.language.eastAsia)}"`);
|
|
161
|
+
if (formatting.language.bidi) languageAttrs.push(`w:bidi="${escapeXml(formatting.language.bidi)}"`);
|
|
162
|
+
if (languageAttrs.length > 0) parts.push(`<w:lang ${languageAttrs.join(" ")}/>`);
|
|
163
|
+
}
|
|
163
164
|
if (parts.length === 0) return "";
|
|
164
165
|
return `<w:rPr>${parts.join("")}</w:rPr>`;
|
|
165
166
|
}
|
|
@@ -250,7 +250,8 @@ function serializeTableCellFormatting(formatting, propertyChanges, structuralCha
|
|
|
250
250
|
if (formatting.textDirection) parts.push(`<w:textDirection w:val="${formatting.textDirection}"/>`);
|
|
251
251
|
if (formatting.fitText) parts.push("<w:tcFitText/>");
|
|
252
252
|
if (formatting.verticalAlign) parts.push(`<w:vAlign w:val="${formatting.verticalAlign}"/>`);
|
|
253
|
-
if (formatting.hideMark
|
|
253
|
+
if (formatting.hideMark === true) parts.push("<w:hideMark/>");
|
|
254
|
+
else if (formatting.hideMark === false) parts.push("<w:hideMark w:val=\"off\"/>");
|
|
254
255
|
}
|
|
255
256
|
if (structuralChange) {
|
|
256
257
|
if (structuralChange.type === "tableCellInsertion") parts.push(`<w:cellIns ${serializeTrackedChangeAttributes(structuralChange.info)}/>`);
|
|
@@ -276,11 +277,30 @@ function serializeTableCellPropertyChange(change) {
|
|
|
276
277
|
return `<w:tcPrChange ${attrs}>${previousTcPrInner.length > 0 ? `<w:tcPr>${previousTcPrInner}</w:tcPr>` : "<w:tcPr/>"}</w:tcPrChange>`;
|
|
277
278
|
}
|
|
278
279
|
/**
|
|
279
|
-
*
|
|
280
|
+
* Columns the grid has to declare: the widest row's span total, because a grid
|
|
281
|
+
* narrower than a row leaves cells with no column to sit in.
|
|
280
282
|
*/
|
|
281
|
-
function
|
|
282
|
-
|
|
283
|
-
|
|
283
|
+
function gridColumnCount(table) {
|
|
284
|
+
let widest = 0;
|
|
285
|
+
for (const row of table.rows) {
|
|
286
|
+
let columns = 0;
|
|
287
|
+
for (const cell of row.cells) columns += cell.formatting?.gridSpan ?? 1;
|
|
288
|
+
widest = Math.max(widest, columns);
|
|
289
|
+
}
|
|
290
|
+
return widest;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Serialize table grid (w:tblGrid).
|
|
294
|
+
*
|
|
295
|
+
* `w:tblGrid` is required on every `w:tbl` and `w:w` is optional on a
|
|
296
|
+
* `w:gridCol`, so a table whose column widths were never measured — one the
|
|
297
|
+
* comparison creates, say — still declares a grid, just without widths.
|
|
298
|
+
*/
|
|
299
|
+
function serializeTableGrid(table) {
|
|
300
|
+
const columnWidths = table.columnWidths;
|
|
301
|
+
if (columnWidths && columnWidths.length > 0) return `<w:tblGrid>${columnWidths.map((w) => `<w:gridCol w:w="${intAttr(w)}"/>`).join("")}</w:tblGrid>`;
|
|
302
|
+
const columns = gridColumnCount(table);
|
|
303
|
+
return columns === 0 ? "<w:tblGrid/>" : `<w:tblGrid>${"<w:gridCol/>".repeat(columns)}</w:tblGrid>`;
|
|
284
304
|
}
|
|
285
305
|
/**
|
|
286
306
|
* Serialize cell content (paragraphs, nested tables)
|
|
@@ -319,11 +339,7 @@ function serializeTableRow(row, serializeParagraph) {
|
|
|
319
339
|
* @returns XML string for the table
|
|
320
340
|
*/
|
|
321
341
|
function serializeTable(table, serializeParagraph) {
|
|
322
|
-
const parts = [];
|
|
323
|
-
const tblPrXml = serializeTableFormatting(table.formatting, table.propertyChanges);
|
|
324
|
-
if (tblPrXml) parts.push(tblPrXml);
|
|
325
|
-
const tblGridXml = serializeTableGrid(table.columnWidths);
|
|
326
|
-
if (tblGridXml) parts.push(tblGridXml);
|
|
342
|
+
const parts = [serializeTableFormatting(table.formatting, table.propertyChanges) || "<w:tblPr/>", serializeTableGrid(table)];
|
|
327
343
|
for (const row of table.rows) parts.push(serializeTableRow(row, serializeParagraph));
|
|
328
344
|
return `<w:tbl>${parts.join("")}</w:tbl>`;
|
|
329
345
|
}
|
|
@@ -2,8 +2,8 @@ import { loadDocxArchive } from "./boundedArchive.js";
|
|
|
2
2
|
import { FOLIO_DOCX_XML_PATCH_PROPOSAL_PROFILE, InvalidFolioDocxXmlPatchProposalError, evaluateDocxXmlPatchProposal, parseFolioDocxXmlPatchProposal } from "./evaluateDocxXmlPatchProposal.js";
|
|
3
3
|
import { FOLIO_DOCX_CONFORMANCE_PROFILE, validateDocxConformance } from "./validateDocxConformance.js";
|
|
4
4
|
import { TaggedError, panic } from "better-result";
|
|
5
|
-
import JSZip from "jszip";
|
|
6
5
|
import { createHash } from "node:crypto";
|
|
6
|
+
import JSZip from "jszip";
|
|
7
7
|
//#region src/docx/server/applyDocxXmlPatchProposal.ts
|
|
8
8
|
const FOLIO_DOCX_XML_PATCH_APPLICATION_VERSION = 1;
|
|
9
9
|
const FOLIO_DOCX_XML_PATCH_APPLICATION_PROFILE = "folio-xml-patch-application-v1";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FolioDocxReviewer } from "../../ai-edits/headless.js";
|
|
2
|
+
import { isFolioAIContentBlock } from "../../ai-edits/snapshot.js";
|
|
2
3
|
import { toArrayBuffer } from "../../utils/docxInput.js";
|
|
3
4
|
import { ensureParaIds } from "../ensureParaIds.js";
|
|
4
5
|
import { parseDocx } from "../parser.js";
|
|
@@ -14,7 +15,7 @@ import { createBilingualDocument, readBilingualDocument } from "./createBilingua
|
|
|
14
15
|
*/
|
|
15
16
|
async function createBilingualDocx(input, options) {
|
|
16
17
|
const stampedBuffer = await toArrayBuffer((await ensureParaIds(input)).docx);
|
|
17
|
-
const editableParagraphIds = new Set((await FolioDocxReviewer.fromBuffer(stampedBuffer)).snapshot().blocks.map(({ id }) => id));
|
|
18
|
+
const editableParagraphIds = new Set((await FolioDocxReviewer.fromBuffer(stampedBuffer)).snapshot().blocks.filter(isFolioAIContentBlock).map(({ id }) => id));
|
|
18
19
|
const { document, warnings } = createBilingualDocument(await parseDocx(stampedBuffer, { preloadFonts: false }), {
|
|
19
20
|
...options,
|
|
20
21
|
editableParagraphIds
|
|
@@ -29,7 +30,7 @@ async function createBilingualDocx(input, options) {
|
|
|
29
30
|
/** Bytes-in form of {@link readBilingualDocument}. */
|
|
30
31
|
async function readBilingualDocx(input) {
|
|
31
32
|
const buffer = await toArrayBuffer(input);
|
|
32
|
-
return readBilingualDocument(await parseDocx(buffer, { preloadFonts: false }), new Set((await FolioDocxReviewer.fromBuffer(buffer)).snapshot().blocks.map(({ id }) => id)));
|
|
33
|
+
return readBilingualDocument(await parseDocx(buffer, { preloadFonts: false }), new Set((await FolioDocxReviewer.fromBuffer(buffer)).snapshot().blocks.filter(isFolioAIContentBlock).map(({ id }) => id)));
|
|
33
34
|
}
|
|
34
35
|
//#endregion
|
|
35
36
|
export { createBilingualDocx, readBilingualDocx };
|
|
@@ -11,6 +11,18 @@ type ParseXmlResult = {
|
|
|
11
11
|
* pass. Unsupported or malformed constructs return a sentinel so callers can
|
|
12
12
|
* retain the general-purpose parser as a compatibility fallback.
|
|
13
13
|
*/
|
|
14
|
+
type AttributeValueSpan = {
|
|
15
|
+
start: number;
|
|
16
|
+
end: number;
|
|
17
|
+
};
|
|
18
|
+
type OpenTagVisitor = (element: XmlElement, attributeValueSpans: ReadonlyMap<string, AttributeValueSpan>) => ReadonlyMap<string, string> | null;
|
|
14
19
|
declare const parseStreamingXml: (xml: string) => ParseXmlResult;
|
|
20
|
+
/** Rewrite selected decimal attribute values while preserving every other source byte. */
|
|
21
|
+
declare const rewriteStreamingXmlDecimalAttributes: (xml: string, visitOpenTag: OpenTagVisitor) => {
|
|
22
|
+
status: "rewritten";
|
|
23
|
+
value: string;
|
|
24
|
+
} | {
|
|
25
|
+
status: "unsupported";
|
|
26
|
+
};
|
|
15
27
|
//#endregion
|
|
16
|
-
export { parseStreamingXml };
|
|
28
|
+
export { parseStreamingXml, rewriteStreamingXmlDecimalAttributes };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { attachXmlNamespaceContext } from "./xmlParser.js";
|
|
1
2
|
import { FOLIO_XML_RESOURCE_LIMITS } from "./xmlResourceLimits.js";
|
|
2
3
|
//#region src/docx/streamingXmlParser.ts
|
|
3
4
|
const BUILT_IN_ENTITIES = {
|
|
@@ -7,14 +8,10 @@ const BUILT_IN_ENTITIES = {
|
|
|
7
8
|
lt: "<",
|
|
8
9
|
quot: "\""
|
|
9
10
|
};
|
|
10
|
-
|
|
11
|
-
* Parse ordinary OOXML into Folio's existing element representation in one
|
|
12
|
-
* pass. Unsupported or malformed constructs return a sentinel so callers can
|
|
13
|
-
* retain the general-purpose parser as a compatibility fallback.
|
|
14
|
-
*/
|
|
15
|
-
const parseStreamingXml = (xml) => {
|
|
11
|
+
const parseStreamingXmlInternal = (xml, visitOpenTag) => {
|
|
16
12
|
const root = { elements: [] };
|
|
17
13
|
const stack = [];
|
|
14
|
+
const replacements = [];
|
|
18
15
|
let cursor = 0;
|
|
19
16
|
let mergeAdjacentText = false;
|
|
20
17
|
while (cursor < xml.length) {
|
|
@@ -55,9 +52,19 @@ const parseStreamingXml = (xml) => {
|
|
|
55
52
|
mergeAdjacentText = false;
|
|
56
53
|
continue;
|
|
57
54
|
}
|
|
58
|
-
const parsedTag = parseOpenTag(xml, open + 1, close);
|
|
55
|
+
const parsedTag = parseOpenTag(xml, open + 1, close, visitOpenTag !== void 0);
|
|
59
56
|
if (parsedTag.status === "unsupported") return parsedTag;
|
|
60
57
|
const parent = stack.at(-1)?.element ?? root;
|
|
58
|
+
attachXmlNamespaceContext(parsedTag.element, parent.namespaceScope);
|
|
59
|
+
const rewritten = visitOpenTag?.(parsedTag.element, parsedTag.attributeValueSpans ?? /* @__PURE__ */ new Map());
|
|
60
|
+
if (rewritten) for (const [attributeName, value] of rewritten) {
|
|
61
|
+
const span = parsedTag.attributeValueSpans?.get(attributeName);
|
|
62
|
+
if (!span) return { status: "unsupported" };
|
|
63
|
+
replacements.push({
|
|
64
|
+
...span,
|
|
65
|
+
value
|
|
66
|
+
});
|
|
67
|
+
}
|
|
61
68
|
appendElement(parent, parsedTag.element);
|
|
62
69
|
if (!parsedTag.selfClosing) {
|
|
63
70
|
if (stack.length >= FOLIO_XML_RESOURCE_LIMITS.maxDepth) return { status: "unsupported" };
|
|
@@ -72,10 +79,35 @@ const parseStreamingXml = (xml) => {
|
|
|
72
79
|
if (stack.length > 0) return { status: "unsupported" };
|
|
73
80
|
return {
|
|
74
81
|
status: "parsed",
|
|
75
|
-
value: root
|
|
82
|
+
value: root,
|
|
83
|
+
replacements
|
|
84
|
+
};
|
|
85
|
+
};
|
|
86
|
+
const parseStreamingXml = (xml) => {
|
|
87
|
+
const parsed = parseStreamingXmlInternal(xml);
|
|
88
|
+
return parsed.status === "parsed" ? {
|
|
89
|
+
status: "parsed",
|
|
90
|
+
value: parsed.value
|
|
91
|
+
} : { status: "unsupported" };
|
|
92
|
+
};
|
|
93
|
+
/** Rewrite selected decimal attribute values while preserving every other source byte. */
|
|
94
|
+
const rewriteStreamingXmlDecimalAttributes = (xml, visitOpenTag) => {
|
|
95
|
+
const parsed = parseStreamingXmlInternal(xml, visitOpenTag);
|
|
96
|
+
if (parsed.status === "unsupported") return parsed;
|
|
97
|
+
const chunks = [];
|
|
98
|
+
let cursor = 0;
|
|
99
|
+
for (const replacement of parsed.replacements.toSorted((left, right) => left.start - right.start)) {
|
|
100
|
+
if (!/^\d+$/u.test(replacement.value)) return { status: "unsupported" };
|
|
101
|
+
chunks.push(xml.slice(cursor, replacement.start), replacement.value);
|
|
102
|
+
cursor = replacement.end;
|
|
103
|
+
}
|
|
104
|
+
chunks.push(xml.slice(cursor));
|
|
105
|
+
return {
|
|
106
|
+
status: "rewritten",
|
|
107
|
+
value: chunks.join("")
|
|
76
108
|
};
|
|
77
109
|
};
|
|
78
|
-
const parseOpenTag = (xml, start, close) => {
|
|
110
|
+
const parseOpenTag = (xml, start, close, captureAttributeSpans) => {
|
|
79
111
|
let cursor = skipWhitespace(xml, start, close);
|
|
80
112
|
const nameStart = cursor;
|
|
81
113
|
cursor = scanName(xml, cursor, close);
|
|
@@ -83,6 +115,7 @@ const parseOpenTag = (xml, start, close) => {
|
|
|
83
115
|
const name = xml.slice(nameStart, cursor);
|
|
84
116
|
if (isUnsafePropertyName(name)) return { status: "unsupported" };
|
|
85
117
|
let attributes;
|
|
118
|
+
const attributeValueSpans = captureAttributeSpans ? /* @__PURE__ */ new Map() : void 0;
|
|
86
119
|
let selfClosing = false;
|
|
87
120
|
while (cursor < close) {
|
|
88
121
|
cursor = skipWhitespace(xml, cursor, close);
|
|
@@ -110,6 +143,10 @@ const parseOpenTag = (xml, start, close) => {
|
|
|
110
143
|
if (decoded === null) return { status: "unsupported" };
|
|
111
144
|
attributes ??= {};
|
|
112
145
|
attributes[attributeName] = decoded;
|
|
146
|
+
attributeValueSpans?.set(attributeName, {
|
|
147
|
+
start: valueStart,
|
|
148
|
+
end: cursor
|
|
149
|
+
});
|
|
113
150
|
cursor += 1;
|
|
114
151
|
}
|
|
115
152
|
const element = {
|
|
@@ -121,7 +158,8 @@ const parseOpenTag = (xml, start, close) => {
|
|
|
121
158
|
status: "parsed",
|
|
122
159
|
element,
|
|
123
160
|
name,
|
|
124
|
-
selfClosing
|
|
161
|
+
selfClosing,
|
|
162
|
+
attributeValueSpans
|
|
125
163
|
};
|
|
126
164
|
};
|
|
127
165
|
const appendElement = (parent, child) => {
|
|
@@ -246,4 +284,4 @@ const isUnsafePropertyName = (name) => {
|
|
|
246
284
|
}
|
|
247
285
|
};
|
|
248
286
|
//#endregion
|
|
249
|
-
export { parseStreamingXml };
|
|
287
|
+
export { parseStreamingXml, rewriteStreamingXmlDecimalAttributes };
|
package/dist/docx/unzip.d.ts
CHANGED
|
@@ -59,7 +59,6 @@ type RawDocxContent = {
|
|
|
59
59
|
* @returns Promise resolving to extracted content
|
|
60
60
|
*/
|
|
61
61
|
declare function unzipDocx(buffer: ArrayBuffer, options?: DocxUnzipOptions): Promise<RawDocxContent>;
|
|
62
|
-
declare function isPreservableDocxEntry(path: string): boolean;
|
|
63
62
|
/**
|
|
64
63
|
* Get a list of all files in the DOCX
|
|
65
64
|
*
|
|
@@ -120,4 +119,4 @@ declare function getContentSummary(content: RawDocxContent): {
|
|
|
120
119
|
totalFiles: number;
|
|
121
120
|
};
|
|
122
121
|
//#endregion
|
|
123
|
-
export { DocxSecurityError, DocxUnzipLimits, DocxUnzipOptions, RawDocxContent, extractFile, getContentSummary, getFileList, getMediaMimeType, hasFile,
|
|
122
|
+
export { DocxSecurityError, DocxUnzipLimits, DocxUnzipOptions, RawDocxContent, extractFile, getContentSummary, getFileList, getMediaMimeType, hasFile, mediaToDataUrl, unzipDocx };
|
package/dist/docx/unzip.js
CHANGED
|
@@ -137,13 +137,13 @@ async function unzipDocx(buffer, options = {}) {
|
|
|
137
137
|
const extractionTasks = [];
|
|
138
138
|
for (const [path, file] of entries) {
|
|
139
139
|
if (!isSafeDocxPath(path)) throw new DocxSecurityError("DOCX file contains an unsafe entry path");
|
|
140
|
-
if (!isPreservableDocxEntry(path)) continue;
|
|
141
|
-
const lowerPath = path.toLowerCase();
|
|
142
140
|
const declaredSize = getEntryUncompressedSize(file);
|
|
143
141
|
if (declaredSize !== null) {
|
|
144
142
|
totalUncompressedBytes += declaredSize;
|
|
145
143
|
if (totalUncompressedBytes > limits.maxTotalUncompressedBytes) throw new DocxSecurityError("DOCX file expands beyond the maximum allowed size");
|
|
146
144
|
}
|
|
145
|
+
if (!isParsedDocxEntry(path)) continue;
|
|
146
|
+
const lowerPath = path.toLowerCase();
|
|
147
147
|
if (lowerPath.endsWith(".xml") || lowerPath.endsWith(".rels")) {
|
|
148
148
|
assertEntrySize(path, declaredSize, limits.maxXmlBytes);
|
|
149
149
|
if (options.extractAllXml === false && !shouldExtractXmlPart(lowerPath)) continue;
|
|
@@ -328,9 +328,28 @@ function isSafeDocxPath(path) {
|
|
|
328
328
|
if (!path || path.startsWith("/") || path.includes("\\")) return false;
|
|
329
329
|
return !path.split("/").some((part) => part === "..");
|
|
330
330
|
}
|
|
331
|
-
|
|
331
|
+
/**
|
|
332
|
+
* Parts folio never looks inside. A macro project and an ActiveX control carry
|
|
333
|
+
* code; folio models neither, so it reads neither — not into the document
|
|
334
|
+
* model, not even into the raw XML map. The save path carries them anyway,
|
|
335
|
+
* straight from the source archive, which is what keeps a `.docm` a `.docm`.
|
|
336
|
+
*/
|
|
337
|
+
const UNINTERPRETED_PART_NAMES = /* @__PURE__ */ new Set(["word/vbaproject.bin", "word/vbadata.xml"]);
|
|
338
|
+
const isUninterpretedPart = (lowerPath) => UNINTERPRETED_PART_NAMES.has(lowerPath) || lowerPath.startsWith("word/activex/");
|
|
339
|
+
/**
|
|
340
|
+
* Whether this entry is one the PARSER reads into the document model. It says
|
|
341
|
+
* nothing about what a save keeps: a repack hands back every part of the
|
|
342
|
+
* package (see `packageParts.ts`), including the ones this returns false for.
|
|
343
|
+
*
|
|
344
|
+
* That asymmetry is the point. A macro project, an ActiveX control and an OLE
|
|
345
|
+
* embedding are opaque bytes to folio — never decoded, never interpreted,
|
|
346
|
+
* never executed — and they travel from the source archive to the saved one
|
|
347
|
+
* without any code here looking inside them.
|
|
348
|
+
*/
|
|
349
|
+
function isParsedDocxEntry(path) {
|
|
332
350
|
if (!isSafeDocxPath(path)) return false;
|
|
333
351
|
const lowerPath = path.toLowerCase();
|
|
352
|
+
if (isUninterpretedPart(lowerPath)) return false;
|
|
334
353
|
if (lowerPath.startsWith("word/media/")) return PRESERVABLE_MEDIA_MIME_TYPES.has(getMediaMimeType(path));
|
|
335
354
|
if (lowerPath.startsWith("docprops/thumbnail.")) return PRESERVABLE_MEDIA_MIME_TYPES.has(getMediaMimeType(path));
|
|
336
355
|
if (lowerPath.startsWith("word/fonts/")) return true;
|
|
@@ -374,7 +393,7 @@ function isMediaContentAllowed(data, mimeType) {
|
|
|
374
393
|
*/
|
|
375
394
|
function getFileList(content) {
|
|
376
395
|
const files = [];
|
|
377
|
-
for (const path of Object.keys(content.originalZip.files)) if (!content.originalZip.files[path]?.dir &&
|
|
396
|
+
for (const path of Object.keys(content.originalZip.files)) if (!content.originalZip.files[path]?.dir && isParsedDocxEntry(path)) files.push(path);
|
|
378
397
|
return files.toSorted();
|
|
379
398
|
}
|
|
380
399
|
/**
|
|
@@ -425,7 +444,7 @@ function mediaToDataUrl(data, mimeType) {
|
|
|
425
444
|
*/
|
|
426
445
|
function extractFile(content, path) {
|
|
427
446
|
const file = content.originalZip.file(path);
|
|
428
|
-
if (!file || !
|
|
447
|
+
if (!file || !isParsedDocxEntry(path)) return Promise.resolve(null);
|
|
429
448
|
const lowerPath = path.toLowerCase();
|
|
430
449
|
if (lowerPath.endsWith(".xml") || lowerPath.endsWith(".rels")) return file.async("text");
|
|
431
450
|
return file.async("arraybuffer");
|
|
@@ -464,4 +483,4 @@ function getContentSummary(content) {
|
|
|
464
483
|
};
|
|
465
484
|
}
|
|
466
485
|
//#endregion
|
|
467
|
-
export { DocxSecurityError, extractFile, getContentSummary, getFileList, getMediaMimeType, hasFile,
|
|
486
|
+
export { DocxSecurityError, extractFile, getContentSummary, getFileList, getMediaMimeType, hasFile, mediaToDataUrl, unzipDocx };
|
package/dist/docx/xmlParser.d.ts
CHANGED
|
@@ -47,6 +47,8 @@ type XmlNamespaceScope = {
|
|
|
47
47
|
bindings: ReadonlyMap<string, string>;
|
|
48
48
|
parent?: XmlNamespaceScope;
|
|
49
49
|
};
|
|
50
|
+
/** Attach the element's resolved namespace metadata from its in-scope declarations. */
|
|
51
|
+
declare const attachXmlNamespaceContext: (element: XmlElement, inheritedNamespaceScope?: XmlNamespaceScope) => XmlNamespaceScope;
|
|
50
52
|
/**
|
|
51
53
|
* Common OOXML namespace URIs — re-exported from @stll/docx-utils.
|
|
52
54
|
*/
|
|
@@ -344,4 +346,4 @@ declare function mergeXmlnsDeclarations(inherited: Record<string, string>, eleme
|
|
|
344
346
|
*/
|
|
345
347
|
declare function cloneWithXmlnsDeclarations(element: XmlElement, xmlnsDecls: Record<string, string>): XmlElement;
|
|
346
348
|
//#endregion
|
|
347
|
-
export { NAMESPACES, WORDPROCESSINGML_NAMESPACE_URIS, XmlAttributeMatch, XmlElement, XmlNamespaceScope, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findAttributeByNamespaceUri, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffValue, parseTableMeasurementValue, parseXml, parseXmlDocument };
|
|
349
|
+
export { NAMESPACES, WORDPROCESSINGML_NAMESPACE_URIS, XmlAttributeMatch, XmlElement, XmlNamespaceScope, attachXmlNamespaceContext, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findAttributeByNamespaceUri, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffValue, parseTableMeasurementValue, parseXml, parseXmlDocument };
|
package/dist/docx/xmlParser.js
CHANGED
|
@@ -64,6 +64,37 @@ const resolveNamespaceUri = (scope, prefix) => {
|
|
|
64
64
|
current = current.parent;
|
|
65
65
|
}
|
|
66
66
|
};
|
|
67
|
+
/** Attach the element's resolved namespace metadata from its in-scope declarations. */
|
|
68
|
+
const attachXmlNamespaceContext = (element, inheritedNamespaceScope = EMPTY_NAMESPACE_SCOPE) => {
|
|
69
|
+
let localBindings = null;
|
|
70
|
+
if (element.attributes) for (const [attribute, value] of Object.entries(element.attributes)) {
|
|
71
|
+
if (typeof value !== "string" || attribute !== "xmlns" && !attribute.startsWith("xmlns:")) continue;
|
|
72
|
+
localBindings ??= /* @__PURE__ */ new Map();
|
|
73
|
+
const prefix = attribute === "xmlns" ? "" : attribute.slice(6);
|
|
74
|
+
localBindings.set(prefix, value);
|
|
75
|
+
}
|
|
76
|
+
const namespaceScope = localBindings === null ? inheritedNamespaceScope : {
|
|
77
|
+
bindings: localBindings,
|
|
78
|
+
parent: inheritedNamespaceScope
|
|
79
|
+
};
|
|
80
|
+
Object.defineProperty(element, "namespaceScope", {
|
|
81
|
+
configurable: false,
|
|
82
|
+
enumerable: false,
|
|
83
|
+
value: namespaceScope,
|
|
84
|
+
writable: false
|
|
85
|
+
});
|
|
86
|
+
const name = element.name ?? "";
|
|
87
|
+
const colonIndex = name.indexOf(":");
|
|
88
|
+
const prefix = colonIndex === -1 ? "" : name.slice(0, colonIndex);
|
|
89
|
+
const namespaceUri = resolveNamespaceUri(namespaceScope, prefix);
|
|
90
|
+
if (namespaceUri !== void 0) Object.defineProperty(element, "namespaceUri", {
|
|
91
|
+
configurable: false,
|
|
92
|
+
enumerable: false,
|
|
93
|
+
value: namespaceUri,
|
|
94
|
+
writable: false
|
|
95
|
+
});
|
|
96
|
+
return namespaceScope;
|
|
97
|
+
};
|
|
67
98
|
/**
|
|
68
99
|
* Convert a fast-xml-parser preserveOrder node into an XmlElement.
|
|
69
100
|
*
|
|
@@ -85,32 +116,7 @@ function fxpNodeToElement(node, inheritedNamespaceScope = EMPTY_NAMESPACE_SCOPE)
|
|
|
85
116
|
name: key
|
|
86
117
|
};
|
|
87
118
|
if (attrs) element.attributes = attrs;
|
|
88
|
-
|
|
89
|
-
if (attrs) for (const [attribute, value] of Object.entries(attrs)) {
|
|
90
|
-
if (attribute !== "xmlns" && !attribute.startsWith("xmlns:")) continue;
|
|
91
|
-
localBindings ??= /* @__PURE__ */ new Map();
|
|
92
|
-
const prefix = attribute === "xmlns" ? "" : attribute.slice(6);
|
|
93
|
-
localBindings.set(prefix, value);
|
|
94
|
-
}
|
|
95
|
-
const namespaceScope = localBindings === null ? inheritedNamespaceScope : {
|
|
96
|
-
bindings: localBindings,
|
|
97
|
-
parent: inheritedNamespaceScope
|
|
98
|
-
};
|
|
99
|
-
Object.defineProperty(element, "namespaceScope", {
|
|
100
|
-
configurable: false,
|
|
101
|
-
enumerable: false,
|
|
102
|
-
value: namespaceScope,
|
|
103
|
-
writable: false
|
|
104
|
-
});
|
|
105
|
-
const colonIndex = key.indexOf(":");
|
|
106
|
-
const prefix = colonIndex === -1 ? "" : key.slice(0, colonIndex);
|
|
107
|
-
const namespaceUri = resolveNamespaceUri(namespaceScope, prefix);
|
|
108
|
-
if (namespaceUri !== void 0) Object.defineProperty(element, "namespaceUri", {
|
|
109
|
-
configurable: false,
|
|
110
|
-
enumerable: false,
|
|
111
|
-
value: namespaceUri,
|
|
112
|
-
writable: false
|
|
113
|
-
});
|
|
119
|
+
const namespaceScope = attachXmlNamespaceContext(element, inheritedNamespaceScope);
|
|
114
120
|
if (children.length > 0) {
|
|
115
121
|
for (let index = 0; index < children.length; index += 1) children[index] = fxpNodeToElement(children[index], namespaceScope);
|
|
116
122
|
element.elements = children;
|
|
@@ -745,4 +751,4 @@ function cloneWithXmlnsDeclarations(element, xmlnsDecls) {
|
|
|
745
751
|
return element;
|
|
746
752
|
}
|
|
747
753
|
//#endregion
|
|
748
|
-
export { NAMESPACES, WORDPROCESSINGML_NAMESPACE_URIS, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findAttributeByNamespaceUri, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffValue, parseTableMeasurementValue, parseXml, parseXmlDocument };
|
|
754
|
+
export { NAMESPACES, WORDPROCESSINGML_NAMESPACE_URIS, attachXmlNamespaceContext, cloneWithXmlnsDeclarations, collectXmlnsDeclarations, elementToXml, findAllDeep, findAttributeByNamespaceUri, findByFullName, findChild, findChildByLocalName, findChildByNamespaceUri, findChildren, findChildrenByLocalName, findDeep, getAttribute, getAttributeAny, getAttributeAnyPrefix, getAttributeByNamespaceUri, getAttributes, getChildElements, getLocalName, getNamespacePrefix, getNamespaceUri, getTextContent, hasChild, hasFlag, matchesName, mergeXmlnsDeclarations, parseBooleanElement, parseColorElement, parseNumberingLevelAttribute, parseNumericAttribute, parseOnOffValue, parseTableMeasurementValue, parseXml, parseXmlDocument };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { DocxInput } from "./utils/docxInput.js";
|
|
2
|
+
import { DisplayMetadata, DisplayUnsupported } from "./display-list/types.js";
|
|
3
|
+
import { HeadlessFontSource, HeadlessFontSubstitution } from "./fonts/headlessMeasure.js";
|
|
4
|
+
import { HeadlessLayoutGap } from "./headless-layout.js";
|
|
5
|
+
import { PdfSubstitution } from "./pdf/fonts.js";
|
|
6
|
+
import { PdfUnencodable } from "./pdf/writePdf.js";
|
|
7
|
+
import { Result } from "better-result";
|
|
8
|
+
//#region src/export-pdf.d.ts
|
|
9
|
+
declare const ExportPdfError_base: import("better-result").TaggedErrorClass<"ExportPdfError">;
|
|
10
|
+
declare class ExportPdfError extends ExportPdfError_base<{
|
|
11
|
+
message: string;
|
|
12
|
+
cause?: unknown;
|
|
13
|
+
}> {}
|
|
14
|
+
type ExportDocxToPdfOptions = {
|
|
15
|
+
/** Supplies face binaries for measurement and for embedding alike. */
|
|
16
|
+
readonly fonts: HeadlessFontSource;
|
|
17
|
+
/**
|
|
18
|
+
* ISO-8601 instant stamped as the PDF's creation and modification date.
|
|
19
|
+
* Required rather than defaulted so a caller cannot get a nondeterministic
|
|
20
|
+
* document by omission; pass the source package's own timestamp or a fixed
|
|
21
|
+
* epoch.
|
|
22
|
+
*/
|
|
23
|
+
readonly timestamp: string;
|
|
24
|
+
readonly metadata?: DisplayMetadata;
|
|
25
|
+
readonly producer?: string;
|
|
26
|
+
};
|
|
27
|
+
type ExportDocxToPdfResult = {
|
|
28
|
+
readonly bytes: Uint8Array;
|
|
29
|
+
readonly pageCount: number;
|
|
30
|
+
/** Constructs the display list could not represent. */
|
|
31
|
+
readonly unsupported: readonly DisplayUnsupported[];
|
|
32
|
+
/** Stories the headless pipeline does not paginate. */
|
|
33
|
+
readonly layoutGaps: readonly HeadlessLayoutGap[];
|
|
34
|
+
/** Faces measured with a stand-in. */
|
|
35
|
+
readonly measurementSubstitutions: readonly HeadlessFontSubstitution[];
|
|
36
|
+
/** Faces embedded as a stand-in. */
|
|
37
|
+
readonly embeddingSubstitutions: readonly PdfSubstitution[];
|
|
38
|
+
/** Code points painted as `.notdef` because no supplied face covers them. */
|
|
39
|
+
readonly unencodable: readonly PdfUnencodable[];
|
|
40
|
+
};
|
|
41
|
+
declare const exportDocxToPdf: (input: DocxInput, options: ExportDocxToPdfOptions) => Promise<Result<ExportDocxToPdfResult, ExportPdfError>>;
|
|
42
|
+
//#endregion
|
|
43
|
+
export { ExportDocxToPdfOptions, ExportDocxToPdfResult, ExportPdfError, exportDocxToPdf };
|