diff --git a/src/patcher/from-docx.ts b/src/patcher/from-docx.ts index bcb69995d3b..7aec7930b72 100644 --- a/src/patcher/from-docx.ts +++ b/src/patcher/from-docx.ts @@ -49,6 +49,7 @@ export type IPatch = ParagraphPatch | FilePatch; export interface PatchDocumentOptions { readonly patches: { readonly [key: string]: IPatch }; + readonly keepOriginalStyles?: boolean; } const imageReplacer = new ImageReplacer(); @@ -128,6 +129,7 @@ export const patchDocument = async (data: InputDataType, options: PatchDocumentO patchText, renderedParagraphs, context, + options.keepOriginalStyles, ); } diff --git a/src/patcher/replacer.spec.ts b/src/patcher/replacer.spec.ts index 30a3ebed9b8..3dd0947f494 100644 --- a/src/patcher/replacer.spec.ts +++ b/src/patcher/replacer.spec.ts @@ -44,6 +44,28 @@ const MOCK_JSON = { }, ], }, + { + type: "element", + name: "w:p", + elements: [ + { + type: "element", + name: "w:r", + elements: [ + { + type: "element", + name: "w:rPr", + elements: [{ type: "element", name: "w:b", attributes: { "w:val": "1" } }], + }, + { + type: "element", + name: "w:t", + elements: [{ type: "text", text: "What a {{bold}} text!" }], + }, + ], + }, + ], + }, ], }, ], @@ -115,6 +137,93 @@ describe("replacer", () => { expect(JSON.stringify(output)).to.contain("Delightful Header"); }); + it("should replace paragraph type keeping original styling if keepOriginalStyles is true", () => { + const output = replacer( + MOCK_JSON, + { + type: PatchType.PARAGRAPH, + children: [new TextRun("sweet")], + }, + "{{bold}}", + [ + { + text: "What a {{bold}} text!", + runs: [ + { + text: "What a {{bold}} text!", + parts: [{ text: "What a {{bold}} text!", index: 1, start: 0, end: 21 }], + index: 0, + start: 0, + end: 21, + }, + ], + index: 0, + path: [0, 0, 1], + }, + ], + { + file: {} as unknown as File, + viewWrapper: { + Relationships: {}, + } as unknown as IViewWrapper, + stack: [], + }, + true, + ); + + expect(JSON.stringify(output)).to.contain("sweet"); + expect(output.elements![0].elements![1].elements).toMatchObject([ + { + type: "element", + name: "w:r", + elements: [ + { + type: "element", + name: "w:rPr", + elements: [{ type: "element", name: "w:b", attributes: { "w:val": "1" } }], + }, + { + type: "element", + name: "w:t", + elements: [{ type: "text", text: "What a " }], + }, + ], + }, + { + type: "element", + name: "w:r", + elements: [ + { + type: "element", + name: "w:rPr", + elements: [{ type: "element", name: "w:b", attributes: { "w:val": "1" } }], + }, + { + type: "element", + name: "w:t", + elements: [{ type: "text", text: "sweet" }], + }, + ], + }, + { + type: "element", + name: "w:r", + elements: [ + { + type: "element", + name: "w:rPr", + elements: [{ type: "element", name: "w:b", attributes: { "w:val": "1" } }], + }, + { + type: "element", + name: "w:t", + elements: [{ type: "text", text: " text!" }], + }, + ], + }, + ]); + }); + it("should replace document type", () => { const output = replacer( MOCK_JSON, diff --git a/src/patcher/replacer.ts b/src/patcher/replacer.ts index 17b6b1971a5..8ae2f4f9a61 100644 --- a/src/patcher/replacer.ts +++ b/src/patcher/replacer.ts @@ -20,6 +20,7 @@ export const replacer = ( patchText: string, renderedParagraphs: readonly IRenderedParagraphNode[], context: IContext, + keepOriginalStyles: boolean = false, ): Element => { for (const renderedParagraph of renderedParagraphs) { const textJson = patch.children @@ -47,9 +48,29 @@ export const replacer = ( const index = findRunElementIndexWithToken(paragraphElement, SPLIT_TOKEN); - const { left, right } = splitRunElement(paragraphElement.elements![index], SPLIT_TOKEN); + const runElementToBeReplaced = paragraphElement.elements![index]; + const { left, right } = splitRunElement(runElementToBeReplaced, SPLIT_TOKEN); + + let newRunElements = textJson; + let patchedRightElement = right; + + if (keepOriginalStyles) { + const runElementNonTextualElements = + runElementToBeReplaced.elements?.filter((e) => e.type === "element" && e.name !== "w:t") ?? []; + + newRunElements = textJson.map((e) => ({ + ...e, + elements: [...runElementNonTextualElements, ...(e.elements ?? [])], + })); + + patchedRightElement = { + ...right, + elements: [...runElementNonTextualElements, ...(right.elements ?? [])], + }; + } + // eslint-disable-next-line functional/immutable-data - paragraphElement.elements!.splice(index, 1, left, ...textJson, right); + paragraphElement.elements!.splice(index, 1, left, ...newRunElements, patchedRightElement); break; } }