@office-open/docx 0.6.4 → 0.6.5

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/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { i as __toCommonJS, n as __exportAll, r as __reExport, t as __esmMin } from "./chunk-090QGkrx.mjs";
2
- import { AppProperties, BaseXmlComponent, BuilderElement, BuilderElement as BuilderElement$1, EMPTY_OBJECT, EmptyElement, Formatter, IgnoreIfEmptyXmlComponent, ImportedRootElementAttributes, ImportedXmlComponent, InitializableXmlComponent, NextAttributeComponent, OoxmlMimeType, PrettifyType, RawPassthrough, Relationships, TargetModeType, XmlAttributeComponent, XmlAttributeComponent as XmlAttributeComponent$1, XmlComponent, ZIP_STORED_LEVEL, addSmartArtRelationships, attrObj, chartAttr, convertEmuToPixels, convertInchesToTwip, convertMillimetersToTwip, convertPixelsToEmu, convertToXmlComponent, createDefault, createOverride, createPacker, getReferencedMedia, hasPlaceholders, hashedId, hpsMeasureObj, hpsMeasureObj as hpsMeasureObj$1, numberValObj, numberValObj as numberValObj$1, onOffObj, onOffObj as onOffObj$1, parseArchive, parseCorePropsElement, replaceChartPlaceholders, replaceImagePlaceholders, replaceSmartArtPlaceholders, strFromU8, stringContainerObj, stringEnumValObj, stringEnumValObj as stringEnumValObj$1, stringValObj, stringValObj as stringValObj$1, uniqueId, uniqueNumericIdCreator, uniqueNumericIdCreator as uniqueNumericIdCreator$1, uniqueUuid, unzipSync, wrapEl, xsdVerticalMergeRev, zipAndConvert } from "@office-open/core";
2
+ import { AppProperties, BaseXmlComponent, BuilderElement, BuilderElement as BuilderElement$1, DOCX_NS, EMPTY_OBJECT, EmptyElement, Formatter, Formatter as Formatter$1, IgnoreIfEmptyXmlComponent, ImportedRootElementAttributes, ImportedXmlComponent, InitializableXmlComponent, NextAttributeComponent, OoxmlMimeType, PrettifyType, RawPassthrough, Relationships, TargetModeType, XmlAttributeComponent, XmlAttributeComponent as XmlAttributeComponent$1, XmlComponent, ZIP_STORED_LEVEL, addSmartArtRelationships, appendContentType, appendRelationship, attrObj, chartAttr, convertEmuToPixels, convertInchesToTwip, convertMillimetersToTwip, convertPixelsToEmu, convertToXmlComponent, createDefault, createOverride, createPacker, createReplacer, createTraverser, getNextRelationshipIndex, getReferencedMedia, hasPlaceholders, hashedId, hpsMeasureObj, hpsMeasureObj as hpsMeasureObj$1, numberValObj, numberValObj as numberValObj$1, onOffObj, onOffObj as onOffObj$1, parseArchive, parseCorePropsElement, replaceChartPlaceholders, replaceImagePlaceholders, replaceSmartArtPlaceholders, strFromU8, stringContainerObj, stringEnumValObj, stringEnumValObj as stringEnumValObj$1, stringValObj, stringValObj as stringValObj$1, toJson, uniqueId, uniqueNumericIdCreator, uniqueNumericIdCreator as uniqueNumericIdCreator$1, uniqueUuid, unzipSync, wrapEl, xsdVerticalMergeRev, zipAndConvert } from "@office-open/core";
3
3
  import { textToUint8Array, toUint8Array } from "undio";
4
4
  import { PresetGeometry, buildFill, createBlipFill, createCustomGeometry, createEffectDag, createEffectList, createOutline, createScene3D, createShape3D, createTransform2D, extractBlipFillMedia } from "@office-open/core/drawingml";
5
5
  import { ChartCollection, ChartSpace } from "@office-open/core/chart";
