Skip to content

Commit 2d2e4cd

Browse files
authored
fix: Missing text property for patchDocument (#2760)
* fix: Missing text property Ignore <br /> for patched files * Fix spelling issues
1 parent 0cadec7 commit 2d2e4cd

7 files changed

+751
-2
lines changed

demo/93-template-document.ts

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// Patch a document with patches
2+
3+
import * as fs from "fs";
4+
import { patchDocument, PatchType, TextRun } from "docx";
5+
6+
patchDocument({
7+
outputType: "nodebuffer",
8+
data: fs.readFileSync("demo/assets/field-trip.docx"),
9+
patches: {
10+
todays_date: {
11+
type: PatchType.PARAGRAPH,
12+
children: [new TextRun({ text: new Date().toLocaleDateString() })],
13+
},
14+
15+
school_name: {
16+
type: PatchType.PARAGRAPH,
17+
children: [new TextRun({ text: "test" })],
18+
},
19+
20+
address: {
21+
type: PatchType.PARAGRAPH,
22+
children: [new TextRun({ text: "blah blah" })],
23+
},
24+
25+
city: {
26+
type: PatchType.PARAGRAPH,
27+
children: [new TextRun({ text: "test" })],
28+
},
29+
30+
state: {
31+
type: PatchType.PARAGRAPH,
32+
children: [new TextRun({ text: "test" })],
33+
},
34+
35+
zip: {
36+
type: PatchType.PARAGRAPH,
37+
children: [new TextRun({ text: "test" })],
38+
},
39+
40+
phone: {
41+
type: PatchType.PARAGRAPH,
42+
children: [new TextRun({ text: "test" })],
43+
},
44+
45+
first_name: {
46+
type: PatchType.PARAGRAPH,
47+
children: [new TextRun({ text: "test" })],
48+
},
49+
50+
last_name: {
51+
type: PatchType.PARAGRAPH,
52+
children: [new TextRun({ text: "test" })],
53+
},
54+
55+
email_address: {
56+
type: PatchType.PARAGRAPH,
57+
children: [new TextRun({ text: "test" })],
58+
},
59+
60+
ft_dates: {
61+
type: PatchType.PARAGRAPH,
62+
children: [new TextRun({ text: "test" })],
63+
},
64+
65+
grade: {
66+
type: PatchType.PARAGRAPH,
67+
children: [new TextRun({ text: "test" })],
68+
},
69+
},
70+
}).then((doc) => {
71+
fs.writeFileSync("My Document.docx", doc);
72+
});

demo/assets/field-trip.docx

24.9 KB
Binary file not shown.

src/patcher/paragraph-split-inject.spec.ts

+60
Original file line numberDiff line numberDiff line change
@@ -273,5 +273,65 @@ describe("paragraph-split-inject", () => {
273273
},
274274
});
275275
});
276+
277+
it("should create an empty end element if it is at the end", () => {
278+
const output = splitRunElement(
279+
{
280+
type: "element",
281+
name: "w:r",
282+
elements: [
283+
{
284+
type: "element",
285+
name: "w:rPr",
286+
elements: [
287+
{ type: "element", name: "w:rFonts", attributes: { "w:eastAsia": "Times New Roman" } },
288+
{ type: "element", name: "w:kern", attributes: { "w:val": "0" } },
289+
{ type: "element", name: "w:sz", attributes: { "w:val": "20" } },
290+
{
291+
type: "element",
292+
name: "w:lang",
293+
attributes: { "w:val": "en-US", "w:eastAsia": "en-US", "w:bidi": "ar-SA" },
294+
},
295+
],
296+
},
297+
{ type: "element", name: "w:t", elements: [], attributes: { "xml:space": "preserve" } },
298+
{ type: "element", name: "w:br" },
299+
{ type: "element", name: "w:t", elements: [{ type: "text", text: "ɵ" }] },
300+
],
301+
},
302+
"ɵ",
303+
);
304+
305+
expect(output).to.deep.equal({
306+
left: {
307+
type: "element",
308+
name: "w:r",
309+
elements: [
310+
{
311+
type: "element",
312+
name: "w:rPr",
313+
elements: [
314+
{ type: "element", name: "w:rFonts", attributes: { "w:eastAsia": "Times New Roman" } },
315+
{ type: "element", name: "w:kern", attributes: { "w:val": "0" } },
316+
{ type: "element", name: "w:sz", attributes: { "w:val": "20" } },
317+
{
318+
type: "element",
319+
name: "w:lang",
320+
attributes: { "w:val": "en-US", "w:eastAsia": "en-US", "w:bidi": "ar-SA" },
321+
},
322+
],
323+
},
324+
{ type: "element", name: "w:t", elements: [], attributes: { "xml:space": "preserve" } },
325+
{ type: "element", name: "w:br" },
326+
{ type: "element", name: "w:t", elements: [], attributes: { "xml:space": "preserve" } },
327+
],
328+
},
329+
right: {
330+
type: "element",
331+
name: "w:r",
332+
elements: [{ type: "element", name: "w:t", elements: [], attributes: { "xml:space": "preserve" } }],
333+
},
334+
});
335+
});
276336
});
277337
});

