UNPKG

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
"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 &gt;&gt;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" }); }); });