From 5af1045a59d9434d304c1574eeec8209e17b76bf Mon Sep 17 00:00:00 2001 From: Venkateshwar Reddy <51945078+venkateshwarreddyr@users.noreply.github.com> Date: Wed, 16 Apr 2025 12:48:55 +0530 Subject: [PATCH] feat: add support for custom patch delimiters in PatchDocumentOptions (#3036) * feat: add support for custom patch delimiters in PatchDocumentOptions * chore: add validation for placeholder delimiters --- demo/96-template-document.ts | 168 +++++++++++++++++++++++++++++ demo/assets/simple-template-4.docx | Bin 0 -> 15783 bytes src/patcher/from-docx.spec.ts | 95 ++++++++++++++++ src/patcher/from-docx.ts | 13 ++- 4 files changed, 275 insertions(+), 1 deletion(-) create mode 100644 demo/96-template-document.ts create mode 100644 demo/assets/simple-template-4.docx diff --git a/demo/96-template-document.ts b/demo/96-template-document.ts new file mode 100644 index 0000000000..949712e898 --- /dev/null +++ b/demo/96-template-document.ts @@ -0,0 +1,168 @@ +// Patch a document with patches + +import * as fs from "fs"; +import { + ExternalHyperlink, + HeadingLevel, + ImageRun, + Paragraph, + patchDocument, + PatchType, + Table, + TableCell, + TableRow, + TextDirection, + TextRun, + VerticalAlign, +} from "docx"; + +patchDocument({ + outputType: "nodebuffer", + data: fs.readFileSync("demo/assets/simple-template-4.docx"), + patches: { + name: { + type: PatchType.PARAGRAPH, + children: [new TextRun("Sir. "), new TextRun("John Doe"), new TextRun("(The Conqueror)")], + }, + table_heading_1: { + type: PatchType.PARAGRAPH, + children: [new TextRun("Heading wow!")], + }, + item_1: { + type: PatchType.PARAGRAPH, + children: [ + new TextRun("#657"), + new ExternalHyperlink({ + children: [ + new TextRun({ + text: "BBC News Link", + }), + ], + link: "https://www.bbc.co.uk/news", + }), + ], + }, + paragraph_replace: { + type: PatchType.DOCUMENT, + children: [ + new Paragraph("Lorem ipsum paragraph"), + new Paragraph("Another paragraph"), + new Paragraph({ + children: [ + new TextRun("This is a "), + new ExternalHyperlink({ + children: [ + new TextRun({ + text: "Google Link", + }), + ], + link: "https://www.google.co.uk", + }), + new ImageRun({ + type: "png", + data: fs.readFileSync("./demo/images/dog.png"), + transformation: { width: 100, height: 100 }, + }), + ], + }), + ], + }, + header_adjective: { + type: PatchType.PARAGRAPH, + children: [new TextRun("Delightful Header")], + }, + footer_text: { + type: PatchType.PARAGRAPH, + children: [ + new TextRun("replaced just as"), + new TextRun(" well"), + new ExternalHyperlink({ + children: [ + new TextRun({ + text: "BBC News Link", + }), + ], + link: "https://www.bbc.co.uk/news", + }), + ], + }, + image_test: { + type: PatchType.PARAGRAPH, + children: [ + new ImageRun({ + type: "jpg", + data: fs.readFileSync("./demo/images/image1.jpeg"), + transformation: { width: 100, height: 100 }, + }), + ], + }, + table: { + type: PatchType.DOCUMENT, + children: [ + new Table({ + rows: [ + new TableRow({ + children: [ + new TableCell({ + children: [new Paragraph({}), new Paragraph({})], + verticalAlign: VerticalAlign.CENTER, + }), + new TableCell({ + children: [new Paragraph({}), new Paragraph({})], + verticalAlign: VerticalAlign.CENTER, + }), + new TableCell({ + children: [new Paragraph({ text: "bottom to top" }), new Paragraph({})], + textDirection: TextDirection.BOTTOM_TO_TOP_LEFT_TO_RIGHT, + }), + new TableCell({ + children: [new Paragraph({ text: "top to bottom" }), new Paragraph({})], + textDirection: TextDirection.TOP_TO_BOTTOM_RIGHT_TO_LEFT, + }), + ], + }), + new TableRow({ + children: [ + new TableCell({ + children: [ + new Paragraph({ + text: "Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah Blah", + heading: HeadingLevel.HEADING_1, + }), + ], + }), + new TableCell({ + children: [ + new Paragraph({ + text: "This text should be in the middle of the cell", + }), + ], + verticalAlign: VerticalAlign.CENTER, + }), + new TableCell({ + children: [ + new Paragraph({ + text: "Text above should be vertical from bottom to top", + }), + ], + verticalAlign: VerticalAlign.CENTER, + }), + new TableCell({ + children: [ + new Paragraph({ + text: "Text above should be vertical from top to bottom", + }), + ], + verticalAlign: VerticalAlign.CENTER, + }), + ], + }), + ], + }), + ], + }, + }, + placeholderDelimiters: { start: "<<", end: ">>" }, +}).then((doc) => { + fs.writeFileSync("My Document.docx", doc); +}); diff --git a/demo/assets/simple-template-4.docx b/demo/assets/simple-template-4.docx new file mode 100644 index 0000000000000000000000000000000000000000..a15d462ed7af523e2d926a7e79b65354470ffed5 GIT binary patch literal 15783 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 literal 0 HcmV?d00001 diff --git a/src/patcher/from-docx.spec.ts b/src/patcher/from-docx.spec.ts index 8eee52260c..908c4a94e5 100644 --- a/src/patcher/from-docx.spec.ts +++ b/src/patcher/from-docx.spec.ts @@ -288,6 +288,101 @@ describe("from-docx", () => { }); expect(output).to.not.be.undefined; }); + + it("should patch the document", async () => { + const output = await patchDocument({ + outputType: "uint8array", + data: Buffer.from(""), + placeholderDelimiters: { start: "{{", end: "}}" }, + patches: { + name: { + type: PatchType.PARAGRAPH, + children: [new TextRun("Sir. "), new TextRun("John Doe"), new TextRun("(The Conqueror)")], + }, + item_1: { + type: PatchType.PARAGRAPH, + children: [ + new TextRun("#657"), + new ExternalHyperlink({ + children: [ + new TextRun({ + text: "BBC News Link", + }), + ], + link: "https://www.bbc.co.uk/news", + }), + ], + }, + // eslint-disable-next-line @typescript-eslint/naming-convention + paragraph_replace: { + type: PatchType.DOCUMENT, + children: [ + new Paragraph({ + children: [ + new TextRun("This is a "), + new ExternalHyperlink({ + children: [ + new TextRun({ + text: "Google Link", + }), + ], + link: "https://www.google.co.uk", + }), + new ImageRun({ + type: "png", + data: Buffer.from(""), + transformation: { width: 100, height: 100 }, + }), + ], + }), + ], + }, + // eslint-disable-next-line @typescript-eslint/naming-convention + image_test: { + type: PatchType.PARAGRAPH, + children: [ + new ImageRun({ + type: "png", + data: Buffer.from(""), + transformation: { width: 100, height: 100 }, + }), + ], + }, + }, + }); + expect(output).to.not.be.undefined; + }); + + it("should patch the document", async () => { + const output = await patchDocument({ + outputType: "uint8array", + data: Buffer.from(""), + patches: {}, + }); + expect(output).to.not.be.undefined; + }); + + it("throws error with empty delimiters", async () => { + await expect(() => + patchDocument({ + outputType: "uint8array", + data: Buffer.from(""), + patches: {}, + placeholderDelimiters: { start: "", end: "" }, + }), + ).rejects.toThrow(); + }); + + it("throws error with whitespace-only delimiters", async () => { + await expect(() => + patchDocument({ + outputType: "uint8array", + data: Buffer.from(""), + patches: {}, + placeholderDelimiters: { start: " ", end: " " }, + }), + ).rejects.toThrowError(); + }); }); describe("document.xml and [Content_Types].xml with relationships", () => { diff --git a/src/patcher/from-docx.ts b/src/patcher/from-docx.ts index 4d42951534..a85bcd06dd 100644 --- a/src/patcher/from-docx.ts +++ b/src/patcher/from-docx.ts @@ -55,6 +55,10 @@ export type PatchDocumentOptions>; readonly keepOriginalStyles?: boolean; + readonly placeholderDelimiters?: Readonly<{ + readonly start: string; + readonly end: string; + }>; }; const imageReplacer = new ImageReplacer(); @@ -64,6 +68,7 @@ export const patchDocument = async ): Promise => { const zipContent = await JSZip.loadAsync(data); const contexts = new Map(); @@ -132,8 +137,14 @@ export const patchDocument = async