79 lines
1.8 KiB
TypeScript
79 lines
1.8 KiB
TypeScript
![]() |
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,
|
||
|
};
|
||
|
};
|