Files
docx-js/src/templater/run-renderer.ts

114 lines
3.1 KiB
TypeScript
Raw Normal View History

2023-02-17 10:38:03 +00:00
import { Element } from "xml-js";
2023-02-18 20:36:24 +00:00
import { ElementWrapper } from "./traverser";
2023-02-17 10:38:03 +00:00
export interface IRenderedParagraphNode {
readonly text: string;
readonly runs: readonly IRenderedRunNode[];
2023-02-18 20:36:24 +00:00
readonly index: number;
readonly path: readonly number[];
}
interface StartAndEnd {
readonly start: number;
readonly end: number;
2023-02-17 10:38:03 +00:00
}
2023-02-18 20:36:24 +00:00
type IParts = {
2023-02-17 10:38:03 +00:00
readonly text: string;
readonly index: number;
2023-02-18 20:36:24 +00:00
} & StartAndEnd;
2023-02-17 10:38:03 +00:00
2023-02-18 20:36:24 +00:00
export type IRenderedRunNode = {
2023-02-17 10:38:03 +00:00
readonly text: string;
readonly parts: readonly IParts[];
readonly index: number;
2023-02-18 20:36:24 +00:00
} & StartAndEnd;
2023-02-17 10:38:03 +00:00
2023-02-18 20:36:24 +00:00
export const renderParagraphNode = (node: ElementWrapper): IRenderedParagraphNode => {
if (node.element.name !== "w:p") {
throw new Error(`Invalid node type: ${node.element.name}`);
2023-02-17 10:38:03 +00:00
}
2023-02-18 20:36:24 +00:00
if (!node.element.elements) {
2023-02-17 10:38:03 +00:00
return {
text: "",
runs: [],
2023-02-18 20:36:24 +00:00
index: -1,
path: [],
2023-02-17 10:38:03 +00:00
};
}
2023-02-18 20:36:24 +00:00
let currentRunStringLength = 0;
const runs = node.element.elements
2023-02-17 10:38:03 +00:00
.map((element, i) => ({ element, i }))
.filter(({ element }) => element.name === "w:r")
2023-02-18 20:36:24 +00:00
.map(({ element, i }) => {
const renderedRunNode = renderRunNode(element, i, currentRunStringLength);
currentRunStringLength += renderedRunNode.text.length;
return renderedRunNode;
})
2023-02-17 10:38:03 +00:00
.filter((e) => !!e)
.map((e) => e as IRenderedRunNode);
const text = runs.reduce((acc, curr) => acc + curr.text, "");
return {
text,
runs,
2023-02-18 20:36:24 +00:00
index: node.index,
path: buildNodePath(node),
2023-02-17 10:38:03 +00:00
};
};
2023-02-18 20:36:24 +00:00
const renderRunNode = (node: Element, index: number, currentRunStringIndex: number): IRenderedRunNode => {
2023-02-17 10:38:03 +00:00
if (node.name !== "w:r") {
throw new Error(`Invalid node type: ${node.name}`);
}
if (!node.elements) {
return {
text: "",
parts: [],
index: -1,
2023-02-18 20:36:24 +00:00
start: currentRunStringIndex,
end: currentRunStringIndex,
2023-02-17 10:38:03 +00:00
};
}
2023-02-18 20:36:24 +00:00
let currentTextStringIndex = currentRunStringIndex;
2023-02-17 10:38:03 +00:00
const parts = node.elements
.map((element, i: number) =>
element.name === "w:t" && element.elements
? {
text: element.elements[0].text?.toString() ?? "",
index: i,
2023-02-18 20:36:24 +00:00
start: currentTextStringIndex,
end: (() => {
// Side effect
currentTextStringIndex += (element.elements[0].text?.toString() ?? "").length - 1;
return currentTextStringIndex;
})(),
2023-02-17 10:38:03 +00:00
}
: undefined,
)
.filter((e) => !!e)
.map((e) => e as IParts);
const text = parts.reduce((acc, curr) => acc + curr.text, "");
return {
text,
parts,
index,
2023-02-18 20:36:24 +00:00
start: currentRunStringIndex,
end: currentTextStringIndex,
2023-02-17 10:38:03 +00:00
};
};
2023-02-18 20:36:24 +00:00
const buildNodePath = (node: ElementWrapper): readonly number[] =>
node.parent ? [...buildNodePath(node.parent), node.index] : [node.index];