Add tests to patcher
This commit is contained in:
@ -56,6 +56,7 @@ patchDocument(fs.readFileSync("demo/assets/simple-template.docx"), {
|
|||||||
],
|
],
|
||||||
link: "https://www.google.co.uk",
|
link: "https://www.google.co.uk",
|
||||||
}),
|
}),
|
||||||
|
new ImageRun({ data: fs.readFileSync("./demo/images/dog.png"), transformation: { width: 100, height: 100 } }),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
|
@ -201,7 +201,7 @@ const MOCK_XML = `
|
|||||||
|
|
||||||
describe("from-docx", () => {
|
describe("from-docx", () => {
|
||||||
describe("patchDocument", () => {
|
describe("patchDocument", () => {
|
||||||
before(() => {
|
beforeEach(() => {
|
||||||
sinon.createStubInstance(JSZip, {});
|
sinon.createStubInstance(JSZip, {});
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
sinon.stub(JSZip, "loadAsync").callsFake(
|
sinon.stub(JSZip, "loadAsync").callsFake(
|
||||||
@ -216,11 +216,11 @@ describe("from-docx", () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
after(() => {
|
afterEach(() => {
|
||||||
(JSZip.loadAsync as unknown as sinon.SinonStub).restore();
|
(JSZip.loadAsync as unknown as sinon.SinonStub).restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should find the index of a run element with a token", async () => {
|
it("should patch the document", async () => {
|
||||||
const output = await patchDocument(Buffer.from(""), {
|
const output = await patchDocument(Buffer.from(""), {
|
||||||
patches: {
|
patches: {
|
||||||
name: {
|
name: {
|
||||||
@ -256,6 +256,10 @@ describe("from-docx", () => {
|
|||||||
],
|
],
|
||||||
link: "https://www.google.co.uk",
|
link: "https://www.google.co.uk",
|
||||||
}),
|
}),
|
||||||
|
new ImageRun({
|
||||||
|
data: Buffer.from(""),
|
||||||
|
transformation: { width: 100, height: 100 },
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
@ -274,5 +278,75 @@ describe("from-docx", () => {
|
|||||||
});
|
});
|
||||||
expect(output).to.not.be.undefined;
|
expect(output).to.not.be.undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should patch the document", async () => {
|
||||||
|
const output = await patchDocument(Buffer.from(""), {
|
||||||
|
patches: {},
|
||||||
|
});
|
||||||
|
expect(output).to.not.be.undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should use the relationships file rather than create one", () => {
|
||||||
|
(JSZip.loadAsync as unknown as sinon.SinonStub).restore();
|
||||||
|
sinon.createStubInstance(JSZip, {});
|
||||||
|
sinon.stub(JSZip, "loadAsync").callsFake(
|
||||||
|
() =>
|
||||||
|
new Promise<JSZip>((resolve) => {
|
||||||
|
const zip = new JSZip();
|
||||||
|
|
||||||
|
zip.file("word/document.xml", MOCK_XML);
|
||||||
|
zip.file("word/_rels/document.xml.rels", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`);
|
||||||
|
zip.file("[Content_Types].xml", `<?xml version="1.0" encoding="UTF-8" standalone="yes"?>`);
|
||||||
|
resolve(zip);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
const output = patchDocument(Buffer.from(""), {
|
||||||
|
patches: {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
image_test: {
|
||||||
|
type: PatchType.PARAGRAPH,
|
||||||
|
children: [
|
||||||
|
new ImageRun({
|
||||||
|
data: Buffer.from(""),
|
||||||
|
transformation: { width: 100, height: 100 },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(output).to.not.be.undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw an error if the content types is not found", () => {
|
||||||
|
(JSZip.loadAsync as unknown as sinon.SinonStub).restore();
|
||||||
|
sinon.createStubInstance(JSZip, {});
|
||||||
|
sinon.stub(JSZip, "loadAsync").callsFake(
|
||||||
|
() =>
|
||||||
|
new Promise<JSZip>((resolve) => {
|
||||||
|
const zip = new JSZip();
|
||||||
|
|
||||||
|
zip.file("word/document.xml", MOCK_XML);
|
||||||
|
resolve(zip);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(() =>
|
||||||
|
patchDocument(Buffer.from(""), {
|
||||||
|
patches: {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||||
|
image_test: {
|
||||||
|
type: PatchType.PARAGRAPH,
|
||||||
|
children: [
|
||||||
|
new ImageRun({
|
||||||
|
data: Buffer.from(""),
|
||||||
|
transformation: { width: 100, height: 100 },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).to.throw();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -140,25 +140,18 @@ export const patchDocument = async (data: InputDataType, options: PatchDocumentO
|
|||||||
for (const { key, mediaDatas } of imageRelationshipAdditions) {
|
for (const { key, mediaDatas } of imageRelationshipAdditions) {
|
||||||
// eslint-disable-next-line functional/immutable-data
|
// eslint-disable-next-line functional/immutable-data
|
||||||
const relationshipKey = `word/_rels/${key.split("/").pop()}.rels`;
|
const relationshipKey = `word/_rels/${key.split("/").pop()}.rels`;
|
||||||
|
const relationshipsJson = map.get(relationshipKey) ?? createRelationshipFile();
|
||||||
if (!map.has(relationshipKey)) {
|
map.set(relationshipKey, relationshipsJson);
|
||||||
map.set(relationshipKey, createRelationshipFile());
|
|
||||||
}
|
|
||||||
|
|
||||||
const relationshipsJson = map.get(relationshipKey);
|
|
||||||
|
|
||||||
if (!relationshipsJson) {
|
|
||||||
throw new Error("Could not find relationships file");
|
|
||||||
}
|
|
||||||
|
|
||||||
const index = getNextRelationshipIndex(relationshipsJson);
|
const index = getNextRelationshipIndex(relationshipsJson);
|
||||||
const newJson = imageReplacer.replace(JSON.stringify(map.get(key)), mediaDatas, index);
|
const newJson = imageReplacer.replace(JSON.stringify(map.get(key)), mediaDatas, index);
|
||||||
map.set(key, JSON.parse(newJson) as Element);
|
map.set(key, JSON.parse(newJson) as Element);
|
||||||
|
|
||||||
for (const { fileName } of mediaDatas) {
|
for (let i = 0; i < mediaDatas.length; i++) {
|
||||||
|
const { fileName } = mediaDatas[i];
|
||||||
appendRelationship(
|
appendRelationship(
|
||||||
relationshipsJson,
|
relationshipsJson,
|
||||||
index,
|
index + i,
|
||||||
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
"http://schemas.openxmlformats.org/officeDocument/2006/relationships/image",
|
||||||
`media/${fileName}`,
|
`media/${fileName}`,
|
||||||
);
|
);
|
||||||
|
@ -32,6 +32,60 @@ describe("paragraph-split-inject", () => {
|
|||||||
);
|
);
|
||||||
expect(output).to.deep.equal(0);
|
expect(output).to.deep.equal(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should throw an exception when ran with empty elements", () => {
|
||||||
|
expect(() =>
|
||||||
|
findRunElementIndexWithToken(
|
||||||
|
{
|
||||||
|
name: "w:p",
|
||||||
|
type: "element",
|
||||||
|
},
|
||||||
|
"hello",
|
||||||
|
),
|
||||||
|
).to.throw();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw an exception when ran with empty elements", () => {
|
||||||
|
expect(() =>
|
||||||
|
findRunElementIndexWithToken(
|
||||||
|
{
|
||||||
|
name: "w:p",
|
||||||
|
type: "element",
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
name: "w:r",
|
||||||
|
type: "element",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"hello",
|
||||||
|
),
|
||||||
|
).to.throw();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw an exception when ran with empty elements", () => {
|
||||||
|
expect(() =>
|
||||||
|
findRunElementIndexWithToken(
|
||||||
|
{
|
||||||
|
name: "w:p",
|
||||||
|
type: "element",
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
name: "w:r",
|
||||||
|
type: "element",
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
name: "w:t",
|
||||||
|
type: "element",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"hello",
|
||||||
|
),
|
||||||
|
).to.throw();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("splitRunElement", () => {
|
describe("splitRunElement", () => {
|
||||||
@ -51,6 +105,10 @@ describe("paragraph-split-inject", () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "w:x",
|
||||||
|
type: "element",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
"*",
|
"*",
|
||||||
@ -91,11 +149,76 @@ describe("paragraph-split-inject", () => {
|
|||||||
name: "w:t",
|
name: "w:t",
|
||||||
type: "element",
|
type: "element",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "w:x",
|
||||||
|
type: "element",
|
||||||
|
},
|
||||||
],
|
],
|
||||||
name: "w:r",
|
name: "w:r",
|
||||||
type: "element",
|
type: "element",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should try to split even if elements is empty for text", () => {
|
||||||
|
const output = splitRunElement(
|
||||||
|
{
|
||||||
|
name: "w:r",
|
||||||
|
type: "element",
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
name: "w:t",
|
||||||
|
type: "element",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"*",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(output).to.deep.equal({
|
||||||
|
left: {
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
attributes: {
|
||||||
|
"xml:space": "preserve",
|
||||||
|
},
|
||||||
|
elements: [],
|
||||||
|
name: "w:t",
|
||||||
|
type: "element",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
name: "w:r",
|
||||||
|
type: "element",
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
elements: [],
|
||||||
|
name: "w:r",
|
||||||
|
type: "element",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return empty elements", () => {
|
||||||
|
const output = splitRunElement(
|
||||||
|
{
|
||||||
|
name: "w:r",
|
||||||
|
type: "element",
|
||||||
|
},
|
||||||
|
"*",
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(output).to.deep.equal({
|
||||||
|
left: {
|
||||||
|
elements: [],
|
||||||
|
name: "w:r",
|
||||||
|
type: "element",
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
elements: [],
|
||||||
|
name: "w:r",
|
||||||
|
type: "element",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -25,7 +25,7 @@ export const splitRunElement = (runElement: Element, token: string): { readonly
|
|||||||
runElement.elements
|
runElement.elements
|
||||||
?.map((e, i) => {
|
?.map((e, i) => {
|
||||||
if (e.type === "element" && e.name === "w:t") {
|
if (e.type === "element" && e.name === "w:t") {
|
||||||
const text = e.elements?.[0].text as string;
|
const text = (e.elements?.[0].text as string) ?? "";
|
||||||
const splitText = text.split(token);
|
const splitText = text.split(token);
|
||||||
const newElements = splitText.map((t) => ({
|
const newElements = splitText.map((t) => ({
|
||||||
...e,
|
...e,
|
||||||
|
@ -70,5 +70,96 @@ describe("paragraph-token-replacer", () => {
|
|||||||
name: "w:p",
|
name: "w:p",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Try to fill rest of test coverage
|
||||||
|
// it("should replace token in paragraph", () => {
|
||||||
|
// const output = replaceTokenInParagraphElement({
|
||||||
|
// paragraphElement: {
|
||||||
|
// name: "w:p",
|
||||||
|
// elements: [
|
||||||
|
// {
|
||||||
|
// name: "w:r",
|
||||||
|
// elements: [
|
||||||
|
// {
|
||||||
|
// name: "w:t",
|
||||||
|
// elements: [
|
||||||
|
// {
|
||||||
|
// type: "text",
|
||||||
|
// text: "test ",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// name: "w:t",
|
||||||
|
// elements: [
|
||||||
|
// {
|
||||||
|
// type: "text",
|
||||||
|
// text: " hello ",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// },
|
||||||
|
// renderedParagraph: {
|
||||||
|
// index: 0,
|
||||||
|
// path: [0],
|
||||||
|
// runs: [
|
||||||
|
// {
|
||||||
|
// end: 4,
|
||||||
|
// index: 0,
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// end: 4,
|
||||||
|
// index: 0,
|
||||||
|
// start: 0,
|
||||||
|
// text: "test ",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// start: 0,
|
||||||
|
// text: "test ",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// end: 10,
|
||||||
|
// index: 0,
|
||||||
|
// parts: [
|
||||||
|
// {
|
||||||
|
// end: 10,
|
||||||
|
// index: 0,
|
||||||
|
// start: 5,
|
||||||
|
// text: "hello ",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// start: 5,
|
||||||
|
// text: "hello ",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// text: "test hello ",
|
||||||
|
// },
|
||||||
|
// originalText: "hello",
|
||||||
|
// replacementText: "world",
|
||||||
|
// });
|
||||||
|
|
||||||
|
// expect(output).to.deep.equal({
|
||||||
|
// elements: [
|
||||||
|
// {
|
||||||
|
// elements: [
|
||||||
|
// {
|
||||||
|
// elements: [
|
||||||
|
// {
|
||||||
|
// text: "test world ",
|
||||||
|
// type: "text",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// name: "w:t",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// name: "w:r",
|
||||||
|
// },
|
||||||
|
// ],
|
||||||
|
// name: "w:p",
|
||||||
|
// });
|
||||||
|
// });
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -20,6 +20,32 @@ describe("relationship-manager", () => {
|
|||||||
});
|
});
|
||||||
expect(output).to.deep.equal(2);
|
expect(output).to.deep.equal(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should work with an empty relationship Id", () => {
|
||||||
|
const output = getNextRelationshipIndex({
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "Relationships",
|
||||||
|
elements: [{ type: "element", name: "Relationship" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(output).to.deep.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should work with no relationships", () => {
|
||||||
|
const output = getNextRelationshipIndex({
|
||||||
|
elements: [
|
||||||
|
{
|
||||||
|
type: "element",
|
||||||
|
name: "Relationships",
|
||||||
|
elements: [],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
expect(output).to.deep.equal(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("appendRelationship", () => {
|
describe("appendRelationship", () => {
|
||||||
|
@ -3,15 +3,18 @@ import { Element } from "xml-js";
|
|||||||
import { RelationshipType, TargetModeType } from "@file/relationships/relationship/relationship";
|
import { RelationshipType, TargetModeType } from "@file/relationships/relationship/relationship";
|
||||||
import { getFirstLevelElements } from "./util";
|
import { getFirstLevelElements } from "./util";
|
||||||
|
|
||||||
const getIdFromRelationshipId = (relationshipId: string): number => parseInt(relationshipId.substring(3), 10);
|
const getIdFromRelationshipId = (relationshipId: string): number => {
|
||||||
|
const output = parseInt(relationshipId.substring(3), 10);
|
||||||
|
return isNaN(output) ? 0 : output;
|
||||||
|
};
|
||||||
|
|
||||||
export const getNextRelationshipIndex = (relationships: Element): number => {
|
export const getNextRelationshipIndex = (relationships: Element): number => {
|
||||||
const relationshipElements = getFirstLevelElements(relationships, "Relationships");
|
const relationshipElements = getFirstLevelElements(relationships, "Relationships");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
(relationshipElements
|
relationshipElements
|
||||||
.map((e) => getIdFromRelationshipId(e.attributes?.Id?.toString() ?? ""))
|
.map((e) => getIdFromRelationshipId(e.attributes?.Id?.toString() ?? ""))
|
||||||
.reduce((acc, curr) => Math.max(acc, curr), 0) ?? 0) + 1
|
.reduce((acc, curr) => Math.max(acc, curr), 0) + 1
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { IViewWrapper } from "@file/document-wrapper";
|
import { IViewWrapper } from "@file/document-wrapper";
|
||||||
import { File } from "@file/file";
|
import { File } from "@file/file";
|
||||||
import { TextRun } from "@file/paragraph";
|
import { Paragraph, TextRun } from "@file/paragraph";
|
||||||
import { IContext } from "@file/xml-components";
|
import { IContext } from "@file/xml-components";
|
||||||
import { expect } from "chai";
|
import { expect } from "chai";
|
||||||
import * as sinon from "sinon";
|
import * as sinon from "sinon";
|
||||||
@ -14,41 +14,6 @@ const MOCK_JSON = {
|
|||||||
{
|
{
|
||||||
type: "element",
|
type: "element",
|
||||||
name: "w:hdr",
|
name: "w:hdr",
|
||||||
attributes: {
|
|
||||||
"xmlns:wpc": "http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas",
|
|
||||||
"xmlns:cx": "http://schemas.microsoft.com/office/drawing/2014/chartex",
|
|
||||||
"xmlns:cx1": "http://schemas.microsoft.com/office/drawing/2015/9/8/chartex",
|
|
||||||
"xmlns:cx2": "http://schemas.microsoft.com/office/drawing/2015/10/21/chartex",
|
|
||||||
"xmlns:cx3": "http://schemas.microsoft.com/office/drawing/2016/5/9/chartex",
|
|
||||||
"xmlns:cx4": "http://schemas.microsoft.com/office/drawing/2016/5/10/chartex",
|
|
||||||
"xmlns:cx5": "http://schemas.microsoft.com/office/drawing/2016/5/11/chartex",
|
|
||||||
"xmlns:cx6": "http://schemas.microsoft.com/office/drawing/2016/5/12/chartex",
|
|
||||||
"xmlns:cx7": "http://schemas.microsoft.com/office/drawing/2016/5/13/chartex",
|
|
||||||
"xmlns:cx8": "http://schemas.microsoft.com/office/drawing/2016/5/14/chartex",
|
|
||||||
"xmlns:mc": "http://schemas.openxmlformats.org/markup-compatibility/2006",
|
|
||||||
"xmlns:aink": "http://schemas.microsoft.com/office/drawing/2016/ink",
|
|
||||||
"xmlns:am3d": "http://schemas.microsoft.com/office/drawing/2017/model3d",
|
|
||||||
"xmlns:o": "urn:schemas-microsoft-com:office:office",
|
|
||||||
"xmlns:oel": "http://schemas.microsoft.com/office/2019/extlst",
|
|
||||||
"xmlns:r": "http://schemas.openxmlformats.org/officeDocument/2006/relationships",
|
|
||||||
"xmlns:m": "http://schemas.openxmlformats.org/officeDocument/2006/math",
|
|
||||||
"xmlns:v": "urn:schemas-microsoft-com:vml",
|
|
||||||
"xmlns:wp14": "http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing",
|
|
||||||
"xmlns:wp": "http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing",
|
|
||||||
"xmlns:w10": "urn:schemas-microsoft-com:office:word",
|
|
||||||
"xmlns:w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main",
|
|
||||||
"xmlns:w14": "http://schemas.microsoft.com/office/word/2010/wordml",
|
|
||||||
"xmlns:w15": "http://schemas.microsoft.com/office/word/2012/wordml",
|
|
||||||
"xmlns:w16cex": "http://schemas.microsoft.com/office/word/2018/wordml/cex",
|
|
||||||
"xmlns:w16cid": "http://schemas.microsoft.com/office/word/2016/wordml/cid",
|
|
||||||
"xmlns:w16": "http://schemas.microsoft.com/office/word/2018/wordml",
|
|
||||||
"xmlns:w16sdtdh": "http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash",
|
|
||||||
"xmlns:w16se": "http://schemas.microsoft.com/office/word/2015/wordml/symex",
|
|
||||||
"xmlns:wpg": "http://schemas.microsoft.com/office/word/2010/wordprocessingGroup",
|
|
||||||
"xmlns:wpi": "http://schemas.microsoft.com/office/word/2010/wordprocessingInk",
|
|
||||||
"xmlns:wne": "http://schemas.microsoft.com/office/word/2006/wordml",
|
|
||||||
"xmlns:wps": "http://schemas.microsoft.com/office/word/2010/wordprocessingShape",
|
|
||||||
},
|
|
||||||
elements: [
|
elements: [
|
||||||
{
|
{
|
||||||
type: "element",
|
type: "element",
|
||||||
@ -106,7 +71,7 @@ describe("replacer", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return the same object if nothing is added", () => {
|
it("should replace paragraph type", () => {
|
||||||
const output = replacer(
|
const output = replacer(
|
||||||
MOCK_JSON,
|
MOCK_JSON,
|
||||||
{
|
{
|
||||||
@ -149,5 +114,93 @@ describe("replacer", () => {
|
|||||||
|
|
||||||
expect(JSON.stringify(output)).to.contain("Delightful Header");
|
expect(JSON.stringify(output)).to.contain("Delightful Header");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should replace document type", () => {
|
||||||
|
const output = replacer(
|
||||||
|
MOCK_JSON,
|
||||||
|
{
|
||||||
|
type: PatchType.DOCUMENT,
|
||||||
|
children: [new Paragraph("Lorem ipsum paragraph")],
|
||||||
|
},
|
||||||
|
"{{header_adjective}}",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: "This is a {{header_adjective}} don’t you think?",
|
||||||
|
runs: [
|
||||||
|
{
|
||||||
|
text: "This is a {{head",
|
||||||
|
parts: [{ text: "This is a {{head", index: 0, start: 0, end: 15 }],
|
||||||
|
index: 1,
|
||||||
|
start: 0,
|
||||||
|
end: 15,
|
||||||
|
},
|
||||||
|
{ text: "er", parts: [{ text: "er", index: 0, start: 16, end: 17 }], index: 2, start: 16, end: 17 },
|
||||||
|
{
|
||||||
|
text: "_adjective}} don’t you think?",
|
||||||
|
parts: [{ text: "_adjective}} don’t you think?", index: 0, start: 18, end: 46 }],
|
||||||
|
index: 3,
|
||||||
|
start: 18,
|
||||||
|
end: 46,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
index: 0,
|
||||||
|
path: [0, 0, 0],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
|
file: {} as unknown as File,
|
||||||
|
viewWrapper: {
|
||||||
|
Relationships: {},
|
||||||
|
} as unknown as IViewWrapper,
|
||||||
|
stack: [],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(JSON.stringify(output)).to.contain("Lorem ipsum paragraph");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw an error if the type is not supported", () => {
|
||||||
|
expect(() =>
|
||||||
|
replacer(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
type: PatchType.DOCUMENT,
|
||||||
|
children: [new Paragraph("Lorem ipsum paragraph")],
|
||||||
|
},
|
||||||
|
"{{header_adjective}}",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: "This is a {{header_adjective}} don’t you think?",
|
||||||
|
runs: [
|
||||||
|
{
|
||||||
|
text: "This is a {{head",
|
||||||
|
parts: [{ text: "This is a {{head", index: 0, start: 0, end: 15 }],
|
||||||
|
index: 1,
|
||||||
|
start: 0,
|
||||||
|
end: 15,
|
||||||
|
},
|
||||||
|
{ text: "er", parts: [{ text: "er", index: 0, start: 16, end: 17 }], index: 2, start: 16, end: 17 },
|
||||||
|
{
|
||||||
|
text: "_adjective}} don’t you think?",
|
||||||
|
parts: [{ text: "_adjective}} don’t you think?", index: 0, start: 18, end: 46 }],
|
||||||
|
index: 3,
|
||||||
|
start: 18,
|
||||||
|
end: 46,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
index: 0,
|
||||||
|
path: [0, 0, 0],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
|
file: {} as unknown as File,
|
||||||
|
viewWrapper: {
|
||||||
|
Relationships: {},
|
||||||
|
} as unknown as IViewWrapper,
|
||||||
|
stack: [],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).to.throw();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -27,25 +27,31 @@ export const replacer = (
|
|||||||
.map((c) => toJson(xml(formatter.format(c as XmlComponent, context))))
|
.map((c) => toJson(xml(formatter.format(c as XmlComponent, context))))
|
||||||
.map((c) => c.elements![0]);
|
.map((c) => c.elements![0]);
|
||||||
|
|
||||||
if (patch.type === PatchType.DOCUMENT) {
|
switch (patch.type) {
|
||||||
const parentElement = goToParentElementFromPath(json, renderedParagraph.path);
|
case PatchType.DOCUMENT: {
|
||||||
const elementIndex = getLastElementIndexFromPath(renderedParagraph.path);
|
const parentElement = goToParentElementFromPath(json, renderedParagraph.path);
|
||||||
// eslint-disable-next-line functional/immutable-data, prefer-destructuring
|
const elementIndex = getLastElementIndexFromPath(renderedParagraph.path);
|
||||||
parentElement.elements?.splice(elementIndex, 1, ...textJson);
|
// eslint-disable-next-line functional/immutable-data, prefer-destructuring
|
||||||
} else if (patch.type === PatchType.PARAGRAPH) {
|
parentElement.elements!.splice(elementIndex, 1, ...textJson);
|
||||||
const paragraphElement = goToElementFromPath(json, renderedParagraph.path);
|
break;
|
||||||
replaceTokenInParagraphElement({
|
}
|
||||||
paragraphElement,
|
case PatchType.PARAGRAPH:
|
||||||
renderedParagraph,
|
default: {
|
||||||
originalText: patchText,
|
const paragraphElement = goToElementFromPath(json, renderedParagraph.path);
|
||||||
replacementText: SPLIT_TOKEN,
|
replaceTokenInParagraphElement({
|
||||||
});
|
paragraphElement,
|
||||||
|
renderedParagraph,
|
||||||
|
originalText: patchText,
|
||||||
|
replacementText: SPLIT_TOKEN,
|
||||||
|
});
|
||||||
|
|
||||||
const index = findRunElementIndexWithToken(paragraphElement, SPLIT_TOKEN);
|
const index = findRunElementIndexWithToken(paragraphElement, SPLIT_TOKEN);
|
||||||
|
|
||||||
const { left, right } = splitRunElement(paragraphElement.elements![index], SPLIT_TOKEN);
|
const { left, right } = splitRunElement(paragraphElement.elements![index], SPLIT_TOKEN);
|
||||||
// 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, ...textJson, right);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,28 +20,24 @@ export const findLocationOfText = (node: Element, text: string): readonly IRende
|
|||||||
|
|
||||||
// eslint-disable-next-line functional/prefer-readonly-type
|
// eslint-disable-next-line functional/prefer-readonly-type
|
||||||
const queue: ElementWrapper[] = [
|
const queue: ElementWrapper[] = [
|
||||||
...(elementsToWrapper({
|
...elementsToWrapper({
|
||||||
element: node,
|
element: node,
|
||||||
index: 0,
|
index: 0,
|
||||||
parent: undefined,
|
parent: undefined,
|
||||||
}) ?? []),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
// eslint-disable-next-line functional/immutable-data
|
// eslint-disable-next-line functional/immutable-data
|
||||||
let currentNode: ElementWrapper | undefined;
|
let currentNode: ElementWrapper | undefined;
|
||||||
while (queue.length > 0) {
|
while (queue.length > 0) {
|
||||||
// eslint-disable-next-line functional/immutable-data
|
// eslint-disable-next-line functional/immutable-data
|
||||||
currentNode = queue.shift();
|
currentNode = queue.shift()!; // This is safe because we check the length of the queue
|
||||||
|
|
||||||
if (!currentNode) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentNode.element.name === "w:p") {
|
if (currentNode.element.name === "w:p") {
|
||||||
renderedParagraphs = [...renderedParagraphs, renderParagraphNode(currentNode)];
|
renderedParagraphs = [...renderedParagraphs, renderParagraphNode(currentNode)];
|
||||||
} else {
|
} else {
|
||||||
// eslint-disable-next-line functional/immutable-data
|
// eslint-disable-next-line functional/immutable-data
|
||||||
queue.push(...(elementsToWrapper(currentNode) ?? []));
|
queue.push(...elementsToWrapper(currentNode));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user