Merge pull request #13018 from calixteman/xfa_bind
XFA - Create Form DOM in merging template and data trees
This commit is contained in:
commit
3d4bb5e5c5
593
src/core/xfa/bind.js
Normal file
593
src/core/xfa/bind.js
Normal file
@ -0,0 +1,593 @@
|
|||||||
|
/* Copyright 2021 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
$appendChild,
|
||||||
|
$clone,
|
||||||
|
$consumed,
|
||||||
|
$content,
|
||||||
|
$data,
|
||||||
|
$finalize,
|
||||||
|
$getAttributeIt,
|
||||||
|
$getChildren,
|
||||||
|
$getParent,
|
||||||
|
$getRealChildrenByNameIt,
|
||||||
|
$global,
|
||||||
|
$hasSettableValue,
|
||||||
|
$indexOf,
|
||||||
|
$insertAt,
|
||||||
|
$isDataValue,
|
||||||
|
$isDescendent,
|
||||||
|
$namespaceId,
|
||||||
|
$nodeName,
|
||||||
|
$removeChild,
|
||||||
|
$setValue,
|
||||||
|
$text,
|
||||||
|
XFAAttribute,
|
||||||
|
XmlObject,
|
||||||
|
} from "./xfa_object.js";
|
||||||
|
import { BindItems, Field, Items, SetProperty, Text } from "./template.js";
|
||||||
|
import { createDataNode, searchNode } from "./som.js";
|
||||||
|
import { NamespaceIds } from "./namespaces.js";
|
||||||
|
import { warn } from "../../shared/util.js";
|
||||||
|
|
||||||
|
function createText(content) {
|
||||||
|
const node = new Text({});
|
||||||
|
node[$content] = content;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Binder {
|
||||||
|
constructor(root) {
|
||||||
|
this.root = root;
|
||||||
|
this.datasets = root.datasets;
|
||||||
|
if (root.datasets && root.datasets.data) {
|
||||||
|
this.emptyMerge = false;
|
||||||
|
this.data = root.datasets.data;
|
||||||
|
} else {
|
||||||
|
this.emptyMerge = true;
|
||||||
|
this.data = new XmlObject(NamespaceIds.datasets.id, "data");
|
||||||
|
}
|
||||||
|
this.root.form = this.form = root.template[$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) {
|
||||||
|
// Nodes must have the same "type": container or value.
|
||||||
|
// Here we make the link between form node and
|
||||||
|
// data node (through $data property): we'll use it
|
||||||
|
// to save form data.
|
||||||
|
|
||||||
|
if (formNode[$hasSettableValue]()) {
|
||||||
|
if (data[$isDataValue]()) {
|
||||||
|
const value = data[$content].trim();
|
||||||
|
// TODO: use picture.
|
||||||
|
formNode[$setValue](createText(value));
|
||||||
|
formNode[$data] = data;
|
||||||
|
} else if (
|
||||||
|
formNode instanceof Field &&
|
||||||
|
formNode.ui &&
|
||||||
|
formNode.ui.choiceList &&
|
||||||
|
formNode.ui.choiceList.open === "multiSelect"
|
||||||
|
) {
|
||||||
|
const value = data[$getChildren]()
|
||||||
|
.map(child => child[$content].trim())
|
||||||
|
.join("\n");
|
||||||
|
formNode[$setValue](createText(value));
|
||||||
|
formNode[$data] = data;
|
||||||
|
} else if (this._isConsumeData()) {
|
||||||
|
warn(`XFA - Nodes haven't the same type.`);
|
||||||
|
}
|
||||||
|
} else if (!data[$isDataValue]() || this._isMatchTemplate()) {
|
||||||
|
this._bindElement(formNode, data);
|
||||||
|
formNode[$data] = data;
|
||||||
|
} else {
|
||||||
|
warn(`XFA - Nodes haven't the same type.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_findDataByNameToConsume(name, dataNode, global) {
|
||||||
|
if (!name) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Firstly, we try to find a node with the given name:
|
||||||
|
// - in dataNode;
|
||||||
|
// - if not found, then in parent;
|
||||||
|
// - and if not in found, then in grand-parent.
|
||||||
|
let generator, match;
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
generator = dataNode[$getRealChildrenByNameIt](
|
||||||
|
name,
|
||||||
|
/* allTransparent = */ false,
|
||||||
|
/* skipConsumed = */ true
|
||||||
|
);
|
||||||
|
match = generator.next().value;
|
||||||
|
if (match) {
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
dataNode[$namespaceId] === NamespaceIds.datasets.id &&
|
||||||
|
dataNode[$nodeName] === "data"
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
dataNode = dataNode[$getParent]();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!global) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secondly, if global try to find it just under the root of datasets
|
||||||
|
// (which is the location of global variables).
|
||||||
|
generator = this.datasets[$getRealChildrenByNameIt](
|
||||||
|
name,
|
||||||
|
/* allTransparent = */ false,
|
||||||
|
/* skipConsumed = */ false
|
||||||
|
);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
match = generator.next().value;
|
||||||
|
if (!match) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match[$global]) {
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thirdly, try to find it in attributes.
|
||||||
|
generator = this.data[$getAttributeIt](name, /* skipConsumed = */ true);
|
||||||
|
match = generator.next().value;
|
||||||
|
if (match && match[$isDataValue]()) {
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_setProperties(formNode, dataNode) {
|
||||||
|
// For example:
|
||||||
|
// <field name="LastName" ...>
|
||||||
|
// <setProperty ref="$data.Main.Style.NameFont" target="font.typeface"/>
|
||||||
|
// <setProperty ref="$data.Main.Style.NameSize" target="font.size"/>
|
||||||
|
// <setProperty ref="$data.Main.Help.LastName" target="assist.toolTip"/>
|
||||||
|
// </field>
|
||||||
|
|
||||||
|
if (!formNode.hasOwnProperty("setProperty")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const { ref, target, connection } of formNode.setProperty.children) {
|
||||||
|
if (connection) {
|
||||||
|
// TODO: evaluate if we should implement this feature.
|
||||||
|
// Skip for security reasons.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!ref) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [node] = searchNode(
|
||||||
|
this.root,
|
||||||
|
dataNode,
|
||||||
|
ref,
|
||||||
|
false /* = dotDotAllowed */,
|
||||||
|
false /* = useCache */
|
||||||
|
);
|
||||||
|
if (!node) {
|
||||||
|
warn(`XFA - Invalid reference: ${ref}.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!node[$isDescendent](this.data)) {
|
||||||
|
warn(`XFA - Invalid node: must be a data node.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [targetNode] = searchNode(
|
||||||
|
this.root,
|
||||||
|
formNode,
|
||||||
|
target,
|
||||||
|
false /* = dotDotAllowed */,
|
||||||
|
false /* = useCache */
|
||||||
|
);
|
||||||
|
if (!targetNode) {
|
||||||
|
warn(`XFA - Invalid target: ${target}.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetNode[$isDescendent](formNode)) {
|
||||||
|
warn(`XFA - Invalid target: must be a property or subproperty.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetParent = targetNode[$getParent]();
|
||||||
|
if (
|
||||||
|
targetNode instanceof SetProperty ||
|
||||||
|
targetParent instanceof SetProperty
|
||||||
|
) {
|
||||||
|
warn(
|
||||||
|
`XFA - Invalid target: cannot be a setProperty or one of its properties.`
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
targetNode instanceof BindItems ||
|
||||||
|
targetParent instanceof BindItems
|
||||||
|
) {
|
||||||
|
warn(
|
||||||
|
`XFA - Invalid target: cannot be a bindItems or one of its properties.`
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const content = node[$text]();
|
||||||
|
const name = targetNode[$nodeName];
|
||||||
|
|
||||||
|
if (targetNode instanceof 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($content)) {
|
||||||
|
warn(`XFA - Invalid node to use in setProperty`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
targetNode[$data] = node;
|
||||||
|
targetNode[$content] = content;
|
||||||
|
targetNode[$finalize]();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_bindItems(formNode, dataNode) {
|
||||||
|
// For example:
|
||||||
|
// <field name="CardName"...>
|
||||||
|
// <bindItems ref="$data.main.ccs.cc[*]" labelRef="uiname"
|
||||||
|
// valueRef="token"/>
|
||||||
|
// <ui><choiceList/></ui>
|
||||||
|
// </field>
|
||||||
|
|
||||||
|
if (
|
||||||
|
!formNode.hasOwnProperty("items") ||
|
||||||
|
!formNode.hasOwnProperty("bindItems") ||
|
||||||
|
formNode.bindItems.isEmpty()
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const item of formNode.items.children) {
|
||||||
|
formNode[$removeChild](item);
|
||||||
|
}
|
||||||
|
|
||||||
|
formNode.items.clear();
|
||||||
|
|
||||||
|
const labels = new Items({});
|
||||||
|
const values = new Items({});
|
||||||
|
|
||||||
|
formNode[$appendChild](labels);
|
||||||
|
formNode.items.push(labels);
|
||||||
|
|
||||||
|
formNode[$appendChild](values);
|
||||||
|
formNode.items.push(values);
|
||||||
|
|
||||||
|
for (const { ref, labelRef, valueRef, connection } of formNode.bindItems
|
||||||
|
.children) {
|
||||||
|
if (connection) {
|
||||||
|
// TODO: evaluate if we should implement this feature.
|
||||||
|
// Skip for security reasons.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!ref) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodes = searchNode(
|
||||||
|
this.root,
|
||||||
|
dataNode,
|
||||||
|
ref,
|
||||||
|
false /* = dotDotAllowed */,
|
||||||
|
false /* = useCache */
|
||||||
|
);
|
||||||
|
if (!nodes) {
|
||||||
|
warn(`XFA - Invalid reference: ${ref}.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (!node[$isDescendent](this.datasets)) {
|
||||||
|
warn(`XFA - Invalid ref (${ref}): must be a datasets child.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [labelNode] = searchNode(
|
||||||
|
this.root,
|
||||||
|
node,
|
||||||
|
labelRef,
|
||||||
|
true /* = dotDotAllowed */,
|
||||||
|
false /* = useCache */
|
||||||
|
);
|
||||||
|
if (!labelNode) {
|
||||||
|
warn(`XFA - Invalid label: ${labelRef}.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!labelNode[$isDescendent](this.datasets)) {
|
||||||
|
warn(`XFA - Invalid label: must be a datasets child.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [valueNode] = searchNode(
|
||||||
|
this.root,
|
||||||
|
node,
|
||||||
|
valueRef,
|
||||||
|
true /* = dotDotAllowed */,
|
||||||
|
false /* = useCache */
|
||||||
|
);
|
||||||
|
if (!valueNode) {
|
||||||
|
warn(`XFA - Invalid value: ${valueRef}.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!valueNode[$isDescendent](this.datasets)) {
|
||||||
|
warn(`XFA - Invalid value: must be a datasets child.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const label = createText(labelNode[$text]());
|
||||||
|
const value = createText(valueNode[$text]());
|
||||||
|
|
||||||
|
labels[$appendChild](label);
|
||||||
|
labels.text.push(label);
|
||||||
|
|
||||||
|
values[$appendChild](value);
|
||||||
|
values.text.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_bindOccurrences(formNode, matches, picture) {
|
||||||
|
// Insert nodes which are not in the template but reflect
|
||||||
|
// what we've in data tree.
|
||||||
|
|
||||||
|
let baseClone;
|
||||||
|
if (matches.length > 1) {
|
||||||
|
// Clone before binding to avoid bad state.
|
||||||
|
baseClone = formNode[$clone]();
|
||||||
|
}
|
||||||
|
|
||||||
|
this._bindValue(formNode, matches[0], picture);
|
||||||
|
this._setProperties(formNode, matches[0]);
|
||||||
|
this._bindItems(formNode, matches[0]);
|
||||||
|
|
||||||
|
if (matches.length === 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = formNode[$getParent]();
|
||||||
|
const name = formNode[$nodeName];
|
||||||
|
const pos = parent[$indexOf](formNode);
|
||||||
|
|
||||||
|
for (let i = 1, ii = matches.length; i < ii; i++) {
|
||||||
|
const match = matches[i];
|
||||||
|
const clone = baseClone[$clone]();
|
||||||
|
clone.occur.min = 1;
|
||||||
|
clone.occur.max = 1;
|
||||||
|
clone.occur.initial = 1;
|
||||||
|
parent[name].push(clone);
|
||||||
|
parent[$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[$getParent]();
|
||||||
|
const name = formNode[$nodeName];
|
||||||
|
|
||||||
|
for (let i = 0, ii = occur.initial; i < ii; i++) {
|
||||||
|
const clone = formNode[$clone]();
|
||||||
|
clone.occur.min = 1;
|
||||||
|
clone.occur.max = 1;
|
||||||
|
clone.occur.initial = 1;
|
||||||
|
parent[name].push(clone);
|
||||||
|
parent[$appendChild](clone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_getOccurInfo(formNode) {
|
||||||
|
const { occur } = formNode;
|
||||||
|
const dataName = formNode.name;
|
||||||
|
if (!occur || !dataName) {
|
||||||
|
return [1, 1];
|
||||||
|
}
|
||||||
|
const max = occur.max === -1 ? Infinity : occur.max;
|
||||||
|
return [occur.min, max];
|
||||||
|
}
|
||||||
|
|
||||||
|
_bindElement(formNode, dataNode) {
|
||||||
|
// Some nodes can be useless because min=0 so remove them
|
||||||
|
// after the loop to avoid bad things.
|
||||||
|
|
||||||
|
const uselessNodes = [];
|
||||||
|
|
||||||
|
this._createOccurrences(formNode);
|
||||||
|
|
||||||
|
for (const child of formNode[$getChildren]()) {
|
||||||
|
if (child[$data]) {
|
||||||
|
// Already bound.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._mergeMode === undefined && child[$nodeName] === "subform") {
|
||||||
|
this._mergeMode = child.mergeMode === "consumeData";
|
||||||
|
}
|
||||||
|
|
||||||
|
let global = false;
|
||||||
|
let picture = null;
|
||||||
|
let ref = null;
|
||||||
|
let match = null;
|
||||||
|
if (child.bind) {
|
||||||
|
switch (child.bind.match) {
|
||||||
|
case "none":
|
||||||
|
continue;
|
||||||
|
case "global":
|
||||||
|
global = true;
|
||||||
|
break;
|
||||||
|
case "dataRef":
|
||||||
|
if (!child.bind.ref) {
|
||||||
|
warn(`XFA - ref is empty in node ${child[$nodeName]}.`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
ref = child.bind.ref;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (child.bind.picture) {
|
||||||
|
picture = child.bind.picture[$content];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const [min, max] = this._getOccurInfo(child);
|
||||||
|
|
||||||
|
if (ref) {
|
||||||
|
// Don't use a cache for searching: nodes can change during binding.
|
||||||
|
match = searchNode(
|
||||||
|
this.root,
|
||||||
|
dataNode,
|
||||||
|
ref,
|
||||||
|
true /* = dotDotAllowed */,
|
||||||
|
false /* = useCache */
|
||||||
|
);
|
||||||
|
if (match === null) {
|
||||||
|
// Nothing found: we must create some nodes in data in order
|
||||||
|
// to have something to match with the given expression.
|
||||||
|
// See http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.364.2157&rep=rep1&type=pdf#page=199
|
||||||
|
match = createDataNode(this.data, dataNode, ref);
|
||||||
|
if (this._isConsumeData()) {
|
||||||
|
match[$consumed] = true;
|
||||||
|
}
|
||||||
|
match = [match];
|
||||||
|
} else {
|
||||||
|
if (this._isConsumeData()) {
|
||||||
|
// Filter out consumed nodes.
|
||||||
|
match = match.filter(node => !node[$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[$consumed] = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!child.name) {
|
||||||
|
this._bindElement(child, dataNode);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (this._isConsumeData()) {
|
||||||
|
// In consumeData mode, search for the next node with the given name.
|
||||||
|
// occurs.max gives us the max number of node to match.
|
||||||
|
const matches = [];
|
||||||
|
while (matches.length < max) {
|
||||||
|
const found = this._findDataByNameToConsume(
|
||||||
|
child.name,
|
||||||
|
dataNode,
|
||||||
|
global
|
||||||
|
);
|
||||||
|
if (!found) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
found[$consumed] = true;
|
||||||
|
matches.push(found);
|
||||||
|
}
|
||||||
|
match = matches.length > 0 ? matches : null;
|
||||||
|
} else {
|
||||||
|
match = dataNode[$getRealChildrenByNameIt](
|
||||||
|
child.name,
|
||||||
|
/* allTransparent = */ false,
|
||||||
|
/* skipConsumed = */ false
|
||||||
|
).next().value;
|
||||||
|
if (!match) {
|
||||||
|
// We're in matchTemplate mode so create a node in data to reflect
|
||||||
|
// what we've in template.
|
||||||
|
match = new XmlObject(dataNode[$namespaceId], child.name);
|
||||||
|
dataNode[$appendChild](match);
|
||||||
|
}
|
||||||
|
match = [match];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
if (match.length < min) {
|
||||||
|
warn(
|
||||||
|
`XFA - Must have at least ${min} occurrences: ${formNode[$nodeName]}.`
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
this._bindOccurrences(child, match, picture);
|
||||||
|
} else if (min > 0) {
|
||||||
|
this._bindElement(child, dataNode);
|
||||||
|
} else {
|
||||||
|
uselessNodes.push(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uselessNodes.forEach(node => node[$getParent]()[$removeChild](node));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Binder };
|
@ -17,6 +17,7 @@ import { $buildXFAObject, NamespaceIds } from "./namespaces.js";
|
|||||||
import {
|
import {
|
||||||
$cleanup,
|
$cleanup,
|
||||||
$finalize,
|
$finalize,
|
||||||
|
$nsAttributes,
|
||||||
$onChild,
|
$onChild,
|
||||||
$resolvePrototypes,
|
$resolvePrototypes,
|
||||||
XFAObject,
|
XFAObject,
|
||||||
@ -88,6 +89,25 @@ class Builder {
|
|||||||
this._addNamespacePrefix(prefixes);
|
this._addNamespacePrefix(prefixes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (attributes.hasOwnProperty($nsAttributes)) {
|
||||||
|
// Only support xfa-data namespace.
|
||||||
|
const dataTemplate = NamespaceSetUp.datasets;
|
||||||
|
const nsAttrs = attributes[$nsAttributes];
|
||||||
|
let xfaAttrs = null;
|
||||||
|
for (const [ns, attrs] of Object.entries(nsAttrs)) {
|
||||||
|
const nsToUse = this._getNamespaceToUse(ns);
|
||||||
|
if (nsToUse === dataTemplate) {
|
||||||
|
xfaAttrs = { xfa: attrs };
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (xfaAttrs) {
|
||||||
|
attributes[$nsAttributes] = xfaAttrs;
|
||||||
|
} else {
|
||||||
|
delete attributes[$nsAttributes];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const namespaceToUse = this._getNamespaceToUse(nsPrefix);
|
const namespaceToUse = this._getNamespaceToUse(nsPrefix);
|
||||||
const node =
|
const node =
|
||||||
(namespaceToUse && namespaceToUse[$buildXFAObject](name, attributes)) ||
|
(namespaceToUse && namespaceToUse[$buildXFAObject](name, attributes)) ||
|
||||||
|
@ -13,14 +13,16 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { $buildXFAObject, NamespaceIds } from "./namespaces.js";
|
|
||||||
import {
|
import {
|
||||||
|
$appendChild,
|
||||||
|
$global,
|
||||||
$namespaceId,
|
$namespaceId,
|
||||||
$nodeName,
|
$nodeName,
|
||||||
$onChildCheck,
|
$onChild,
|
||||||
XFAObject,
|
XFAObject,
|
||||||
XmlObject,
|
XmlObject,
|
||||||
} from "./xfa_object.js";
|
} from "./xfa_object.js";
|
||||||
|
import { $buildXFAObject, NamespaceIds } from "./namespaces.js";
|
||||||
|
|
||||||
const DATASETS_NS_ID = NamespaceIds.datasets.id;
|
const DATASETS_NS_ID = NamespaceIds.datasets.id;
|
||||||
|
|
||||||
@ -37,15 +39,18 @@ class Datasets extends XFAObject {
|
|||||||
this.Signature = null;
|
this.Signature = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
[$onChildCheck](child) {
|
[$onChild](child) {
|
||||||
const name = child[$nodeName];
|
const name = child[$nodeName];
|
||||||
if (name === "data") {
|
if (
|
||||||
return child[$namespaceId] === DATASETS_NS_ID;
|
(name === "data" && child[$namespaceId] === DATASETS_NS_ID) ||
|
||||||
|
(name === "Signature" &&
|
||||||
|
child[$namespaceId] === NamespaceIds.signature.id)
|
||||||
|
) {
|
||||||
|
this[name] = child;
|
||||||
|
} else {
|
||||||
|
child[$global] = true;
|
||||||
}
|
}
|
||||||
if (name === "Signature") {
|
this[$appendChild](child);
|
||||||
return child[$namespaceId] === NamespaceIds.signature.id;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
33
src/core/xfa/factory.js
Normal file
33
src/core/xfa/factory.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/* Copyright 2021 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Binder } from "./bind.js";
|
||||||
|
import { XFAParser } from "./parser.js";
|
||||||
|
|
||||||
|
class XFAFactory {
|
||||||
|
constructor(data) {
|
||||||
|
this.root = new XFAParser().parse(XFAFactory._createDocument(data));
|
||||||
|
this.form = new Binder(this.root).bind();
|
||||||
|
}
|
||||||
|
|
||||||
|
static _createDocument(data) {
|
||||||
|
if (!data["/xdp:xdp"]) {
|
||||||
|
return data["xdp:xdp"];
|
||||||
|
}
|
||||||
|
return Object.values(data).join("");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { XFAFactory };
|
@ -13,7 +13,14 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { $clean, $finalize, $onChild, $onText, $setId } from "./xfa_object.js";
|
import {
|
||||||
|
$clean,
|
||||||
|
$finalize,
|
||||||
|
$nsAttributes,
|
||||||
|
$onChild,
|
||||||
|
$onText,
|
||||||
|
$setId,
|
||||||
|
} from "./xfa_object.js";
|
||||||
import { XMLParserBase, XMLParserErrorCode } from "../xml_parser.js";
|
import { XMLParserBase, XMLParserErrorCode } from "../xml_parser.js";
|
||||||
import { Builder } from "./builder.js";
|
import { Builder } from "./builder.js";
|
||||||
import { warn } from "../../shared/util.js";
|
import { warn } from "../../shared/util.js";
|
||||||
@ -57,7 +64,7 @@ class XFAParser extends XMLParserBase {
|
|||||||
// namespaces information.
|
// namespaces information.
|
||||||
let namespace = null;
|
let namespace = null;
|
||||||
let prefixes = null;
|
let prefixes = null;
|
||||||
const attributeObj = Object.create(null);
|
const attributeObj = Object.create({});
|
||||||
for (const { name, value } of attributes) {
|
for (const { name, value } of attributes) {
|
||||||
if (name === "xmlns") {
|
if (name === "xmlns") {
|
||||||
if (!namespace) {
|
if (!namespace) {
|
||||||
@ -72,7 +79,23 @@ class XFAParser extends XMLParserBase {
|
|||||||
}
|
}
|
||||||
prefixes.push({ prefix, value });
|
prefixes.push({ prefix, value });
|
||||||
} else {
|
} else {
|
||||||
attributeObj[name] = value;
|
const i = name.indexOf(":");
|
||||||
|
if (i === -1) {
|
||||||
|
attributeObj[name] = value;
|
||||||
|
} else {
|
||||||
|
// Attributes can have their own namespace.
|
||||||
|
// For example in data, we can have <foo xfa:dataNode="dataGroup"/>
|
||||||
|
let nsAttrs = attributeObj[$nsAttributes];
|
||||||
|
if (!nsAttrs) {
|
||||||
|
nsAttrs = attributeObj[$nsAttributes] = Object.create(null);
|
||||||
|
}
|
||||||
|
const [ns, attrName] = [name.slice(0, i), name.slice(i + 1)];
|
||||||
|
let attrs = nsAttrs[ns];
|
||||||
|
if (!attrs) {
|
||||||
|
attrs = nsAttrs[ns] = Object.create(null);
|
||||||
|
}
|
||||||
|
attrs[attrName] = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,11 +14,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
$appendChild,
|
||||||
$getChildrenByClass,
|
$getChildrenByClass,
|
||||||
$getChildrenByName,
|
$getChildrenByName,
|
||||||
$getParent,
|
$getParent,
|
||||||
|
$namespaceId,
|
||||||
XFAObject,
|
XFAObject,
|
||||||
XFAObjectArray,
|
XFAObjectArray,
|
||||||
|
XmlObject,
|
||||||
} from "./xfa_object.js";
|
} from "./xfa_object.js";
|
||||||
import { warn } from "../../shared/util.js";
|
import { warn } from "../../shared/util.js";
|
||||||
|
|
||||||
@ -33,17 +36,18 @@ const operators = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const shortcuts = new Map([
|
const shortcuts = new Map([
|
||||||
["$data", root => root.datasets.data],
|
["$data", (root, current) => root.datasets.data],
|
||||||
["$template", root => root.template],
|
["$template", (root, current) => root.template],
|
||||||
["$connectionSet", root => root.connectionSet],
|
["$connectionSet", (root, current) => root.connectionSet],
|
||||||
["$form", root => root.form],
|
["$form", (root, current) => root.form],
|
||||||
["$layout", root => root.layout],
|
["$layout", (root, current) => root.layout],
|
||||||
["$host", root => root.host],
|
["$host", (root, current) => root.host],
|
||||||
["$dataWindow", root => root.dataWindow],
|
["$dataWindow", (root, current) => root.dataWindow],
|
||||||
["$event", root => root.event],
|
["$event", (root, current) => root.event],
|
||||||
["!", root => root.datasets],
|
["!", (root, current) => root.datasets],
|
||||||
["$xfa", root => root],
|
["$xfa", (root, current) => root],
|
||||||
["xfa", root => root],
|
["xfa", (root, current) => root],
|
||||||
|
["$", (root, current) => current],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const somCache = new WeakMap();
|
const somCache = new WeakMap();
|
||||||
@ -138,17 +142,24 @@ function parseExpression(expr, dotDotAllowed) {
|
|||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
function searchNode(root, container, expr, dotDotAllowed = true) {
|
function searchNode(
|
||||||
|
root,
|
||||||
|
container,
|
||||||
|
expr,
|
||||||
|
dotDotAllowed = true,
|
||||||
|
useCache = true
|
||||||
|
) {
|
||||||
const parsed = parseExpression(expr, dotDotAllowed);
|
const parsed = parseExpression(expr, dotDotAllowed);
|
||||||
if (!parsed) {
|
if (!parsed) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fn = shortcuts.get(parsed[0].name);
|
const fn = shortcuts.get(parsed[0].name);
|
||||||
let i = 0;
|
let i = 0;
|
||||||
let isQualified;
|
let isQualified;
|
||||||
if (fn) {
|
if (fn) {
|
||||||
isQualified = true;
|
isQualified = true;
|
||||||
root = [fn(root)];
|
root = [fn(root, container)];
|
||||||
i = 1;
|
i = 1;
|
||||||
} else {
|
} else {
|
||||||
isQualified = container === null;
|
isQualified = container === null;
|
||||||
@ -163,13 +174,17 @@ function searchNode(root, container, expr, dotDotAllowed = true) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let cached = somCache.get(node);
|
let children, cached;
|
||||||
if (!cached) {
|
|
||||||
cached = new Map();
|
if (useCache) {
|
||||||
somCache.set(node, cached);
|
cached = somCache.get(node);
|
||||||
|
if (!cached) {
|
||||||
|
cached = new Map();
|
||||||
|
somCache.set(node, cached);
|
||||||
|
}
|
||||||
|
children = cached.get(cacheName);
|
||||||
}
|
}
|
||||||
|
|
||||||
let children = cached.get(cacheName);
|
|
||||||
if (!children) {
|
if (!children) {
|
||||||
switch (operator) {
|
switch (operator) {
|
||||||
case operators.dot:
|
case operators.dot:
|
||||||
@ -189,7 +204,9 @@ function searchNode(root, container, expr, dotDotAllowed = true) {
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
cached.set(cacheName, children);
|
if (useCache) {
|
||||||
|
cached.set(cacheName, children);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (children.length > 0) {
|
if (children.length > 0) {
|
||||||
@ -222,11 +239,72 @@ function searchNode(root, container, expr, dotDotAllowed = true) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.length === 1) {
|
|
||||||
return root[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
return root;
|
return root;
|
||||||
}
|
}
|
||||||
|
|
||||||
export { searchNode };
|
function createNodes(root, path) {
|
||||||
|
let node = null;
|
||||||
|
for (const { name, index } of path) {
|
||||||
|
for (let i = 0; i <= index; i++) {
|
||||||
|
node = new XmlObject(root[$namespaceId], name);
|
||||||
|
root[$appendChild](node);
|
||||||
|
}
|
||||||
|
|
||||||
|
root = node;
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDataNode(root, container, expr) {
|
||||||
|
const parsed = parseExpression(expr);
|
||||||
|
if (!parsed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parsed.some(x => x.operator === operators.dotDot)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fn = shortcuts.get(parsed[0].name);
|
||||||
|
let i = 0;
|
||||||
|
if (fn) {
|
||||||
|
root = fn(root, container);
|
||||||
|
i = 1;
|
||||||
|
} else {
|
||||||
|
root = container || root;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let ii = parsed.length; i < ii; i++) {
|
||||||
|
const { cacheName, index } = parsed[i];
|
||||||
|
if (!isFinite(index)) {
|
||||||
|
parsed[i].index = 0;
|
||||||
|
return createNodes(root, parsed.slice(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
const cached = somCache.get(root);
|
||||||
|
if (!cached) {
|
||||||
|
warn(`XFA - createDataNode must be called after searchNode.`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const children = cached.get(cacheName);
|
||||||
|
if (children.length === 0) {
|
||||||
|
return createNodes(root, parsed.slice(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index < children.length) {
|
||||||
|
const child = children[index];
|
||||||
|
if (!(child instanceof XFAObject)) {
|
||||||
|
warn(`XFA - Cannot create a node.`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
root = child;
|
||||||
|
} else {
|
||||||
|
parsed[i].index = children.length - index;
|
||||||
|
return createNodes(root, parsed.slice(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { createDataNode, searchNode };
|
||||||
|
@ -13,15 +13,19 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { $buildXFAObject, NamespaceIds } from "./namespaces.js";
|
|
||||||
import {
|
import {
|
||||||
|
$appendChild,
|
||||||
$content,
|
$content,
|
||||||
$finalize,
|
$finalize,
|
||||||
|
$hasItem,
|
||||||
|
$hasSettableValue,
|
||||||
$isTransparent,
|
$isTransparent,
|
||||||
$namespaceId,
|
$namespaceId,
|
||||||
$nodeName,
|
$nodeName,
|
||||||
$onChild,
|
$onChild,
|
||||||
|
$removeChild,
|
||||||
$setSetAttributes,
|
$setSetAttributes,
|
||||||
|
$setValue,
|
||||||
ContentObject,
|
ContentObject,
|
||||||
Option01,
|
Option01,
|
||||||
OptionObject,
|
OptionObject,
|
||||||
@ -29,6 +33,7 @@ import {
|
|||||||
XFAObject,
|
XFAObject,
|
||||||
XFAObjectArray,
|
XFAObjectArray,
|
||||||
} from "./xfa_object.js";
|
} from "./xfa_object.js";
|
||||||
|
import { $buildXFAObject, NamespaceIds } from "./namespaces.js";
|
||||||
import {
|
import {
|
||||||
getBBox,
|
getBBox,
|
||||||
getColor,
|
getColor,
|
||||||
@ -44,6 +49,15 @@ import { warn } from "../../shared/util.js";
|
|||||||
|
|
||||||
const TEMPLATE_NS_ID = NamespaceIds.template.id;
|
const TEMPLATE_NS_ID = NamespaceIds.template.id;
|
||||||
|
|
||||||
|
function _setValue(templateNode, value) {
|
||||||
|
if (!templateNode.value) {
|
||||||
|
const nodeValue = new Value({});
|
||||||
|
templateNode[$appendChild](nodeValue);
|
||||||
|
templateNode.value = nodeValue;
|
||||||
|
}
|
||||||
|
templateNode.value[$setValue](value);
|
||||||
|
}
|
||||||
|
|
||||||
class AppearanceFilter extends StringObject {
|
class AppearanceFilter extends StringObject {
|
||||||
constructor(attributes) {
|
constructor(attributes) {
|
||||||
super(TEMPLATE_NS_ID, "appearanceFilter");
|
super(TEMPLATE_NS_ID, "appearanceFilter");
|
||||||
@ -496,6 +510,10 @@ class Caption extends XFAObject {
|
|||||||
this.para = null;
|
this.para = null;
|
||||||
this.value = null;
|
this.value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$setValue](value) {
|
||||||
|
_setValue(this, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Certificate extends StringObject {
|
class Certificate extends StringObject {
|
||||||
@ -586,6 +604,10 @@ class Color extends XFAObject {
|
|||||||
this.value = getColor(attributes.value);
|
this.value = getColor(attributes.value);
|
||||||
this.extras = null;
|
this.extras = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$hasSettableValue]() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Comb extends XFAObject {
|
class Comb extends XFAObject {
|
||||||
@ -869,6 +891,10 @@ class Draw extends XFAObject {
|
|||||||
this.value = null;
|
this.value = null;
|
||||||
this.setProperty = new XFAObjectArray();
|
this.setProperty = new XFAObjectArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$setValue](value) {
|
||||||
|
_setValue(this, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Edge extends XFAObject {
|
class Edge extends XFAObject {
|
||||||
@ -1045,7 +1071,7 @@ class Event extends XFAObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ExData extends ContentObject {
|
class ExData extends ContentObject {
|
||||||
constructor(builder, attributes) {
|
constructor(attributes) {
|
||||||
super(TEMPLATE_NS_ID, "exData");
|
super(TEMPLATE_NS_ID, "exData");
|
||||||
this.contentType = attributes.contentType || "";
|
this.contentType = attributes.contentType || "";
|
||||||
this.href = attributes.href || "";
|
this.href = attributes.href || "";
|
||||||
@ -1188,6 +1214,32 @@ class ExclGroup extends XFAObject {
|
|||||||
this.field = new XFAObjectArray();
|
this.field = new XFAObjectArray();
|
||||||
this.setProperty = new XFAObjectArray();
|
this.setProperty = new XFAObjectArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$hasSettableValue]() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$setValue](value) {
|
||||||
|
for (const field of this.field.children) {
|
||||||
|
if (!field.value) {
|
||||||
|
const nodeValue = new Value({});
|
||||||
|
field[$appendChild](nodeValue);
|
||||||
|
field.value = nodeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nodeBoolean = new BooleanElement({});
|
||||||
|
nodeBoolean[$content] = 0;
|
||||||
|
|
||||||
|
for (const item of field.items.children) {
|
||||||
|
if (item[$hasItem](value)) {
|
||||||
|
nodeBoolean[$content] = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
field.value[$setValue](nodeBoolean);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Execute extends XFAObject {
|
class Execute extends XFAObject {
|
||||||
@ -1294,6 +1346,8 @@ class Field extends XFAObject {
|
|||||||
this.extras = null;
|
this.extras = null;
|
||||||
this.font = null;
|
this.font = null;
|
||||||
this.format = null;
|
this.format = null;
|
||||||
|
// For a choice list, one list is used to have display entries
|
||||||
|
// and the other for the exported values
|
||||||
this.items = new XFAObjectArray(2);
|
this.items = new XFAObjectArray(2);
|
||||||
this.keep = null;
|
this.keep = null;
|
||||||
this.margin = null;
|
this.margin = null;
|
||||||
@ -1307,6 +1361,10 @@ class Field extends XFAObject {
|
|||||||
this.event = new XFAObjectArray();
|
this.event = new XFAObjectArray();
|
||||||
this.setProperty = new XFAObjectArray();
|
this.setProperty = new XFAObjectArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$setValue](value) {
|
||||||
|
_setValue(this, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Fill extends XFAObject {
|
class Fill extends XFAObject {
|
||||||
@ -1590,6 +1648,15 @@ class Items extends XFAObject {
|
|||||||
this.text = new XFAObjectArray();
|
this.text = new XFAObjectArray();
|
||||||
this.time = new XFAObjectArray();
|
this.time = new XFAObjectArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$hasItem](value) {
|
||||||
|
return (
|
||||||
|
this.hasOwnProperty(value[$nodeName]) &&
|
||||||
|
this[value[$nodeName]].children.some(
|
||||||
|
node => node[$content] === value[$content]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Keep extends XFAObject {
|
class Keep extends XFAObject {
|
||||||
@ -1917,10 +1984,7 @@ class Para extends XFAObject {
|
|||||||
"right",
|
"right",
|
||||||
]);
|
]);
|
||||||
this.id = attributes.id || "";
|
this.id = attributes.id || "";
|
||||||
this.lineHeight = getMeasurement(attributes.lineHeight, [
|
this.lineHeight = getMeasurement(attributes.lineHeight, "0pt");
|
||||||
"0pt",
|
|
||||||
"measurement",
|
|
||||||
]);
|
|
||||||
this.marginLeft = getMeasurement(attributes.marginLeft, "0");
|
this.marginLeft = getMeasurement(attributes.marginLeft, "0");
|
||||||
this.marginRight = getMeasurement(attributes.marginRight, "0");
|
this.marginRight = getMeasurement(attributes.marginRight, "0");
|
||||||
this.orphans = getInteger({
|
this.orphans = getInteger({
|
||||||
@ -2735,6 +2799,26 @@ class Value extends XFAObject {
|
|||||||
this.text = null;
|
this.text = null;
|
||||||
this.time = null;
|
this.time = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$setValue](value) {
|
||||||
|
const valueName = value[$nodeName];
|
||||||
|
if (this[valueName] !== null) {
|
||||||
|
this[valueName][$content] = value[$content];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset all the properties.
|
||||||
|
for (const name of Object.getOwnPropertyNames(this)) {
|
||||||
|
const obj = this[name];
|
||||||
|
if (obj instanceof XFAObject) {
|
||||||
|
this[name] = null;
|
||||||
|
this[$removeChild](obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this[value[$nodeName]] = value;
|
||||||
|
this[$appendChild](value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Variables extends XFAObject {
|
class Variables extends XFAObject {
|
||||||
@ -3225,4 +3309,13 @@ class TemplateNamespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Template, TemplateNamespace };
|
export {
|
||||||
|
BindItems,
|
||||||
|
Field,
|
||||||
|
Items,
|
||||||
|
SetProperty,
|
||||||
|
Template,
|
||||||
|
TemplateNamespace,
|
||||||
|
Text,
|
||||||
|
Value,
|
||||||
|
};
|
||||||
|
@ -19,43 +19,57 @@ import { NamespaceIds } from "./namespaces.js";
|
|||||||
|
|
||||||
// We use these symbols to avoid name conflict between tags
|
// We use these symbols to avoid name conflict between tags
|
||||||
// and properties/methods names.
|
// and properties/methods names.
|
||||||
|
const $appendChild = Symbol();
|
||||||
const $clean = Symbol();
|
const $clean = Symbol();
|
||||||
const $cleanup = Symbol();
|
const $cleanup = Symbol();
|
||||||
|
const $clone = Symbol();
|
||||||
|
const $consumed = Symbol();
|
||||||
const $content = Symbol("content");
|
const $content = Symbol("content");
|
||||||
|
const $data = Symbol("data");
|
||||||
const $dump = Symbol();
|
const $dump = Symbol();
|
||||||
const $finalize = Symbol();
|
const $finalize = Symbol();
|
||||||
const $isDataValue = Symbol();
|
|
||||||
const $getAttributeIt = Symbol();
|
const $getAttributeIt = Symbol();
|
||||||
const $getChildrenByClass = Symbol();
|
const $getChildrenByClass = Symbol();
|
||||||
const $getChildrenByName = Symbol();
|
const $getChildrenByName = Symbol();
|
||||||
const $getChildrenByNameIt = Symbol();
|
const $getChildrenByNameIt = Symbol();
|
||||||
|
const $getRealChildrenByNameIt = Symbol();
|
||||||
const $getChildren = Symbol();
|
const $getChildren = Symbol();
|
||||||
const $getParent = Symbol();
|
const $getParent = Symbol();
|
||||||
|
const $global = Symbol();
|
||||||
|
const $hasItem = Symbol();
|
||||||
|
const $hasSettableValue = Symbol();
|
||||||
|
const $indexOf = Symbol();
|
||||||
|
const $insertAt = Symbol();
|
||||||
|
const $isDataValue = Symbol();
|
||||||
|
const $isDescendent = Symbol();
|
||||||
const $isTransparent = Symbol();
|
const $isTransparent = Symbol();
|
||||||
const $lastAttribute = Symbol();
|
const $lastAttribute = Symbol();
|
||||||
const $namespaceId = Symbol("namespaceId");
|
const $namespaceId = Symbol("namespaceId");
|
||||||
const $nodeName = Symbol("nodeName");
|
const $nodeName = Symbol("nodeName");
|
||||||
|
const $nsAttributes = Symbol();
|
||||||
const $onChild = Symbol();
|
const $onChild = Symbol();
|
||||||
const $onChildCheck = Symbol();
|
const $onChildCheck = Symbol();
|
||||||
const $onText = Symbol();
|
const $onText = Symbol();
|
||||||
|
const $removeChild = Symbol();
|
||||||
const $resolvePrototypes = Symbol();
|
const $resolvePrototypes = Symbol();
|
||||||
const $setId = Symbol();
|
const $setId = Symbol();
|
||||||
const $setSetAttributes = Symbol();
|
const $setSetAttributes = Symbol();
|
||||||
|
const $setValue = Symbol();
|
||||||
const $text = Symbol();
|
const $text = Symbol();
|
||||||
|
|
||||||
const _applyPrototype = Symbol();
|
const _applyPrototype = Symbol();
|
||||||
const _attributes = Symbol();
|
const _attributes = Symbol();
|
||||||
const _attributeNames = Symbol();
|
const _attributeNames = Symbol();
|
||||||
const _children = Symbol();
|
const _children = Symbol("_children");
|
||||||
const _clone = Symbol();
|
|
||||||
const _cloneAttribute = Symbol();
|
const _cloneAttribute = Symbol();
|
||||||
|
const _dataValue = Symbol();
|
||||||
const _defaultValue = Symbol();
|
const _defaultValue = Symbol();
|
||||||
const _getPrototype = Symbol();
|
const _getPrototype = Symbol();
|
||||||
const _getUnsetAttributes = Symbol();
|
const _getUnsetAttributes = Symbol();
|
||||||
const _hasChildren = Symbol();
|
const _hasChildren = Symbol();
|
||||||
const _max = Symbol();
|
const _max = Symbol();
|
||||||
const _options = Symbol();
|
const _options = Symbol();
|
||||||
const _parent = Symbol();
|
const _parent = Symbol("parent");
|
||||||
const _setAttributes = Symbol();
|
const _setAttributes = Symbol();
|
||||||
const _validator = Symbol();
|
const _validator = Symbol();
|
||||||
|
|
||||||
@ -78,18 +92,27 @@ class XFAObject {
|
|||||||
|
|
||||||
if (node instanceof XFAObjectArray) {
|
if (node instanceof XFAObjectArray) {
|
||||||
if (node.push(child)) {
|
if (node.push(child)) {
|
||||||
child[_parent] = this;
|
this[$appendChild](child);
|
||||||
this[_children].push(child);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else if (node === null) {
|
} else {
|
||||||
|
// IRL it's possible to already have a node.
|
||||||
|
// So just replace it with the last version.
|
||||||
|
if (node !== null) {
|
||||||
|
this[$removeChild](node);
|
||||||
|
}
|
||||||
this[name] = child;
|
this[name] = child;
|
||||||
child[_parent] = this;
|
this[$appendChild](child);
|
||||||
this[_children].push(child);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
warn(`XFA - node "${this[$nodeName]}" has already enough "${name}"!`);
|
let id = "";
|
||||||
|
if (this.id) {
|
||||||
|
id = ` (id: ${this.id})`;
|
||||||
|
} else if (this.name) {
|
||||||
|
id = ` (name: ${this.name} ${this.h.value})`;
|
||||||
|
}
|
||||||
|
warn(`XFA - node "${this[$nodeName]}"${id} has already enough "${name}"!`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,6 +129,22 @@ class XFAObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$appendChild](child) {
|
||||||
|
child[_parent] = this;
|
||||||
|
this[_children].push(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
[$removeChild](child) {
|
||||||
|
const i = this[_children].indexOf(child);
|
||||||
|
this[_children].splice(i, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[$hasSettableValue]() {
|
||||||
|
return this.hasOwnProperty("value");
|
||||||
|
}
|
||||||
|
|
||||||
|
[$setValue](_) {}
|
||||||
|
|
||||||
[$onText](_) {}
|
[$onText](_) {}
|
||||||
|
|
||||||
[$finalize]() {}
|
[$finalize]() {}
|
||||||
@ -118,6 +157,19 @@ class XFAObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$hasItem]() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$indexOf](child) {
|
||||||
|
return this[_children].indexOf(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
[$insertAt](i, child) {
|
||||||
|
child[_parent] = this;
|
||||||
|
this[_children].splice(i, 0, child);
|
||||||
|
}
|
||||||
|
|
||||||
[$isTransparent]() {
|
[$isTransparent]() {
|
||||||
return this.name === "";
|
return this.name === "";
|
||||||
}
|
}
|
||||||
@ -126,6 +178,13 @@ class XFAObject {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$text]() {
|
||||||
|
if (this[_children].length === 0) {
|
||||||
|
return this[$content];
|
||||||
|
}
|
||||||
|
return this[_children].map(c => c[$text]()).join("");
|
||||||
|
}
|
||||||
|
|
||||||
get [_attributeNames]() {
|
get [_attributeNames]() {
|
||||||
// Lazily get attributes names
|
// Lazily get attributes names
|
||||||
const proto = Object.getPrototypeOf(this);
|
const proto = Object.getPrototypeOf(this);
|
||||||
@ -145,6 +204,17 @@ class XFAObject {
|
|||||||
return shadow(this, _attributeNames, proto._attributes);
|
return shadow(this, _attributeNames, proto._attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$isDescendent](parent) {
|
||||||
|
let node = this;
|
||||||
|
while (node) {
|
||||||
|
if (node === parent) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
node = node[$getParent]();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
[$getParent]() {
|
[$getParent]() {
|
||||||
return this[_parent];
|
return this[_parent];
|
||||||
}
|
}
|
||||||
@ -297,7 +367,7 @@ class XFAObject {
|
|||||||
i < ii;
|
i < ii;
|
||||||
i++
|
i++
|
||||||
) {
|
) {
|
||||||
const child = proto[_children][i][_clone]();
|
const child = proto[_children][i][$clone]();
|
||||||
if (value.push(child)) {
|
if (value.push(child)) {
|
||||||
child[_parent] = this;
|
child[_parent] = this;
|
||||||
this[_children].push(child);
|
this[_children].push(child);
|
||||||
@ -316,7 +386,7 @@ class XFAObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (protoValue !== null) {
|
if (protoValue !== null) {
|
||||||
const child = protoValue[_clone]();
|
const child = protoValue[$clone]();
|
||||||
child[_parent] = this;
|
child[_parent] = this;
|
||||||
this[name] = child;
|
this[name] = child;
|
||||||
this[_children].push(child);
|
this[_children].push(child);
|
||||||
@ -335,7 +405,7 @@ class XFAObject {
|
|||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
|
|
||||||
[_clone]() {
|
[$clone]() {
|
||||||
const clone = Object.create(Object.getPrototypeOf(this));
|
const clone = Object.create(Object.getPrototypeOf(this));
|
||||||
for (const $symbol of Object.getOwnPropertySymbols(this)) {
|
for (const $symbol of Object.getOwnPropertySymbols(this)) {
|
||||||
try {
|
try {
|
||||||
@ -361,7 +431,7 @@ class XFAObject {
|
|||||||
|
|
||||||
for (const child of this[_children]) {
|
for (const child of this[_children]) {
|
||||||
const name = child[$nodeName];
|
const name = child[$nodeName];
|
||||||
const clonedChild = child[_clone]();
|
const clonedChild = child[$clone]();
|
||||||
clone[_children].push(clonedChild);
|
clone[_children].push(clonedChild);
|
||||||
clonedChild[_parent] = clone;
|
clonedChild[_parent] = clone;
|
||||||
if (clone[name] === null) {
|
if (clone[name] === null) {
|
||||||
@ -444,15 +514,19 @@ class XFAObjectArray {
|
|||||||
: this[_children].map(x => x[$dump]());
|
: this[_children].map(x => x[$dump]());
|
||||||
}
|
}
|
||||||
|
|
||||||
[_clone]() {
|
[$clone]() {
|
||||||
const clone = new XFAObjectArray(this[_max]);
|
const clone = new XFAObjectArray(this[_max]);
|
||||||
clone[_children] = this[_children].map(c => c[_clone]());
|
clone[_children] = this[_children].map(c => c[$clone]());
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
get children() {
|
get children() {
|
||||||
return this[_children];
|
return this[_children];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clear() {
|
||||||
|
this[_children].length = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class XFAAttribute {
|
class XFAAttribute {
|
||||||
@ -460,6 +534,7 @@ class XFAAttribute {
|
|||||||
this[_parent] = node;
|
this[_parent] = node;
|
||||||
this[$nodeName] = name;
|
this[$nodeName] = name;
|
||||||
this[$content] = value;
|
this[$content] = value;
|
||||||
|
this[$consumed] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
[$getParent]() {
|
[$getParent]() {
|
||||||
@ -473,27 +548,46 @@ class XFAAttribute {
|
|||||||
[$text]() {
|
[$text]() {
|
||||||
return this[$content];
|
return this[$content];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$isDescendent](parent) {
|
||||||
|
return this[_parent] === parent || this[_parent][$isDescendent](parent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class XmlObject extends XFAObject {
|
class XmlObject extends XFAObject {
|
||||||
constructor(nsId, name, attributes = null) {
|
constructor(nsId, name, attributes = {}) {
|
||||||
super(nsId, name);
|
super(nsId, name);
|
||||||
this[$content] = "";
|
this[$content] = "";
|
||||||
|
this[_dataValue] = null;
|
||||||
if (name !== "#text") {
|
if (name !== "#text") {
|
||||||
this[_attributes] = attributes;
|
const map = new Map();
|
||||||
|
this[_attributes] = map;
|
||||||
|
for (const [attrName, value] of Object.entries(attributes)) {
|
||||||
|
map.set(attrName, new XFAAttribute(this, attrName, value));
|
||||||
|
}
|
||||||
|
if (attributes.hasOwnProperty($nsAttributes)) {
|
||||||
|
// XFA attributes.
|
||||||
|
const dataNode = attributes[$nsAttributes].xfa.dataNode;
|
||||||
|
if (dataNode !== undefined) {
|
||||||
|
if (dataNode === "dataGroup") {
|
||||||
|
this[_dataValue] = false;
|
||||||
|
} else if (dataNode === "dataValue") {
|
||||||
|
this[_dataValue] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
this[$consumed] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
[$onChild](child) {
|
[$onChild](child) {
|
||||||
if (this[$content]) {
|
if (this[$content]) {
|
||||||
const node = new XmlObject(this[$namespaceId], "#text");
|
const node = new XmlObject(this[$namespaceId], "#text");
|
||||||
node[_parent] = this;
|
this[$appendChild](node);
|
||||||
node[$content] = this[$content];
|
node[$content] = this[$content];
|
||||||
this[$content] = "";
|
this[$content] = "";
|
||||||
this[_children].push(node);
|
|
||||||
}
|
}
|
||||||
child[_parent] = this;
|
this[$appendChild](child);
|
||||||
this[_children].push(child);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -504,20 +598,12 @@ class XmlObject extends XFAObject {
|
|||||||
[$finalize]() {
|
[$finalize]() {
|
||||||
if (this[$content] && this[_children].length > 0) {
|
if (this[$content] && this[_children].length > 0) {
|
||||||
const node = new XmlObject(this[$namespaceId], "#text");
|
const node = new XmlObject(this[$namespaceId], "#text");
|
||||||
node[_parent] = this;
|
this[$appendChild](node);
|
||||||
node[$content] = this[$content];
|
node[$content] = this[$content];
|
||||||
this[_children].push(node);
|
|
||||||
delete this[$content];
|
delete this[$content];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[$text]() {
|
|
||||||
if (this[_children].length === 0) {
|
|
||||||
return this[$content];
|
|
||||||
}
|
|
||||||
return this[_children].map(c => c[$text]()).join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
[$getChildren](name = null) {
|
[$getChildren](name = null) {
|
||||||
if (!name) {
|
if (!name) {
|
||||||
return this[_children];
|
return this[_children];
|
||||||
@ -527,7 +613,7 @@ class XmlObject extends XFAObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
[$getChildrenByClass](name) {
|
[$getChildrenByClass](name) {
|
||||||
const value = this[_attributes][name];
|
const value = this[_attributes].get(name);
|
||||||
if (value !== undefined) {
|
if (value !== undefined) {
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@ -535,9 +621,9 @@ class XmlObject extends XFAObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
*[$getChildrenByNameIt](name, allTransparent) {
|
*[$getChildrenByNameIt](name, allTransparent) {
|
||||||
const value = this[_attributes][name];
|
const value = this[_attributes].get(name);
|
||||||
if (value !== undefined) {
|
if (value) {
|
||||||
yield new XFAAttribute(this, name, value);
|
yield value;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const child of this[_children]) {
|
for (const child of this[_children]) {
|
||||||
@ -551,19 +637,57 @@ class XmlObject extends XFAObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*[$getAttributeIt](name) {
|
*[$getAttributeIt](name, skipConsumed) {
|
||||||
const value = this[_attributes][name];
|
const value = this[_attributes].get(name);
|
||||||
if (value !== undefined) {
|
if (value && (!skipConsumed || !value[$consumed])) {
|
||||||
yield new XFAAttribute(this, name, value);
|
yield value;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const child of this[_children]) {
|
for (const child of this[_children]) {
|
||||||
yield* child[$getAttributeIt](name);
|
yield* child[$getAttributeIt](name, skipConsumed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*[$getRealChildrenByNameIt](name, allTransparent, skipConsumed) {
|
||||||
|
for (const child of this[_children]) {
|
||||||
|
if (child[$nodeName] === name && (!skipConsumed || !child[$consumed])) {
|
||||||
|
yield child;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (allTransparent) {
|
||||||
|
yield* child[$getRealChildrenByNameIt](
|
||||||
|
name,
|
||||||
|
allTransparent,
|
||||||
|
skipConsumed
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[$isDataValue]() {
|
[$isDataValue]() {
|
||||||
return this[_children].length === 0;
|
if (this[_dataValue] === null) {
|
||||||
|
return this[_children].length === 0;
|
||||||
|
}
|
||||||
|
return this[_dataValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
[$dump]() {
|
||||||
|
const dumped = Object.create(null);
|
||||||
|
if (this[$content]) {
|
||||||
|
dumped.$content = this[$content];
|
||||||
|
}
|
||||||
|
dumped.$name = this[$nodeName];
|
||||||
|
|
||||||
|
dumped.children = [];
|
||||||
|
for (const child of this[_children]) {
|
||||||
|
dumped.children.push(child[$dump]());
|
||||||
|
}
|
||||||
|
|
||||||
|
dumped.attributes = Object.create(null);
|
||||||
|
for (const [name, value] of this[_attributes]) {
|
||||||
|
dumped.attributes[name] = value[$content];
|
||||||
|
}
|
||||||
|
|
||||||
|
return dumped;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -641,9 +765,13 @@ class Option10 extends IntegerObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
$appendChild,
|
||||||
$clean,
|
$clean,
|
||||||
$cleanup,
|
$cleanup,
|
||||||
|
$clone,
|
||||||
|
$consumed,
|
||||||
$content,
|
$content,
|
||||||
|
$data,
|
||||||
$dump,
|
$dump,
|
||||||
$finalize,
|
$finalize,
|
||||||
$getAttributeIt,
|
$getAttributeIt,
|
||||||
@ -652,16 +780,26 @@ export {
|
|||||||
$getChildrenByName,
|
$getChildrenByName,
|
||||||
$getChildrenByNameIt,
|
$getChildrenByNameIt,
|
||||||
$getParent,
|
$getParent,
|
||||||
|
$getRealChildrenByNameIt,
|
||||||
|
$global,
|
||||||
|
$hasItem,
|
||||||
|
$hasSettableValue,
|
||||||
|
$indexOf,
|
||||||
|
$insertAt,
|
||||||
$isDataValue,
|
$isDataValue,
|
||||||
|
$isDescendent,
|
||||||
$isTransparent,
|
$isTransparent,
|
||||||
$namespaceId,
|
$namespaceId,
|
||||||
$nodeName,
|
$nodeName,
|
||||||
|
$nsAttributes,
|
||||||
$onChild,
|
$onChild,
|
||||||
$onChildCheck,
|
$onChildCheck,
|
||||||
$onText,
|
$onText,
|
||||||
|
$removeChild,
|
||||||
$resolvePrototypes,
|
$resolvePrototypes,
|
||||||
$setId,
|
$setId,
|
||||||
$setSetAttributes,
|
$setSetAttributes,
|
||||||
|
$setValue,
|
||||||
$text,
|
$text,
|
||||||
ContentObject,
|
ContentObject,
|
||||||
IntegerObject,
|
IntegerObject,
|
||||||
|
@ -20,6 +20,7 @@ import {
|
|||||||
$getChildrenByName,
|
$getChildrenByName,
|
||||||
$text,
|
$text,
|
||||||
} from "../../src/core/xfa/xfa_object.js";
|
} from "../../src/core/xfa/xfa_object.js";
|
||||||
|
import { Binder } from "../../src/core/xfa/bind.js";
|
||||||
import { searchNode } from "../../src/core/xfa/som.js";
|
import { searchNode } from "../../src/core/xfa/som.js";
|
||||||
import { XFAParser } from "../../src/core/xfa/parser.js";
|
import { XFAParser } from "../../src/core/xfa/parser.js";
|
||||||
|
|
||||||
@ -507,39 +508,45 @@ describe("XFAParser", function () {
|
|||||||
</xdp:xdp>
|
</xdp:xdp>
|
||||||
`;
|
`;
|
||||||
const root = new XFAParser().parse(xml);
|
const root = new XFAParser().parse(xml);
|
||||||
expect(searchNode(root, null, "$template..Description.id")[$text]()).toBe(
|
|
||||||
"a"
|
|
||||||
);
|
|
||||||
expect(searchNode(root, null, "$template..Description.id")[$text]()).toBe(
|
|
||||||
"a"
|
|
||||||
);
|
|
||||||
expect(
|
expect(
|
||||||
searchNode(root, null, "$template..Description[0].id")[$text]()
|
searchNode(root, null, "$template..Description.id")[0][$text]()
|
||||||
).toBe("a");
|
).toBe("a");
|
||||||
expect(
|
expect(
|
||||||
searchNode(root, null, "$template..Description[1].id")[$text]()
|
searchNode(root, null, "$template..Description.id")[0][$text]()
|
||||||
|
).toBe("a");
|
||||||
|
expect(
|
||||||
|
searchNode(root, null, "$template..Description[0].id")[0][$text]()
|
||||||
|
).toBe("a");
|
||||||
|
expect(
|
||||||
|
searchNode(root, null, "$template..Description[1].id")[0][$text]()
|
||||||
).toBe("e");
|
).toBe("e");
|
||||||
expect(
|
expect(
|
||||||
searchNode(root, null, "$template..Description[2].id")[$text]()
|
searchNode(root, null, "$template..Description[2].id")[0][$text]()
|
||||||
).toBe("p");
|
).toBe("p");
|
||||||
expect(searchNode(root, null, "$template.Receipt.id")[$text]()).toBe("l");
|
expect(searchNode(root, null, "$template.Receipt.id")[0][$text]()).toBe(
|
||||||
|
"l"
|
||||||
|
);
|
||||||
expect(
|
expect(
|
||||||
searchNode(root, null, "$template.Receipt.Description[1].id")[$text]()
|
searchNode(root, null, "$template.Receipt.Description[1].id")[0][
|
||||||
|
$text
|
||||||
|
]()
|
||||||
).toBe("e");
|
).toBe("e");
|
||||||
expect(searchNode(root, null, "$template.Receipt.Description[2]")).toBe(
|
expect(searchNode(root, null, "$template.Receipt.Description[2]")).toBe(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
searchNode(root, null, "$template.Receipt.foo.Description.id")[$text]()
|
searchNode(root, null, "$template.Receipt.foo.Description.id")[0][
|
||||||
|
$text
|
||||||
|
]()
|
||||||
).toBe("p");
|
).toBe("p");
|
||||||
expect(
|
expect(
|
||||||
searchNode(root, null, "$template.#subform.Sub_Total.id")[$text]()
|
searchNode(root, null, "$template.#subform.Sub_Total.id")[0][$text]()
|
||||||
).toBe("i");
|
).toBe("i");
|
||||||
expect(
|
expect(
|
||||||
searchNode(root, null, "$template.#subform.Units.id")[$text]()
|
searchNode(root, null, "$template.#subform.Units.id")[0][$text]()
|
||||||
).toBe("b");
|
).toBe("b");
|
||||||
expect(
|
expect(
|
||||||
searchNode(root, null, "$template.#subform.Units.parent.id")[$text]()
|
searchNode(root, null, "$template.#subform.Units.parent.id")[0][$text]()
|
||||||
).toBe("m");
|
).toBe("m");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -620,10 +627,10 @@ describe("XFAParser", function () {
|
|||||||
searchNode(root, receipt, "Detail[*].Total_Price").map(x => x[$text]())
|
searchNode(root, receipt, "Detail[*].Total_Price").map(x => x[$text]())
|
||||||
).toEqual(["250.00", "60.00"]);
|
).toEqual(["250.00", "60.00"]);
|
||||||
|
|
||||||
const units = searchNode(root, receipt, "Detail[1].Units");
|
const [units] = searchNode(root, receipt, "Detail[1].Units");
|
||||||
expect(units[$text]()).toBe("5");
|
expect(units[$text]()).toBe("5");
|
||||||
|
|
||||||
let found = searchNode(root, units, "Total_Price");
|
let [found] = searchNode(root, units, "Total_Price");
|
||||||
expect(found[$text]()).toBe("60.00");
|
expect(found[$text]()).toBe("60.00");
|
||||||
|
|
||||||
found = searchNode(root, units, "Total_Pric");
|
found = searchNode(root, units, "Total_Pric");
|
||||||
@ -645,18 +652,503 @@ describe("XFAParser", function () {
|
|||||||
</xdp:xdp>
|
</xdp:xdp>
|
||||||
`;
|
`;
|
||||||
const root = new XFAParser().parse(xml);
|
const root = new XFAParser().parse(xml);
|
||||||
expect(searchNode(root, null, "$data.Receipt.Detail")[$text]()).toBe(
|
expect(searchNode(root, null, "$data.Receipt.Detail")[0][$text]()).toBe(
|
||||||
"Acme"
|
"Acme"
|
||||||
);
|
);
|
||||||
expect(searchNode(root, null, "$data.Receipt.Detail[0]")[$text]()).toBe(
|
expect(
|
||||||
"Acme"
|
searchNode(root, null, "$data.Receipt.Detail[0]")[0][$text]()
|
||||||
|
).toBe("Acme");
|
||||||
|
expect(
|
||||||
|
searchNode(root, null, "$data.Receipt.Detail[1]")[0][$text]()
|
||||||
|
).toBe("foo");
|
||||||
|
expect(
|
||||||
|
searchNode(root, null, "$data.Receipt.Detail[2]")[0][$text]()
|
||||||
|
).toBe("bar");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Bind data into form", function () {
|
||||||
|
it("should make a basic binding", function () {
|
||||||
|
const xml = `
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
||||||
|
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3">
|
||||||
|
<subform name="A">
|
||||||
|
<subform name="B">
|
||||||
|
<field name="C">
|
||||||
|
</field>
|
||||||
|
<field name="D">
|
||||||
|
</field>
|
||||||
|
</subform>
|
||||||
|
</subform>
|
||||||
|
</template>
|
||||||
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
||||||
|
<xfa:data>
|
||||||
|
<A>
|
||||||
|
<C>xyz</C>
|
||||||
|
</A>
|
||||||
|
</xfa:data>
|
||||||
|
</xfa:datasets>
|
||||||
|
</xdp:xdp>
|
||||||
|
`;
|
||||||
|
const root = new XFAParser().parse(xml);
|
||||||
|
const form = new Binder(root).bind();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "A.B.C.value.text")[0][$dump]().$content
|
||||||
|
).toBe("xyz");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should make another basic binding", function () {
|
||||||
|
const xml = `
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
||||||
|
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3">
|
||||||
|
<subform name="registration">
|
||||||
|
<field name="first"> </field>
|
||||||
|
<field name="last"> </field>
|
||||||
|
<field name="apt"> </field>
|
||||||
|
<field name="street"> </field>
|
||||||
|
<field name="city"> </field>
|
||||||
|
<field name="country"> </field>
|
||||||
|
<field name="postalcode"/>
|
||||||
|
</subform>
|
||||||
|
</template>
|
||||||
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
||||||
|
<xfa:data>
|
||||||
|
<registration>
|
||||||
|
<first>Jack</first>
|
||||||
|
<last>Spratt</last>
|
||||||
|
<apt/>
|
||||||
|
<street>99 Candlestick Lane</street>
|
||||||
|
<city>London</city>
|
||||||
|
<country>UK</country>
|
||||||
|
<postalcode>SW1</postalcode>
|
||||||
|
</registration>
|
||||||
|
</xfa:data>
|
||||||
|
</xfa:datasets>
|
||||||
|
</xdp:xdp>
|
||||||
|
`;
|
||||||
|
const root = new XFAParser().parse(xml);
|
||||||
|
const form = new Binder(root).bind();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "registration.first..text")[0][$dump]().$content
|
||||||
|
).toBe("Jack");
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "registration.last..text")[0][$dump]().$content
|
||||||
|
).toBe("Spratt");
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "registration.apt..text")[0][$dump]().$content
|
||||||
|
).toBe(undefined);
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "registration.street..text")[0][$dump]().$content
|
||||||
|
).toBe("99 Candlestick Lane");
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "registration.city..text")[0][$dump]().$content
|
||||||
|
).toBe("London");
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "registration.country..text")[0][$dump]()
|
||||||
|
.$content
|
||||||
|
).toBe("UK");
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "registration.postalcode..text")[0][$dump]()
|
||||||
|
.$content
|
||||||
|
).toBe("SW1");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should make basic binding with extra subform", function () {
|
||||||
|
const xml = `
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
||||||
|
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3">
|
||||||
|
<subform name="registration">
|
||||||
|
<field name="first"> </field>
|
||||||
|
<field name="last"> </field>
|
||||||
|
<subform name="address">
|
||||||
|
<field name="apt"> </field>
|
||||||
|
<field name="street"> </field>
|
||||||
|
<field name="city"> </field>
|
||||||
|
<field name="country"> </field>
|
||||||
|
<field name="postalcode"> </field>
|
||||||
|
</subform>
|
||||||
|
</subform>
|
||||||
|
</template>
|
||||||
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
||||||
|
<xfa:data>
|
||||||
|
<registration>
|
||||||
|
<first>Jack</first>
|
||||||
|
<last>Spratt</last>
|
||||||
|
<apt/>
|
||||||
|
<street>99 Candlestick Lane</street>
|
||||||
|
<city>London</city>
|
||||||
|
<country>UK</country>
|
||||||
|
<postalcode>SW1</postalcode>
|
||||||
|
</registration>
|
||||||
|
</xfa:data>
|
||||||
|
</xfa:datasets>
|
||||||
|
</xdp:xdp>
|
||||||
|
`;
|
||||||
|
const root = new XFAParser().parse(xml);
|
||||||
|
const form = new Binder(root).bind();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "registration..first..text")[0][$dump]().$content
|
||||||
|
).toBe("Jack");
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "registration..last..text")[0][$dump]().$content
|
||||||
|
).toBe("Spratt");
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "registration..apt..text")[0][$dump]().$content
|
||||||
|
).toBe(undefined);
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "registration..street..text")[0][$dump]()
|
||||||
|
.$content
|
||||||
|
).toBe("99 Candlestick Lane");
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "registration..city..text")[0][$dump]().$content
|
||||||
|
).toBe("London");
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "registration..country..text")[0][$dump]()
|
||||||
|
.$content
|
||||||
|
).toBe("UK");
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "registration..postalcode..text")[0][$dump]()
|
||||||
|
.$content
|
||||||
|
).toBe("SW1");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should make basic binding with extra subform", function () {
|
||||||
|
const xml = `
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
||||||
|
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3">
|
||||||
|
<subform name="registration" mergeMode="consumeData">
|
||||||
|
<subform name="address">
|
||||||
|
<field name="first"/>
|
||||||
|
<field name="last"/>
|
||||||
|
<field name="apt"/>
|
||||||
|
<field name="street"/>
|
||||||
|
<field name="city"/>
|
||||||
|
</subform>
|
||||||
|
</subform>
|
||||||
|
</template>
|
||||||
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
||||||
|
<xfa:data>
|
||||||
|
<registration>
|
||||||
|
<first>Jack</first>
|
||||||
|
<last>Spratt</last>
|
||||||
|
<address>
|
||||||
|
<apt>7</apt>
|
||||||
|
<street>99 Candlestick Lane</street>
|
||||||
|
<city>London</city>
|
||||||
|
</address>
|
||||||
|
</registration>
|
||||||
|
</xfa:data>
|
||||||
|
</xfa:datasets>
|
||||||
|
</xdp:xdp>
|
||||||
|
`;
|
||||||
|
const root = new XFAParser().parse(xml);
|
||||||
|
const form = new Binder(root).bind();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "registration..first..text")[0][$dump]().$content
|
||||||
|
).toBe("Jack");
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "registration..last..text")[0][$dump]().$content
|
||||||
|
).toBe("Spratt");
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "registration..apt..text")[0][$dump]().$content
|
||||||
|
).toBe("7");
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "registration..street..text")[0][$dump]()
|
||||||
|
.$content
|
||||||
|
).toBe("99 Candlestick Lane");
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "registration..city..text")[0][$dump]().$content
|
||||||
|
).toBe("London");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should make basic binding with same names in different parts", function () {
|
||||||
|
const xml = `
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
||||||
|
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3">
|
||||||
|
<subform name="application" mergeMode="consumeData">
|
||||||
|
<subform name="sponsor">
|
||||||
|
<field name="lastname"> </field>
|
||||||
|
<!-- sponsor's last name -->
|
||||||
|
</subform>
|
||||||
|
<field name="lastname"> </field>
|
||||||
|
<!-- applicant's last name -->
|
||||||
|
</subform>
|
||||||
|
</template>
|
||||||
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
||||||
|
<xfa:data>
|
||||||
|
<application>
|
||||||
|
<lastname>Abott</lastname>
|
||||||
|
<sponsor>
|
||||||
|
<lastname>Costello</lastname>
|
||||||
|
</sponsor>
|
||||||
|
</application>
|
||||||
|
</xfa:data>
|
||||||
|
</xfa:datasets>
|
||||||
|
</xdp:xdp>
|
||||||
|
`;
|
||||||
|
const root = new XFAParser().parse(xml);
|
||||||
|
const form = new Binder(root).bind();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "application.sponsor.lastname..text")[0][$dump]()
|
||||||
|
.$content
|
||||||
|
).toBe("Costello");
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "application.lastname..text")[0][$dump]()
|
||||||
|
.$content
|
||||||
|
).toBe("Abott");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should make binding and create nodes in data", function () {
|
||||||
|
const xml = `
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
||||||
|
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3">
|
||||||
|
<subform name="root" mergeMode="matchTemplate">
|
||||||
|
<subform name="A">
|
||||||
|
<field name="a"/>
|
||||||
|
<field name="b"/>
|
||||||
|
<subform name="B">
|
||||||
|
<field name="c"/>
|
||||||
|
<field name="d"/>
|
||||||
|
<subform name="C">
|
||||||
|
<field name="e"/>
|
||||||
|
<field name="f"/>
|
||||||
|
</subform>
|
||||||
|
</subform>
|
||||||
|
</subform>
|
||||||
|
</subform>
|
||||||
|
</template>
|
||||||
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
||||||
|
<xfa:data>
|
||||||
|
<root>
|
||||||
|
<A>
|
||||||
|
<b>1</b>
|
||||||
|
</A>
|
||||||
|
</root>
|
||||||
|
</xfa:data>
|
||||||
|
</xfa:datasets>
|
||||||
|
</xdp:xdp>
|
||||||
|
`;
|
||||||
|
const root = new XFAParser().parse(xml);
|
||||||
|
const binder = new Binder(root);
|
||||||
|
const form = binder.bind();
|
||||||
|
const data = binder.getData();
|
||||||
|
|
||||||
|
expect(searchNode(form, form, "root..b..text")[0][$dump]().$content).toBe(
|
||||||
|
"1"
|
||||||
);
|
);
|
||||||
expect(searchNode(root, null, "$data.Receipt.Detail[1]")[$text]()).toBe(
|
expect(searchNode(data, data, "root.A.a")[0][$dump]().$name).toBe("a");
|
||||||
"foo"
|
expect(searchNode(data, data, "root.A.B.c")[0][$dump]().$name).toBe("c");
|
||||||
|
expect(searchNode(data, data, "root.A.B.d")[0][$dump]().$name).toBe("d");
|
||||||
|
expect(searchNode(data, data, "root.A.B.C.e")[0][$dump]().$name).toBe(
|
||||||
|
"e"
|
||||||
);
|
);
|
||||||
expect(searchNode(root, null, "$data.Receipt.Detail[2]")[$text]()).toBe(
|
expect(searchNode(data, data, "root.A.B.C.f")[0][$dump]().$name).toBe(
|
||||||
"bar"
|
"f"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should make binding and set properties", function () {
|
||||||
|
const xml = `
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
||||||
|
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3">
|
||||||
|
<subform name="Id">
|
||||||
|
<field name="LastName">
|
||||||
|
<setProperty ref="$data.Main.Style.NameFont" target="font.typeface"/>
|
||||||
|
<setProperty ref="$data.Main.Style.NameSize" target="font.size"/>
|
||||||
|
<setProperty ref="$data.Main.Help.LastName" target="assist.toolTip"/>
|
||||||
|
<font></font>
|
||||||
|
<assist>
|
||||||
|
<toolTip>
|
||||||
|
</toolTip>
|
||||||
|
</assist>
|
||||||
|
</field>
|
||||||
|
</subform>
|
||||||
|
</template>
|
||||||
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
||||||
|
<xfa:data>
|
||||||
|
<Id>
|
||||||
|
<LastName>foo</LastName>
|
||||||
|
</Id>
|
||||||
|
<Main>
|
||||||
|
<Style>
|
||||||
|
<NameFont>myfont</NameFont>
|
||||||
|
<NameSize>123.4pt</NameSize>
|
||||||
|
</Style>
|
||||||
|
<Help>
|
||||||
|
<LastName>Give the name!</LastName>
|
||||||
|
</Help>
|
||||||
|
</Main>
|
||||||
|
</xfa:data>
|
||||||
|
</xfa:datasets>
|
||||||
|
</xdp:xdp>
|
||||||
|
`;
|
||||||
|
const root = new XFAParser().parse(xml);
|
||||||
|
const form = new Binder(root).bind();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "Id.LastName..text")[0][$dump]().$content
|
||||||
|
).toBe("foo");
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "Id.LastName.font.typeface")[0][$text]()
|
||||||
|
).toBe("myfont");
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "Id.LastName.font.size")[0][$text]()
|
||||||
|
).toEqual({
|
||||||
|
value: 123.4,
|
||||||
|
unit: "pt",
|
||||||
|
});
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "Id.LastName.assist.toolTip")[0][$dump]()
|
||||||
|
.$content
|
||||||
|
).toBe("Give the name!");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should make binding and bind items", function () {
|
||||||
|
const xml = `
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
||||||
|
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3">
|
||||||
|
<subform name="main">
|
||||||
|
<field name="CardName">
|
||||||
|
<bindItems ref="$data.main.ccs.cc[*]" labelRef="uiname" valueRef="token"/>
|
||||||
|
<ui>
|
||||||
|
<choiceList/>
|
||||||
|
</ui>
|
||||||
|
</field>
|
||||||
|
</subform>
|
||||||
|
</template>
|
||||||
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
||||||
|
<xfa:data>
|
||||||
|
<main>
|
||||||
|
<ccs>
|
||||||
|
<cc uiname="Visa" token="VISA"/>
|
||||||
|
<cc uiname="Mastercard" token="MC"/>
|
||||||
|
<cc uiname="American Express" token="AMEX"/>
|
||||||
|
</ccs>
|
||||||
|
<CardName>MC</CardName>
|
||||||
|
</main>
|
||||||
|
</xfa:data>
|
||||||
|
</xfa:datasets>
|
||||||
|
</xdp:xdp>
|
||||||
|
`;
|
||||||
|
const root = new XFAParser().parse(xml);
|
||||||
|
const form = new Binder(root).bind();
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "subform.CardName.items[*].text[*]").map(x =>
|
||||||
|
x[$text]()
|
||||||
|
)
|
||||||
|
).toEqual([
|
||||||
|
"Visa",
|
||||||
|
"Mastercard",
|
||||||
|
"American Express",
|
||||||
|
"VISA",
|
||||||
|
"MC",
|
||||||
|
"AMEX",
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should make binding with occurrences in consumeData mode", function () {
|
||||||
|
const xml = `
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
||||||
|
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3">
|
||||||
|
<subform name="root" mergeMode="consumeData">
|
||||||
|
<subform name="section" id="section1">
|
||||||
|
<occur min="0" max="-1"/>
|
||||||
|
<bind match="dataRef" ref="$.section[*]"/>
|
||||||
|
<field name="line-item"/>
|
||||||
|
</subform>
|
||||||
|
<subform name="section" id="section2">
|
||||||
|
<occur min="0" max="-1"/>
|
||||||
|
<bind match="dataRef" ref="$.section[*]"/>
|
||||||
|
<field name="line-item"/>
|
||||||
|
</subform>
|
||||||
|
</subform>
|
||||||
|
</template>
|
||||||
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
||||||
|
<xfa:data>
|
||||||
|
<root>
|
||||||
|
<section>
|
||||||
|
<line-item>item1</line-item>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<line-item>item2</line-item>
|
||||||
|
</section>
|
||||||
|
</root>
|
||||||
|
</xfa:data>
|
||||||
|
</xfa:datasets>
|
||||||
|
</xdp:xdp>
|
||||||
|
`;
|
||||||
|
const root = new XFAParser().parse(xml);
|
||||||
|
const form = new Binder(root).bind();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "root.section[*].id").map(x => x[$text]())
|
||||||
|
).toEqual(["section1", "section1"]);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "root.section[*].line-item..text").map(x =>
|
||||||
|
x[$text]()
|
||||||
|
)
|
||||||
|
).toEqual(["item1", "item2"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should make binding with occurrences in matchTemplate mode", function () {
|
||||||
|
const xml = `
|
||||||
|
<?xml version="1.0"?>
|
||||||
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
||||||
|
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3">
|
||||||
|
<subform name="root" mergeMode="matchTemplate">
|
||||||
|
<subform name="section" id="section1">
|
||||||
|
<occur min="0" max="-1"/>
|
||||||
|
<bind match="dataRef" ref="$.section[*]"/>
|
||||||
|
<field name="line-item"/>
|
||||||
|
</subform>
|
||||||
|
<subform name="section" id="section2">
|
||||||
|
<occur min="0" max="-1"/>
|
||||||
|
<bind match="dataRef" ref="$.section[*]"/>
|
||||||
|
<field name="line-item"/>
|
||||||
|
</subform>
|
||||||
|
</subform>
|
||||||
|
</template>
|
||||||
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
||||||
|
<xfa:data>
|
||||||
|
<root>
|
||||||
|
<section>
|
||||||
|
<line-item>item1</line-item>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<line-item>item2</line-item>
|
||||||
|
</section>
|
||||||
|
</root>
|
||||||
|
</xfa:data>
|
||||||
|
</xfa:datasets>
|
||||||
|
</xdp:xdp>
|
||||||
|
`;
|
||||||
|
const root = new XFAParser().parse(xml);
|
||||||
|
const form = new Binder(root).bind();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "root.section[*].id").map(x => x[$text]())
|
||||||
|
).toEqual(["section1", "section1", "section2", "section2"]);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
searchNode(form, form, "root.section[*].line-item..text").map(x =>
|
||||||
|
x[$text]()
|
||||||
|
)
|
||||||
|
).toEqual(["item1", "item2", "item1", "item2"]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user