"use strict"; var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; Object.defineProperty(exports, "__esModule", { value: true }); exports.splitCommand = exports.getCommand = exports.walkTemplate = exports.findHighestImgId = exports.produceJsReport = exports.extractQuery = exports.newContext = void 0; var reportUtils_1 = require("./reportUtils"); var jsSandbox_1 = require("./jsSandbox"); var types_1 = require("./types"); var errors_1 = require("./errors"); var debug_1 = require("./debug"); function newContext(options, imageAndShapeIdIncrement) { if (imageAndShapeIdIncrement === void 0) { imageAndShapeIdIncrement = 0; } return { gCntIf: 0, gCntEndIf: 0, level: 1, fCmd: false, cmd: '', fSeekQuery: false, buffers: { 'w:p': { text: '', cmds: '', fInsertedText: false }, 'w:tr': { text: '', cmds: '', fInsertedText: false }, 'w:tc': { text: '', cmds: '', fInsertedText: false }, }, imageAndShapeIdIncrement: imageAndShapeIdIncrement, images: {}, linkId: 0, links: {}, htmlId: 0, htmls: {}, vars: {}, loops: [], fJump: false, shorthands: {}, options: options, // To verfiy we don't have a nested if within the same p or tr tag pIfCheckMap: new Map(), trIfCheckMap: new Map(), }; } exports.newContext = newContext; // Go through the document until the query string is found (normally at the beginning) function extractQuery(template, options) { return __awaiter(this, void 0, void 0, function () { var ctx, nodeIn, fFound, parent_1, nextSibling, parent_2; return __generator(this, function (_a) { switch (_a.label) { case 0: ctx = newContext(options); // ensure no command will be processed, except QUERY ctx.fSeekQuery = true; nodeIn = template; _a.label = 1; case 1: if (!true) return [3 /*break*/, 4]; // Move down if (nodeIn._children.length) nodeIn = nodeIn._children[0]; else { fFound = false; while (nodeIn._parent != null) { parent_1 = nodeIn._parent; nextSibling = (0, reportUtils_1.getNextSibling)(nodeIn); if (nextSibling) { nodeIn = nextSibling; fFound = true; break; } nodeIn = parent_1; } if (!fFound) return [3 /*break*/, 4]; } if (!nodeIn) return [3 /*break*/, 4]; parent_2 = nodeIn._parent; if (!(nodeIn._fTextNode && parent_2 && !parent_2._fTextNode && parent_2._tag === 'w:t')) return [3 /*break*/, 3]; return [4 /*yield*/, processText(null, nodeIn, ctx, processCmd)]; case 2: _a.sent(); _a.label = 3; case 3: if (ctx.query != null) return [3 /*break*/, 4]; return [3 /*break*/, 1]; case 4: return [2 /*return*/, ctx.query]; } }); }); } exports.extractQuery = extractQuery; function produceJsReport(data, template, ctx) { return __awaiter(this, void 0, void 0, function () { return __generator(this, function (_a) { return [2 /*return*/, walkTemplate(data, template, ctx, processCmd)]; }); }); } exports.produceJsReport = produceJsReport; function findHighestImgId(mainDoc) { var doc_ids = []; var search = function (n) { for (var _i = 0, _a = n._children; _i < _a.length; _i++) { var c = _a[_i]; var tag = c._fTextNode ? null : c._tag; if (tag == null) continue; if (tag === 'wp:docPr') { if (c._fTextNode) continue; var raw = c._attrs.id; if (typeof raw !== 'string') continue; var id = Number.parseInt(raw, 10); if (Number.isSafeInteger(id)) doc_ids.push(id); } if (c._children.length > 0) search(c); } }; search(mainDoc); if (doc_ids.length > 0) return Math.max.apply(Math, doc_ids); return 0; } exports.findHighestImgId = findHighestImgId; var debugPrintNode = function (node) { return JSON.stringify(node._fTextNode ? { _ifName: node._ifName, _fTextNode: node._fTextNode, _text: node === null || node === void 0 ? void 0 : node._text, } : { _ifName: node._ifName, _fTextNode: node._fTextNode, _tag: node === null || node === void 0 ? void 0 : node._tag, _attrs: node === null || node === void 0 ? void 0 : node._attrs, }); }; var findParentPorTrNode = function (node) { var parentNode = node._parent; var resultNode = null; while (parentNode != null && resultNode == null) { var parentNodeTag = parentNode._fTextNode ? null : parentNode._tag; if (parentNodeTag === 'w:p') { // check also for w:tr tag var grandParentNode = parentNode._parent != null ? parentNode._parent._parent : null; if (grandParentNode != null && !grandParentNode._fTextNode && grandParentNode._tag === 'w:tr') { resultNode = grandParentNode; } else { resultNode = parentNode; } } parentNode = parentNode._parent; } return resultNode; }; function walkTemplate(data, template, ctx, processor) { return __awaiter(this, void 0, void 0, function () { var out, nodeIn, nodeOut, move, deltaJump, errors, loopCount, maximumWalkingDepth, curLoop, nextSibling, parent_3, tag, fRemoveNode, buffers, nodeOutParent, imgNode, captionNodes, parent_4, linkNode, parent_5, htmlNode, parent_6, tag, newNode, newNodeTag, parent_7, result, err, innermost_loop, err; var _a; var _b; return __generator(this, function (_c) { switch (_c.label) { case 0: out = (0, reportUtils_1.cloneNodeWithoutChildren)(template); nodeIn = template; nodeOut = out; deltaJump = 0; errors = []; loopCount = 0; maximumWalkingDepth = ((_b = ctx.options) === null || _b === void 0 ? void 0 : _b.maximumWalkingDepth) || 1000000; _c.label = 1; case 1: if (!true) return [3 /*break*/, 5]; curLoop = (0, reportUtils_1.getCurLoop)(ctx); nextSibling = null; // ============================================= // Move input node pointer // ============================================= if (ctx.fJump) { if (!curLoop) throw new errors_1.InternalError('jumping while curLoop is null'); // TODO: comment debug statements back out, as creating the debug string creates overhead. debug_1.logger.debug("Jumping to level ".concat(curLoop.refNodeLevel, "..."), debugPrintNode(curLoop.refNode)); deltaJump = ctx.level - curLoop.refNodeLevel; nodeIn = curLoop.refNode; ctx.level = curLoop.refNodeLevel; ctx.fJump = false; move = 'JUMP'; // Down (only if he haven't just moved up) } else if (nodeIn._children.length && move !== 'UP') { nodeIn = nodeIn._children[0]; ctx.level += 1; move = 'DOWN'; // Sideways } else if ((nextSibling = (0, reportUtils_1.getNextSibling)(nodeIn))) { nodeIn = nextSibling; move = 'SIDE'; // Up } else { parent_3 = nodeIn._parent; if (parent_3 == null) { debug_1.logger.debug("=== parent is null, breaking after ".concat(loopCount, " loops...")); return [3 /*break*/, 5]; } else if (loopCount > maximumWalkingDepth) { // adding a emergency exit to avoid infit loops debug_1.logger.debug("=== parent is still not null after ".concat(loopCount, " loops, something must be wrong ..."), debugPrintNode(parent_3)); throw new errors_1.InternalError('infinite loop or massive dataset detected. Please review and try again'); } nodeIn = parent_3; ctx.level -= 1; move = 'UP'; } debug_1.logger.debug("Next node [".concat(move, ", level ").concat(ctx.level, "]"), debugPrintNode(nodeIn)); // ============================================= // Process input node // ============================================= // Delete the last generated output node in several special cases // -------------------------------------------------------------- if (move !== 'DOWN') { tag = nodeOut._fTextNode ? null : nodeOut._tag; fRemoveNode = false; // Delete last generated output node if we're skipping nodes due to an empty FOR loop if ((tag === 'w:p' || tag === 'w:tbl' || tag === 'w:tr' || tag === 'w:tc') && (0, reportUtils_1.isLoopExploring)(ctx)) { fRemoveNode = true; // Delete last generated output node if the user inserted a paragraph // (or table row) with just a command } else if (tag === 'w:p' || tag === 'w:tr' || tag === 'w:tc') { buffers = ctx.buffers[tag]; fRemoveNode = buffers.text === '' && buffers.cmds !== '' && !buffers.fInsertedText; // If the last generated output node is a table row, and it is set to be deleted, // don't delete if it has exactly one nested row (i.e. within nested table) if (tag === 'w:tr' && fRemoveNode) { fRemoveNode = nodeIn._children.filter(function (child) { return !child._fTextNode && child._tag === 'w:tr'; }).length !== 1; } // If the last generated output node is a table column, and it is set to be deleted, // don't delete if it has a table as a child if (tag === 'w:tc' && fRemoveNode) { fRemoveNode = !(nodeOut._children.filter(function (child) { return !child._fTextNode && child._tag === 'w:tbl'; }).length > 0); } } // Execute removal, if needed. The node will no longer be part of the output, but // the parent will be accessible from the child (so that we can still move up the tree) if (fRemoveNode && nodeOut._parent != null) { nodeOut._parent._children.pop(); } } // Handle an UP movement // --------------------- if (move === 'UP') { // Loop exploring? Update the reference node for the current loop if ((0, reportUtils_1.isLoopExploring)(ctx) && curLoop && nodeIn === curLoop.refNode._parent) { curLoop.refNode = nodeIn; curLoop.refNodeLevel -= 1; debug_1.logger.debug("Updated loop '".concat(curLoop.varName, "' refNode: ") + debugPrintNode(nodeIn)); } nodeOutParent = nodeOut._parent; if (nodeOutParent == null) throw new errors_1.InternalError('node parent is null'); // Execute the move in the output tree nodeOut = nodeOutParent; // If an image was generated, replace the parent `w:t` node with // the image node if (ctx.pendingImageNode && !nodeOut._fTextNode && nodeOut._tag === 'w:t') { imgNode = ctx.pendingImageNode.image; captionNodes = ctx.pendingImageNode.caption; parent_4 = nodeOut._parent; if (parent_4) { imgNode._parent = parent_4; parent_4._children.pop(); parent_4._children.push(imgNode); if (captionNodes) { (_a = parent_4._children).push.apply(_a, captionNodes); } // Prevent containing paragraph or table row from being removed ctx.buffers['w:p'].fInsertedText = true; ctx.buffers['w:tr'].fInsertedText = true; ctx.buffers['w:tc'].fInsertedText = true; } delete ctx.pendingImageNode; } // If a link was generated, replace the parent `w:r` node with // the link node if (ctx.pendingLinkNode && !nodeOut._fTextNode && nodeOut._tag === 'w:r') { linkNode = ctx.pendingLinkNode; parent_5 = nodeOut._parent; if (parent_5) { linkNode._parent = parent_5; parent_5._children.pop(); parent_5._children.push(linkNode); // Prevent containing paragraph or table row from being removed ctx.buffers['w:p'].fInsertedText = true; ctx.buffers['w:tr'].fInsertedText = true; ctx.buffers['w:tc'].fInsertedText = true; } delete ctx.pendingLinkNode; } // If a html page was generated, replace the parent `w:p` node with // the html node if (ctx.pendingHtmlNode && !nodeOut._fTextNode && nodeOut._tag === 'w:p') { htmlNode = ctx.pendingHtmlNode; parent_6 = nodeOut._parent; if (parent_6) { htmlNode._parent = parent_6; parent_6._children.pop(); parent_6._children.push(htmlNode); // Prevent containing paragraph or table row from being removed ctx.buffers['w:p'].fInsertedText = true; ctx.buffers['w:tr'].fInsertedText = true; ctx.buffers['w:tc'].fInsertedText = true; } delete ctx.pendingHtmlNode; } // `w:tc` nodes shouldn't be left with no `w:p` or 'w:altChunk' children; if that's the // case, add an empty `w:p` inside if (!nodeOut._fTextNode && nodeOut._tag === 'w:tc' && !nodeOut._children.filter(function (o) { return !o._fTextNode && (o._tag === 'w:p' || o._tag === 'w:altChunk'); }).length) { nodeOut._children.push({ _parent: nodeOut, _children: [], _fTextNode: false, _tag: 'w:p', _attrs: {}, }); } // Save latest `w:rPr` node that was visited (for LINK properties) if (!nodeOut._fTextNode && nodeOut._tag === 'w:rPr') { ctx.textRunPropsNode = nodeOut; } if (!nodeIn._fTextNode && nodeIn._tag === 'w:r') { delete ctx.textRunPropsNode; } } if (!(move === 'DOWN' || move === 'SIDE')) return [3 /*break*/, 4]; // Move nodeOut to point to the new node's parent if (move === 'SIDE') { if (nodeOut._parent == null) throw new errors_1.InternalError('node parent is null'); nodeOut = nodeOut._parent; } tag = nodeIn._fTextNode ? null : nodeIn._tag; if (tag === 'w:p' || tag === 'w:tr' || tag === 'w:tc') { ctx.buffers[tag] = { text: '', cmds: '', fInsertedText: false }; } newNode = (0, reportUtils_1.cloneNodeWithoutChildren)(nodeIn); newNode._parent = nodeOut; nodeOut._children.push(newNode); newNodeTag = newNode._tag; if (!(0, reportUtils_1.isLoopExploring)(ctx) && (newNodeTag === 'wp:docPr' || newNodeTag === 'v:shape')) { debug_1.logger.debug('detected a - ', debugPrintNode(newNode)); updateID(newNode, ctx); } parent_7 = nodeIn._parent; if (!(nodeIn._fTextNode && parent_7 && !parent_7._fTextNode && parent_7._tag === 'w:t')) return [3 /*break*/, 3]; return [4 /*yield*/, processText(data, nodeIn, ctx, processor)]; case 2: result = _c.sent(); if (typeof result === 'string') { // TODO: improve typesafety of conversion Node to TextNode. newNode._text = result; debug_1.logger.debug("Inserted command result string into node. Updated node: " + debugPrintNode(newNode)); } else { errors.push.apply(errors, result); } _c.label = 3; case 3: // Execute the move in the output tree nodeOut = newNode; _c.label = 4; case 4: // JUMP to the target level of the tree. // ------------------------------------------- if (move === 'JUMP') { while (deltaJump > 0) { if (nodeOut._parent == null) throw new errors_1.InternalError('node parent is null'); nodeOut = nodeOut._parent; deltaJump -= 1; } } loopCount++; return [3 /*break*/, 1]; case 5: if (ctx.gCntIf !== ctx.gCntEndIf) { err = new errors_1.IncompleteConditionalStatementError(); if (ctx.options.failFast) { throw err; } else { errors.push(err); } } if (ctx.loops.filter(function (l) { return !l.isIf; }).length > 0) { innermost_loop = ctx.loops[ctx.loops.length - 1]; err = new errors_1.UnterminatedForLoopError(innermost_loop); if (ctx.options.failFast) { throw err; } else { errors.push(err); } } if (errors.length > 0) return [2 /*return*/, { status: 'errors', errors: errors, }]; return [2 /*return*/, { status: 'success', report: out, images: ctx.images, links: ctx.links, htmls: ctx.htmls, }]; } }); }); } exports.walkTemplate = walkTemplate; var processText = function (data, node, ctx, onCommand) { return __awaiter(void 0, void 0, void 0, function () { var _a, cmdDelimiter, failFast, text, segments, outText, errors, idx, segment, cmdResultText; return __generator(this, function (_b) { switch (_b.label) { case 0: _a = ctx.options, cmdDelimiter = _a.cmdDelimiter, failFast = _a.failFast; text = node._text; if (text == null || text === '') return [2 /*return*/, '']; segments = text .split(cmdDelimiter[0]) .map(function (s) { return s.split(cmdDelimiter[1]); }) .reduce(function (x, y) { return x.concat(y); }); outText = ''; errors = []; idx = 0; _b.label = 1; case 1: if (!(idx < segments.length)) return [3 /*break*/, 5]; // Include the separators in the `buffers` field (used for deleting paragraphs if appropriate) if (idx > 0) appendTextToTagBuffers(cmdDelimiter[0], ctx, { fCmd: true }); segment = segments[idx]; // logger.debug(`Token: '${segment}' (${ctx.fCmd})`); if (ctx.fCmd) ctx.cmd += segment; else if (!(0, reportUtils_1.isLoopExploring)(ctx)) outText += segment; appendTextToTagBuffers(segment, ctx, { fCmd: ctx.fCmd }); if (!(idx < segments.length - 1)) return [3 /*break*/, 4]; if (!ctx.fCmd) return [3 /*break*/, 3]; return [4 /*yield*/, onCommand(data, node, ctx)]; case 2: cmdResultText = _b.sent(); if (cmdResultText != null) { if (typeof cmdResultText === 'string') { outText += cmdResultText; appendTextToTagBuffers(cmdResultText, ctx, { fCmd: false, fInsertedText: true, }); } else { if (failFast) throw cmdResultText; errors.push(cmdResultText); } } _b.label = 3; case 3: ctx.fCmd = !ctx.fCmd; _b.label = 4; case 4: idx++; return [3 /*break*/, 1]; case 5: if (errors.length > 0) return [2 /*return*/, errors]; return [2 /*return*/, outText]; } }); }); }; // ========================================== // Command processor // ========================================== var processCmd = function (data, node, ctx) { return __awaiter(void 0, void 0, void 0, function () { var cmd, _a, cmdName, cmdRest, aliasMatch, aliasName, fullCmd, result, nerr, str, literalXmlDelimiter, splitByLineBreak, LINE_BREAK, END_OF_TEXT, START_OF_TEXT, img, pars, html, err_1; return __generator(this, function (_b) { switch (_b.label) { case 0: cmd = getCommand(ctx.cmd, ctx.shorthands, ctx.options.fixSmartQuotes); ctx.cmd = ''; // flush the context _b.label = 1; case 1: _b.trys.push([1, 28, , 29]); _a = splitCommand(cmd), cmdName = _a.cmdName, cmdRest = _a.cmdRest; if (cmdName !== 'CMD_NODE') debug_1.logger.debug("Processing cmd: ".concat(cmd)); // Seeking query? if (ctx.fSeekQuery) { if (cmdName === 'QUERY') ctx.query = cmdRest; return [2 /*return*/]; } if (!(cmdName === 'QUERY' || cmdName === 'CMD_NODE')) return [3 /*break*/, 2]; return [3 /*break*/, 27]; case 2: if (!(cmdName === 'ALIAS')) return [3 /*break*/, 3]; aliasMatch = /^(\S+)\s+(.+)/.exec(cmdRest); if (!aliasMatch) throw new errors_1.InvalidCommandError('Invalid ALIAS command', cmd); aliasName = aliasMatch[1]; fullCmd = aliasMatch[2]; ctx.shorthands[aliasName] = fullCmd; debug_1.logger.debug("Defined alias '".concat(aliasName, "' for: ").concat(fullCmd)); return [3 /*break*/, 27]; case 3: if (!(cmdName === 'FOR' || cmdName === 'IF')) return [3 /*break*/, 5]; return [4 /*yield*/, processForIf(data, node, ctx, cmd, cmdName, cmdRest)]; case 4: _b.sent(); return [3 /*break*/, 27]; case 5: if (!(cmdName === 'END-FOR' || cmdName === 'END-IF')) return [3 /*break*/, 6]; processEndForIf(node, ctx, cmd, cmdName, cmdRest); return [3 /*break*/, 27]; case 6: if (!(cmdName === 'INS')) return [3 /*break*/, 12]; if (!!(0, reportUtils_1.isLoopExploring)(ctx)) return [3 /*break*/, 11]; return [4 /*yield*/, (0, jsSandbox_1.runUserJsAndGetRaw)(data, cmdRest, ctx)]; case 7: result = _b.sent(); if (result == null) { return [2 /*return*/, '']; } if (!(typeof result === 'object' && !Array.isArray(result))) return [3 /*break*/, 10]; nerr = new errors_1.ObjectCommandResultError(cmdRest, result); if (!(ctx.options.errorHandler != null)) return [3 /*break*/, 9]; return [4 /*yield*/, ctx.options.errorHandler(nerr, cmdRest)]; case 8: result = _b.sent(); return [3 /*break*/, 10]; case 9: throw nerr; case 10: str = String(result); if (ctx.options.processLineBreaks) { literalXmlDelimiter = ctx.options.literalXmlDelimiter; if (ctx.options.processLineBreaksAsNewText) { splitByLineBreak = str.split('\n'); LINE_BREAK = "".concat(literalXmlDelimiter, "").concat(literalXmlDelimiter); END_OF_TEXT = "".concat(literalXmlDelimiter, "").concat(literalXmlDelimiter); START_OF_TEXT = "".concat(literalXmlDelimiter, "").concat(literalXmlDelimiter); str = splitByLineBreak.join("".concat(END_OF_TEXT).concat(LINE_BREAK).concat(START_OF_TEXT)); } else { str = str.replace(/\n/g, "".concat(literalXmlDelimiter, "").concat(literalXmlDelimiter)); } } return [2 /*return*/, str]; case 11: return [3 /*break*/, 27]; case 12: if (!(cmdName === 'EXEC')) return [3 /*break*/, 15]; if (!!(0, reportUtils_1.isLoopExploring)(ctx)) return [3 /*break*/, 14]; return [4 /*yield*/, (0, jsSandbox_1.runUserJsAndGetRaw)(data, cmdRest, ctx)]; case 13: _b.sent(); _b.label = 14; case 14: return [3 /*break*/, 27]; case 15: if (!(cmdName === 'IMAGE')) return [3 /*break*/, 18]; if (!!(0, reportUtils_1.isLoopExploring)(ctx)) return [3 /*break*/, 17]; return [4 /*yield*/, (0, jsSandbox_1.runUserJsAndGetRaw)(data, cmdRest, ctx)]; case 16: img = _b.sent(); if (img != null) { try { processImage(ctx, img); } catch (e) { if (!(0, errors_1.isError)(e)) throw e; throw new errors_1.ImageError(e, cmd); } } _b.label = 17; case 17: return [3 /*break*/, 27]; case 18: if (!(cmdName === 'LINK')) return [3 /*break*/, 22]; if (!!(0, reportUtils_1.isLoopExploring)(ctx)) return [3 /*break*/, 21]; return [4 /*yield*/, (0, jsSandbox_1.runUserJsAndGetRaw)(data, cmdRest, ctx)]; case 19: pars = _b.sent(); if (!(pars != null)) return [3 /*break*/, 21]; return [4 /*yield*/, processLink(ctx, pars)]; case 20: _b.sent(); _b.label = 21; case 21: return [3 /*break*/, 27]; case 22: if (!(cmdName === 'HTML')) return [3 /*break*/, 26]; if (!!(0, reportUtils_1.isLoopExploring)(ctx)) return [3 /*break*/, 25]; return [4 /*yield*/, (0, jsSandbox_1.runUserJsAndGetRaw)(data, cmdRest, ctx)]; case 23: html = _b.sent(); if (!(html != null)) return [3 /*break*/, 25]; return [4 /*yield*/, processHtml(ctx, html)]; case 24: _b.sent(); _b.label = 25; case 25: return [3 /*break*/, 27]; case 26: throw new errors_1.CommandSyntaxError(cmd); case 27: return [2 /*return*/]; case 28: err_1 = _b.sent(); if (!(0, errors_1.isError)(err_1)) throw err_1; if (ctx.options.errorHandler != null) { return [2 /*return*/, ctx.options.errorHandler(err_1)]; } return [2 /*return*/, err_1]; case 29: return [2 /*return*/]; } }); }); }; var builtInRegexes = types_1.BUILT_IN_COMMANDS.map(function (word) { return new RegExp("^".concat(word, "\\b")); }); var notBuiltIns = function (cmd) { return !builtInRegexes.some(function (r) { return r.test(cmd.toUpperCase()); }); }; function getCommand(command, shorthands, fixSmartQuotes) { // Get a cleaned version of the command var cmd = command.trim(); if (cmd[0] === '*') { var aliasName = cmd.slice(1).trim(); if (!shorthands[aliasName]) throw new errors_1.InvalidCommandError('Unknown alias', cmd); cmd = shorthands[aliasName]; debug_1.logger.debug("Alias for: ".concat(cmd)); } else if (cmd[0] === '=') { cmd = "INS ".concat(cmd.slice(1).trim()); } else if (cmd[0] === '!') { cmd = "EXEC ".concat(cmd.slice(1).trim()); } else if (notBuiltIns(cmd)) { cmd = "INS ".concat(cmd.trim()); } //replace 'smart' quotes with straight quotes if (fixSmartQuotes) { cmd = cmd .replace(/[\u201C\u201D\u201E]/g, '"') .replace(/[\u2018\u2019\u201A]/g, "'"); } return cmd.trim(); } exports.getCommand = getCommand; function splitCommand(cmd) { // Extract command name var cmdNameMatch = /^(\S+)\s*/.exec(cmd); var cmdName; var cmdRest = ''; if (cmdNameMatch != null) { cmdName = cmdNameMatch[1].toUpperCase(); cmdRest = cmd.slice(cmdName.length).trim(); } return { cmdName: cmdName, cmdRest: cmdRest }; } exports.splitCommand = splitCommand; // ========================================== // Individual commands // ========================================== var processForIf = function (data, node, ctx, cmd, cmdName, cmdRest) { return __awaiter(void 0, void 0, void 0, function () { var isIf, forMatch, varName, curLoop, parentPorTrNode, parentPorTrNodeTag, parentLoopLevel, fParentIsExploring, loopOver, shouldRun; return __generator(this, function (_a) { switch (_a.label) { case 0: isIf = cmdName === 'IF'; forMatch = null; varName = undefined; if (isIf) { if (!node._ifName) { node._ifName = "__if_".concat(ctx.gCntIf); ctx.gCntIf += 1; } varName = node._ifName; } else { forMatch = /^(\S+)\s+IN\s+(.+)/i.exec(cmdRest); if (!forMatch) throw new errors_1.InvalidCommandError('Invalid FOR command', cmd); varName = forMatch[1]; } curLoop = (0, reportUtils_1.getCurLoop)(ctx); if (!!(curLoop && curLoop.varName === varName)) return [3 /*break*/, 6]; // Check whether we already started a nested IF without and END-IF for this p or tr tag if (isIf) { parentPorTrNode = findParentPorTrNode(node); parentPorTrNodeTag = parentPorTrNode != null ? parentPorTrNode._fTextNode ? null : parentPorTrNode._tag : null; if (parentPorTrNode != null) { if (parentPorTrNodeTag === 'w:p') { if (ctx.pIfCheckMap.has(parentPorTrNode) && ctx.pIfCheckMap.get(parentPorTrNode) !== cmd) throw new errors_1.InvalidCommandError('Invalid IF command nested into another IF command on the same line', cmd); else ctx.pIfCheckMap.set(parentPorTrNode, cmd); } else if (parentPorTrNodeTag === 'w:tr') { if (ctx.trIfCheckMap.has(parentPorTrNode) && ctx.trIfCheckMap.get(parentPorTrNode) !== cmd) throw new errors_1.InvalidCommandError('Invalid IF command nested into another IF command on the same table row', cmd); else ctx.trIfCheckMap.set(parentPorTrNode, cmd); } } } parentLoopLevel = ctx.loops.length - 1; fParentIsExploring = parentLoopLevel >= 0 && ctx.loops[parentLoopLevel].idx === -1; loopOver = void 0; if (!fParentIsExploring) return [3 /*break*/, 1]; loopOver = []; return [3 /*break*/, 5]; case 1: if (!isIf) return [3 /*break*/, 3]; return [4 /*yield*/, (0, jsSandbox_1.runUserJsAndGetRaw)(data, cmdRest, ctx)]; case 2: shouldRun = !!(_a.sent()); loopOver = shouldRun ? [1] : []; return [3 /*break*/, 5]; case 3: if (!forMatch) throw new errors_1.InvalidCommandError('Invalid FOR command', cmd); return [4 /*yield*/, (0, jsSandbox_1.runUserJsAndGetRaw)(data, forMatch[2], ctx)]; case 4: loopOver = _a.sent(); if (!Array.isArray(loopOver)) throw new errors_1.InvalidCommandError('Invalid FOR command (can only iterate over Array)', cmd); _a.label = 5; case 5: ctx.loops.push({ refNode: node, refNodeLevel: ctx.level, varName: varName, loopOver: loopOver, isIf: isIf, // run through the loop once first, without outputting anything // (if we don't do it like this, we could not run empty loops!) idx: -1, }); _a.label = 6; case 6: (0, reportUtils_1.logLoop)(ctx.loops); return [2 /*return*/]; } }); }); }; var processEndForIf = function (node, ctx, cmd, cmdName, cmdRest) { var isIf = cmdName === 'END-IF'; var curLoop = (0, reportUtils_1.getCurLoop)(ctx); if (!curLoop) throw new errors_1.InvalidCommandError("Unexpected ".concat(cmdName, " outside of ").concat(isIf ? 'IF statement' : 'FOR loop', " context"), cmd); // Reset the if check flag for the corresponding p or tr parent node var parentPorTrNode = findParentPorTrNode(node); var parentPorTrNodeTag = parentPorTrNode != null ? parentPorTrNode._fTextNode ? null : parentPorTrNode._tag : null; if (parentPorTrNodeTag === 'w:p') { ctx.pIfCheckMap.delete(parentPorTrNode); } else if (parentPorTrNodeTag === 'w:tr') { ctx.trIfCheckMap.delete(parentPorTrNode); } // First time we visit an END-IF node, we assign it the arbitrary name // generated when the IF was processed if (isIf && !node._ifName) { node._ifName = curLoop.varName; ctx.gCntEndIf += 1; } // Check if this is the expected END-IF/END-FOR. If not: // - If it's one of the nested varNames, throw // - If it's not one of the nested varNames, ignore it; we find // cases in which an END-IF/FOR is found that belongs to a previous // part of the paragraph of the current loop. var varName = isIf ? node._ifName : cmdRest; if (curLoop.varName !== varName) { if (ctx.loops.find(function (o) { return o.varName === varName; }) == null) { debug_1.logger.debug("Ignoring ".concat(cmd, " (").concat(varName, ", but we're expecting ").concat(curLoop.varName, ")")); return; } throw new errors_1.InvalidCommandError('Invalid command', cmd); } // Get the next item in the loop var nextIdx = curLoop.idx + 1; var nextItem = curLoop.loopOver[nextIdx]; if (nextItem != null) { // next iteration ctx.vars[varName] = nextItem; ctx.fJump = true; curLoop.idx = nextIdx; } else { // loop finished ctx.loops.pop(); } }; var imageToContext = function (ctx, img) { validateImage(img); ctx.imageAndShapeIdIncrement += 1; var id = String(ctx.imageAndShapeIdIncrement); var relId = "img".concat(id); ctx.images[relId] = img; return relId; }; function validateImage(img) { if (!(img.data instanceof Uint8Array || img.data instanceof ArrayBuffer || typeof img.data === 'string')) { throw new Error('image .data property needs to be provided as Uint8Array (e.g. Buffer), ArrayBuffer, or as a base64-encoded string'); } if (!types_1.ImageExtensions.includes(img.extension)) { throw new Error("An extension (one of ".concat(types_1.ImageExtensions, ") needs to be provided when providing an image or a thumbnail.")); } } function validateImagePars(pars) { if (!Number.isFinite(pars.width)) throw new Error("invalid image width: ".concat(pars.width, " (in cm)")); if (!Number.isFinite(pars.height)) throw new Error("invalid image height: ".concat(pars.height, " (in cm)")); validateImage(pars); if (pars.thumbnail) validateImage(pars.thumbnail); } var processImage = function (ctx, imagePars) { var _a; validateImagePars(imagePars); var cx = (imagePars.width * 360e3).toFixed(0); var cy = (imagePars.height * 360e3).toFixed(0); var imgRelId = imageToContext(ctx, getImageData(imagePars)); var id = String(ctx.imageAndShapeIdIncrement); var alt = imagePars.alt || 'desc'; var node = reportUtils_1.newNonTextNode; var extNodes = []; extNodes.push(node('a:ext', { uri: '{28A0092B-C50C-407E-A947-70E740481C1C}' }, [ node('a14:useLocalDpi', { 'xmlns:a14': 'http://schemas.microsoft.com/office/drawing/2010/main', val: '0', }), ])); // http://officeopenxml.com/drwSp-rotate.php // Values are in 60,000ths of a degree, with positive angles moving clockwise or towards the positive y-axis. var rot = imagePars.rotation ? (imagePars.rotation * 60e3).toString() : undefined; if (ctx.images[imgRelId].extension === '.svg') { // Default to an empty thumbnail, as it is not critical and just part of the docx standard's scaffolding. // Without a thumbnail, the svg won't render (even in newer versions of Word that don't need the thumbnail). var thumbnail = (_a = imagePars.thumbnail) !== null && _a !== void 0 ? _a : { data: 'bm90aGluZwo=', extension: '.png', }; var thumbRelId = imageToContext(ctx, thumbnail); extNodes.push(node('a:ext', { uri: '{96DAC541-7B7A-43D3-8B79-37D633B846F1}' }, [ node('asvg:svgBlip', { 'xmlns:asvg': 'http://schemas.microsoft.com/office/drawing/2016/SVG/main', 'r:embed': imgRelId, }), ])); // For SVG the thumb is placed where the image normally goes. imgRelId = thumbRelId; } var pic = node('pic:pic', { 'xmlns:pic': 'http://schemas.openxmlformats.org/drawingml/2006/picture' }, [ node('pic:nvPicPr', {}, [ node('pic:cNvPr', { id: '0', name: "Picture ".concat(id), descr: alt }), node('pic:cNvPicPr', {}, [ node('a:picLocks', { noChangeAspect: '1', noChangeArrowheads: '1' }), ]), ]), node('pic:blipFill', {}, [ node('a:blip', { 'r:embed': imgRelId, cstate: 'print' }, [ node('a:extLst', {}, extNodes), ]), node('a:srcRect'), node('a:stretch', {}, [node('a:fillRect')]), ]), node('pic:spPr', { bwMode: 'auto' }, [ node('a:xfrm', rot ? { rot: rot } : {}, [ node('a:off', { x: '0', y: '0' }), node('a:ext', { cx: cx, cy: cy }), ]), node('a:prstGeom', { prst: 'rect' }, [node('a:avLst')]), node('a:noFill'), node('a:ln', {}, [node('a:noFill')]), ]), ]); var drawing = node('w:drawing', {}, [ node('wp:inline', { distT: '0', distB: '0', distL: '0', distR: '0' }, [ node('wp:extent', { cx: cx, cy: cy }), node('wp:docPr', { id: id, name: "Picture ".concat(id), descr: alt }), node('wp:cNvGraphicFramePr', {}, [ node('a:graphicFrameLocks', { 'xmlns:a': 'http://schemas.openxmlformats.org/drawingml/2006/main', noChangeAspect: '1', }), ]), node('a:graphic', { 'xmlns:a': 'http://schemas.openxmlformats.org/drawingml/2006/main' }, [ node('a:graphicData', { uri: 'http://schemas.openxmlformats.org/drawingml/2006/picture' }, [pic]), ]), ]), ]); ctx.pendingImageNode = { image: drawing }; if (imagePars.caption) { ctx.pendingImageNode.caption = [ node('w:br'), node('w:t', {}, [(0, reportUtils_1.newTextNode)(imagePars.caption)]), ]; } }; function getImageData(imagePars) { var data = imagePars.data, extension = imagePars.extension; if (!extension) { throw new Error('If you return image `data`, make sure you return an extension as well!'); } return { extension: extension, data: data }; } var processLink = function (ctx, linkPars) { return __awaiter(void 0, void 0, void 0, function () { var url, _a, label, id, relId, node, textRunPropsNode, link; return __generator(this, function (_b) { url = linkPars.url, _a = linkPars.label, label = _a === void 0 ? url : _a; ctx.linkId += 1; id = String(ctx.linkId); relId = "link".concat(id); ctx.links[relId] = { url: url }; node = reportUtils_1.newNonTextNode; textRunPropsNode = ctx.textRunPropsNode; link = node('w:hyperlink', { 'r:id': relId, 'w:history': '1' }, [ node('w:r', {}, [ textRunPropsNode || node('w:rPr', {}, [node('w:u', { 'w:val': 'single' })]), node('w:t', {}, [(0, reportUtils_1.newTextNode)(label)]), ]), ]); ctx.pendingLinkNode = link; return [2 /*return*/]; }); }); }; var processHtml = function (ctx, data) { return __awaiter(void 0, void 0, void 0, function () { var id, relId, node, html; return __generator(this, function (_a) { ctx.htmlId += 1; id = String(ctx.htmlId); relId = "html".concat(id); ctx.htmls[relId] = data; node = reportUtils_1.newNonTextNode; html = node('w:altChunk', { 'r:id': relId }); ctx.pendingHtmlNode = html; return [2 /*return*/]; }); }); }; // ========================================== // Helpers // ========================================== var BufferKeys = ['w:p', 'w:tr', 'w:tc']; var appendTextToTagBuffers = function (text, ctx, options) { if (ctx.fSeekQuery) return; var fCmd = options.fCmd, fInsertedText = options.fInsertedText; var type = fCmd ? 'cmds' : 'text'; BufferKeys.forEach(function (key) { var buf = ctx.buffers[key]; buf[type] += text; if (fInsertedText) buf.fInsertedText = true; }); }; function updateID(newNode, ctx) { ctx.imageAndShapeIdIncrement += 1; var id = String(ctx.imageAndShapeIdIncrement); newNode._attrs = __assign(__assign({}, newNode._attrs), { id: "".concat(id) }); }