docxtemplater
Version:
Generate docx, pptx, and xlsx from templates (Word, Powerpoint and Excel documents), from Node.js, the Browser and the command line
1,133 lines (1,132 loc) • 35 kB
JavaScript
"use strict";
var _require = require("../utils.js"),
expectToThrow = _require.expectToThrow,
createDoc = _require.createDoc,
createDocV4 = _require.createDocV4,
shouldBeSame = _require.shouldBeSame,
expect = _require.expect,
makeDocxV4 = _require.makeDocxV4,
captureLogs = _require.captureLogs;
var printy = require("../printy.js");
var angularParser = require("../../expressions.js");
var Errors = require("../../errors.js");
describe("Simple templating", function () {
describe("text templating", function () {
it("should change values with template data", function () {
var doc = createDocV4("tag-example.docx").render({
first_name: "Hipp",
last_name: "Edgar",
phone: "0652455478",
description: "New Website"
});
expect(doc.getFullText()).to.be.equal("Edgar Hipp");
expect(doc.getFullText("word/header1.xml")).to.be.equal("Edgar Hipp0652455478New Website");
expect(doc.getFullText("word/footer1.xml")).to.be.equal("EdgarHipp0652455478");
shouldBeSame({
doc: doc,
expectedName: "expected-tag-example.docx"
});
});
});
it("should replace custom properties text", function () {
var doc = createDoc("properties.docx");
var app = doc.getZip().files["docProps/app.xml"].asText();
var core = doc.getZip().files["docProps/core.xml"].asText();
var filePaths = [];
var module = {
name: "Test module",
requiredAPIVersion: "3.0.0",
render: function render(a, options) {
if (filePaths.indexOf(options.filePath) === -1) {
filePaths.push(options.filePath);
}
}
};
doc.attachModule(module);
expect(app).to.contain("{tag1}");
expect(core).to.contain("{tag1}");
expect(core).to.contain("{tag2}");
expect(core).to.contain("{tag3}");
expect(app).to.contain("{tag4}");
expect(app).to.contain("{tag5}");
expect(core).to.contain("{tag6}");
expect(core).to.contain("{tag7}");
expect(core).to.contain("{tag8}");
expect(app).to.contain("{tag9}");
doc.render({
tag1: "resolvedvalue1",
tag2: "resolvedvalue2",
tag3: "resolvedvalue3",
tag4: "resolvedvalue4",
tag5: "resolvedvalue5",
tag6: "resolvedvalue6",
tag7: "resolvedvalue7",
tag8: "resolvedvalue8",
tag9: "resolvedvalue9"
});
/* The order here is important !
* We expect the word/settings.xml templating to happen before the
* "word/document.xml"
*
* This way, users can write assignments in the word/settings.xml, and
* use the exposed variables in the word/document.xml
*
* Fixed since 3.37.6
*/
expect(filePaths).to.deep.equal(["word/settings.xml", "docProps/core.xml", "docProps/app.xml", "word/document.xml"]);
app = doc.getZip().files["docProps/app.xml"].asText();
core = doc.getZip().files["docProps/core.xml"].asText();
expect(app).to.contain("resolvedvalue1");
expect(core).to.contain("resolvedvalue1");
expect(core).to.contain("resolvedvalue2");
expect(core).to.contain("resolvedvalue3");
expect(app).to.contain("resolvedvalue4");
expect(app).to.contain("resolvedvalue5");
expect(core).to.contain("resolvedvalue6");
expect(core).to.contain("resolvedvalue7");
expect(core).to.contain("resolvedvalue8");
expect(app).to.contain("resolvedvalue9");
});
});
describe("Docxtemplater internal properties", function () {
it("should calculate filesContentTypes and invertedContentTypes", function () {
var doc = createDocV4("tag-example.docx");
expect(doc.filesContentTypes).to.deep.equal({
"_rels/.rels": "application/vnd.openxmlformats-package.relationships+xml",
"word/_rels/document.xml.rels": "application/vnd.openxmlformats-package.relationships+xml",
"docProps/app.xml": "application/vnd.openxmlformats-officedocument.extended-properties+xml",
"docProps/core.xml": "application/vnd.openxmlformats-package.core-properties+xml",
"word/document.xml": "application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml",
"word/endnotes.xml": "application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml",
"word/fontTable.xml": "application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml",
"word/footer1.xml": "application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml",
"word/footnotes.xml": "application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml",
"word/header1.xml": "application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml",
"word/settings.xml": "application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml",
"word/styles.xml": "application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml",
"word/stylesWithEffects.xml": "application/vnd.ms-word.stylesWithEffects+xml",
"word/theme/theme1.xml": "application/vnd.openxmlformats-officedocument.theme+xml",
"word/webSettings.xml": "application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml"
});
expect(doc.invertedContentTypes).to.deep.equal({
"application/vnd.openxmlformats-package.relationships+xml": ["_rels/.rels", "word/_rels/document.xml.rels"],
"application/vnd.ms-word.stylesWithEffects+xml": ["word/stylesWithEffects.xml"],
"application/vnd.openxmlformats-officedocument.extended-properties+xml": ["docProps/app.xml"],
"application/vnd.openxmlformats-officedocument.theme+xml": ["word/theme/theme1.xml"],
"application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml": ["word/document.xml"],
"application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml": ["word/endnotes.xml"],
"application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml": ["word/fontTable.xml"],
"application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml": ["word/footer1.xml"],
"application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml": ["word/footnotes.xml"],
"application/vnd.openxmlformats-officedocument.wordprocessingml.header+xml": ["word/header1.xml"],
"application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml": ["word/settings.xml"],
"application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml": ["word/styles.xml"],
"application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml": ["word/webSettings.xml"],
"application/vnd.openxmlformats-package.core-properties+xml": ["docProps/core.xml"]
});
});
it("should calculate filesContentTypes and invertedContentTypes", function () {
var doc = createDocV4("cond-image.docx");
var jpegImages = doc.invertedContentTypes["image/jpeg"];
expect(jpegImages).to.deep.equal(["word/media/image1.jpeg"]);
expect(doc.invertedContentTypes["application/vnd.openxmlformats-package.relationships+xml"]).to.deep.equal(["_rels/.rels", "word/_rels/document.xml.rels"]);
});
it("should load relationships with xmlDocuments", function () {
var xmlDocs = null;
var mod = {
name: "XmlDocumentsModule",
set: function set(options) {
if (options.xmlDocuments) {
xmlDocs = options.xmlDocuments;
}
}
};
createDocV4("with-default-contenttype.docx", {
modules: [mod]
});
var keys = Object.keys(xmlDocs);
var ct = "[Content_Types].xml";
expect(keys).to.deep.equal([ct]);
var mainDoc = xmlDocs[ct];
expect(mainDoc.getElementsByTagName("Override")[0].getAttribute("PartName")).to.equal("/docProps/core.xml");
expect(mainDoc.getElementsByTagName("parsererror").length).to.equal(0);
});
});
describe("Special characters", function () {
it("should not escape tab character", function () {
return this.renderV4({
name: "tab-character.pptx",
expectedName: "expected-tab-character.pptx"
});
});
it("should not double escape loop containing hebrew", function () {
return this.renderV4({
name: "loop-hebrew.docx",
data: {
title: "Default title",
products: [{
title: "Duk",
name: "DukSoftware",
reference: "DS0"
}, {
title: "Tingerloo",
name: "Tingerlee",
reference: "T00"
}, {
title: "Tingerloo",
name: "Tingerlee",
reference: "T00"
}, {
title: "Tingerloo",
name: "Tingerlee",
reference: "T00"
}]
},
expectedName: "expected-loop-hebrew.docx"
});
});
});
describe("Regression", function () {
it("should not corrupt when having sdt content", function () {
return this.renderV4({
name: "sdt-content.docx",
data: {},
expectedName: "expected-sdt-content.docx"
});
});
it("should not corrupt document with tag inside title with linebreaks #docprops-linebreak", function () {
// Fixed in version 3.40.3
return this.render({
data: {
title: "Foo\nBar\nBaz",
creator: "Foo\nBar\nBaz",
lmb: "Foo\nBar\nBaz"
},
name: "tag-in-title.pptx",
options: {
paragraphLoop: true,
linebreaks: true
},
expectedName: "expected-tag-in-title.pptx"
});
});
});
describe("Spacing/Linebreaks", function () {
it("should show spaces with linebreak option", function () {
var doc = createDoc("tag-multiline.docx").setOptions({
linebreaks: true
}).render({
description: "hello there\n deep indentation\n goes here\n end"
});
shouldBeSame({
doc: doc,
expectedName: "expected-multiline-indent.docx"
});
});
it("should be possible to have linebreaks if setting the option", function () {
return this.render({
name: "tag-multiline.docx",
options: {
linebreaks: true
},
data: {
description: "The description,\nmultiline"
},
expectedName: "expected-multiline.docx"
});
});
it("should not remove section if having normal loop just before", function () {
return this.render({
name: "loop-with-section-break-after.docx",
expectedName: "expected-loop-with-section-break-after.docx"
});
});
it("should not remove section if having paragraph loop just before", function () {
return this.render({
name: "paragraph-loop-with-section-break-after.docx",
options: {
paragraphLoop: true
},
expectedName: "paragraph-loop-with-section-break-after.docx"
});
});
it("should work with linebreaks and copy the run style onto new lines in docx", function () {
return this.renderV4({
name: "multi-tags.docx",
options: {
linebreaks: true
},
data: {
test: "The tag1,\nmultiline\nfoobaz",
test2: "The tag2,\nmultiline\nfoobar"
},
async: true,
expectedName: "expected-two-multiline.docx"
});
});
it("should work with linebreaks and copy the run style onto new lines in pptx", function () {
return this.render({
name: "tag-multiline.pptx",
options: {
linebreaks: true
},
data: {
description: "The description,\nmultiline"
},
expectedName: "expected-multiline.pptx"
});
});
it("should not fail when using linebreaks and tagvalue not a string", function () {
return this.render({
name: "tag-multiline.pptx",
options: {
linebreaks: true
},
data: {
description: true
},
expectedName: "expected-regression-multiline.pptx"
});
});
it("should drop empty lines found inside the tags", function () {
return this.renderV4({
name: "tag-spanning-multiple-lines.docx",
options: {
parser: function parser() {
return {
get: function get() {
return "";
}
};
}
},
expectedName: "expected-no-multiline.docx"
});
});
it("should drop empty lines found inside the tags", function () {
return this.renderV4({
name: "tag-spanning-multiple-lines.pptx",
options: {
parser: function parser() {
return {
get: function get() {
return "";
}
};
}
},
expectedName: "expected-no-multiline.pptx"
});
});
it("should keep run props (font-size) for pptx file", function () {
return this.renderV4({
name: "run-props-linebreak.pptx",
data: {
data: "blabla\nbloblo\nblublu"
},
options: {
linebreaks: true
},
expectedName: "expected-run-props-linebreak.pptx"
});
});
});
describe("Comments", function () {
it("should be possible to template values in comments", function () {
return this.renderV4({
name: "with-comments.docx",
data: {
name: "John"
},
expectedName: "expected-comments.docx"
});
});
});
describe("Assignment", function () {
it("should be possible to assign a value from the template", function () {
return this.render({
name: "assignment.docx",
data: {
first_name: "Jane",
last_name: "Doe"
},
options: {
paragraphLoop: true,
parser: angularParser
},
expectedName: "expected-assignment.docx"
});
});
});
describe("Unusual document extensions", function () {
it("should work with docm", function () {
return this.render({
name: "input.docm",
data: {
user: "John"
},
expectedName: "expected-docm.docm"
});
});
it("should work with pptm", function () {
return this.render({
name: "input.pptm",
data: {
user: "John"
},
expectedName: "expected-pptm.pptm"
});
});
it("should work with dotx", function () {
return this.render({
name: "input.dotx",
data: {
user: "John"
},
expectedName: "expected-dotx.dotx"
});
});
it("should work with dotm", function () {
return this.render({
name: "input.dotm",
data: {
user: "John"
},
expectedName: "expected-dotm.dotm"
});
});
});
describe("Dash Loop", function () {
it("should work on simple table -> w:tr", function () {
var doc = createDoc("tag-dash-loop.docx").render({
os: [{
type: "linux",
price: "0",
reference: "Ubuntu10"
}, {
type: "DOS",
price: "500",
reference: "Win7"
}, {
type: "apple",
price: "1200",
reference: "MACOSX"
}]
});
var expectedText = "linux0Ubuntu10DOS500Win7apple1200MACOSX";
var text = doc.getFullText();
expect(text).to.be.equal(expectedText);
});
it("should work on simple table -> w:table", function () {
var doc = createDoc("tag-dash-loop-table.docx").render({
os: [{
type: "linux",
price: "0",
reference: "Ubuntu10"
}, {
type: "DOS",
price: "500",
reference: "Win7"
}, {
type: "apple",
price: "1200",
reference: "MACOSX"
}]
});
var expectedText = "linux0Ubuntu10DOS500Win7apple1200MACOSX";
var text = doc.getFullText();
expect(text).to.be.equal(expectedText);
});
it("should work on simple list -> w:p", function () {
var doc = createDoc("tag-dash-loop-list.docx").render({
os: [{
type: "linux",
price: "0",
reference: "Ubuntu10"
}, {
type: "DOS",
price: "500",
reference: "Win7"
}, {
type: "apple",
price: "1200",
reference: "MACOSX"
}]
});
var expectedText = "linux 0 Ubuntu10 DOS 500 Win7 apple 1200 MACOSX ";
var text = doc.getFullText();
expect(text).to.be.equal(expectedText);
});
it("should not corrupt document if using empty {-a:p} inside table cell", function () {
return this.render({
name: "regression-dash-loop-in-table-cell.pptx",
expectedName: "expected-table-3-cells.pptx"
});
});
it("should not corrupt document if using empty {-a:p} inside table cell", function () {
return this.render({
name: "regression-dash-loop-in-table-cell.pptx",
data: {
cond: [1, 2, 3]
},
expectedName: "expected-table-3-true-cells.pptx"
});
});
});
describe("Section breaks inside loops", function () {
it("should work at beginning of paragraph loop with 3 elements", function () {
// Warning : In libreoffice, this is not rendered correctly, use WPS or Word
return this.renderV4({
name: "page-break-inside-condition.docx",
data: {
cond: [1, 2, 3]
},
options: {
paragraphLoop: true
},
expectedName: "expected-with-page-break-3-els.docx"
});
});
it("should work at beginning of paragraph loop with false", function () {
// Warning : In libreoffice, this is not rendered correctly, use WPS or Word
return this.renderV4({
name: "page-break-inside-condition.docx",
data: {
cond: false
},
options: {
paragraphLoop: true
},
expectedName: "expected-with-page-break-falsy.docx"
});
});
it("should work at beginning of std loop with false", function () {
// Warning : In libreoffice, this is not rendered correctly, use WPS or Word
return this.render({
name: "page-break-inside-condition.docx",
data: {
cond: false
},
expectedName: "expected-page-break-falsy-std-loop.docx"
});
});
it("should work at beginning of std loop with 3 elements", function () {
// Warning : In libreoffice, this is not rendered correctly, use WPS or Word
return this.render({
name: "page-break-inside-condition.docx",
data: {
cond: [1, 2, 3]
},
expectedName: "expected-page-break-3-els-std-loop.docx"
});
});
it("should work at beginning of std loop with truthy", function () {
// Warning : In libreoffice, this is not rendered correctly, use WPS or Word
return this.render({
name: "page-break-inside-condition.docx",
data: {
cond: true
},
expectedName: "expected-page-break-truthy-std-loop.docx"
});
});
it("should work with table inside paragraph loop", function () {
return this.renderV4({
name: "pagebreak-table-loop.docx",
options: {
paragraphLoop: true
},
data: {
loop: [1, 2, 3]
},
expectedName: "expected-pagebreak-table-loop.docx"
});
});
it("should work at end of std loop", function () {
return this.render({
name: "paragraph-loop-with-pagebreak.docx",
data: {
users: [{
name: "Bar"
}, {
name: "John"
}, {
name: "Baz"
}]
},
expectedName: "expected-noparagraph-loop-with-pagebreak.docx"
});
});
it("should work at end of paragraph loop", function () {
return this.renderV4({
name: "paragraph-loop-with-pagebreak.docx",
options: {
paragraphLoop: true
},
data: {
users: [{
name: "Bar"
}, {
name: "John"
}, {
name: "Baz"
}]
},
expectedName: "expected-paragraph-loop-with-pagebreak.docx"
});
});
it("should work with pagebreak afterwards with falsy value", function () {
return this.renderV4({
name: "paragraph-loop-with-pagebreak.docx",
options: {
paragraphLoop: true
},
data: {
users: false
},
expectedName: "expected-paragraph-loop-empty-with-pagebreak.docx"
});
});
it("should make first section break of the file continuous", function () {
return this.renderV4({
name: "loop-with-continuous-section-break.docx",
data: {
loop: [1, 2, 3]
},
options: {
paragraphLoop: true,
parser: angularParser
},
expectedName: "expected-loop-with-continuous-section-break.docx"
});
});
it("should work with delimiters << >> when saved in word as >>test>>", function () {
return this.renderV4({
name: "gt-delimiters.docx",
data: {
my_tag: "Hello John"
},
options: {
parser: angularParser,
delimiters: {
start: "<<",
end: ">>"
}
},
expectedName: "expected-rendered-hello.docx"
});
});
it("should work with quotes in tag with angular parser", function () {
return this.renderV4({
name: "quotes-in-tag.docx",
data: {
user: "John",
hobbies: ["hiking", "reading"]
},
options: {
parser: angularParser,
paragraphLoop: true
},
expectedName: "expected-quotes-in-tag.docx"
});
});
it("should make first section break of the file continuous", function () {
return this.renderV4({
name: "loop-with-continuous-section-break.docx",
data: {
loop: [1, 2, 3]
},
options: {
parser: angularParser
},
expectedName: "expected-loop-with-continuous-section-break-2.docx"
});
});
it('should work with w:type val="nextPage" section break', function () {
return this.render({
name: "nextpage-section-break.docx",
data: {
items: [1, 2, 3, 4]
},
options: {
paragraphLoop: true
},
expectedName: "expected-nextpage-section-break.docx"
});
});
});
describe("ParagraphLoop", function () {
it("should work with docx", function () {
return this.render({
name: "users.docx",
data: {
users: ["John", "Jane", "Louis"]
},
options: {
paragraphLoop: true
},
expectedName: "expected-users.docx"
});
});
it("should not drop image with text", function () {
return this.render({
name: "cond-image.docx",
data: {
cond: true
},
options: {
paragraphLoop: true
},
expectedName: "expected-cond-image.docx"
});
});
it("should not drop image without text", function () {
return this.render({
name: "cond-image-no-innertext.docx",
data: {
cond: true
},
options: {
paragraphLoop: true
},
expectedName: "expected-cond-image-no-innertext.docx"
});
});
it("should not drop image without text at beginning", function () {
return this.render({
name: "cond-image-no-innertext-before.docx",
data: {
cond: true
},
options: {
paragraphLoop: true
},
expectedName: "expected-cond-image-no-innertext-before.docx"
});
});
it("should work without removing extra text", function () {
return this.render({
name: "paragraph-loops.docx",
data: {
condition: [1, 2],
l1: [{
l2: ["a", "b", "c"]
}, {
l2: ["d", "e", "f"]
}],
placeholder: "placeholder-value"
},
options: {
paragraphLoop: true
},
expectedName: "expected-paragraph-loop.docx"
});
});
it("should work with pptx", function () {
return this.render({
name: "paragraph-loop.pptx",
data: {
users: [{
age: 10,
name: "Bar"
}, {
age: 18,
name: "Bar"
}, {
age: 22,
name: "Bar"
}]
},
options: {
paragraphLoop: true
},
expectedName: "expected-paragraph-loop.pptx"
});
});
it("should fail if trying to attach a module that has none of the properties", function () {
var expectedError = {
name: "InternalError",
message: "This module cannot be wrapped, because it doesn't define any of the necessary functions",
properties: {
id: "module_cannot_be_wrapped"
}
};
expectToThrow(function () {
createDocV4("regression-par-in-par.docx", {
modules: [Promise.resolve(1)]
});
}, Error, expectedError);
});
it("should not produce corrupt document when having comment inside tag", function () {
return this.renderV4({
name: "tag-with-comment.docx",
expectedName: "expected-tag-with-comment.docx"
});
});
it("should not fail when having paragraph in paragraph", function () {
var printedPostparsed = [];
var filePath = "";
var doc = createDoc("regression-par-in-par.docx").attachModule({
name: "MyModule",
set: function set(obj) {
if (obj.inspect) {
if (obj.inspect.filePath) {
filePath = obj.inspect.filePath;
}
if (obj.inspect.postparsed) {
printedPostparsed[filePath] = printy(obj.inspect.postparsed);
}
}
}
}).setOptions({
paragraphLoop: true,
parser: function parser() {
return {
get: function get() {
return "foo";
}
};
}
}).render();
shouldBeSame({
doc: doc,
expectedName: "expected-rendered-par-in-par.docx"
});
expect(printedPostparsed["word/document.xml"]).to.matchSnapshot();
});
it("should work with spacing at the end", function () {
return this.render({
name: "spacing-end.docx",
data: {
name: "John"
},
options: {
paragraphLoop: true
},
expectedName: "expected-spacing-end.docx"
});
});
it("should throw specific error if calling .render() on document with invalid tags", function () {
var doc = createDoc("errors-footer-and-header.docx", {
paragraphLoop: true,
parser: angularParser
});
var catched = false;
var capture = captureLogs();
try {
doc.compile();
} catch (e) {
catched = true;
var expectedError = {
name: "InternalError",
message: "You should not call .render on a document that had compilation errors",
properties: {
id: "render_on_invalid_template"
}
};
capture.stop();
expectToThrow(function () {
return doc.render();
}, Errors.XTInternalError, expectedError);
}
expect(catched).to.equal(true);
});
it("should fail with errors from header and footer", function () {
var doc = createDoc("errors-footer-and-header.docx");
doc.setOptions({
paragraphLoop: true,
errorLogging: false,
parser: angularParser
});
var expectedError = {
message: "Multi error",
name: "TemplateError",
properties: {
id: "multi_error",
errors: [{
name: "TemplateError",
message: "Duplicate close tag, expected one close tag",
properties: {
file: "word/header1.xml",
xtag: "itle}}",
id: "duplicate_close_tag",
context: "itle}}",
offset: 15
}
}, {
name: "TemplateError",
message: "Closing tag does not match opening tag",
properties: {
closingtag: "bang",
openingtag: "users",
file: "word/document.xml",
id: "closing_tag_does_not_match_opening_tag",
offset: [8, 16]
}
}, {
name: "TemplateError",
message: "Unclosed tag",
properties: {
file: "word/footer1.xml",
xtag: "footer",
id: "unclosed_tag",
context: "{footer",
offset: 2
}
}]
}
};
expectToThrow(function () {
return doc.render();
}, Errors.XTTemplateError, expectedError);
});
it("should fail properly when having lexed + postparsed errors", function () {
var doc = createDoc("multi-errors.docx", {
paragraphLoop: true,
errorLogging: false,
parser: angularParser
});
var expectedError = {
message: "Multi error",
name: "TemplateError",
properties: {
id: "multi_error",
errors: [{
name: "TemplateError",
message: "Unclosed tag",
properties: {
file: "word/document.xml",
xtag: "firstName",
id: "unclosed_tag",
context: "{firstName ",
offset: 0
}
}, {
name: "TemplateError",
message: "Unclosed tag",
properties: {
file: "word/document.xml",
xtag: "error",
id: "unclosed_tag",
context: "{error ",
offset: 22
}
}, {
name: "TemplateError",
message: "Duplicate close tag, expected one close tag",
properties: {
file: "word/document.xml",
xtag: "{tag}}",
id: "duplicate_close_tag",
context: "{tag}}",
offset: 34
}
}, {
name: "TemplateError",
message: "Duplicate open tag, expected one open tag",
properties: {
file: "word/document.xml",
xtag: "{{bar}",
id: "duplicate_open_tag",
context: "{{bar}",
offset: 42
}
}]
}
};
expectToThrow(function () {
return doc.compile();
}, Errors.XTTemplateError, expectedError);
});
it("should fail when placing paragraph loop inside normal loop", function () {
var doc = createDoc("paragraph-loop-error.docx", {
errorLogging: false
});
var expectedError = {
message: "Multi error",
name: "TemplateError",
properties: {
id: "multi_error",
errors: [{
name: "TemplateError",
message: 'No tag "w:p" was found at the left',
properties: {
file: "word/document.xml",
id: "no_xml_tag_found_at_left",
element: "w:p",
index: 1,
parsedLength: 4,
offset: 12,
part: {
endLindex: 19,
expandTo: "w:p",
inverted: false,
lIndex: 19,
location: "start",
module: "loop",
offset: 12,
raw: "-w:p loop",
type: "placeholder",
value: "loop"
}
}
}, {
name: "TemplateError",
message: 'No tag "w:p" was found at the right',
properties: {
file: "word/document.xml",
id: "no_xml_tag_found_at_right",
element: "w:p",
index: 3,
parsedLength: 4,
offset: 26,
part: {
endLindex: 23,
lIndex: 23,
location: "end",
module: "loop",
offset: 26,
raw: "/",
type: "placeholder",
value: ""
}
}
}]
}
};
expectToThrow(function () {
return doc.compile();
}, Errors.XTTemplateError, expectedError);
});
});
describe("Prefixes", function () {
it("should be possible to change the prefix of the loop module", function () {
var content = "<w:t>{##tables}{user}{/tables}</w:t>";
var scope = {
tables: [{
user: "John"
}, {
user: "Jane"
}]
};
var doc = makeDocxV4(content, {
modules: [{
optionsTransformer: function optionsTransformer(options, doc) {
doc.modules.forEach(function (module) {
if (module.name === "LoopModule") {
module.prefix.start = "##";
}
});
return options;
}
}]
});
doc.render(scope);
expect(doc.getFullText()).to.be.equal("JohnJane");
});
it("should be possible to change the prefix of the loop module to a regexp", function () {
var content = "<w:t>{##tables}{user}{/tables}{#tables}{user}{/tables}</w:t>";
var scope = {
tables: [{
user: "A"
}, {
user: "B"
}]
};
var doc = makeDocxV4(content, {
modules: [{
optionsTransformer: function optionsTransformer(options, doc) {
doc.modules.forEach(function (module) {
if (module.name === "LoopModule") {
module.prefix.start = /^##?(.*)$/;
}
});
return options;
}
}]
});
doc.render(scope);
expect(doc.getFullText()).to.be.equal("ABAB");
});
it("should be possible to change the prefix of the raw xml module to a regexp", function () {
var content = "<w:p><w:t>{!!raw}</w:t></w:p>";
var scope = {
raw: "<w:p><w:t>HoHo</w:t></w:p>"
};
var doc = makeDocxV4(content, {
modules: [{
optionsTransformer: function optionsTransformer(options, doc) {
doc.modules.forEach(function (module) {
if (module.name === "RawXmlModule") {
module.prefix = /^!!?(.*)$/;
}
});
return options;
}
}]
});
doc.render(scope);
expect(doc.getFullText()).to.be.equal("HoHo");
});
it("should be possible to change the prefix of the raw xml module to a function", function () {
var content = "<w:p><w:t>{raw}</w:t></w:p><w:p><w:t> {text}</w:t></w:p>";
var scope = {
raw: "<w:p><w:t>HoHo</w:t></w:p>",
text: "Huhu"
};
var doc = makeDocxV4(content, {
modules: [{
optionsTransformer: function optionsTransformer(options, doc) {
doc.modules.forEach(function (module) {
if (module.name === "RawXmlModule") {
module.prefix = function (placeholderContent) {
if (placeholderContent === "raw") {
return "raw";
}
};
}
});
return options;
}
}]
});
doc.render(scope);
expect(doc.zip.file("word/document.xml").asText()).to.be.equal('<w:p><w:t>HoHo</w:t></w:p><w:p><w:t xml:space="preserve"> Huhu</w:t></w:p>');
});
});
describe("Load Office 365 file", function () {
it("should handle files with word/document2.xml", function () {
return this.renderV4({
name: "office365.docx",
data: {
test: "Value",
test2: "Value2"
},
options: {
paragraphLoop: true
},
expectedName: "expected-office365.docx",
expectedText: "Value Value2"
});
});
it("should template header.xml (without digit like header1.xml)", function () {
return this.render({
name: "header-without-digit.docx",
data: {
name: "John"
},
expectedName: "expected-header-without-digit.docx"
});
});
});
describe("Loops", function () {
it("should work with template", function () {
return this.renderV4({
name: "empty-loop-regression.docx",
data: {
ice: [1, 2, 3]
},
expectedName: "expected-loop-regression.docx"
});
});
});