diff --git a/demo/87-template-document.ts b/demo/87-template-document.ts new file mode 100644 index 0000000000..f1f595b2aa --- /dev/null +++ b/demo/87-template-document.ts @@ -0,0 +1,15 @@ +// Patch a document with patches +// Import from 'docx' rather than '../build' if you install from npm +import * as fs from "fs"; +import { patchDocument, PatchType, TextRun } from "../build"; + +patchDocument(fs.readFileSync("demo/assets/simple-template-2.docx"), { + patches: { + name: { + type: PatchType.PARAGRAPH, + children: [new TextRun("Max")], + }, + }, +}).then((doc) => { + fs.writeFileSync("My Document.docx", doc); +}); diff --git a/demo/assets/simple-template-2.docx b/demo/assets/simple-template-2.docx new file mode 100644 index 0000000000..92c46cd041 Binary files /dev/null and b/demo/assets/simple-template-2.docx differ diff --git a/src/patcher/paragraph-token-replacer.spec.ts b/src/patcher/paragraph-token-replacer.spec.ts index bcfc72e75c..fff9d954eb 100644 --- a/src/patcher/paragraph-token-replacer.spec.ts +++ b/src/patcher/paragraph-token-replacer.spec.ts @@ -71,6 +71,156 @@ describe("paragraph-token-replacer", () => { }); }); + it("should handle case where it cannot find any text to replace", () => { + const output = replaceTokenInParagraphElement({ + paragraphElement: { + name: "w:p", + attributes: { + "w14:paraId": "2499FE9F", + "w14:textId": "27B4FBC2", + "w:rsidR": "00B51233", + "w:rsidRDefault": "007B52ED", + "w:rsidP": "007B52ED", + }, + elements: [ + { + type: "element", + name: "w:pPr", + elements: [{ type: "element", name: "w:pStyle", attributes: { "w:val": "Title" } }], + }, + { + type: "element", + name: "w:r", + elements: [ + { + type: "element", + name: "w:t", + attributes: { "xml:space": "preserve" }, + elements: [{ type: "text", text: "Hello " }], + }, + ], + }, + { + type: "element", + name: "w:r", + attributes: { "w:rsidR": "007F116B" }, + elements: [ + { + type: "element", + name: "w:t", + attributes: { "xml:space": "preserve" }, + elements: [{ type: "text", text: "{{name}} " }], + }, + ], + }, + { + type: "element", + name: "w:r", + elements: [{ type: "element", name: "w:t", elements: [{ type: "text", text: "World" }] }], + }, + ], + }, + renderedParagraph: { + text: "Hello {{name}} World", + runs: [ + { text: "Hello ", parts: [{ text: "Hello ", index: 0, start: 0, end: 5 }], index: 1, start: 0, end: 5 }, + { text: "{{name}} ", parts: [{ text: "{{name}} ", index: 0, start: 6, end: 14 }], index: 2, start: 6, end: 14 }, + { text: "World", parts: [{ text: "World", index: 0, start: 15, end: 19 }], index: 3, start: 15, end: 19 }, + ], + index: 0, + path: [0, 1, 0, 0], + }, + originalText: "{{name}}", + replacementText: "John", + }); + + expect(output).to.deep.equal({ + attributes: { + "w14:paraId": "2499FE9F", + "w14:textId": "27B4FBC2", + "w:rsidP": "007B52ED", + "w:rsidR": "00B51233", + "w:rsidRDefault": "007B52ED", + }, + elements: [ + { + elements: [ + { + attributes: { + "w:val": "Title", + }, + name: "w:pStyle", + type: "element", + }, + ], + name: "w:pPr", + type: "element", + }, + { + elements: [ + { + attributes: { + "xml:space": "preserve", + }, + elements: [ + { + text: "Hello ", + type: "text", + }, + ], + name: "w:t", + type: "element", + }, + ], + name: "w:r", + type: "element", + }, + { + attributes: { + "w:rsidR": "007F116B", + }, + elements: [ + { + attributes: { + "xml:space": "preserve", + }, + elements: [ + { + text: "John ", + type: "text", + }, + ], + name: "w:t", + type: "element", + }, + ], + name: "w:r", + type: "element", + }, + { + elements: [ + { + attributes: { + "xml:space": "preserve", + }, + elements: [ + { + text: "World", + type: "text", + }, + ], + name: "w:t", + type: "element", + }, + ], + name: "w:r", + type: "element", + }, + ], + name: "w:p", + }); + }); + // Try to fill rest of test coverage // it("should replace token in paragraph", () => { // const output = replaceTokenInParagraphElement({ diff --git a/src/patcher/paragraph-token-replacer.ts b/src/patcher/paragraph-token-replacer.ts index 06593c625f..fe3583c0e8 100644 --- a/src/patcher/paragraph-token-replacer.ts +++ b/src/patcher/paragraph-token-replacer.ts @@ -30,9 +30,15 @@ export const replaceTokenInParagraphElement = ({ switch (replaceMode) { case ReplaceMode.START: if (startIndex >= start) { - const partToReplace = run.text.substring(Math.max(startIndex, start), Math.min(endIndex, end) + 1); + const offsetStartIndex = startIndex - start; + const offsetEndIndex = Math.min(endIndex, end) - start; + const partToReplace = run.text.substring(offsetStartIndex, offsetEndIndex + 1); // We use a token to split the text if the replacement is within the same run // If not, we just add text to the middle of the run later + if (partToReplace === "") { + continue; + } + const firstPart = text.replace(partToReplace, replacementText); patchTextElement(paragraphElement.elements![run.index].elements![index], firstPart); replaceMode = ReplaceMode.MIDDLE; diff --git a/src/patcher/util.ts b/src/patcher/util.ts index 6f9ed57dd0..6faef2fb97 100644 --- a/src/patcher/util.ts +++ b/src/patcher/util.ts @@ -7,7 +7,7 @@ import { Text } from "@file/paragraph/run/run-components/text"; const formatter = new Formatter(); export const toJson = (xmlData: string): Element => { - const xmlObj = xml2js(xmlData, { compact: false }) as Element; + const xmlObj = xml2js(xmlData, { compact: false, captureSpacesBetweenElements: true }) as Element; return xmlObj; };