/** * @licstart The following is the entire license notice for the * JavaScript code in this page * * Copyright 2022 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * @licend The above is the entire license notice for the * JavaScript code in this page */ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Binder = void 0; var _xfa_object = require("./xfa_object.js"); var _template = require("./template.js"); var _som = require("./som.js"); var _namespaces = require("./namespaces.js"); var _util = require("../../shared/util.js"); const NS_DATASETS = _namespaces.NamespaceIds.datasets.id; function createText(content) { const node = new _template.Text({}); node[_xfa_object.$content] = content; return node; } class Binder { constructor(root) { this.root = root; this.datasets = root.datasets; if (root.datasets && root.datasets.data) { this.data = root.datasets.data; } else { this.data = new _xfa_object.XmlObject(_namespaces.NamespaceIds.datasets.id, "data"); } this.emptyMerge = this.data[_xfa_object.$getChildren]().length === 0; this.root.form = this.form = root.template[_xfa_object.$clone](); } _isConsumeData() { return !this.emptyMerge && this._mergeMode; } _isMatchTemplate() { return !this._isConsumeData(); } bind() { this._bindElement(this.form, this.data); return this.form; } getData() { return this.data; } _bindValue(formNode, data, picture) { formNode[_xfa_object.$data] = data; if (formNode[_xfa_object.$hasSettableValue]()) { if (data[_xfa_object.$isDataValue]()) { const value = data[_xfa_object.$getDataValue](); formNode[_xfa_object.$setValue](createText(value)); } else if (formNode instanceof _template.Field && formNode.ui && formNode.ui.choiceList && formNode.ui.choiceList.open === "multiSelect") { const value = data[_xfa_object.$getChildren]().map(child => child[_xfa_object.$content].trim()).join("\n"); formNode[_xfa_object.$setValue](createText(value)); } else if (this._isConsumeData()) { (0, _util.warn)(`XFA - Nodes haven't the same type.`); } } else if (!data[_xfa_object.$isDataValue]() || this._isMatchTemplate()) { this._bindElement(formNode, data); } else { (0, _util.warn)(`XFA - Nodes haven't the same type.`); } } _findDataByNameToConsume(name, isValue, dataNode, global) { if (!name) { return null; } let generator, match; for (let i = 0; i < 3; i++) { generator = dataNode[_xfa_object.$getRealChildrenByNameIt](name, false, true); while (true) { match = generator.next().value; if (!match) { break; } if (isValue === match[_xfa_object.$isDataValue]()) { return match; } } if (dataNode[_xfa_object.$namespaceId] === _namespaces.NamespaceIds.datasets.id && dataNode[_xfa_object.$nodeName] === "data") { break; } dataNode = dataNode[_xfa_object.$getParent](); } if (!global) { return null; } generator = this.data[_xfa_object.$getRealChildrenByNameIt](name, true, false); match = generator.next().value; if (match) { return match; } generator = this.data[_xfa_object.$getAttributeIt](name, true); match = generator.next().value; if (match && match[_xfa_object.$isDataValue]()) { return match; } return null; } _setProperties(formNode, dataNode) { if (!formNode.hasOwnProperty("setProperty")) { return; } for (const { ref, target, connection } of formNode.setProperty.children) { if (connection) { continue; } if (!ref) { continue; } const nodes = (0, _som.searchNode)(this.root, dataNode, ref, false, false); if (!nodes) { (0, _util.warn)(`XFA - Invalid reference: ${ref}.`); continue; } const [node] = nodes; if (!node[_xfa_object.$isDescendent](this.data)) { (0, _util.warn)(`XFA - Invalid node: must be a data node.`); continue; } const targetNodes = (0, _som.searchNode)(this.root, formNode, target, false, false); if (!targetNodes) { (0, _util.warn)(`XFA - Invalid target: ${target}.`); continue; } const [targetNode] = targetNodes; if (!targetNode[_xfa_object.$isDescendent](formNode)) { (0, _util.warn)(`XFA - Invalid target: must be a property or subproperty.`); continue; } const targetParent = targetNode[_xfa_object.$getParent](); if (targetNode instanceof _template.SetProperty || targetParent instanceof _template.SetProperty) { (0, _util.warn)(`XFA - Invalid target: cannot be a setProperty or one of its properties.`); continue; } if (targetNode instanceof _template.BindItems || targetParent instanceof _template.BindItems) { (0, _util.warn)(`XFA - Invalid target: cannot be a bindItems or one of its properties.`); continue; } const content = node[_xfa_object.$text](); const name = targetNode[_xfa_object.$nodeName]; if (targetNode instanceof _xfa_object.XFAAttribute) { const attrs = Object.create(null); attrs[name] = content; const obj = Reflect.construct(Object.getPrototypeOf(targetParent).constructor, [attrs]); targetParent[name] = obj[name]; continue; } if (!targetNode.hasOwnProperty(_xfa_object.$content)) { (0, _util.warn)(`XFA - Invalid node to use in setProperty`); continue; } targetNode[_xfa_object.$data] = node; targetNode[_xfa_object.$content] = content; targetNode[_xfa_object.$finalize](); } } _bindItems(formNode, dataNode) { if (!formNode.hasOwnProperty("items") || !formNode.hasOwnProperty("bindItems") || formNode.bindItems.isEmpty()) { return; } for (const item of formNode.items.children) { formNode[_xfa_object.$removeChild](item); } formNode.items.clear(); const labels = new _template.Items({}); const values = new _template.Items({}); formNode[_xfa_object.$appendChild](labels); formNode.items.push(labels); formNode[_xfa_object.$appendChild](values); formNode.items.push(values); for (const { ref, labelRef, valueRef, connection } of formNode.bindItems.children) { if (connection) { continue; } if (!ref) { continue; } const nodes = (0, _som.searchNode)(this.root, dataNode, ref, false, false); if (!nodes) { (0, _util.warn)(`XFA - Invalid reference: ${ref}.`); continue; } for (const node of nodes) { if (!node[_xfa_object.$isDescendent](this.datasets)) { (0, _util.warn)(`XFA - Invalid ref (${ref}): must be a datasets child.`); continue; } const labelNodes = (0, _som.searchNode)(this.root, node, labelRef, true, false); if (!labelNodes) { (0, _util.warn)(`XFA - Invalid label: ${labelRef}.`); continue; } const [labelNode] = labelNodes; if (!labelNode[_xfa_object.$isDescendent](this.datasets)) { (0, _util.warn)(`XFA - Invalid label: must be a datasets child.`); continue; } const valueNodes = (0, _som.searchNode)(this.root, node, valueRef, true, false); if (!valueNodes) { (0, _util.warn)(`XFA - Invalid value: ${valueRef}.`); continue; } const [valueNode] = valueNodes; if (!valueNode[_xfa_object.$isDescendent](this.datasets)) { (0, _util.warn)(`XFA - Invalid value: must be a datasets child.`); continue; } const label = createText(labelNode[_xfa_object.$text]()); const value = createText(valueNode[_xfa_object.$text]()); labels[_xfa_object.$appendChild](label); labels.text.push(label); values[_xfa_object.$appendChild](value); values.text.push(value); } } } _bindOccurrences(formNode, matches, picture) { let baseClone; if (matches.length > 1) { baseClone = formNode[_xfa_object.$clone](); baseClone[_xfa_object.$removeChild](baseClone.occur); baseClone.occur = null; } this._bindValue(formNode, matches[0], picture); this._setProperties(formNode, matches[0]); this._bindItems(formNode, matches[0]); if (matches.length === 1) { return; } const parent = formNode[_xfa_object.$getParent](); const name = formNode[_xfa_object.$nodeName]; const pos = parent[_xfa_object.$indexOf](formNode); for (let i = 1, ii = matches.length; i < ii; i++) { const match = matches[i]; const clone = baseClone[_xfa_object.$clone](); parent[name].push(clone); parent[_xfa_object.$insertAt](pos + i, clone); this._bindValue(clone, match, picture); this._setProperties(clone, match); this._bindItems(clone, match); } } _createOccurrences(formNode) { if (!this.emptyMerge) { return; } const { occur } = formNode; if (!occur || occur.initial <= 1) { return; } const parent = formNode[_xfa_object.$getParent](); const name = formNode[_xfa_object.$nodeName]; if (!(parent[name] instanceof _xfa_object.XFAObjectArray)) { return; } let currentNumber; if (formNode.name) { currentNumber = parent[name].children.filter(e => e.name === formNode.name).length; } else { currentNumber = parent[name].children.length; } const pos = parent[_xfa_object.$indexOf](formNode) + 1; const ii = occur.initial - currentNumber; if (ii) { const nodeClone = formNode[_xfa_object.$clone](); nodeClone[_xfa_object.$removeChild](nodeClone.occur); nodeClone.occur = null; parent[name].push(nodeClone); parent[_xfa_object.$insertAt](pos, nodeClone); for (let i = 1; i < ii; i++) { const clone = nodeClone[_xfa_object.$clone](); parent[name].push(clone); parent[_xfa_object.$insertAt](pos + i, clone); } } } _getOccurInfo(formNode) { const { name, occur } = formNode; if (!occur || !name) { return [1, 1]; } const max = occur.max === -1 ? Infinity : occur.max; return [occur.min, max]; } _setAndBind(formNode, dataNode) { this._setProperties(formNode, dataNode); this._bindItems(formNode, dataNode); this._bindElement(formNode, dataNode); } _bindElement(formNode, dataNode) { const uselessNodes = []; this._createOccurrences(formNode); for (const child of formNode[_xfa_object.$getChildren]()) { if (child[_xfa_object.$data]) { continue; } if (this._mergeMode === undefined && child[_xfa_object.$nodeName] === "subform") { this._mergeMode = child.mergeMode === "consumeData"; const dataChildren = dataNode[_xfa_object.$getChildren](); if (dataChildren.length > 0) { this._bindOccurrences(child, [dataChildren[0]], null); } else if (this.emptyMerge) { const nsId = dataNode[_xfa_object.$namespaceId] === NS_DATASETS ? -1 : dataNode[_xfa_object.$namespaceId]; const dataChild = child[_xfa_object.$data] = new _xfa_object.XmlObject(nsId, child.name || "root"); dataNode[_xfa_object.$appendChild](dataChild); this._bindElement(child, dataChild); } continue; } if (!child[_xfa_object.$isBindable]()) { continue; } let global = false; let picture = null; let ref = null; let match = null; if (child.bind) { switch (child.bind.match) { case "none": this._setAndBind(child, dataNode); continue; case "global": global = true; break; case "dataRef": if (!child.bind.ref) { (0, _util.warn)(`XFA - ref is empty in node ${child[_xfa_object.$nodeName]}.`); this._setAndBind(child, dataNode); continue; } ref = child.bind.ref; break; default: break; } if (child.bind.picture) { picture = child.bind.picture[_xfa_object.$content]; } } const [min, max] = this._getOccurInfo(child); if (ref) { match = (0, _som.searchNode)(this.root, dataNode, ref, true, false); if (match === null) { match = (0, _som.createDataNode)(this.data, dataNode, ref); if (!match) { continue; } if (this._isConsumeData()) { match[_xfa_object.$consumed] = true; } this._setAndBind(child, match); continue; } else { if (this._isConsumeData()) { match = match.filter(node => !node[_xfa_object.$consumed]); } if (match.length > max) { match = match.slice(0, max); } else if (match.length === 0) { match = null; } if (match && this._isConsumeData()) { match.forEach(node => { node[_xfa_object.$consumed] = true; }); } } } else { if (!child.name) { this._setAndBind(child, dataNode); continue; } if (this._isConsumeData()) { const matches = []; while (matches.length < max) { const found = this._findDataByNameToConsume(child.name, child[_xfa_object.$hasSettableValue](), dataNode, global); if (!found) { break; } found[_xfa_object.$consumed] = true; matches.push(found); } match = matches.length > 0 ? matches : null; } else { match = dataNode[_xfa_object.$getRealChildrenByNameIt](child.name, false, this.emptyMerge).next().value; if (!match) { if (min === 0) { uselessNodes.push(child); continue; } const nsId = dataNode[_xfa_object.$namespaceId] === NS_DATASETS ? -1 : dataNode[_xfa_object.$namespaceId]; match = child[_xfa_object.$data] = new _xfa_object.XmlObject(nsId, child.name); if (this.emptyMerge) { match[_xfa_object.$consumed] = true; } dataNode[_xfa_object.$appendChild](match); this._setAndBind(child, match); continue; } if (this.emptyMerge) { match[_xfa_object.$consumed] = true; } match = [match]; } } if (match) { this._bindOccurrences(child, match, picture); } else if (min > 0) { this._setAndBind(child, dataNode); } else { uselessNodes.push(child); } } uselessNodes.forEach(node => node[_xfa_object.$getParent]()[_xfa_object.$removeChild](node)); } } exports.Binder = Binder;