src/patcher/paragraph-split-inject.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export const splitRunElement = (runElement: Element, token: string): { readonly
2929
runElement.elements
3030
?.map((e, i) => {
3131
if (e.type === "element" && e.name === "w:t") {
32-
const text = (e.elements?.[0].text as string) ?? "";
32+
const text = (e.elements?.[0]?.text as string) ?? "";
3333
const splitText = text.split(token);
3434
const newElements = splitText.map((t) => ({
3535
...e,

src/patcher/patch-detector.spec.ts

+168
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,147 @@ const MOCK_XML = `
195195
</w:document>
196196
`;
197197

198+
// cspell:disable
199+
const MOCK_XML_2 = `
200+
<w:body>
201+
<w:tbl>
202+
<w:tblPr>
203+
<w:tblStyle w:val="TableGrid" />
204+
<w:tblW w:w="9350" w:type="dxa" />
205+
<w:jc w:val="left" />
206+
<w:tblInd w:w="0" w:type="dxa" />
207+
<w:tblLayout w:type="fixed" />
208+
<w:tblCellMar>
209+
<w:top w:w="0" w:type="dxa" />
210+
<w:left w:w="108" w:type="dxa" />
211+
<w:bottom w:w="0" w:type="dxa" />
212+
<w:right w:w="108" w:type="dxa" />
213+
</w:tblCellMar>
214+
<w:tblLook w:firstRow="1" w:noVBand="1" w:lastRow="0" w:firstColumn="1"
215+
w:lastColumn="0" w:noHBand="0" w:val="04a0" />
216+
</w:tblPr>
217+
<w:tblGrid>
218+
<w:gridCol w:w="3119" />
219+
<w:gridCol w:w="3141" />
220+
<w:gridCol w:w="3090" />
221+
</w:tblGrid>
222+
<w:tr>
223+
<w:trPr></w:trPr>
224+
<w:tc>
225+
<w:tcPr>
226+
<w:tcW w:w="3119" w:type="dxa" />
227+
<w:tcBorders>
228+
<w:right w:val="nil" />
229+
</w:tcBorders>
230+
<w:shd w:color="auto" w:fill="D9D9D9" w:themeFill="background1"
231+
w:themeFillShade="d9" w:val="clear" />
232+
</w:tcPr>
233+
<w:p>
234+
<w:pPr>
235+
<w:pStyle w:val="NormalSpaceAboveandBelow" />
236+
<w:widowControl />
237+
<w:spacing w:before="120" w:after="120" />
238+
<w:jc w:val="left" />
239+
<w:rPr>
240+
<w:rFonts w:eastAsia="Times New Roman" />
241+
<w:kern w:val="0" />
242+
<w:sz w:val="20" />
243+
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
244+
</w:rPr>
245+
</w:pPr>
246+
<w:r>
247+
<w:rPr>
248+
<w:rFonts w:eastAsia="Times New Roman" />
249+
<w:kern w:val="0" />
250+
<w:sz w:val="20" />
251+
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
252+
</w:rPr>
253+
<w:t>{{</w:t>
254+
</w:r>
255+
<w:r>
256+
<w:rPr>
257+
<w:rFonts w:eastAsia="Times New Roman" />
258+
<w:kern w:val="0" />
259+
<w:sz w:val="20" />
260+
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
261+
</w:rPr>
262+
<w:t>s</w:t>
263+
</w:r>
264+
<w:r>
265+
<w:rPr>
266+
<w:rFonts w:eastAsia="Times New Roman" />
267+
<w:kern w:val="0" />
268+
<w:sz w:val="20" />
269+
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
270+
</w:rPr>
271+
<w:t>chool_</w:t>
272+
</w:r>
273+
<w:r>
274+
<w:rPr>
275+
<w:rFonts w:eastAsia="Times New Roman" />
276+
<w:kern w:val="0" />
277+
<w:sz w:val="20" />
278+
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
279+
</w:rPr>
280+
<w:t>n</w:t>
281+
</w:r>
282+
<w:r>
283+
<w:rPr>
284+
<w:rFonts w:eastAsia="Times New Roman" />
285+
<w:kern w:val="0" />
286+
<w:sz w:val="20" />
287+
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
288+
</w:rPr>
289+
<w:t>ame}}</w:t>
290+
<w:br />
291+
<w:t>{{</w:t>
292+
</w:r>
293+
<w:r>
294+
<w:rPr>
295+
<w:rFonts w:eastAsia="Times New Roman" />
296+
<w:kern w:val="0" />
297+
<w:sz w:val="20" />
298+
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
299+
</w:rPr>
300+
<w:t>a</w:t>
301+
</w:r>
302+
<w:r>
303+
<w:rPr>
304+
<w:rFonts w:eastAsia="Times New Roman" />
305+
<w:kern w:val="0" />
306+
<w:sz w:val="20" />
307+
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
308+
</w:rPr>
309+
<w:t>ddr</w:t>
310+
</w:r>
311+
<w:r>
312+
<w:rPr>
313+
<w:rFonts w:eastAsia="Times New Roman" />
314+
<w:kern w:val="0" />
315+
<w:sz w:val="20" />
316+
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
317+
</w:rPr>
318+
<w:t>ess</w:t>
319+
</w:r>
320+
<w:r>
321+
<w:rPr>
322+
<w:rFonts w:eastAsia="Times New Roman" />
323+
<w:kern w:val="0" />
324+
<w:sz w:val="20" />
325+
<w:lang w:val="en-US" w:eastAsia="en-US" w:bidi="ar-SA" />
326+
</w:rPr>
327+
<w:t>}}</w:t>
328+
<w:br />
329+
<w:t>{{</w:t>
330+
</w:r>
331+
</w:p>
332+
</w:tc>
333+
</w:tr>
334+
</w:tbl>
335+
</w:body>
336+
`;
337+
// cspell:enable
338+
198339
describe("patch-detector", () => {
199340
describe("patchDetector", () => {
200341
describe("document.xml and [Content_Types].xml", () => {
@@ -222,4 +363,31 @@ describe("patch-detector", () => {
222363
});
223364
});
224365
});
366+
367+
describe("patchDetector", () => {
368+
describe("document.xml and [Content_Types].xml", () => {
369+
beforeEach(() => {
370+
vi.spyOn(JSZip, "loadAsync").mockReturnValue(
371+
new Promise<JSZip>((resolve) => {
372+
const zip = new JSZip();
373+
374+
zip.file("word/document.xml", MOCK_XML_2);
375+
zip.file("[Content_Types].xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`);
376+
resolve(zip);
377+
}),
378+
);
379+
});
380+
381+
afterEach(() => {
382+
vi.restoreAllMocks();
383+
});
384+
385+
it("should patch the document", async () => {
386+
const output = await patchDetector({
387+
data: Buffer.from(""),
388+
});
389+
expect(output).toMatchObject(["school_name", "address"]);
390+
});
391+
});
392+
});
225393
});

0 commit comments

Comments
 (0)