docxtemplater
Version:
docx and pptx generator working with templates and data (like Mustache, for Word and Powerpoint documents)
520 lines (441 loc) • 17.7 kB
JavaScript
"use strict";
var _excluded = ["modules"];
function _objectWithoutProperties(source, excluded) { if (source == null) return {}; var target = _objectWithoutPropertiesLoose(source, excluded); var key, i; if (Object.getOwnPropertySymbols) { var sourceSymbolKeys = Object.getOwnPropertySymbols(source); for (i = 0; i < sourceSymbolKeys.length; i++) { key = sourceSymbolKeys[i]; if (excluded.indexOf(key) >= 0) continue; if (!Object.prototype.propertyIsEnumerable.call(source, key)) continue; target[key] = source[key]; } } return target; }
function _objectWithoutPropertiesLoose(source, excluded) { if (source == null) return {}; var target = {}; var sourceKeys = Object.keys(source); var key, i; for (i = 0; i < sourceKeys.length; i++) { key = sourceKeys[i]; if (excluded.indexOf(key) >= 0) continue; target[key] = source[key]; } return target; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
var DocUtils = require("./doc-utils.js");
DocUtils.traits = require("./traits.js");
DocUtils.moduleWrapper = require("./module-wrapper.js");
var createScope = require("./scope-manager.js");
var _require = require("./errors.js"),
throwMultiError = _require.throwMultiError,
throwResolveBeforeCompile = _require.throwResolveBeforeCompile,
throwRenderInvalidTemplate = _require.throwRenderInvalidTemplate;
var collectContentTypes = require("./collect-content-types.js");
var ctXML = "[Content_Types].xml";
var commonModule = require("./modules/common.js");
var Lexer = require("./lexer.js");
var defaults = DocUtils.defaults,
str2xml = DocUtils.str2xml,
xml2str = DocUtils.xml2str,
moduleWrapper = DocUtils.moduleWrapper,
concatArrays = DocUtils.concatArrays,
uniq = DocUtils.uniq;
var _require2 = require("./errors.js"),
XTInternalError = _require2.XTInternalError,
throwFileTypeNotIdentified = _require2.throwFileTypeNotIdentified,
throwFileTypeNotHandled = _require2.throwFileTypeNotHandled,
throwApiVersionError = _require2.throwApiVersionError;
var currentModuleApiVersion = [3, 26, 0];
var Docxtemplater = /*#__PURE__*/function () {
function Docxtemplater(zip) {
var _this = this;
var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
_ref$modules = _ref.modules,
modules = _ref$modules === void 0 ? [] : _ref$modules,
options = _objectWithoutProperties(_ref, _excluded);
_classCallCheck(this, Docxtemplater);
if (!Array.isArray(modules)) {
throw new Error("The modules argument of docxtemplater's constructor must be an array");
}
this.scopeManagers = {};
this.compiled = {};
this.modules = [commonModule()];
this.setOptions(options);
modules.forEach(function (module) {
_this.attachModule(module);
});
if (arguments.length > 0) {
if (!zip || !zip.files || typeof zip.file !== "function") {
throw new Error("The first argument of docxtemplater's constructor must be a valid zip file (jszip v2 or pizzip v3)");
}
this.loadZip(zip); // remove the unsupported modules
this.modules = this.modules.filter(function (module) {
if (module.supportedFileTypes) {
if (!Array.isArray(module.supportedFileTypes)) {
throw new Error("The supportedFileTypes field of the module must be an array");
}
var isSupportedModule = module.supportedFileTypes.indexOf(_this.fileType) !== -1;
if (!isSupportedModule) {
module.on("detached");
}
return isSupportedModule;
}
return true;
});
this.compile();
this.v4Constructor = true;
}
}
_createClass(Docxtemplater, [{
key: "getModuleApiVersion",
value: function getModuleApiVersion() {
return currentModuleApiVersion.join(".");
}
}, {
key: "verifyApiVersion",
value: function verifyApiVersion(neededVersion) {
neededVersion = neededVersion.split(".").map(function (i) {
return parseInt(i, 10);
});
if (neededVersion.length !== 3) {
throwApiVersionError("neededVersion is not a valid version", {
neededVersion: neededVersion,
explanation: "the neededVersion must be an array of length 3"
});
}
if (neededVersion[0] !== currentModuleApiVersion[0]) {
throwApiVersionError("The major api version do not match, you probably have to update docxtemplater with npm install --save docxtemplater", {
neededVersion: neededVersion,
currentModuleApiVersion: currentModuleApiVersion,
explanation: "moduleAPIVersionMismatch : needed=".concat(neededVersion.join("."), ", current=").concat(currentModuleApiVersion.join("."))
});
}
if (neededVersion[1] > currentModuleApiVersion[1]) {
throwApiVersionError("The minor api version is not uptodate, you probably have to update docxtemplater with npm install --save docxtemplater", {
neededVersion: neededVersion,
currentModuleApiVersion: currentModuleApiVersion,
explanation: "moduleAPIVersionMismatch : needed=".concat(neededVersion.join("."), ", current=").concat(currentModuleApiVersion.join("."))
});
}
if (neededVersion[1] === currentModuleApiVersion[1] && neededVersion[2] > currentModuleApiVersion[2]) {
throwApiVersionError("The patch api version is not uptodate, you probably have to update docxtemplater with npm install --save docxtemplater", {
neededVersion: neededVersion,
currentModuleApiVersion: currentModuleApiVersion,
explanation: "moduleAPIVersionMismatch : needed=".concat(neededVersion.join("."), ", current=").concat(currentModuleApiVersion.join("."))
});
}
return true;
}
}, {
key: "setModules",
value: function setModules(obj) {
this.modules.forEach(function (module) {
module.set(obj);
});
}
}, {
key: "sendEvent",
value: function sendEvent(eventName) {
this.modules.forEach(function (module) {
module.on(eventName);
});
}
}, {
key: "attachModule",
value: function attachModule(module) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
if (this.v4Constructor) {
throw new Error("attachModule() should not be called manually when using the v4 constructor");
}
if (module.requiredAPIVersion) {
this.verifyApiVersion(module.requiredAPIVersion);
}
if (module.attached === true) {
throw new Error("Cannot attach a module that was already attached : \"".concat(module.name, "\". Maybe you are instantiating the module at the root level, and using it for multiple instances of Docxtemplater"));
}
module.attached = true;
var prefix = options.prefix;
if (prefix) {
module.prefix = prefix;
}
var wrappedModule = moduleWrapper(module);
this.modules.push(wrappedModule);
wrappedModule.on("attached");
return this;
}
}, {
key: "setOptions",
value: function setOptions(options) {
var _this2 = this;
if (this.v4Constructor) {
throw new Error("setOptions() should not be called manually when using the v4 constructor");
}
if (!options) {
throw new Error("setOptions should be called with an object as first parameter");
}
this.options = {};
Object.keys(defaults).forEach(function (key) {
var defaultValue = defaults[key];
_this2.options[key] = options[key] != null ? options[key] : defaultValue;
_this2[key] = _this2.options[key];
});
if (this.zip) {
this.updateFileTypeConfig();
}
return this;
}
}, {
key: "loadZip",
value: function loadZip(zip) {
if (this.v4Constructor) {
throw new Error("loadZip() should not be called manually when using the v4 constructor");
}
if (zip.loadAsync) {
throw new XTInternalError("Docxtemplater doesn't handle JSZip version >=3, please use pizzip");
}
this.zip = zip;
this.updateFileTypeConfig();
this.modules = concatArrays([this.fileTypeConfig.baseModules.map(function (moduleFunction) {
return moduleFunction();
}), this.modules]);
return this;
}
}, {
key: "precompileFile",
value: function precompileFile(fileName) {
var currentFile = this.createTemplateClass(fileName);
currentFile.preparse();
this.compiled[fileName] = currentFile;
}
}, {
key: "compileFile",
value: function compileFile(fileName) {
this.compiled[fileName].parse();
}
}, {
key: "getScopeManager",
value: function getScopeManager(to, currentFile, tags) {
if (!this.scopeManagers[to]) {
this.scopeManagers[to] = createScope({
tags: tags || {},
parser: this.parser,
cachedParsers: currentFile.cachedParsers
});
}
return this.scopeManagers[to];
}
}, {
key: "resolveData",
value: function resolveData(data) {
var _this3 = this;
var errors = [];
if (!Object.keys(this.compiled).length) {
throwResolveBeforeCompile();
}
return Promise.resolve(data).then(function (data) {
_this3.setData(data);
_this3.setModules({
data: _this3.data,
Lexer: Lexer
});
_this3.mapper = _this3.modules.reduce(function (value, module) {
return module.getRenderedMap(value);
}, {});
return Promise.all(Object.keys(_this3.mapper).map(function (to) {
var _this3$mapper$to = _this3.mapper[to],
from = _this3$mapper$to.from,
data = _this3$mapper$to.data;
return Promise.resolve(data).then(function (data) {
var currentFile = _this3.compiled[from];
currentFile.filePath = to;
currentFile.scopeManager = _this3.getScopeManager(to, currentFile, data);
currentFile.scopeManager.resolved = [];
return currentFile.resolveTags(data).then(function (result) {
currentFile.scopeManager.finishedResolving = true;
return result;
}, function (errs) {
errors = errors.concat(errs);
});
});
})).then(function (resolved) {
if (errors.length !== 0) {
throwMultiError(errors);
}
return concatArrays(resolved);
});
});
}
}, {
key: "compile",
value: function compile() {
var _this4 = this;
if (Object.keys(this.compiled).length) {
return this;
}
this.options = this.modules.reduce(function (options, module) {
return module.optionsTransformer(options, _this4);
}, this.options);
this.options.xmlFileNames = uniq(this.options.xmlFileNames);
this.xmlDocuments = this.options.xmlFileNames.reduce(function (xmlDocuments, fileName) {
var content = _this4.zip.files[fileName].asText();
xmlDocuments[fileName] = str2xml(content);
return xmlDocuments;
}, {});
this.setModules({
zip: this.zip,
xmlDocuments: this.xmlDocuments
});
this.getTemplatedFiles(); // Loop inside all templatedFiles (ie xml files with content).
// Sometimes they don't exist (footer.xml for example)
this.templatedFiles.forEach(function (fileName) {
if (_this4.zip.files[fileName] != null) {
_this4.precompileFile(fileName);
}
});
this.templatedFiles.forEach(function (fileName) {
if (_this4.zip.files[fileName] != null) {
_this4.compileFile(fileName);
}
});
this.setModules({
compiled: this.compiled
});
verifyErrors(this);
return this;
}
}, {
key: "updateFileTypeConfig",
value: function updateFileTypeConfig() {
var _this5 = this;
var fileType;
if (this.zip.files.mimetype) {
fileType = "odt";
}
var contentTypes = this.zip.files[ctXML];
this.targets = [];
var contentTypeXml = contentTypes ? str2xml(contentTypes.asText()) : null;
var overrides = contentTypeXml ? contentTypeXml.getElementsByTagName("Override") : null;
var defaults = contentTypeXml ? contentTypeXml.getElementsByTagName("Default") : null;
if (contentTypeXml) {
this.filesContentTypes = collectContentTypes(overrides, defaults, this.zip);
this.invertedContentTypes = DocUtils.invertMap(this.filesContentTypes);
this.setModules({
contentTypes: this.contentTypes,
invertedContentTypes: this.invertedContentTypes
});
}
this.modules.forEach(function (module) {
fileType = module.getFileType({
zip: _this5.zip,
contentTypes: contentTypes,
contentTypeXml: contentTypeXml,
overrides: overrides,
defaults: defaults,
doc: _this5
}) || fileType;
});
if (fileType === "odt") {
throwFileTypeNotHandled(fileType);
}
if (!fileType) {
throwFileTypeNotIdentified();
}
this.fileType = fileType;
this.fileTypeConfig = this.options.fileTypeConfig || this.fileTypeConfig || Docxtemplater.FileTypeConfig[this.fileType]();
return this;
}
}, {
key: "render",
value: function render() {
var _this6 = this;
this.compile();
if (this.errors.length > 0) {
throwRenderInvalidTemplate();
}
this.setModules({
data: this.data,
Lexer: Lexer
});
this.mapper = this.mapper || this.modules.reduce(function (value, module) {
return module.getRenderedMap(value);
}, {});
this.fileTypeConfig.tagsXmlLexedArray = uniq(this.fileTypeConfig.tagsXmlLexedArray);
this.fileTypeConfig.tagsXmlTextArray = uniq(this.fileTypeConfig.tagsXmlTextArray);
Object.keys(this.mapper).forEach(function (to) {
var _this6$mapper$to = _this6.mapper[to],
from = _this6$mapper$to.from,
data = _this6$mapper$to.data;
var currentFile = _this6.compiled[from];
currentFile.setTags(data);
currentFile.scopeManager = _this6.getScopeManager(to, currentFile, data);
currentFile.render(to);
_this6.zip.file(to, currentFile.content, {
createFolders: true
});
});
verifyErrors(this);
this.sendEvent("syncing-zip");
this.syncZip();
return this;
}
}, {
key: "syncZip",
value: function syncZip() {
var _this7 = this;
Object.keys(this.xmlDocuments).forEach(function (fileName) {
_this7.zip.remove(fileName);
var content = xml2str(_this7.xmlDocuments[fileName]);
return _this7.zip.file(fileName, content, {
createFolders: true
});
});
}
}, {
key: "setData",
value: function setData(data) {
this.data = data;
return this;
}
}, {
key: "getZip",
value: function getZip() {
return this.zip;
}
}, {
key: "createTemplateClass",
value: function createTemplateClass(path) {
var content = this.zip.files[path].asText();
return this.createTemplateClassFromContent(content, path);
}
}, {
key: "createTemplateClassFromContent",
value: function createTemplateClassFromContent(content, filePath) {
var _this8 = this;
var xmltOptions = {
filePath: filePath,
contentType: this.filesContentTypes[filePath]
};
Object.keys(defaults).concat(["filesContentTypes", "fileTypeConfig", "modules"]).forEach(function (key) {
xmltOptions[key] = _this8[key];
});
return new Docxtemplater.XmlTemplater(content, xmltOptions);
}
}, {
key: "getFullText",
value: function getFullText(path) {
return this.createTemplateClass(path || this.fileTypeConfig.textPath(this)).getFullText();
}
}, {
key: "getTemplatedFiles",
value: function getTemplatedFiles() {
var _this9 = this;
this.templatedFiles = this.fileTypeConfig.getTemplatedFiles(this.zip);
this.targets.forEach(function (target) {
_this9.templatedFiles.push(target);
});
return this.templatedFiles;
}
}]);
return Docxtemplater;
}();
function verifyErrors(doc) {
var compiled = doc.compiled;
var allErrors = [];
Object.keys(compiled).forEach(function (name) {
var templatePart = compiled[name];
allErrors = concatArrays([allErrors, templatePart.allErrors]);
});
doc.errors = allErrors;
if (allErrors.length !== 0) {
throwMultiError(allErrors);
}
}
Docxtemplater.DocUtils = DocUtils;
Docxtemplater.Errors = require("./errors.js");
Docxtemplater.XmlTemplater = require("./xml-templater.js");
Docxtemplater.FileTypeConfig = require("./file-type-config.js");
Docxtemplater.XmlMatcher = require("./xml-matcher.js");
module.exports = Docxtemplater;