Fix style being missed on patchDocument

There were scenarios in which patching a document would result in loss
of style for the template runs and, possibly, their right-adjacent run
as well (post-splitting). That was due to the run style elements not
being added to the newly created runs.

This commit addresses this issue by introducing a new, optional, flag
for the `patchDocument` function: `keepOriginalStyles`. It defaults to
`false` (current behavior) and, when `true`, ensures that there is no
loss of styling.

This should address https://github.com/dolanmiu/docx/issues/2293.
This commit is contained in:
Wilk Maia
2023-10-18 12:58:01 -03:00
parent d5d80550e7
commit a89ee463e6
3 changed files with 134 additions and 2 deletions

View File

@ -49,6 +49,7 @@ export type IPatch = ParagraphPatch | FilePatch;
export interface PatchDocumentOptions { export interface PatchDocumentOptions {
readonly patches: { readonly [key: string]: IPatch }; readonly patches: { readonly [key: string]: IPatch };
readonly keepOriginalStyles?: boolean;
} }
const imageReplacer = new ImageReplacer(); const imageReplacer = new ImageReplacer();
@ -128,6 +129,7 @@ export const patchDocument = async (data: InputDataType, options: PatchDocumentO
patchText, patchText,
renderedParagraphs, renderedParagraphs,
context, context,
options.keepOriginalStyles,
); );
} }

View File

@ -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"); 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", () => { it("should replace document type", () => {
const output = replacer( const output = replacer(
MOCK_JSON, MOCK_JSON,

View File

@ -20,6 +20,7 @@ export const replacer = (
patchText: string, patchText: string,
renderedParagraphs: readonly IRenderedParagraphNode[], renderedParagraphs: readonly IRenderedParagraphNode[],
context: IContext, context: IContext,
keepOriginalStyles: boolean = false,
): Element => { ): Element => {
for (const renderedParagraph of renderedParagraphs) { for (const renderedParagraph of renderedParagraphs) {
const textJson = patch.children const textJson = patch.children
@ -47,9 +48,29 @@ export const replacer = (
const index = findRunElementIndexWithToken(paragraphElement, SPLIT_TOKEN); 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 // eslint-disable-next-line functional/immutable-data
paragraphElement.elements!.splice(index, 1, left, ...textJson, right); paragraphElement.elements!.splice(index, 1, left, ...newRunElements, patchedRightElement);
break; break;
} }
} }