@cj-tech-master/excelts 9.5.0 → 9.5.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/browser/modules/pdf/excel-bridge.js +27 -1
- package/dist/browser/modules/pdf/render/layout-engine.js +74 -9
- package/dist/browser/modules/pdf/render/style-converter.d.ts +1 -1
- package/dist/browser/modules/pdf/render/style-converter.js +98 -1
- package/dist/browser/modules/pdf/types.d.ts +1 -0
- package/dist/browser/modules/word/color-utils.d.ts +18 -0
- package/dist/browser/modules/word/color-utils.js +94 -0
- package/dist/browser/modules/word/content-types.d.ts +15 -15
- package/dist/browser/modules/word/content-types.js +39 -43
- package/dist/browser/modules/word/crypto.d.ts +17 -0
- package/dist/browser/modules/word/crypto.js +18 -0
- package/dist/browser/modules/word/document-io.d.ts +58 -0
- package/dist/browser/modules/word/document-io.js +239 -0
- package/dist/browser/modules/word/document.d.ts +64 -135
- package/dist/browser/modules/word/document.js +207 -469
- package/dist/browser/modules/word/docx-packager.js +90 -90
- package/dist/browser/modules/word/html-renderer.js +1 -1
- package/dist/browser/modules/word/html.d.ts +13 -0
- package/dist/browser/modules/word/html.js +12 -0
- package/dist/browser/modules/word/index.base.d.ts +6 -9
- package/dist/browser/modules/word/index.base.js +7 -10
- package/dist/browser/modules/word/namespaces.d.ts +159 -0
- package/dist/browser/modules/word/namespaces.js +189 -0
- package/dist/browser/modules/word/relationships.d.ts +15 -16
- package/dist/browser/modules/word/relationships.js +37 -45
- package/dist/cjs/modules/pdf/excel-bridge.js +27 -1
- package/dist/cjs/modules/pdf/render/layout-engine.js +74 -9
- package/dist/cjs/modules/pdf/render/style-converter.js +98 -1
- package/dist/cjs/modules/word/color-utils.js +97 -0
- package/dist/cjs/modules/word/content-types.js +44 -45
- package/dist/cjs/modules/word/crypto.js +34 -0
- package/dist/cjs/modules/word/document-io.js +244 -0
- package/dist/cjs/modules/word/document.js +209 -473
- package/dist/cjs/modules/word/docx-packager.js +88 -88
- package/dist/cjs/modules/word/html-renderer.js +2 -2
- package/dist/cjs/modules/word/html.js +16 -0
- package/dist/cjs/modules/word/index.base.js +17 -27
- package/dist/cjs/modules/word/namespaces.js +192 -0
- package/dist/cjs/modules/word/relationships.js +42 -47
- package/dist/esm/modules/pdf/excel-bridge.js +27 -1
- package/dist/esm/modules/pdf/render/layout-engine.js +74 -9
- package/dist/esm/modules/pdf/render/style-converter.js +98 -1
- package/dist/esm/modules/word/color-utils.js +94 -0
- package/dist/esm/modules/word/content-types.js +39 -43
- package/dist/esm/modules/word/crypto.js +18 -0
- package/dist/esm/modules/word/document-io.js +239 -0
- package/dist/esm/modules/word/document.js +207 -469
- package/dist/esm/modules/word/docx-packager.js +90 -90
- package/dist/esm/modules/word/html-renderer.js +1 -1
- package/dist/esm/modules/word/html.js +12 -0
- package/dist/esm/modules/word/index.base.js +7 -10
- package/dist/esm/modules/word/namespaces.js +189 -0
- package/dist/esm/modules/word/relationships.js +37 -45
- package/dist/iife/excelts.iife.js +153 -11
- package/dist/iife/excelts.iife.js.map +1 -1
- package/dist/iife/excelts.iife.min.js +4 -4
- package/dist/types/modules/pdf/render/style-converter.d.ts +1 -1
- package/dist/types/modules/pdf/types.d.ts +1 -0
- package/dist/types/modules/word/color-utils.d.ts +18 -0
- package/dist/types/modules/word/content-types.d.ts +15 -15
- package/dist/types/modules/word/crypto.d.ts +17 -0
- package/dist/types/modules/word/document-io.d.ts +58 -0
- package/dist/types/modules/word/document.d.ts +64 -135
- package/dist/types/modules/word/html.d.ts +13 -0
- package/dist/types/modules/word/index.base.d.ts +6 -9
- package/dist/types/modules/word/namespaces.d.ts +159 -0
- package/dist/types/modules/word/relationships.d.ts +15 -16
- package/package.json +1 -1
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOCX Module - Theme Color Utilities
|
|
3
|
+
*
|
|
4
|
+
* Resolves OOXML theme colors with tint/shade transformations.
|
|
5
|
+
* Extracted to a standalone file so that html-renderer and document.ts
|
|
6
|
+
* can both import it without creating circular heavy dependencies.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Map OOXML theme color attribute names to theme color scheme keys.
|
|
10
|
+
* Word uses different names in run/paragraph properties vs the theme XML.
|
|
11
|
+
*/
|
|
12
|
+
const THEME_COLOR_MAP = {
|
|
13
|
+
dark1: "dk1",
|
|
14
|
+
light1: "lt1",
|
|
15
|
+
dark2: "dk2",
|
|
16
|
+
light2: "lt2",
|
|
17
|
+
accent1: "accent1",
|
|
18
|
+
accent2: "accent2",
|
|
19
|
+
accent3: "accent3",
|
|
20
|
+
accent4: "accent4",
|
|
21
|
+
accent5: "accent5",
|
|
22
|
+
accent6: "accent6",
|
|
23
|
+
hyperlink: "hlink",
|
|
24
|
+
followedHyperlink: "folHlink",
|
|
25
|
+
// Direct names also work
|
|
26
|
+
dk1: "dk1",
|
|
27
|
+
lt1: "lt1",
|
|
28
|
+
dk2: "dk2",
|
|
29
|
+
lt2: "lt2",
|
|
30
|
+
hlink: "hlink",
|
|
31
|
+
folHlink: "folHlink"
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Resolve a ColorSpec to an actual hex RGB color using the document theme.
|
|
35
|
+
*
|
|
36
|
+
* Applies theme color lookup + tint/shade transformations per OOXML spec.
|
|
37
|
+
*
|
|
38
|
+
* @param color - The color value (HexColor string or ColorSpec).
|
|
39
|
+
* @param theme - The document theme (from `doc.theme`).
|
|
40
|
+
* @returns Resolved hex color string (6 chars, no #), or undefined if unresolvable.
|
|
41
|
+
*/
|
|
42
|
+
export function resolveThemeColor(color, theme) {
|
|
43
|
+
if (color === undefined) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
if (typeof color === "string") {
|
|
47
|
+
return color;
|
|
48
|
+
}
|
|
49
|
+
// ColorSpec with val — use directly
|
|
50
|
+
if (color.val && color.val !== "auto") {
|
|
51
|
+
return color.val;
|
|
52
|
+
}
|
|
53
|
+
// Resolve via theme
|
|
54
|
+
if (!color.themeColor || !theme) {
|
|
55
|
+
return color.val;
|
|
56
|
+
}
|
|
57
|
+
const key = THEME_COLOR_MAP[color.themeColor] ?? color.themeColor;
|
|
58
|
+
const base = theme.colorScheme.colors[key];
|
|
59
|
+
if (!base) {
|
|
60
|
+
return color.val;
|
|
61
|
+
}
|
|
62
|
+
// Apply tint or shade
|
|
63
|
+
if (color.themeTint) {
|
|
64
|
+
return applyTint(base, parseInt(color.themeTint, 16) / 255);
|
|
65
|
+
}
|
|
66
|
+
if (color.themeShade) {
|
|
67
|
+
return applyShade(base, parseInt(color.themeShade, 16) / 255);
|
|
68
|
+
}
|
|
69
|
+
return base;
|
|
70
|
+
}
|
|
71
|
+
/** Apply tint to a hex color. tint=1 → white, tint=0 → original. */
|
|
72
|
+
function applyTint(hex, tint) {
|
|
73
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
74
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
75
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
76
|
+
const nr = Math.round(r + (255 - r) * tint);
|
|
77
|
+
const ng = Math.round(g + (255 - g) * tint);
|
|
78
|
+
const nb = Math.round(b + (255 - b) * tint);
|
|
79
|
+
return toHex2(nr) + toHex2(ng) + toHex2(nb);
|
|
80
|
+
}
|
|
81
|
+
/** Apply shade to a hex color. shade=1 → original, shade=0 → black. */
|
|
82
|
+
function applyShade(hex, shade) {
|
|
83
|
+
const r = parseInt(hex.slice(0, 2), 16);
|
|
84
|
+
const g = parseInt(hex.slice(2, 4), 16);
|
|
85
|
+
const b = parseInt(hex.slice(4, 6), 16);
|
|
86
|
+
const nr = Math.round(r * shade);
|
|
87
|
+
const ng = Math.round(g * shade);
|
|
88
|
+
const nb = Math.round(b * shade);
|
|
89
|
+
return toHex2(nr) + toHex2(ng) + toHex2(nb);
|
|
90
|
+
}
|
|
91
|
+
function toHex2(n) {
|
|
92
|
+
const h = Math.max(0, Math.min(255, n)).toString(16);
|
|
93
|
+
return h.length < 2 ? "0" + h : h;
|
|
94
|
+
}
|
|
@@ -2,52 +2,48 @@
|
|
|
2
2
|
* DOCX Module - Content Types Generator
|
|
3
3
|
*
|
|
4
4
|
* Generates [Content_Types].xml for the DOCX package.
|
|
5
|
+
* Uses a plain data record + free functions for tree-shakeability.
|
|
5
6
|
*/
|
|
6
7
|
import { NS_CONTENT_TYPES, STD_DOC_ATTRIBUTES, ContentType, IMAGE_CONTENT_TYPES } from "./constants.js";
|
|
7
|
-
/**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
for (const ext of extensions) {
|
|
32
|
-
const ct = IMAGE_CONTENT_TYPES[ext.toLowerCase()];
|
|
33
|
-
if (ct) {
|
|
34
|
-
this._defaults.set(ext.toLowerCase(), ct);
|
|
35
|
-
}
|
|
8
|
+
/** Create a new ContentTypesState with standard defaults (rels, xml). */
|
|
9
|
+
export function createContentTypes() {
|
|
10
|
+
const defaults = new Map();
|
|
11
|
+
defaults.set("rels", ContentType.Relationships);
|
|
12
|
+
defaults.set("xml", ContentType.Xml);
|
|
13
|
+
return { defaults, overrides: [] };
|
|
14
|
+
}
|
|
15
|
+
/** Add a default content type for a file extension. */
|
|
16
|
+
export function addContentTypeDefault(state, extension, contentType) {
|
|
17
|
+
state.defaults.set(extension, contentType);
|
|
18
|
+
}
|
|
19
|
+
/** Add an override content type for a specific part. */
|
|
20
|
+
export function addContentTypeOverride(state, partName, contentType) {
|
|
21
|
+
state.overrides.push({
|
|
22
|
+
partName: partName.startsWith("/") ? partName : `/${partName}`,
|
|
23
|
+
contentType
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
/** Add image extension defaults from a set of used extensions. */
|
|
27
|
+
export function addImageContentTypeDefaults(state, extensions) {
|
|
28
|
+
for (const ext of extensions) {
|
|
29
|
+
const ct = IMAGE_CONTENT_TYPES[ext.toLowerCase()];
|
|
30
|
+
if (ct) {
|
|
31
|
+
state.defaults.set(ext.toLowerCase(), ct);
|
|
36
32
|
}
|
|
37
33
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
xml.closeNode();
|
|
34
|
+
}
|
|
35
|
+
/** Render the [Content_Types].xml to a sink. */
|
|
36
|
+
export function renderContentTypes(state, xml) {
|
|
37
|
+
xml.openXml(STD_DOC_ATTRIBUTES);
|
|
38
|
+
xml.openNode("Types", { xmlns: NS_CONTENT_TYPES });
|
|
39
|
+
// Defaults sorted by extension
|
|
40
|
+
const sortedDefaults = [...state.defaults.entries()].sort((a, b) => a[0].localeCompare(b[0]));
|
|
41
|
+
for (const [ext, ct] of sortedDefaults) {
|
|
42
|
+
xml.leafNode("Default", { Extension: ext, ContentType: ct });
|
|
43
|
+
}
|
|
44
|
+
// Overrides in order
|
|
45
|
+
for (const override of state.overrides) {
|
|
46
|
+
xml.leafNode("Override", { PartName: override.partName, ContentType: override.contentType });
|
|
52
47
|
}
|
|
48
|
+
xml.closeNode();
|
|
53
49
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOCX Module - Encryption & Digital Signatures (Subpath Export)
|
|
3
|
+
*
|
|
4
|
+
* Import separately to avoid pulling crypto code into the bundle
|
|
5
|
+
* when only core document building is needed.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { isEncryptedDocx, decryptPackage } from "excelts/word/crypto";
|
|
10
|
+
* import { extractSignatures } from "excelts/word/crypto";
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
// Encryption utilities
|
|
14
|
+
export { isEncryptedDocx, verifyPassword, decryptPackage, parseEncryptionInfoXml, deriveEncryptionKey, AGILE_BLOCK_KEYS } from "./encryption.js";
|
|
15
|
+
// Digital signature utilities
|
|
16
|
+
export { hasDigitalSignatures, parseSignatureXml, extractSignatures, isWellFormedSignature } from "./digital-signatures.js";
|
|
17
|
+
// Font obfuscation utilities
|
|
18
|
+
export { deobfuscateFont, obfuscateFont, generateFontKey } from "./font-obfuscation.js";
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DOCX Module - Document IO
|
|
3
|
+
*
|
|
4
|
+
* IO operations that depend on docx-packager and docx-reader.
|
|
5
|
+
* Separated from document.ts so that builder helpers can be imported
|
|
6
|
+
* without pulling in archive/xml/writer code.
|
|
7
|
+
*/
|
|
8
|
+
import { packageDocx } from "./docx-packager.js";
|
|
9
|
+
import { readDocx } from "./docx-reader.js";
|
|
10
|
+
import { bytesToBase64 } from "./internal-utils.js";
|
|
11
|
+
// =============================================================================
|
|
12
|
+
// Document IO (toBuffer / toBase64)
|
|
13
|
+
// =============================================================================
|
|
14
|
+
/** Package a DocxDocument model to DOCX bytes. */
|
|
15
|
+
export async function toBuffer(doc, compressionLevel) {
|
|
16
|
+
return packageDocx(doc, compressionLevel);
|
|
17
|
+
}
|
|
18
|
+
/** Package a DocxDocument model to base64 string. */
|
|
19
|
+
export async function toBase64(doc, compressionLevel) {
|
|
20
|
+
const bytes = await toBuffer(doc, compressionLevel);
|
|
21
|
+
return bytesToBase64(bytes);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Read an existing DOCX file, replace placeholders with content, and produce a new DOCX.
|
|
25
|
+
*
|
|
26
|
+
* Placeholders are strings like `{{name}}` embedded in the document text.
|
|
27
|
+
* They may span across multiple runs — the patcher handles cross-run matching.
|
|
28
|
+
*
|
|
29
|
+
* Supported patch content types:
|
|
30
|
+
* - `text` — simple text replacement (preserves formatting of the first run)
|
|
31
|
+
* - `paragraph` — replaces the entire paragraph containing the placeholder
|
|
32
|
+
* - `table` — replaces the entire paragraph with a table
|
|
33
|
+
* - `image` — replaces the placeholder with an inline image
|
|
34
|
+
*
|
|
35
|
+
* @param buffer - The source DOCX file as a Uint8Array.
|
|
36
|
+
* @param patches - Array of patch operations to apply.
|
|
37
|
+
* @param options - Optional compression settings.
|
|
38
|
+
* @returns New DOCX file as a Uint8Array.
|
|
39
|
+
*/
|
|
40
|
+
export async function patchDocument(buffer, patches, options) {
|
|
41
|
+
const doc = await readDocx(buffer);
|
|
42
|
+
// Build lookup map for quick placeholder matching
|
|
43
|
+
const patchMap = new Map();
|
|
44
|
+
for (const patch of patches) {
|
|
45
|
+
patchMap.set(patch.placeholder, patch);
|
|
46
|
+
}
|
|
47
|
+
// Process body content
|
|
48
|
+
const newBody = [];
|
|
49
|
+
for (const block of doc.body) {
|
|
50
|
+
if (block.type === "paragraph") {
|
|
51
|
+
const result = patchParagraph(block, patchMap);
|
|
52
|
+
if (result) {
|
|
53
|
+
if (Array.isArray(result)) {
|
|
54
|
+
newBody.push(...result);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
newBody.push(result);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else if (block.type === "table") {
|
|
62
|
+
patchTable(block, patchMap);
|
|
63
|
+
newBody.push(block);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
newBody.push(block);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Patch headers
|
|
70
|
+
if (doc.headers) {
|
|
71
|
+
for (const [, headerDef] of doc.headers) {
|
|
72
|
+
patchHeaderFooterContent(headerDef.content, patchMap);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Patch footers
|
|
76
|
+
if (doc.footers) {
|
|
77
|
+
for (const [, footerDef] of doc.footers) {
|
|
78
|
+
patchHeaderFooterContent(footerDef.content, patchMap);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// Add any new images from patches
|
|
82
|
+
const images = doc.images ? [...doc.images] : [];
|
|
83
|
+
for (const patch of patches) {
|
|
84
|
+
if (patch.content.type === "image") {
|
|
85
|
+
const imgContent = patch.content;
|
|
86
|
+
const existing = images.find(i => i.fileName === imgContent.image.fileName);
|
|
87
|
+
if (!existing) {
|
|
88
|
+
images.push(imgContent.image);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const patched = {
|
|
93
|
+
...doc,
|
|
94
|
+
body: newBody,
|
|
95
|
+
images: images.length > 0 ? images : undefined
|
|
96
|
+
};
|
|
97
|
+
return packageDocx(patched, options?.compressionLevel);
|
|
98
|
+
}
|
|
99
|
+
// =============================================================================
|
|
100
|
+
// Internal helpers
|
|
101
|
+
// =============================================================================
|
|
102
|
+
/** Extract concatenated plain text from a paragraph's runs. */
|
|
103
|
+
function paragraphText(para) {
|
|
104
|
+
let t = "";
|
|
105
|
+
for (const child of para.children) {
|
|
106
|
+
if ("content" in child && Array.isArray(child.content)) {
|
|
107
|
+
for (const c of child.content) {
|
|
108
|
+
if ("type" in c && c.type === "text" && "text" in c) {
|
|
109
|
+
t += c.text;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return t;
|
|
115
|
+
}
|
|
116
|
+
/** Replace text within a single paragraph. */
|
|
117
|
+
function replaceInParagraph(para, search, replacement) {
|
|
118
|
+
for (const child of para.children) {
|
|
119
|
+
if (!("content" in child) || !Array.isArray(child.content)) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
for (const c of child.content) {
|
|
123
|
+
if (!("type" in c) || c.type !== "text" || !("text" in c)) {
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
const before = c.text;
|
|
127
|
+
if (before.includes(search)) {
|
|
128
|
+
c.text = before.replaceAll(search, replacement);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// Cross-run replacement fallback
|
|
133
|
+
const fullText = paragraphText(para);
|
|
134
|
+
if (fullText.includes(search)) {
|
|
135
|
+
const newText = fullText.replaceAll(search, replacement);
|
|
136
|
+
let placed = false;
|
|
137
|
+
for (const child of para.children) {
|
|
138
|
+
if (!("content" in child) || !Array.isArray(child.content)) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
for (const c of child.content) {
|
|
142
|
+
if (!("type" in c) || c.type !== "text" || !("text" in c)) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
if (!placed) {
|
|
146
|
+
c.text = newText;
|
|
147
|
+
placed = true;
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
c.text = "";
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/** Patch a paragraph — returns replacement content or null to remove. */
|
|
157
|
+
function patchParagraph(para, patchMap) {
|
|
158
|
+
const text = paragraphText(para);
|
|
159
|
+
for (const [placeholder, patch] of patchMap) {
|
|
160
|
+
if (!text.includes(placeholder)) {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
switch (patch.content.type) {
|
|
164
|
+
case "text": {
|
|
165
|
+
replaceInParagraph(para, placeholder, patch.content.text);
|
|
166
|
+
return para;
|
|
167
|
+
}
|
|
168
|
+
case "paragraph": {
|
|
169
|
+
return patch.content.children;
|
|
170
|
+
}
|
|
171
|
+
case "table": {
|
|
172
|
+
return patch.content.table;
|
|
173
|
+
}
|
|
174
|
+
case "image": {
|
|
175
|
+
const img = patch.content.image;
|
|
176
|
+
const rId = img.rId ?? `rId_img_${img.fileName}`;
|
|
177
|
+
const imgContent = {
|
|
178
|
+
type: "image",
|
|
179
|
+
rId,
|
|
180
|
+
width: patch.content.width,
|
|
181
|
+
height: patch.content.height,
|
|
182
|
+
altText: img.fileName,
|
|
183
|
+
name: img.fileName
|
|
184
|
+
};
|
|
185
|
+
const newPara = {
|
|
186
|
+
type: "paragraph",
|
|
187
|
+
properties: para.properties,
|
|
188
|
+
children: [{ content: [imgContent] }]
|
|
189
|
+
};
|
|
190
|
+
return newPara;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return para;
|
|
195
|
+
}
|
|
196
|
+
/** Patch text inside table cells recursively. */
|
|
197
|
+
function patchTable(table, patchMap) {
|
|
198
|
+
for (const row of table.rows) {
|
|
199
|
+
for (const cell of row.cells) {
|
|
200
|
+
const newContent = [];
|
|
201
|
+
for (const block of cell.content) {
|
|
202
|
+
if (block.type === "paragraph") {
|
|
203
|
+
const result = patchParagraph(block, patchMap);
|
|
204
|
+
if (result) {
|
|
205
|
+
if (Array.isArray(result)) {
|
|
206
|
+
newContent.push(...result);
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
newContent.push(result);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
else if (block.type === "table") {
|
|
214
|
+
patchTable(block, patchMap);
|
|
215
|
+
newContent.push(block);
|
|
216
|
+
}
|
|
217
|
+
else {
|
|
218
|
+
newContent.push(block);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
cell.content = newContent;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/** Patch text in header/footer content. */
|
|
226
|
+
function patchHeaderFooterContent(content, patchMap) {
|
|
227
|
+
for (const child of content.children) {
|
|
228
|
+
if (child.type === "paragraph") {
|
|
229
|
+
for (const [placeholder, patch] of patchMap) {
|
|
230
|
+
if (patch.content.type === "text") {
|
|
231
|
+
const text = paragraphText(child);
|
|
232
|
+
if (text.includes(placeholder)) {
|
|
233
|
+
replaceInParagraph(child, placeholder, patch.content.text);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|