@stll/folio-core 0.47.4 → 0.47.6
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/compare/inline-atoms.js +218 -31
- package/dist/compare/section-boundary-properties.js +1 -0
- package/dist/content-controls/mutateContentControls.js +1 -3
- package/dist/controller/headerFooterEditorManager.d.ts +2 -1
- package/dist/controller/headerFooterEditorManager.js +2 -1
- package/dist/controller/noteEditorManager.d.ts +2 -1
- package/dist/controller/noteEditorManager.js +2 -1
- package/dist/docx/commentRangeIntegrity.js +7 -0
- package/dist/docx/documentParser.js +55 -3
- package/dist/docx/inlineWrapperContent.d.ts +18 -16
- package/dist/docx/inlineWrapperContent.js +18 -16
- package/dist/docx/normalizeBaseDirection.js +4 -0
- package/dist/docx/rezip.js +1 -0
- package/dist/docx/selectiveSave.js +11 -2
- package/dist/docx/serializer/documentSerializer.js +16 -1
- package/dist/docx/serializer/paragraphSerializer.js +8 -0
- package/dist/docx/serializer/tableSerializer.js +5 -2
- package/dist/docx/server/createBilingualDocument.js +23 -14
- package/dist/docx/tableParser.js +101 -75
- package/dist/markdown/renderRuns.js +4 -0
- package/dist/markdown/renderTable.js +46 -12
- package/dist/prosemirror/attrs/index.d.ts +3 -2
- package/dist/prosemirror/attrs/index.js +85 -4
- package/dist/prosemirror/commands/comments.js +61 -3
- package/dist/prosemirror/commentReferenceAttrs.js +2 -2
- package/dist/prosemirror/commentReferenceIntegrity.d.ts +7 -3
- package/dist/prosemirror/commentReferenceIntegrity.js +9 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +177 -51
- package/dist/prosemirror/conversion/toProseDoc.js +108 -55
- package/dist/prosemirror/extensions/markRegistry.d.ts +5 -8
- package/dist/prosemirror/extensions/markRegistry.js +5 -8
- package/dist/prosemirror/extensions/marks/HyperlinkExtension.js +13 -6
- package/dist/prosemirror/extensions/marks/InlineWrapperExtension.js +24 -6
- package/dist/prosemirror/extensions/marks/RunIdentityExtension.js +2 -1
- package/dist/prosemirror/extensions/marks/TrackedChangeExtensions.js +32 -7
- package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +9 -5
- package/dist/prosemirror/extensions/nodes/TableExtension.js +2 -2
- package/dist/prosemirror/runIdentity.d.ts +5 -5
- package/dist/prosemirror/runIdentity.js +5 -5
- package/dist/prosemirror/schema/index.d.ts +2 -2
- package/dist/prosemirror/schema/marks.d.ts +28 -2
- package/dist/prosemirror/schema/nodes.d.ts +6 -4
- package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -1
- package/dist/prosemirror/trackedRunInlineAtoms.js +1 -1
- package/dist/prosemirror/utils/extractTrackedChanges.js +21 -6
- package/dist/prosemirror/validation.js +48 -7
- package/dist/types/content.d.ts +2 -2
- package/dist/types/index.d.ts +3 -1
- package/dist/utils/mergeDocumentContent.js +1 -7
- package/package.json +2 -2
|
@@ -3,17 +3,17 @@ import { document_d_exports } from "../types/document.js";
|
|
|
3
3
|
/**
|
|
4
4
|
* What a run-level tracked-change wrapper keeps inside itself (CT_RunTrackChange).
|
|
5
5
|
*
|
|
6
|
-
* Runs, links, fields, bookmark boundaries, equations, nested
|
|
7
|
-
* markup folio does not model.
|
|
6
|
+
* Runs, links, fields, bookmark and move-range boundaries, equations, nested
|
|
7
|
+
* revisions and markup folio does not model.
|
|
8
8
|
*
|
|
9
9
|
* The transparent wrappers are admitted too: `w:bdo` / `w:dir` state how their
|
|
10
10
|
* content is laid out, and `w:sdt` states what the content is bound to.
|
|
11
11
|
* Neither says anything about the revision, so lifting one out of a revision
|
|
12
12
|
* takes its text out of the revision with it.
|
|
13
13
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
14
|
+
* Move-range markers stay inside the revision that owns their authored
|
|
15
|
+
* position. Comment ranges remain paragraph-level because the editor projects
|
|
16
|
+
* them as marks over content rather than as boundary atoms.
|
|
17
17
|
*/
|
|
18
18
|
declare const TRACKED_CHANGE_WRAPPER_CONTENT: {
|
|
19
19
|
readonly inlineWrapper: true;
|
|
@@ -33,10 +33,10 @@ declare const TRACKED_CHANGE_WRAPPER_CONTENT: {
|
|
|
33
33
|
readonly commentRangeEnd: false;
|
|
34
34
|
readonly commentRangeStart: false;
|
|
35
35
|
readonly commentReference: false;
|
|
36
|
-
readonly moveFromRangeEnd:
|
|
37
|
-
readonly moveFromRangeStart:
|
|
38
|
-
readonly moveToRangeEnd:
|
|
39
|
-
readonly moveToRangeStart:
|
|
36
|
+
readonly moveFromRangeEnd: true;
|
|
37
|
+
readonly moveFromRangeStart: true;
|
|
38
|
+
readonly moveToRangeEnd: true;
|
|
39
|
+
readonly moveToRangeStart: true;
|
|
40
40
|
};
|
|
41
41
|
/**
|
|
42
42
|
* What an inline content control keeps inside `<w:sdtContent>`.
|
|
@@ -54,9 +54,11 @@ declare const TRACKED_CHANGE_WRAPPER_CONTENT: {
|
|
|
54
54
|
* control's content comes back covering the control plus whatever follows, so
|
|
55
55
|
* a `REF` field or a link to it resolves to the wrong text.
|
|
56
56
|
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
57
|
+
* Move-range boundaries stay where the source put them. The editor has
|
|
58
|
+
* zero-width boundary nodes for them, so lifting one beside the control would
|
|
59
|
+
* change which content the tracked move covers without any representational
|
|
60
|
+
* need. Comment ranges remain paragraph-level because the editor projects
|
|
61
|
+
* them as marks shared across the whole story rather than boundary atoms.
|
|
60
62
|
*/
|
|
61
63
|
declare const INLINE_SDT_CONTENT: {
|
|
62
64
|
readonly inlineWrapper: true;
|
|
@@ -76,10 +78,10 @@ declare const INLINE_SDT_CONTENT: {
|
|
|
76
78
|
readonly commentRangeEnd: false;
|
|
77
79
|
readonly commentRangeStart: false;
|
|
78
80
|
readonly commentReference: false;
|
|
79
|
-
readonly moveFromRangeEnd:
|
|
80
|
-
readonly moveFromRangeStart:
|
|
81
|
-
readonly moveToRangeEnd:
|
|
82
|
-
readonly moveToRangeStart:
|
|
81
|
+
readonly moveFromRangeEnd: true;
|
|
82
|
+
readonly moveFromRangeStart: true;
|
|
83
|
+
readonly moveToRangeEnd: true;
|
|
84
|
+
readonly moveToRangeStart: true;
|
|
83
85
|
};
|
|
84
86
|
/**
|
|
85
87
|
* What a `w:hyperlink` keeps inside itself (CT_Hyperlink).
|
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* What a run-level tracked-change wrapper keeps inside itself (CT_RunTrackChange).
|
|
4
4
|
*
|
|
5
|
-
* Runs, links, fields, bookmark boundaries, equations, nested
|
|
6
|
-
* markup folio does not model.
|
|
5
|
+
* Runs, links, fields, bookmark and move-range boundaries, equations, nested
|
|
6
|
+
* revisions and markup folio does not model.
|
|
7
7
|
*
|
|
8
8
|
* The transparent wrappers are admitted too: `w:bdo` / `w:dir` state how their
|
|
9
9
|
* content is laid out, and `w:sdt` states what the content is bound to.
|
|
10
10
|
* Neither says anything about the revision, so lifting one out of a revision
|
|
11
11
|
* takes its text out of the revision with it.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* Move-range markers stay inside the revision that owns their authored
|
|
14
|
+
* position. Comment ranges remain paragraph-level because the editor projects
|
|
15
|
+
* them as marks over content rather than as boundary atoms.
|
|
16
16
|
*/
|
|
17
17
|
const TRACKED_CHANGE_WRAPPER_CONTENT = {
|
|
18
18
|
inlineWrapper: true,
|
|
@@ -32,10 +32,10 @@ const TRACKED_CHANGE_WRAPPER_CONTENT = {
|
|
|
32
32
|
commentRangeEnd: false,
|
|
33
33
|
commentRangeStart: false,
|
|
34
34
|
commentReference: false,
|
|
35
|
-
moveFromRangeEnd:
|
|
36
|
-
moveFromRangeStart:
|
|
37
|
-
moveToRangeEnd:
|
|
38
|
-
moveToRangeStart:
|
|
35
|
+
moveFromRangeEnd: true,
|
|
36
|
+
moveFromRangeStart: true,
|
|
37
|
+
moveToRangeEnd: true,
|
|
38
|
+
moveToRangeStart: true
|
|
39
39
|
};
|
|
40
40
|
/**
|
|
41
41
|
* What an inline content control keeps inside `<w:sdtContent>`.
|
|
@@ -53,9 +53,11 @@ const TRACKED_CHANGE_WRAPPER_CONTENT = {
|
|
|
53
53
|
* control's content comes back covering the control plus whatever follows, so
|
|
54
54
|
* a `REF` field or a link to it resolves to the wrong text.
|
|
55
55
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
56
|
+
* Move-range boundaries stay where the source put them. The editor has
|
|
57
|
+
* zero-width boundary nodes for them, so lifting one beside the control would
|
|
58
|
+
* change which content the tracked move covers without any representational
|
|
59
|
+
* need. Comment ranges remain paragraph-level because the editor projects
|
|
60
|
+
* them as marks shared across the whole story rather than boundary atoms.
|
|
59
61
|
*/
|
|
60
62
|
const INLINE_SDT_CONTENT = {
|
|
61
63
|
inlineWrapper: true,
|
|
@@ -75,10 +77,10 @@ const INLINE_SDT_CONTENT = {
|
|
|
75
77
|
commentRangeEnd: false,
|
|
76
78
|
commentRangeStart: false,
|
|
77
79
|
commentReference: false,
|
|
78
|
-
moveFromRangeEnd:
|
|
79
|
-
moveFromRangeStart:
|
|
80
|
-
moveToRangeEnd:
|
|
81
|
-
moveToRangeStart:
|
|
80
|
+
moveFromRangeEnd: true,
|
|
81
|
+
moveFromRangeStart: true,
|
|
82
|
+
moveToRangeEnd: true,
|
|
83
|
+
moveToRangeStart: true
|
|
82
84
|
};
|
|
83
85
|
/**
|
|
84
86
|
* What a `w:hyperlink` keeps inside itself (CT_Hyperlink).
|
|
@@ -52,6 +52,10 @@ const normalizeCellBlock = (block, styles) => {
|
|
|
52
52
|
switch (block.type) {
|
|
53
53
|
case "paragraph": return normalizeParagraph(block, styles);
|
|
54
54
|
case "table": return normalizeTable(block, styles);
|
|
55
|
+
case "blockSdt": return {
|
|
56
|
+
...block,
|
|
57
|
+
content: block.content.map((child) => normalizeCellBlock(child, styles))
|
|
58
|
+
};
|
|
55
59
|
case "preservedBlock":
|
|
56
60
|
case "bookmarkStart":
|
|
57
61
|
case "bookmarkEnd": return block;
|
package/dist/docx/rezip.js
CHANGED
|
@@ -404,6 +404,7 @@ function collectNewImages(blocks) {
|
|
|
404
404
|
for (const sub of item.content) if (sub.type === "run") collectFromRun(sub);
|
|
405
405
|
}
|
|
406
406
|
} else if (block.type === "table") for (const row of block.rows) for (const cell of row.cells) images.push(...collectNewImages(cell.content));
|
|
407
|
+
else if (block.type === "blockSdt") images.push(...collectNewImages(block.content));
|
|
407
408
|
return images;
|
|
408
409
|
}
|
|
409
410
|
/** Map MIME type to file extension (inverse of getContentTypeForExtension) */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseCommentsExtended } from "./commentParser.js";
|
|
2
2
|
import { withoutOrphanCommentRanges } from "./commentRangeIntegrity.js";
|
|
3
3
|
import { hasUnsynthesizedReplyRanges } from "./commentReplyMarkers.js";
|
|
4
|
+
import { detectDocxConformanceClass } from "./conformance.js";
|
|
4
5
|
import { validateFolioDocumentModel } from "./modelValidation.js";
|
|
5
6
|
import { parseNumbering } from "./numberingParser.js";
|
|
6
7
|
import { isUnsafePackagePath } from "./packageParts.js";
|
|
@@ -13,6 +14,7 @@ import { serializeDocument } from "./serializer/documentSerializer.js";
|
|
|
13
14
|
import { serializeEndnotes, serializeFootnotes } from "./serializer/noteSerializer.js";
|
|
14
15
|
import { serializeNumberingXml } from "./serializer/numberingSerializer.js";
|
|
15
16
|
import { readRootNamespaceBindings } from "./serializer/partNamespaces.js";
|
|
17
|
+
import { DOCX_CONFORMANCE_CLASSES } from "@stll/docx-core/model";
|
|
16
18
|
//#region src/docx/selectiveSave.ts
|
|
17
19
|
/**
|
|
18
20
|
* Splice edited footnote/endnote paragraphs into their parts and return the
|
|
@@ -217,8 +219,9 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
|
|
|
217
219
|
}
|
|
218
220
|
const sourceCommentsFile = zip.file("word/comments.xml");
|
|
219
221
|
if (hasComments || sourceCommentsFile) {
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
+
const sourceCommentsXml = sourceCommentsFile ? await sourceCommentsFile.async("text") : void 0;
|
|
223
|
+
const commentsXml = serializeComments(commentPlan, sourceCommentsXml === void 0 ? void 0 : readRootNamespaceBindings(sourceCommentsXml));
|
|
224
|
+
if (commentsXml !== sourceCommentsXml) updates.set("word/comments.xml", commentsXml);
|
|
222
225
|
}
|
|
223
226
|
if (hasComments) {
|
|
224
227
|
const ctFile = zip.file("[Content_Types].xml");
|
|
@@ -245,6 +248,12 @@ async function attemptSelectiveSave(doc, originalBuffer, options) {
|
|
|
245
248
|
const corePropsXml = await corePropsFile.async("text");
|
|
246
249
|
updates.set("docProps/core.xml", updateCoreProperties(corePropsXml, { updateModifiedDate: true }));
|
|
247
250
|
}
|
|
251
|
+
if ([...updates.keys()].some((path) => {
|
|
252
|
+
const lowerPath = path.toLowerCase();
|
|
253
|
+
return lowerPath.startsWith("word/") && (lowerPath.endsWith(".xml") || lowerPath.endsWith(".rels"));
|
|
254
|
+
})) {
|
|
255
|
+
if (detectDocxConformanceClass(await findZipEntryCaseInsensitive(zip, "word/document.xml")?.async("text") ?? null) === DOCX_CONFORMANCE_CLASSES.STRICT) return null;
|
|
256
|
+
}
|
|
248
257
|
return await applyUpdatesToZip(zip, updates);
|
|
249
258
|
} catch {
|
|
250
259
|
return null;
|
|
@@ -5,6 +5,8 @@ import { serializePartElement } from "./partNamespaces.js";
|
|
|
5
5
|
import { resetAutoIdCounter } from "./runSerializer.js";
|
|
6
6
|
import { serializeSectionProperties } from "./sectionPropertiesSerializer.js";
|
|
7
7
|
import { serializeTable } from "./tableSerializer.js";
|
|
8
|
+
import { escapeXmlAttribute } from "@stll/docx-core";
|
|
9
|
+
import { themeColorToken } from "@stll/docx-core/model";
|
|
8
10
|
//#region src/docx/serializer/documentSerializer.ts
|
|
9
11
|
/**
|
|
10
12
|
* Prefixes document.xml declares whether or not the body uses them.
|
|
@@ -55,6 +57,19 @@ function serializeBlockContent(block) {
|
|
|
55
57
|
function serializeBodyContent(content) {
|
|
56
58
|
return content.map((block) => serializeBlockContent(block)).join("");
|
|
57
59
|
}
|
|
60
|
+
/** Serialize the optional page background that precedes `w:body`. */
|
|
61
|
+
function serializeDocumentBackground(background) {
|
|
62
|
+
if (background === void 0) return "";
|
|
63
|
+
const attributes = [];
|
|
64
|
+
if (background.color?.auto) attributes.push("w:color=\"auto\"");
|
|
65
|
+
else if (background.color?.rgb !== void 0) attributes.push(`w:color="${escapeXmlAttribute(background.color.rgb)}"`);
|
|
66
|
+
if (background.themeColor !== void 0) attributes.push(`w:themeColor="${escapeXmlAttribute(themeColorToken(background.themeColor))}"`);
|
|
67
|
+
if (background.themeTint !== void 0) attributes.push(`w:themeTint="${escapeXmlAttribute(background.themeTint)}"`);
|
|
68
|
+
if (background.themeShade !== void 0) attributes.push(`w:themeShade="${escapeXmlAttribute(background.themeShade)}"`);
|
|
69
|
+
const attributeText = attributes.length === 0 ? "" : ` ${attributes.join(" ")}`;
|
|
70
|
+
const drawing = background.drawing?.rawXml;
|
|
71
|
+
return drawing === void 0 ? `<w:background${attributeText}/>` : `<w:background${attributeText}>${drawing}</w:background>`;
|
|
72
|
+
}
|
|
58
73
|
/**
|
|
59
74
|
* Serialize a DocumentBody to document.xml body content
|
|
60
75
|
*
|
|
@@ -77,7 +92,7 @@ function serializeDocumentBody(body) {
|
|
|
77
92
|
*/
|
|
78
93
|
function serializeDocument(doc, sourceBindings) {
|
|
79
94
|
resetAutoIdCounter();
|
|
80
|
-
const body = `<w:body>${serializeDocumentBody(doc.package.document)}</w:body>`;
|
|
95
|
+
const body = serializeDocumentBackground(doc.package.document.background) + `<w:body>${serializeDocumentBody(doc.package.document)}</w:body>`;
|
|
81
96
|
return "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" + serializePartElement({
|
|
82
97
|
partPath: "word/document.xml",
|
|
83
98
|
rootName: "w:document",
|
|
@@ -358,6 +358,10 @@ function serializeInlineSdt(sdt, disposition = "kept") {
|
|
|
358
358
|
case "inlineWrapper": return serializeParagraphContent(item, disposition);
|
|
359
359
|
case "bookmarkStart": return serializeBookmarkMarker(item);
|
|
360
360
|
case "bookmarkEnd": return serializeBookmarkMarker(item);
|
|
361
|
+
case "moveFromRangeStart": return serializeMoveRangeStart("moveFromRangeStart", item);
|
|
362
|
+
case "moveFromRangeEnd": return `<w:moveFromRangeEnd ${markupRangeAttributes(item).join(" ")}/>`;
|
|
363
|
+
case "moveToRangeStart": return serializeMoveRangeStart("moveToRangeStart", item);
|
|
364
|
+
case "moveToRangeEnd": return `<w:moveToRangeEnd ${markupRangeAttributes(item).join(" ")}/>`;
|
|
361
365
|
case "insertion": return serializeTrackedChange("ins", item);
|
|
362
366
|
case "deletion": return serializeTrackedChange("del", item);
|
|
363
367
|
case "moveFrom": return serializeTrackedChange("moveFrom", item);
|
|
@@ -420,6 +424,10 @@ function serializeTrackedChange(tag, change) {
|
|
|
420
424
|
case "moveTo": return serializeTrackedChange(trackedChangeTag(item), item);
|
|
421
425
|
case "bookmarkStart":
|
|
422
426
|
case "bookmarkEnd": return serializeBookmarkMarker(item);
|
|
427
|
+
case "moveFromRangeStart": return serializeMoveRangeStart("moveFromRangeStart", item);
|
|
428
|
+
case "moveFromRangeEnd": return `<w:moveFromRangeEnd ${markupRangeAttributes(item).join(" ")}/>`;
|
|
429
|
+
case "moveToRangeStart": return serializeMoveRangeStart("moveToRangeStart", item);
|
|
430
|
+
case "moveToRangeEnd": return `<w:moveToRangeEnd ${markupRangeAttributes(item).join(" ")}/>`;
|
|
423
431
|
case "preservedInline": return item.xml;
|
|
424
432
|
case "inlineWrapper":
|
|
425
433
|
case "inlineSdt": return serializeParagraphContent(item, disposition);
|
|
@@ -6,6 +6,7 @@ import { TRANSITIONAL_NAME_BY_STRICT_NAME } from "../strictNames.gen.js";
|
|
|
6
6
|
import { TABLE_LOOK_FLAGS } from "../tableLook.js";
|
|
7
7
|
import { parseTableCellProperties, parseTableGrid, parseTableProperties, parseTablePropertyExceptions, parseTableRowProperties } from "../tableParser.js";
|
|
8
8
|
import { OOXML_NAMESPACE_SCOPE, parseXml } from "../xmlParser.js";
|
|
9
|
+
import { serializeBlockSdt } from "./blockSdtSerializer.js";
|
|
9
10
|
import { serializeBorder } from "./borderSerializer.js";
|
|
10
11
|
import { serializeBookmarkMarker } from "./markupRangeAttributes.js";
|
|
11
12
|
import { serializeSdtWrapper } from "./sdtPropertiesSerializer.js";
|
|
@@ -511,16 +512,18 @@ const preservedChild = (xml, child) => ({
|
|
|
511
512
|
* Serialize cell content (paragraphs, nested tables)
|
|
512
513
|
*/
|
|
513
514
|
function serializeCellContent(content, serializeParagraph) {
|
|
514
|
-
const
|
|
515
|
+
const serializeCellBlock = (block) => {
|
|
515
516
|
switch (block.type) {
|
|
516
517
|
case "paragraph": return serializeParagraph(block);
|
|
517
518
|
case "table": return serializeTable(block, serializeParagraph);
|
|
519
|
+
case "blockSdt": return serializeBlockSdt(block, serializeCellBlock);
|
|
518
520
|
case "preservedBlock": return block.xml;
|
|
519
521
|
case "bookmarkStart":
|
|
520
522
|
case "bookmarkEnd": return serializeBookmarkMarker(block);
|
|
521
523
|
default: return block;
|
|
522
524
|
}
|
|
523
|
-
}
|
|
525
|
+
};
|
|
526
|
+
const parts = content.map(serializeCellBlock);
|
|
524
527
|
if (parts.length === 0) parts.push("<w:p/>");
|
|
525
528
|
return parts.join("");
|
|
526
529
|
}
|
|
@@ -464,30 +464,39 @@ const remapListRendering = (rendering, cloner) => {
|
|
|
464
464
|
};
|
|
465
465
|
const collectTableParagraphs = (table) => {
|
|
466
466
|
const out = [];
|
|
467
|
-
|
|
468
|
-
|
|
467
|
+
const collectBlocks = (blocks) => {
|
|
468
|
+
for (const item of blocks) if (item.type === "paragraph") out.push(item);
|
|
469
|
+
else if (item.type === "table") out.push(...collectTableParagraphs(item));
|
|
470
|
+
else if (item.type === "blockSdt") collectBlocks(item.content);
|
|
471
|
+
};
|
|
472
|
+
for (const row of table.rows) for (const cell of row.cells) collectBlocks(cell.content);
|
|
469
473
|
return out;
|
|
470
474
|
};
|
|
471
475
|
const cloneTableForTarget = ({ table, editableParagraphIds, paraIds, styleCloner, cloner, bookmarkIds }) => {
|
|
472
476
|
const paragraphs = [];
|
|
477
|
+
const cloneBlocks = (blocks) => blocks.map((item) => {
|
|
478
|
+
if (item.type === "table") return cloneTable(item);
|
|
479
|
+
if (item.type === "blockSdt") return {
|
|
480
|
+
...structuredClone(item),
|
|
481
|
+
content: cloneBlocks(item.content)
|
|
482
|
+
};
|
|
483
|
+
if (item.type !== "paragraph") return structuredClone(item);
|
|
484
|
+
const targetParaId = paraIds.mint(item.paraId);
|
|
485
|
+
const copy = cloneParagraphForTarget(item, targetParaId, styleCloner, cloner, bookmarkIds);
|
|
486
|
+
if (isTranslatableParagraph(item, editableParagraphIds)) paragraphs.push({
|
|
487
|
+
sourceParaId: item.paraId,
|
|
488
|
+
targetParaId,
|
|
489
|
+
sourceText: getParagraphText(item)
|
|
490
|
+
});
|
|
491
|
+
return copy;
|
|
492
|
+
});
|
|
473
493
|
const cloneTable = (source) => ({
|
|
474
494
|
...structuredClone(source),
|
|
475
495
|
rows: source.rows.map((row) => ({
|
|
476
496
|
...structuredClone(row),
|
|
477
497
|
cells: row.cells.map((cell) => ({
|
|
478
498
|
...structuredClone(cell),
|
|
479
|
-
content: cell.content
|
|
480
|
-
if (item.type === "table") return cloneTable(item);
|
|
481
|
-
if (item.type !== "paragraph") return structuredClone(item);
|
|
482
|
-
const targetParaId = paraIds.mint(item.paraId);
|
|
483
|
-
const copy = cloneParagraphForTarget(item, targetParaId, styleCloner, cloner, bookmarkIds);
|
|
484
|
-
if (isTranslatableParagraph(item, editableParagraphIds)) paragraphs.push({
|
|
485
|
-
sourceParaId: item.paraId,
|
|
486
|
-
targetParaId,
|
|
487
|
-
sourceText: getParagraphText(item)
|
|
488
|
-
});
|
|
489
|
-
return copy;
|
|
490
|
-
})
|
|
499
|
+
content: cloneBlocks(cell.content)
|
|
491
500
|
}))
|
|
492
501
|
}))
|
|
493
502
|
});
|
package/dist/docx/tableParser.js
CHANGED
|
@@ -766,81 +766,104 @@ function withContainerXmlns(options, element) {
|
|
|
766
766
|
};
|
|
767
767
|
}
|
|
768
768
|
function parseCellContent(tcElement, styles, theme, numbering, rels, media, options) {
|
|
769
|
-
const
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
undeclared: { AlternateContent: (child) => {
|
|
777
|
-
const selectedBranch = selectAlternateContentBranch(child);
|
|
778
|
-
if (!selectedBranch) return;
|
|
779
|
-
dispatchCellChildren(selectedBranch, withContainerXmlns(withContainerXmlns(childOptions, child), selectedBranch));
|
|
780
|
-
} },
|
|
781
|
-
handlers: {
|
|
782
|
-
p: (child) => {
|
|
783
|
-
const para = parseParagraph(child, styles, theme, numbering, rels, media, {
|
|
784
|
-
...childOptions,
|
|
785
|
-
runConsolidation: "deferred"
|
|
786
|
-
});
|
|
787
|
-
enrichParagraphTextBoxes(para, child, styles, theme, numbering, rels, media, parseTable);
|
|
788
|
-
modelled.push(para);
|
|
789
|
-
},
|
|
790
|
-
tbl: (child) => {
|
|
791
|
-
const table = parseTable(child, styles, theme, numbering, rels, media, childOptions);
|
|
792
|
-
if (!table) return;
|
|
793
|
-
modelled.push(table);
|
|
794
|
-
},
|
|
795
|
-
sdt: (child) => {
|
|
796
|
-
const sdtContent = findWordprocessingChild(child, "sdtContent");
|
|
797
|
-
if (!sdtContent) return;
|
|
798
|
-
dispatchCellChildren(sdtContent, withContainerXmlns(withContainerXmlns(childOptions, child), sdtContent));
|
|
799
|
-
},
|
|
800
|
-
bookmarkStart: (child) => {
|
|
801
|
-
modelled.push(parseBookmarkStart(child));
|
|
802
|
-
},
|
|
803
|
-
bookmarkEnd: (child) => {
|
|
804
|
-
modelled.push(parseBookmarkEnd(child));
|
|
805
|
-
},
|
|
806
|
-
tcPr: CELL_PROPERTIES_OWNER,
|
|
807
|
-
sectPr: CAPTURE,
|
|
808
|
-
altChunk: CAPTURE,
|
|
809
|
-
commentRangeEnd: CAPTURE,
|
|
810
|
-
commentRangeStart: CAPTURE,
|
|
811
|
-
customXml: CAPTURE,
|
|
812
|
-
customXmlDelRangeEnd: CAPTURE,
|
|
813
|
-
customXmlDelRangeStart: CAPTURE,
|
|
814
|
-
customXmlInsRangeEnd: CAPTURE,
|
|
815
|
-
customXmlInsRangeStart: CAPTURE,
|
|
816
|
-
customXmlMoveFromRangeEnd: CAPTURE,
|
|
817
|
-
customXmlMoveFromRangeStart: CAPTURE,
|
|
818
|
-
customXmlMoveToRangeEnd: CAPTURE,
|
|
819
|
-
customXmlMoveToRangeStart: CAPTURE,
|
|
820
|
-
del: CAPTURE,
|
|
821
|
-
ins: CAPTURE,
|
|
822
|
-
moveFrom: CAPTURE,
|
|
823
|
-
moveFromRangeEnd: CAPTURE,
|
|
824
|
-
moveFromRangeStart: CAPTURE,
|
|
825
|
-
moveTo: CAPTURE,
|
|
826
|
-
moveToRangeEnd: CAPTURE,
|
|
827
|
-
moveToRangeStart: CAPTURE,
|
|
828
|
-
permEnd: CAPTURE,
|
|
829
|
-
permStart: CAPTURE,
|
|
830
|
-
proofErr: CAPTURE
|
|
769
|
+
const findLastFlowBlock = (blocks) => {
|
|
770
|
+
for (let index = blocks.length - 1; index >= 0; index -= 1) {
|
|
771
|
+
const block = blocks[index];
|
|
772
|
+
if (block?.type === "paragraph" || block?.type === "table") return block;
|
|
773
|
+
if (block?.type === "blockSdt") {
|
|
774
|
+
const nested = findLastFlowBlock(block.content);
|
|
775
|
+
if (nested) return nested;
|
|
831
776
|
}
|
|
832
|
-
}
|
|
833
|
-
if (preserved?.children) captured.push(...preserved.children);
|
|
777
|
+
}
|
|
834
778
|
};
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
779
|
+
const parseCellChildren = (element, childOptions, requireTrailingParagraph) => {
|
|
780
|
+
const modelled = [];
|
|
781
|
+
const captured = [];
|
|
782
|
+
const dispatchCellChildren = (childContainer, nestedOptions) => {
|
|
783
|
+
const preserved = dispatchChildren({
|
|
784
|
+
element: childContainer,
|
|
785
|
+
container: "block-content",
|
|
786
|
+
capturePosition: () => modelled.length,
|
|
787
|
+
undeclared: { AlternateContent: (child) => {
|
|
788
|
+
const selectedBranch = selectAlternateContentBranch(child);
|
|
789
|
+
if (!selectedBranch) return;
|
|
790
|
+
dispatchCellChildren(selectedBranch, withContainerXmlns(withContainerXmlns(nestedOptions, child), selectedBranch));
|
|
791
|
+
} },
|
|
792
|
+
handlers: {
|
|
793
|
+
p: (child) => {
|
|
794
|
+
const para = parseParagraph(child, styles, theme, numbering, rels, media, {
|
|
795
|
+
...nestedOptions,
|
|
796
|
+
runConsolidation: "deferred"
|
|
797
|
+
});
|
|
798
|
+
enrichParagraphTextBoxes(para, child, styles, theme, numbering, rels, media, parseTable);
|
|
799
|
+
modelled.push(para);
|
|
800
|
+
},
|
|
801
|
+
tbl: (child) => {
|
|
802
|
+
const table = parseTable(child, styles, theme, numbering, rels, media, nestedOptions);
|
|
803
|
+
if (!table) return;
|
|
804
|
+
modelled.push(table);
|
|
805
|
+
},
|
|
806
|
+
sdt: (child) => {
|
|
807
|
+
const properties = parseSdtProperties(findWordprocessingChild(child, "sdtPr"), findWordprocessingChild(child, "sdtEndPr"));
|
|
808
|
+
const siblings = captureSdtSiblingMarkers(child);
|
|
809
|
+
if (siblings.before.length > 0) properties.rawSdtChildrenBeforeContent = siblings.before;
|
|
810
|
+
if (siblings.after.length > 0) properties.rawSdtChildrenAfterContent = siblings.after;
|
|
811
|
+
const sdtContent = findWordprocessingChild(child, "sdtContent");
|
|
812
|
+
if (!sdtContent) return CAPTURE;
|
|
813
|
+
modelled.push({
|
|
814
|
+
type: "blockSdt",
|
|
815
|
+
properties,
|
|
816
|
+
content: parseCellChildren(sdtContent, withContainerXmlns(withContainerXmlns(nestedOptions, child), sdtContent), false)
|
|
817
|
+
});
|
|
818
|
+
},
|
|
819
|
+
bookmarkStart: (child) => {
|
|
820
|
+
modelled.push(parseBookmarkStart(child));
|
|
821
|
+
},
|
|
822
|
+
bookmarkEnd: (child) => {
|
|
823
|
+
modelled.push(parseBookmarkEnd(child));
|
|
824
|
+
},
|
|
825
|
+
tcPr: CELL_PROPERTIES_OWNER,
|
|
826
|
+
sectPr: CAPTURE,
|
|
827
|
+
altChunk: CAPTURE,
|
|
828
|
+
commentRangeEnd: CAPTURE,
|
|
829
|
+
commentRangeStart: CAPTURE,
|
|
830
|
+
customXml: CAPTURE,
|
|
831
|
+
customXmlDelRangeEnd: CAPTURE,
|
|
832
|
+
customXmlDelRangeStart: CAPTURE,
|
|
833
|
+
customXmlInsRangeEnd: CAPTURE,
|
|
834
|
+
customXmlInsRangeStart: CAPTURE,
|
|
835
|
+
customXmlMoveFromRangeEnd: CAPTURE,
|
|
836
|
+
customXmlMoveFromRangeStart: CAPTURE,
|
|
837
|
+
customXmlMoveToRangeEnd: CAPTURE,
|
|
838
|
+
customXmlMoveToRangeStart: CAPTURE,
|
|
839
|
+
del: CAPTURE,
|
|
840
|
+
ins: CAPTURE,
|
|
841
|
+
moveFrom: CAPTURE,
|
|
842
|
+
moveFromRangeEnd: CAPTURE,
|
|
843
|
+
moveFromRangeStart: CAPTURE,
|
|
844
|
+
moveTo: CAPTURE,
|
|
845
|
+
moveToRangeEnd: CAPTURE,
|
|
846
|
+
moveToRangeStart: CAPTURE,
|
|
847
|
+
permEnd: CAPTURE,
|
|
848
|
+
permStart: CAPTURE,
|
|
849
|
+
proofErr: CAPTURE
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
if (preserved?.children) captured.push(...preserved.children);
|
|
853
|
+
};
|
|
854
|
+
dispatchCellChildren(element, childOptions);
|
|
855
|
+
if (requireTrailingParagraph) {
|
|
856
|
+
if (findLastFlowBlock(modelled)?.type !== "paragraph") modelled.push({
|
|
857
|
+
type: "paragraph",
|
|
858
|
+
content: []
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
return withPreservedChildren(modelled, { children: captured }, (xml) => ({
|
|
862
|
+
type: "preservedBlock",
|
|
863
|
+
xml
|
|
864
|
+
}));
|
|
865
|
+
};
|
|
866
|
+
return parseCellChildren(tcElement, options, true);
|
|
844
867
|
}
|
|
845
868
|
/**
|
|
846
869
|
* Parse a table cell (w:tc)
|
|
@@ -993,12 +1016,15 @@ function parseTableRow(trElement, styles, theme, numbering, rels, media, options
|
|
|
993
1016
|
function parseTableGrid(tblGridElement) {
|
|
994
1017
|
if (!tblGridElement) return;
|
|
995
1018
|
const widths = [];
|
|
1019
|
+
let hasExplicitZero = false;
|
|
996
1020
|
const gridCols = findChildren(tblGridElement, "w", "gridCol");
|
|
997
1021
|
for (const col of gridCols) {
|
|
998
|
-
const
|
|
1022
|
+
const statedWidth = parseNumericAttribute(col, "w", "w");
|
|
1023
|
+
hasExplicitZero ||= statedWidth === 0;
|
|
1024
|
+
const width = statedWidth ?? 0;
|
|
999
1025
|
widths.push(width);
|
|
1000
1026
|
}
|
|
1001
|
-
if (widths.length > 0 && widths.every((width) => width <= 0)) return;
|
|
1027
|
+
if (widths.length > 0 && widths.every((width) => width <= 0) && !hasExplicitZero) return;
|
|
1002
1028
|
return widths.length > 0 ? widths : void 0;
|
|
1003
1029
|
}
|
|
1004
1030
|
/**
|
|
@@ -165,6 +165,10 @@ function renderTrackedWrapper(ctx, pkg, wrapper, paraId) {
|
|
|
165
165
|
case "complexField": return renderParagraphInline(ctx, pkg, [child], paraId);
|
|
166
166
|
case "bookmarkStart":
|
|
167
167
|
case "bookmarkEnd":
|
|
168
|
+
case "moveFromRangeStart":
|
|
169
|
+
case "moveFromRangeEnd":
|
|
170
|
+
case "moveToRangeStart":
|
|
171
|
+
case "moveToRangeEnd":
|
|
168
172
|
case "preservedInline": return "";
|
|
169
173
|
default: return child;
|
|
170
174
|
}
|
|
@@ -17,10 +17,11 @@ function needsHtmlFallback(rows) {
|
|
|
17
17
|
for (const row of rows) for (const cell of row.cells) {
|
|
18
18
|
if ((cell.formatting?.gridSpan ?? 1) > 1) return true;
|
|
19
19
|
if (cell.formatting?.vMerge) return true;
|
|
20
|
-
if (cell.content
|
|
20
|
+
if (containsTable(cell.content)) return true;
|
|
21
21
|
}
|
|
22
22
|
return false;
|
|
23
23
|
}
|
|
24
|
+
const containsTable = (blocks) => blocks.some((block) => block.type === "table" || block.type === "blockSdt" && containsTable(block.content));
|
|
24
25
|
function renderGfmTable(ctx, pkg, rows, firstRowIsHeader) {
|
|
25
26
|
const cellTexts = rows.map((row) => renderGfmRow(ctx, pkg, row));
|
|
26
27
|
let maxCols = 0;
|
|
@@ -55,10 +56,24 @@ function renderGfmRow(ctx, pkg, row) {
|
|
|
55
56
|
}
|
|
56
57
|
function renderGfmCell(ctx, pkg, cell) {
|
|
57
58
|
const blocks = [];
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
const renderBlocks = (content) => {
|
|
60
|
+
for (const item of content) switch (item.type) {
|
|
61
|
+
case "paragraph": {
|
|
62
|
+
const md = renderParagraph(ctx, pkg, item);
|
|
63
|
+
if (md.trim()) blocks.push(md);
|
|
64
|
+
break;
|
|
65
|
+
}
|
|
66
|
+
case "blockSdt":
|
|
67
|
+
renderBlocks(item.content);
|
|
68
|
+
break;
|
|
69
|
+
case "table":
|
|
70
|
+
case "preservedBlock":
|
|
71
|
+
case "bookmarkStart":
|
|
72
|
+
case "bookmarkEnd": break;
|
|
73
|
+
default: return item;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
renderBlocks(cell.content);
|
|
62
77
|
return escapeTableCell(blocks.join("\n"));
|
|
63
78
|
}
|
|
64
79
|
function renderHtmlTable(ctx, pkg, rows, firstRowIsHeader) {
|
|
@@ -111,13 +126,28 @@ function cellAtGridColumn(row, gridCol) {
|
|
|
111
126
|
}
|
|
112
127
|
function renderHtmlCell(ctx, pkg, cell) {
|
|
113
128
|
const parts = [];
|
|
114
|
-
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
129
|
+
const renderBlocks = (content) => {
|
|
130
|
+
for (const item of content) switch (item.type) {
|
|
131
|
+
case "paragraph": {
|
|
132
|
+
const inner = renderHtmlInline(ctx, pkg, item.content, item.paraId);
|
|
133
|
+
if (inner) parts.push(inner);
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
case "table": {
|
|
137
|
+
const nested = renderHtmlTable(ctx, pkg, item.rows, true);
|
|
138
|
+
if (nested) parts.push(nested);
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
case "blockSdt":
|
|
142
|
+
renderBlocks(item.content);
|
|
143
|
+
break;
|
|
144
|
+
case "preservedBlock":
|
|
145
|
+
case "bookmarkStart":
|
|
146
|
+
case "bookmarkEnd": break;
|
|
147
|
+
default: return item;
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
renderBlocks(cell.content);
|
|
121
151
|
return parts.join("<br>");
|
|
122
152
|
}
|
|
123
153
|
function escapeHtml(text) {
|
|
@@ -175,6 +205,10 @@ function renderHtmlChildren(ctx, pkg, children, paraId) {
|
|
|
175
205
|
case "moveTo": return renderHtmlInline(ctx, pkg, [child], paraId);
|
|
176
206
|
case "bookmarkStart":
|
|
177
207
|
case "bookmarkEnd":
|
|
208
|
+
case "moveFromRangeStart":
|
|
209
|
+
case "moveFromRangeEnd":
|
|
210
|
+
case "moveToRangeStart":
|
|
211
|
+
case "moveToRangeEnd":
|
|
178
212
|
case "preservedInline": return "";
|
|
179
213
|
default: return child;
|
|
180
214
|
}
|