From c9d86619de14cff72a0e1136c0405cbbf5abe314 Mon Sep 17 00:00:00 2001 From: Dolan Miu Date: Sat, 25 Feb 2023 22:18:56 +0000 Subject: [PATCH] Clean up --- demo/85-template-document.ts | 15 +++++++-------- demo/86-generate-template.ts | 2 +- demo/assets/generated-template.docx | Bin 0 -> 6684 bytes src/patcher/from-docx.ts | 1 + src/patcher/paragraph-split-inject.ts | 6 ++---- src/patcher/paragraph-token-replacer.ts | 7 ++++++- src/patcher/replacer.ts | 8 ++++++-- src/patcher/traverser.ts | 15 +-------------- src/patcher/util.ts | 7 +++++++ 9 files changed, 31 insertions(+), 30 deletions(-) create mode 100644 demo/assets/generated-template.docx diff --git a/demo/85-template-document.ts b/demo/85-template-document.ts index b36e6be4c3..f015dbe7cf 100644 --- a/demo/85-template-document.ts +++ b/demo/85-template-document.ts @@ -16,31 +16,31 @@ import { patchDocument(fs.readFileSync("demo/assets/simple-template.docx"), { patches: { - "name":{ + name: { type: PatchType.PARAGRAPH, children: [new TextRun("Sir. "), new TextRun("John Doe"), new TextRun("(The Conqueror)")], }, - "table_heading_1": { + table_heading_1: { type: PatchType.PARAGRAPH, children: [new TextRun("Heading wow!")], }, - "item_1": { + item_1: { type: PatchType.PARAGRAPH, children: [new TextRun("#657")], }, - "paragraph_replace": { + paragraph_replace: { type: PatchType.DOCUMENT, children: [new Paragraph("Lorem ipsum paragraph"), new Paragraph("Another paragraph")], }, - "header_adjective": { + header_adjective: { type: PatchType.PARAGRAPH, children: [new TextRun("Delightful Header")], }, - "footer_text": { + footer_text: { type: PatchType.PARAGRAPH, children: [new TextRun("replaced just as well")], }, - "table": { + table: { type: PatchType.DOCUMENT, children: [ new Table({ @@ -102,7 +102,6 @@ patchDocument(fs.readFileSync("demo/assets/simple-template.docx"), { ], }), ], - }), ], }, diff --git a/demo/86-generate-template.ts b/demo/86-generate-template.ts index 4af0a2dfd8..e16060f6a6 100644 --- a/demo/86-generate-template.ts +++ b/demo/86-generate-template.ts @@ -8,7 +8,7 @@ const doc = new Document({ { children: [ new Paragraph({ - children: [new TextRun("{{ template }}")], + children: [new TextRun("{{template}}")], }), ], }, diff --git a/demo/assets/generated-template.docx b/demo/assets/generated-template.docx new file mode 100644 index 0000000000000000000000000000000000000000..f7244e0c4b55308315f54489957fb7fbf7ee7378 GIT binary patch literal 6684 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 literal 0 HcmV?d00001 diff --git a/src/patcher/from-docx.ts b/src/patcher/from-docx.ts index 0dd38f91c3..535336e70d 100644 --- a/src/patcher/from-docx.ts +++ b/src/patcher/from-docx.ts @@ -43,6 +43,7 @@ export const patchDocument = async (data: InputDataType, options: PatchDocumentO for (const [patchKey, patchValue] of Object.entries(options.patches)) { const patchText = `{{${patchKey}}}`; const renderedParagraphs = findLocationOfText(json, patchText); + // TODO: mutates json. Make it immutable replacer(json, patchValue, patchText, renderedParagraphs); } } diff --git a/src/patcher/paragraph-split-inject.ts b/src/patcher/paragraph-split-inject.ts index 23a68159c8..d3fe8d5e6c 100644 --- a/src/patcher/paragraph-split-inject.ts +++ b/src/patcher/paragraph-split-inject.ts @@ -1,5 +1,5 @@ import { Element } from "xml-js"; -import { createTextElementContents } from "./util"; +import { createTextElementContents, patchSpaceAttribute } from "./util"; export const findRunElementIndexWithToken = (paragraphElement: Element, token: string): number => { for (let i = 0; i < (paragraphElement.elements ?? []).length; i++) { @@ -29,9 +29,7 @@ export const splitRunElement = (runElement: Element, token: string): { readonly const splitText = text.split(token); const newElements = splitText.map((t) => ({ ...e, - attributes: { - "xml:space": "preserve", - }, + ...patchSpaceAttribute(e), elements: createTextElementContents(t), })); splitIndex = i; diff --git a/src/patcher/paragraph-token-replacer.ts b/src/patcher/paragraph-token-replacer.ts index 45fb2bc302..06593c625f 100644 --- a/src/patcher/paragraph-token-replacer.ts +++ b/src/patcher/paragraph-token-replacer.ts @@ -1,6 +1,6 @@ import { Element } from "xml-js"; -import { createTextElementContents } from "./util"; +import { createTextElementContents, patchSpaceAttribute } from "./util"; import { IRenderedParagraphNode } from "./run-renderer"; enum ReplaceMode { @@ -43,6 +43,11 @@ export const replaceTokenInParagraphElement = ({ if (endIndex <= end) { const lastPart = text.substring(endIndex - start + 1); patchTextElement(paragraphElement.elements![run.index].elements![index], lastPart); + const currentElement = paragraphElement.elements![run.index].elements![index]; + // We need to add xml:space="preserve" to the last element to preserve the whitespace + // Otherwise, the text will be merged with the next element + // eslint-disable-next-line functional/immutable-data + paragraphElement.elements![run.index].elements![index] = patchSpaceAttribute(currentElement); replaceMode = ReplaceMode.END; } else { patchTextElement(paragraphElement.elements![run.index].elements![index], ""); diff --git a/src/patcher/replacer.ts b/src/patcher/replacer.ts index ce6e987d62..488b59760d 100644 --- a/src/patcher/replacer.ts +++ b/src/patcher/replacer.ts @@ -14,7 +14,12 @@ const formatter = new Formatter(); const SPLIT_TOKEN = "ɵ"; -export const replacer = (json: Element, patch: IPatch, patchText: string, renderedParagraphs: readonly IRenderedParagraphNode[]): Element => { +export const replacer = ( + json: Element, + patch: IPatch, + patchText: string, + renderedParagraphs: readonly IRenderedParagraphNode[], +): Element => { for (const renderedParagraph of renderedParagraphs) { const textJson = patch.children.map((c) => toJson(xml(formatter.format(c as XmlComponent)))).map((c) => c.elements![0]); @@ -24,7 +29,6 @@ export const replacer = (json: Element, patch: IPatch, patchText: string, render // eslint-disable-next-line functional/immutable-data, prefer-destructuring parentElement.elements?.splice(elementIndex, 1, ...textJson); } else if (patch.type === PatchType.PARAGRAPH) { - // Hard case where the text is only part of the paragraph const paragraphElement = goToElementFromPath(json, renderedParagraph.path); replaceTokenInParagraphElement({ diff --git a/src/patcher/traverser.ts b/src/patcher/traverser.ts index 95f7829042..5ee2f792ff 100644 --- a/src/patcher/traverser.ts +++ b/src/patcher/traverser.ts @@ -8,17 +8,6 @@ export interface ElementWrapper { readonly parent: ElementWrapper | undefined; } -export interface ILocationOfText { - readonly parent: Element; - readonly startIndex: number; - readonly endIndex: number; - readonly currentText: string; - // This is optional because the text could start in the middle of a tag - readonly startElement?: Element; - // This is optional because the text could end in the middle of a tag - readonly endElement?: Element; -} - const elementsToWrapper = (wrapper: ElementWrapper): readonly ElementWrapper[] => wrapper.element.elements?.map((e, i) => ({ element: e, @@ -56,7 +45,5 @@ export const findLocationOfText = (node: Element, text: string): readonly IRende } } - const filteredParagraphs = renderedParagraphs.filter((p) => p.text.includes(text)); - - return filteredParagraphs; + return renderedParagraphs.filter((p) => p.text.includes(text)); }; diff --git a/src/patcher/util.ts b/src/patcher/util.ts index f385a00025..bdb79eb0d0 100644 --- a/src/patcher/util.ts +++ b/src/patcher/util.ts @@ -17,3 +17,10 @@ export const createTextElementContents = (text: string): Element[] => { return textJson.elements![0].elements ?? []; }; + +export const patchSpaceAttribute = (element: Element): Element => ({ + ...element, + attributes: { + "xml:space": "preserve", + }, +});