@@ -14559,7 +14559,7 @@ function replaceNumberingPlaceholders(xml, concreteNumberings) {
14559
14559
  var Compiler = class {
14560
14560
  formatter;
14561
14561
  constructor() {
14562
- this.formatter = new Formatter();
14562
+ this.formatter = new Formatter$1();
14563
14563
  }
14564
14564
  /**
14565
14565
  * Compiles a File object into a flat file map suitable for fflate zipSync.
@@ -15097,581 +15097,30 @@ init_convenience_functions();
15097
15097
  init_values();
15098
15098
  __reExport(util_exports, values_exports);
15099
15099
  //#endregion
15100
- //#region src/patcher/util.ts
15101
- /**
15102
- * Utility functions for XML manipulation in document patching.
15103
- *
15104
- * @module
15105
- */
15106
- init_text();
15107
- /**
15108
- * Converts XML string to JSON element structure.
15109
- *
15110
- * Parses XML text into a JavaScript object representation that can be
15111
- * manipulated programmatically. Preserves spaces between elements for
15112
- * accurate text handling.
15113
- *
15114
- * @param xmlData - The XML string to parse
15115
- * @returns Parsed XML as an Element object
15116
- *
15117
- * @example
15118
- * ```typescript
15119
- * const element = toJson('<w:p><w:r><w:t>Hello</w:t></w:r></w:p>');
15120
- * ```
15121
- */
15122
- const toJson = (xmlData) => {
15123
- return xml2js(xmlData, {
15124
- captureSpacesBetweenElements: true,
15125
- compact: false
15126
- });
15127
- };
15128
- /**
15129
- * Creates text element contents from a text string.
15130
- *
15131
- * Generates the XML element structure for a text node (w:t) by formatting
15132
- * a Text component and extracting its element contents. Used when creating
15133
- * new text runs during replacement operations.
15134
- *
15135
- * @param text - The text content to wrap in element structure
15136
- * @returns Array of XML elements representing the text
15137
- *
15138
- * @example
15139
- * ```typescript
15140
- * const elements = createTextElementContents("Hello World");
15141
- * // Returns XML elements for <w:t>Hello World</w:t>
15142
- * ```
15143
- */
15144
- const createTextElementContents = (text) => {
15145
- return toJson(xml(buildText({ text }))).elements[0].elements ?? [];
15146
- };
15147
- /**
15148
- * Adds xml:space="preserve" attribute to an element.
15149
- *
15150
- * The xml:space attribute instructs XML processors to preserve whitespace
15151
- * in the element's content. This is important when text contains leading
15152
- * or trailing spaces that must be maintained.
15153
- *
15154
- * @param element - The element to patch
15155
- * @returns New element with xml:space attribute added
15156
- *
15157
- * @example
15158
- * ```typescript
15159
- * const patched = patchSpaceAttribute(textElement);
15160
- * // Adds xml:space="preserve" to maintain whitespace
15161
- * ```
15162
- */
15163
- const patchSpaceAttribute = (element) => ({
15164
- ...element,
15165
- attributes: { "xml:space": "preserve" }
15166
- });
15167
- /**
15168
- * Retrieves first-level child elements by parent element name.
15169
- *
15170
- * Finds the first element with the specified name and returns its children.
15171
- * Used to access collections like relationship elements or content type definitions.
15172
- *
15173
- * @param relationships - The parent XML element to search
15174
- * @param id - The element name to find
15175
- * @returns Array of child elements
15176
- *
15177
- * @example
15178
- * ```typescript
15179
- * const rels = getFirstLevelElements(relationshipsXml, "Relationships");
15180
- * // Returns array of Relationship elements
15181
- * ```
15182
- */
15183
- const getFirstLevelElements = (relationships, id) => relationships.elements?.filter((e) => e.name === id)[0].elements ?? [];
15184
- //#endregion
15185
- //#region src/patcher/content-types-manager.ts
15186
- /**
15187
- * Appends a content type definition to the [Content_Types].xml structure.
15188
- *
15189
- * The [Content_Types].xml file declares the MIME types for all file extensions
15190
- * in the OOXML package. This function adds a new content type if it doesn't
15191
- * already exist, ensuring that newly added media files are properly declared.
15192
- *
15193
- * @param element - The [Content_Types].xml root element
15194
- * @param contentType - The MIME type (e.g., "image/png")
15195
- * @param extension - The file extension (e.g., "png")
15196
- *
15197
- * @example
15198
- * ```typescript
15199
- * appendContentType(contentTypesElement, "image/png", "png");
15200
- * appendContentType(contentTypesElement, "image/jpeg", "jpg");
15201
- * ```
15202
- */
15203
- const appendContentType = (element, contentType, extension) => {
15204
- const relationshipElements = getFirstLevelElements(element, "Types");
15205
- if (relationshipElements.some((el) => el.type === "element" && el.name === "Default" && el?.attributes?.ContentType === contentType && el?.attributes?.Extension === extension)) return;
15206
- relationshipElements.push({
15207
- attributes: {
15208
- ContentType: contentType,
15209
- Extension: extension
15210
- },
15211
- name: "Default",
15212
- type: "element"
15213
- });
15214
- };
15215
- //#endregion
15216
- //#region src/patcher/relationship-manager.ts
15217
- /**
15218
- * Extracts the numeric ID from a relationship ID string.
15219
- *
15220
- * @param relationshipId - Relationship ID in format "rId123"
15221
- * @returns The numeric portion of the ID
15222
- */
15223
- const getIdFromRelationshipId = (relationshipId) => {
15224
- const output = parseInt(relationshipId.substring(3), 10);
15225
- return isNaN(output) ? 0 : output;
15226
- };
15227
- /**
15228
- * Determines the next available relationship ID number.
15229
- *
15230
- * Scans all existing relationships and returns the next sequential ID number
15231
- * to use when adding a new relationship.
15232
- *
15233
- * @param relationships - The relationships XML element
15234
- * @returns The next available relationship ID number
15235
- *
15236
- * @example
15237
- * ```typescript
15238
- * const nextId = getNextRelationshipIndex(relationshipsElement);
15239
- * // If highest existing ID is rId5, returns 6
15240
- * ```
15241
- */
15242
- const getNextRelationshipIndex = (relationships) => {
15243
- return getFirstLevelElements(relationships, "Relationships").map((e) => getIdFromRelationshipId(e.attributes?.Id?.toString() ?? "")).reduce((acc, curr) => Math.max(acc, curr), 0) + 1;
15244
- };
15245
- /**
15246
- * Appends a new relationship to a .rels file structure.
15247
- *
15248
- * Relationships define connections between parts of an OOXML package,
15249
- * such as linking documents to images, hyperlinks, or other resources.
15250
- *
15251
- * @param relationships - The relationships XML element
15252
- * @param id - The relationship ID (number or string)
15253
- * @param type - The relationship type URI
15254
- * @param target - The target path or URI
15255
- * @param targetMode - Optional target mode (Internal or External)
15256
- * @returns The updated relationship elements array
15257
- *
15258
- * @example
15259
- * ```typescript
15260
- * appendRelationship(
15261
- * relationshipsElement,
15262
- * 6,
15263
- * "http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
15264
- * "media/image1.png"
15265
- * );
15266
- * ```
15267
- */
15268
- const appendRelationship = (relationships, id, type, target, targetMode) => {
15269
- const relationshipElements = getFirstLevelElements(relationships, "Relationships");
15270
- relationshipElements.push({
15271
- attributes: {
15272
- Id: `rId${id}`,
15273
- Target: target,
15274
- TargetMode: targetMode,
15275
- Type: type
15276
- },
15277
- name: "Relationship",
15278
- type: "element"
15279
- });
15280
- return relationshipElements;
15281
- };
15282
- //#endregion
15283
- //#region src/patcher/paragraph-split-inject.ts
15284
- var TokenNotFoundError = class extends Error {
15285
- constructor(token) {
15286
- super(`Token ${token} not found`);
15287
- this.name = "TokenNotFoundError";
15288
- }
15289
- };
15290
- /**
15291
- * Finds the index of the run element containing a specific token.
15292
- *
15293
- * Searches through all run (w:r) elements in a paragraph to find which one
15294
- * contains the specified token text. This is used to locate where new content
15295
- * should be injected during replacement operations.
15296
- *
15297
- * @param paragraphElement - The paragraph element to search
15298
- * @param token - The token text to find
15299
- * @returns The index of the run element containing the token
15300
- * @throws Error if the token is not found in any run
15301
- *
15302
- * @example
15303
- * ```typescript
15304
- * const index = findRunElementIndexWithToken(paragraph, "ɵ");
15305
- * // Returns the index of the run containing the split token
15306
- * ```
15307
- */
15308
- const findRunElementIndexWithToken = (paragraphElement, token) => {
15309
- for (let i = 0; i < (paragraphElement.elements ?? []).length; i++) {
15310
- const element = paragraphElement.elements[i];
15311
- if (element.type === "element" && element.name === "w:r") {
15312
- const textElement = (element.elements ?? []).filter((e) => e.type === "element" && e.name === "w:t");
15313
- for (const text of textElement) {
15314
- if (!text.elements?.[0]) continue;
15315
- if (text.elements[0].text?.includes(token)) return i;
15316
- }
15317
- }
15318
- }
15319
- throw new TokenNotFoundError(token);
15320
- };
15321
- /**
15322
- * Splits a run element at a token position into left and right parts.
15323
- *
15324
- * Divides a run element at the location of a token, creating two separate
15325
- * runs. This allows new content to be injected between the split parts while
15326
- * preserving the original run's formatting properties.
15327
- *
15328
- * @param runElement - The run element to split
15329
- * @param token - The token text marking the split point
15330
- * @returns Object containing the left and right run elements
15331
- *
15332
- * @example
15333
- * ```typescript
15334
- * const { left, right } = splitRunElement(run, "ɵ");
15335
- * // If run contains "Helloɵworld", left contains "Hello" and right contains "world"
15336
- * ```
15337
- */
15338
- const splitRunElement = (runElement, token) => {
15339
- let splitIndex = -1;
15340
- const splitElements = runElement.elements?.map((e, i) => {
15341
- if (splitIndex !== -1) return e;
15342
- if (e.type === "element" && e.name === "w:t") {
15343
- const splitText = (e.elements?.[0]?.text ?? "").split(token);
15344
- const newElements = splitText.map((t) => ({
15345
- ...e,
15346
- ...patchSpaceAttribute(e),
15347
- elements: createTextElementContents(t)
15348
- }));
15349
- if (splitText.length > 1) splitIndex = i;
15350
- return newElements;
15351
- } else return e;
15352
- }).flat() ?? [];
15353
- return {
15354
- left: {
15355
- ...JSON.parse(JSON.stringify(runElement)),
15356
- elements: splitElements.slice(0, splitIndex + 1)
15357
- },
15358
- right: {
15359
- ...JSON.parse(JSON.stringify(runElement)),
15360
- elements: splitElements.slice(splitIndex + 1)
15361
- }
15362
- };
15363
- };
15364
- //#endregion
15365
- //#region src/patcher/paragraph-token-replacer.ts
15366
- /**
15367
- * Replacement modes for multi-run text replacement.
15368
- */
15369
- const ReplaceMode = {
15370
- /** Looking for the start of the replacement text */
15371
- START: 0,
15372
- /** Processing runs in the middle of the replacement text */
15373
- MIDDLE: 1,
15374
- /** Reached the end of the replacement text */
15375
- END: 2
15376
- };
15377
- /**
15378
- * Replaces a token with replacement text within a paragraph's run elements.
15379
- *
15380
- * Handles the complex case where placeholder text may span multiple runs
15381
- * (text fragments) within a paragraph. Processes each run to replace the
15382
- * appropriate portion of the token, handling start, middle, and end sections.
15383
- *
15384
- * @param paragraphElement - The paragraph XML element to modify
15385
- * @param renderedParagraph - Pre-rendered paragraph structure with text positions
15386
- * @param originalText - The token text to replace (e.g., "{{name}}")
15387
- * @param replacementText - The text to replace it with (often a split token)
15388
- * @returns The modified paragraph element
15389
- *
15390
- * @example
15391
- * ```typescript
15392
- * replaceTokenInParagraphElement({
15393
- * paragraphElement,
15394
- * renderedParagraph,
15395
- * originalText: "{{placeholder}}",
15396
- * replacementText: "ɵ",
15397
- * });
15398
- * ```
15399
- */
15400
- const replaceTokenInParagraphElement = ({ paragraphElement, renderedParagraph, originalText, replacementText }) => {
15401
- const startIndex = renderedParagraph.text.indexOf(originalText);
15402
- const endIndex = startIndex + originalText.length - 1;
15403
- let replaceMode = ReplaceMode.START;
15404
- for (const run of renderedParagraph.runs) for (const { text, index, start, end } of run.parts) switch (replaceMode) {
15405
- case ReplaceMode.START:
15406
- if (startIndex >= start && startIndex <= end) {
15407
- const offsetStartIndex = startIndex - start;
15408
- const offsetEndIndex = Math.min(endIndex, end) - start;
15409
- const partToReplace = text.substring(offsetStartIndex, offsetEndIndex + 1);
15410
- if (partToReplace === "") continue;
15411
- const firstPart = text.replace(partToReplace, replacementText);
15412
- patchTextElement(paragraphElement.elements[run.index].elements[index], firstPart);
15413
- replaceMode = ReplaceMode.MIDDLE;
15414
- continue;
15415
- }
15416
- break;
15417
- case ReplaceMode.MIDDLE:
15418
- if (endIndex <= end) {
15419
- const lastPart = text.substring(endIndex - start + 1);
15420
- patchTextElement(paragraphElement.elements[run.index].elements[index], lastPart);
15421
- const currentElement = paragraphElement.elements[run.index].elements[index];
15422
- paragraphElement.elements[run.index].elements[index] = patchSpaceAttribute(currentElement);
15423
- replaceMode = ReplaceMode.END;
15424
- } else patchTextElement(paragraphElement.elements[run.index].elements[index], "");
15425
- break;
15426
- default:
15427
- }
15428
- return paragraphElement;
15429
- };
15430
- const patchTextElement = (element, text) => {
15431
- element.elements = createTextElementContents(text);
15432
- return element;
15433
- };
15434
- //#endregion
15435
- //#region src/patcher/run-renderer.ts
15436
- /**
15437
- * Renders a paragraph element into a structured representation with text content.
15438
- *
15439
- * Extracts all text content from a paragraph (w:p) element by processing its
15440
- * run (w:r) children. Calculates character positions for each text fragment
15441
- * to enable precise text replacement operations.
15442
- *
15443
- * @param node - The paragraph element wrapper to render
15444
- * @returns Rendered paragraph with text content, runs, and position information
15445
- * @throws Error if the node is not a paragraph element
15446
- *
15447
- * @example
15448
- * ```typescript
15449
- * const rendered = renderParagraphNode(paragraphWrapper);
15450
- * console.log(rendered.text); // "Hello World"
15451
- * console.log(rendered.runs.length); // 2 (if text is in separate runs)
15452
- * ```
15453
- */
15454
- const renderParagraphNode = (node) => {
15455
- if (node.element.name !== "w:p") throw new Error(`Invalid node type: ${node.element.name}`);
15456
- if (!node.element.elements) return {
15457
- index: -1,
15458
- pathToParagraph: [],
15459
- runs: [],
15460
- text: ""
15461
- };
15462
- let currentRunStringLength = 0;
15463
- const runs = node.element.elements.map((element, i) => ({
15464
- element,
15465
- i
15466
- })).filter(({ element }) => element.name === "w:r").map(({ element, i }) => {
15467
- const renderedRunNode = renderRunNode(element, i, currentRunStringLength);
15468
- currentRunStringLength += renderedRunNode.text.length;
15469
- return renderedRunNode;
15470
- }).filter((e) => Boolean(e));
15471
- const text = runs.reduce((acc, curr) => acc + curr.text, "");
15472
- return {
15473
- index: node.index,
15474
- pathToParagraph: buildNodePath(node),
15475
- runs,
15476
- text
15477
- };
15478
- };
15479
- const renderRunNode = (node, index, currentRunStringIndex) => {
15480
- if (!node.elements) return {
15481
- end: currentRunStringIndex,
15482
- index: -1,
15483
- parts: [],
15484
- start: currentRunStringIndex,
15485
- text: ""
15486
- };
15487
- let currentTextStringIndex = currentRunStringIndex;
15488
- const parts = node.elements.map((element, i) => element.name === "w:t" && element.elements && element.elements.length > 0 ? (() => {
15489
- const partStart = currentTextStringIndex;
15490
- currentTextStringIndex += (element.elements[0].text?.toString() ?? "").length;
15491
- return {
15492
- end: currentTextStringIndex - 1,
15493
- index: i,
15494
- start: partStart,
15495
- text: element.elements[0].text?.toString() ?? ""
15496
- };
15497
- })() : void 0).filter((e) => Boolean(e)).map((e) => e);
15498
- const text = parts.reduce((acc, curr) => acc + curr.text, "");
15499
- return {
15500
- end: currentTextStringIndex - 1,
15501
- index,
15502
- parts,
15503
- start: currentRunStringIndex,
15504
- text
15505
- };
15506
- };
15507
- const buildNodePath = (node) => node.parent ? [...buildNodePath(node.parent), node.index] : [node.index];
15508
- //#endregion
15509
- //#region src/patcher/traverser.ts
15510
- const elementsToWrapper = (wrapper) => wrapper.element.elements?.map((e, i) => ({
15511
- element: e,
15512
- index: i,
15513
- parent: wrapper
15514
- })) ?? [];
15515
- /**
15516
- * Traverses an XML document tree to find and render all paragraphs.
15517
- *
15518
- * Uses breadth-first search to walk through the XML structure, identifying
15519
- * all paragraph elements (w:p) and rendering their text content along with
15520
- * positional information.
15521
- *
15522
- * @param node - The root XML element to traverse
15523
- * @returns Array of rendered paragraph nodes with text content and positions
15524
- *
15525
- * @example
15526
- * ```typescript
15527
- * const paragraphs = traverse(documentElement);
15528
- * paragraphs.forEach(p => console.log(p.text));
15529
- * ```
15530
- */
15531
- const traverse = (node) => {
15532
- let renderedParagraphs = [];
15533
- const queue = [...elementsToWrapper({
15534
- element: node,
15535
- index: 0,
15536
- parent: void 0
15537
- })];
15538
- let currentNode;
15539
- while (queue.length > 0) {
15540
- currentNode = queue.shift();
15541
- if (currentNode.element.name === "w:p") renderedParagraphs = [...renderedParagraphs, renderParagraphNode(currentNode)];
15542
- queue.push(...elementsToWrapper(currentNode));
15543
- }
15544
- return renderedParagraphs;
15545
- };
15546
- /**
15547
- * Finds all paragraphs containing specific text.
15548
- *
15549
- * Traverses the document and filters paragraphs to find those containing
15550
- * the specified text string. Useful for locating placeholder text that
15551
- * needs to be replaced.
15552
- *
15553
- * @param node - The root XML element to search
15554
- * @param text - The text to search for
15555
- * @returns Array of paragraph nodes containing the text
15556
- *
15557
- * @example
15558
- * ```typescript
15559
- * const matches = findLocationOfText(documentElement, "{{name}}");
15560
- * // Returns all paragraphs containing "{{name}}"
15561
- * ```
15562
- */
15563
- const findLocationOfText = (node, text) => traverse(node).filter((p) => p.text.includes(text));
15564
- //#endregion
15565
- //#region src/patcher/replacer.ts
15566
- /**
15567
- * Replacer module for performing placeholder substitution in XML structures.
15568
- *
15569
- * @module
15570
- */
15571
- const formatter = new Formatter();
15572
- const SPLIT_TOKEN = "ɵ";
15573
- /**
15574
- * Replaces placeholder text in XML with new content from a patch.
15575
- *
15576
- * This function locates placeholder text within the XML structure and performs
15577
- * the appropriate replacement based on the patch type (document or paragraph level).
15578
- * It handles splitting runs, preserving styles, and injecting the new content.
15579
- *
15580
- * @param json - The XML element structure to search
15581
- * @param patch - The patch definition containing replacement content
15582
- * @param patchText - The placeholder text to find (e.g., "{{name}}")
15583
- * @param context - The document context for formatting
15584
- * @param keepOriginalStyles - Whether to preserve original text formatting
15585
- * @returns Result containing the modified element and whether a replacement occurred
15586
- */
15587
- const replacer = ({ json, patch, patchText, context, keepOriginalStyles = true }) => {
15588
- const renderedParagraphs = findLocationOfText(json, patchText);
15589
- if (renderedParagraphs.length === 0) return {
15590
- didFindOccurrence: false,
15591
- element: json
15592
- };
15593
- for (const renderedParagraph of renderedParagraphs) {
15594
- const textJson = patch.children.map((c) => toJson(xml(formatter.format(c, context)))).map((c) => c.elements[0]);
15595
- switch (patch.type) {
15596
- case PatchType.DOCUMENT: {
15597
- const parentElement = goToParentElementFromPath(json, renderedParagraph.pathToParagraph);
15598
- const elementIndex = getLastElementIndexFromPath(renderedParagraph.pathToParagraph);
15599
- parentElement.elements.splice(elementIndex, 1, ...textJson);
15600
- break;
15601
- }
15602
- case PatchType.PARAGRAPH:
15603
- default: {
15604
- const paragraphElement = goToElementFromPath(json, renderedParagraph.pathToParagraph);
15605
- replaceTokenInParagraphElement({
15606
- originalText: patchText,
15607
- paragraphElement,
15608
- renderedParagraph,
15609
- replacementText: SPLIT_TOKEN
15610
- });
15611
- const index = findRunElementIndexWithToken(paragraphElement, SPLIT_TOKEN);
15612
- const runElementToBeReplaced = paragraphElement.elements[index];
15613
- const { left, right } = splitRunElement(runElementToBeReplaced, SPLIT_TOKEN);
15614
- let newRunElements = textJson;
15615
- let patchedRightElement = right;
15616
- if (keepOriginalStyles) {
15617
- const runElementNonTextualElements = runElementToBeReplaced.elements.filter((e) => e.type === "element" && e.name === "w:rPr");
15618
- newRunElements = textJson.map((e) => {
15619
- if (e.type !== "element" || e.name !== "w:r" || e.elements?.some((c) => c.type === "element" && c.name === "w:rPr")) return e;
15620
- return {
15621
- ...e,
15622
- elements: [...runElementNonTextualElements, ...e.elements ?? []]
15623
- };
15624
- });
15625
- patchedRightElement = {
15626
- ...right,
15627
- elements: [...runElementNonTextualElements, ...right.elements]
15628
- };
15629
- }
15630
- paragraphElement.elements.splice(index, 1, left, ...newRunElements, patchedRightElement);
15631
- break;
15632
- }
15633
- }
15634
- }
15635
- return {
15636
- didFindOccurrence: true,
15637
- element: json
15638
- };
15639
- };
15640
- const goToElementFromPath = (json, path) => {
15641
- let element = json;
15642
- for (let i = 1; i < path.length; i++) {
15643
- const index = path[i];
15644
- element = element.elements[index];
15645
- }
15646
- return element;
15647
- };
15648
- const goToParentElementFromPath = (json, path) => goToElementFromPath(json, path.slice(0, -1));
15649
- const getLastElementIndexFromPath = (path) => path[path.length - 1];
15650
- //#endregion
15651
15100
  //#region src/patcher/from-docx.ts
15101
+ init_media();
15102
+ init_paragraph();
15103
+ init_relationship();
15104
+ init_convenience_functions();
15652
15105
  /**
15653
15106
  * Document patching module for modifying existing .docx files.
15654
15107
  *
15655
- * This module provides functionality to patch existing Word documents by replacing
15656
- * placeholder text with new content while preserving the original document structure.
15657
- *
15658
15108
  * @module
15659
15109
  */
15660
- init_media();
15661
- init_paragraph();
15662
- init_relationship();
15663
- init_convenience_functions();
15110
+ const formatter = new Formatter();
15111
+ const docxReplacer = createReplacer({
15112
+ ns: DOCX_NS,
15113
+ formatChild: (child, context) => {
15114
+ return [toJson(xml(formatter.format(child, context))).elements[0]];
15115
+ }
15116
+ });
15664
15117
  /**
15665
15118
  * Patch type enumeration.
15666
15119
  *
15667
- * Determines how the replacement content should be inserted into the document.
15668
- *
15669
15120
  * @publicApi
15670
15121
  */
15671
15122
  const PatchType = {
15672
- /** Replace entire file-level elements (e.g., whole paragraphs) */
15673
15123
  DOCUMENT: "file",
15674
- /** Replace content within paragraphs (inline replacement) */
15675
15124
  PARAGRAPH: "paragraph"
15676
15125
  };
15677
15126
  const UTF16LE = new Uint8Array([255, 254]);
@@ -15684,36 +15133,6 @@ const compareByteArrays = (a, b) => {
15684
15133
  /**
15685
15134
  * Patches an existing .docx document by replacing placeholders with new content.
15686
15135
  *
15687
- * This function opens an existing Word document, searches for placeholder text
15688
- * (e.g., {{name}}), and replaces it with the provided content while preserving
15689
- * the original document structure and optionally the original formatting.
15690
- *
15691
- * @param options - Configuration options for patching
15692
- * @returns A promise resolving to the patched document in the specified output format
15693
- *
15694
- * @example
15695
- * ```typescript
15696
- * // Patch with paragraph content
15697
- * const buffer = await patchDocument({
15698
- * outputType: "nodebuffer",
15699
- * data: templateBuffer,
15700
- * patches: {
15701
- * name: {
15702
- * type: PatchType.PARAGRAPH,
15703
- * children: [new TextRun({ text: "John Doe", bold: true })],
15704
- * },
15705
- * },
15706
- * });
15707
- *
15708
- * // Patch with custom delimiters
15709
- * const buffer = await patchDocument({
15710
- * outputType: "nodebuffer",
15711
- * data: templateBuffer,
15712
- * patches: { ... },
15713
- * placeholderDelimiters: { start: "<<", end: ">>" },
15714
- * });
15715
- * ```
15716
- *
15717
15136
  * @publicApi
15718
15137
  */
15719
15138
  const patchDocument = async ({ outputType, data, patches, keepOriginalStyles, placeholderDelimiters = {
@@ -15722,7 +15141,7 @@ const patchDocument = async ({ outputType, data, patches, keepOriginalStyles, pl
15722
15141
  }, recursive = true }) => {
15723
15142
  const zipContent = unzipSync(toUint8Array(data));
15724
15143
  const contexts = /* @__PURE__ */ new Map();
15725
- const file = { Media: new Media() };
15144
+ const file = { media: new Media() };
15726
15145
  const map = /* @__PURE__ */ new Map();
15727
15146
  const imageRelationshipAdditions = [];
15728
15147
  const hyperlinkRelationshipAdditions = [];
@@ -15757,7 +15176,7 @@ const patchDocument = async ({ outputType, data, patches, keepOriginalStyles, pl
15757
15176
  fileData: file,
15758
15177
  file,
15759
15178
  stack: [],
15760
- viewWrapper: { Relationships: { addRelationship: (linkId, _, target, __) => {
15179
+ viewWrapper: { relationships: { addRelationship: (linkId, _, target, __) => {
15761
15180
  hyperlinkRelationshipAdditions.push({
15762
15181
  hyperlink: {
15763
15182
  id: linkId,
@@ -15773,7 +15192,7 @@ const patchDocument = async ({ outputType, data, patches, keepOriginalStyles, pl
15773
15192
  for (const [patchKey, patchValue] of Object.entries(patches)) {
15774
15193
  const patchText = `${start}${patchKey}${end}`;
15775
15194
  while (true) {
15776
- const { didFindOccurrence } = replacer({
15195
+ const { didFindOccurrence } = docxReplacer({
15777
15196
  context,
15778
15197
  json,
15779
15198
  keepOriginalStyles,
@@ -15838,14 +15257,11 @@ const patchDocument = async ({ outputType, data, patches, keepOriginalStyles, pl
15838
15257
  appendContentType(contentTypesJson, "image/svg+xml", "svg");
15839
15258
  }
15840
15259
  const files = {};
15841
- for (const [key, value] of map) files[key] = textToUint8Array(toXml(value));
15260
+ for (const [key, value] of map) files[key] = textToUint8Array(js2xml(value));
15842
15261
  for (const [key, value] of binaryContentMap) files[key] = value;
15843
15262
  for (const { data: mediaData, fileName } of file.media.array) files[`word/media/${fileName}`] = mediaData instanceof Uint8Array ? mediaData : new Uint8Array(mediaData);
15844
15263
  return await zipAndConvert(files, outputType, OoxmlMimeType.DOCX);
15845
15264
  };
15846
- const toXml = (jsonObj) => {
15847
- return js2xml(jsonObj, { attributeValueFn: (str) => String(str).replace(/&(?!amp;|lt;|gt;|quot;|apos;)/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&apos;") });
15848
- };
15849
15265
  const createRelationshipFile = () => ({
15850
15266
  declaration: { attributes: {
15851
15267
  encoding: "UTF-8",
@@ -15896,7 +15312,11 @@ const patchDetector = async ({ data }) => {
15896
15312
  const patches = /* @__PURE__ */ new Set();
15897
15313
  for (const [key, value] of Object.entries(zipContent)) {
15898
15314
  if (!key.endsWith(".xml") && !key.endsWith(".rels")) continue;
15899
- if (key.startsWith("word/") && !key.endsWith(".xml.rels")) traverse(toJson(strFromU8(value))).forEach((p) => findPatchKeys(p.text).forEach((patch) => patches.add(patch)));
15315
+ if (key.startsWith("word/") && !key.endsWith(".xml.rels")) {
15316
+ const json = toJson(strFromU8(value));
15317
+ const { traverse } = createTraverser(DOCX_NS);
15318
+ traverse(json).forEach((p) => findPatchKeys(p.text).forEach((patch) => patches.add(patch)));
15319
+ }
15900
15320
  }
15901
15321
  return [...patches];
15902
15322
  };