From 86de252a52b053974501905ecd35def980589118 Mon Sep 17 00:00:00 2001 From: Dolan Date: Fri, 17 Feb 2023 10:38:03 +0000 Subject: [PATCH] Extract runs and text --- demo/85-template-document.ts | 7 ++- demo/assets/simple-template.docx | Bin 12379 -> 12624 bytes src/templater/from-docx.ts | 24 ++++++---- src/templater/replacer.ts | 4 +- src/templater/run-renderer.ts | 78 +++++++++++++++++++++++++++++++ src/templater/traverser.ts | 43 +++++++++++++++++ 6 files changed, 144 insertions(+), 12 deletions(-) create mode 100644 src/templater/run-renderer.ts create mode 100644 src/templater/traverser.ts diff --git a/demo/85-template-document.ts b/demo/85-template-document.ts index 43e345b0b0..68537abaa6 100644 --- a/demo/85-template-document.ts +++ b/demo/85-template-document.ts @@ -4,7 +4,12 @@ import * as fs from "fs"; import { Paragraph, patchDocument, TextRun } from "../build"; patchDocument(fs.readFileSync("demo/assets/simple-template.docx"), { - children: [new Paragraph("ff"), new TextRun("fgf")], + patches: [ + { + children: [new Paragraph("ff"), new TextRun("fgf")], + text: "{{ name }}", + }, + ], }).then((doc) => { fs.writeFileSync("My Document.docx", doc); }); diff --git a/demo/assets/simple-template.docx b/demo/assets/simple-template.docx index c9afe4f66dd93bd80a191133b469030453b61a4c..1ff29bc52974b12438f3e01b325c0a84c6113f19 100644 GIT binary patch delta 7443 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 => { const zipContent = await JSZip.loadAsync(data); - const map = new Map(); - console.log(options); + const map = new Map(); for (const [key, value] of Object.entries(zipContent.files)) { const json = toJson(await value.async("text")); if (key === "word/document.xml") { - console.log(json); - replacer(json, options); + for (const patch of options.patches) { + findLocationOfText(json, patch.text); + replacer(json, patch); + } } map.set(key, json); @@ -42,12 +48,12 @@ export const patchDocument = async (data: InputDataType, options: PatchDocumentO return zipData; }; -const toJson = (xmlData: string): ElementCompact => { - const xmlObj = xml2js(xmlData, { compact: false }) as ElementCompact; +const toJson = (xmlData: string): Element => { + const xmlObj = xml2js(xmlData, { compact: false }) as Element; return xmlObj; }; -const toXml = (jsonObj: ElementCompact): string => { +const toXml = (jsonObj: Element): string => { const output = js2xml(jsonObj); return output; }; diff --git a/src/templater/replacer.ts b/src/templater/replacer.ts index 830c0a5054..be6e301ebf 100644 --- a/src/templater/replacer.ts +++ b/src/templater/replacer.ts @@ -1,8 +1,8 @@ import { Paragraph, TextRun } from "@file/paragraph"; import { ElementCompact } from "xml-js"; -import { PatchDocumentOptions } from "./from-docx"; +import { IPatch } from "./from-docx"; -export const replacer = (json: ElementCompact, options: PatchDocumentOptions): ElementCompact => { +export const replacer = (json: ElementCompact, options: IPatch): ElementCompact => { for (const child of options.children) { if (child instanceof Paragraph) { console.log("is para"); diff --git a/src/templater/run-renderer.ts b/src/templater/run-renderer.ts new file mode 100644 index 0000000000..6efc7a04b9 --- /dev/null +++ b/src/templater/run-renderer.ts @@ -0,0 +1,78 @@ +import { Element } from "xml-js"; + +export interface IRenderedParagraphNode { + readonly text: string; + readonly runs: readonly IRenderedRunNode[]; +} + +interface IParts { + readonly text: string; + readonly index: number; +} + +export interface IRenderedRunNode { + readonly text: string; + readonly parts: readonly IParts[]; + readonly index: number; +} + +export const renderParagraphNode = (node: Element): IRenderedParagraphNode => { + if (node.name !== "w:p") { + throw new Error(`Invalid node type: ${node.name}`); + } + + if (!node.elements) { + return { + text: "", + runs: [], + }; + } + + const runs = node.elements + .map((element, i) => ({ element, i })) + .filter(({ element }) => element.name === "w:r") + .map(({ element, i }) => renderRunNode(element, i)) + .filter((e) => !!e) + .map((e) => e as IRenderedRunNode); + + const text = runs.reduce((acc, curr) => acc + curr.text, ""); + + return { + text, + runs, + }; +}; + +const renderRunNode = (node: Element, index: number): IRenderedRunNode => { + if (node.name !== "w:r") { + throw new Error(`Invalid node type: ${node.name}`); + } + + if (!node.elements) { + return { + text: "", + parts: [], + index: -1, + }; + } + + const parts = node.elements + .map((element, i: number) => + element.name === "w:t" && element.elements + ? { + text: element.elements[0].text?.toString() ?? "", + index: i, + } + : undefined, + ) + .filter((e) => !!e) + .map((e) => e as IParts); + + const text = parts.reduce((acc, curr) => acc + curr.text, ""); + + return { + text, + parts, + index, + }; +}; diff --git a/src/templater/traverser.ts b/src/templater/traverser.ts new file mode 100644 index 0000000000..61abf5f8d6 --- /dev/null +++ b/src/templater/traverser.ts @@ -0,0 +1,43 @@ +import { Element } from "xml-js"; +import { IRenderedParagraphNode, renderParagraphNode } from "./run-renderer"; + +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; +} + +export const findLocationOfText = (node: Element, text: string): void => { + let renderedParagraphs: readonly IRenderedParagraphNode[] = []; + + // eslint-disable-next-line functional/prefer-readonly-type + const queue: Element[] = [...(node.elements ?? [])]; + + // eslint-disable-next-line functional/immutable-data + let currentNode: Element | undefined; + while (queue.length > 0) { + // eslint-disable-next-line functional/immutable-data + currentNode = queue.shift(); + + if (!currentNode) { + break; + } + + if (currentNode.name === "w:p") { + renderedParagraphs = [...renderedParagraphs, renderParagraphNode(currentNode)]; + } else { + // eslint-disable-next-line functional/immutable-data + queue.push(...(currentNode.elements ?? [])); + } + } + + const filteredParagraphs = renderedParagraphs.filter((p) => p.text.includes(text)); + + console.log("paragrapghs", JSON.stringify(filteredParagraphs, null, 2)); + return undefined; +};