in src/core/xfa/bind.js [480:671]
_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";
// XFA specs p. 182:
// The highest-level subform and the data node representing
// the current record are special; they are always
// bound even if their names don't match.
const dataChildren = dataNode[$getChildren]();
if (dataChildren.length > 0) {
this._bindOccurrences(child, [dataChildren[0]], null);
} else if (this.emptyMerge) {
const nsId =
dataNode[$namespaceId] === NS_DATASETS
? -1
: dataNode[$namespaceId];
const dataChild = (child[$data] = new XmlObject(
nsId,
child.name || "root"
));
dataNode[$appendChild](dataChild);
this._bindElement(child, dataChild);
}
continue;
}
if (!child[$isBindable]()) {
// The node cannot contain some new data so there is nothing
// to create in the data node.
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) {
warn(`XFA - ref is empty in node ${child[$nodeName]}.`);
this._setAndBind(child, dataNode);
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 (!match) {
// For example if the node contains a .(...) then it isn't
// findable.
// TODO: remove this when .(...) is implemented.
continue;
}
if (this._isConsumeData()) {
match[$consumed] = true;
}
// Don't bind the value in newly created node because it's empty.
this._setAndBind(child, match);
continue;
} 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._setAndBind(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,
child[$hasSettableValue](),
dataNode,
global
);
if (!found) {
break;
}
found[$consumed] = true;
matches.push(found);
}
match = matches.length > 0 ? matches : null;
} else {
// If we've an empty merge, there are no reason
// to make multiple bind so skip consumed nodes.
match = dataNode[$getRealChildrenByNameIt](
child.name,
/* allTransparent = */ false,
/* skipConsumed = */ this.emptyMerge
).next().value;
if (!match) {
// If there is no match (no data) and `min === 0` then
// the container is entirely excluded.
// https://www.pdfa.org/norm-refs/XFA-3_3.pdf#G12.1428332
if (min === 0) {
uselessNodes.push(child);
continue;
}
// We're in matchTemplate mode so create a node in data to reflect
// what we've in template.
const nsId =
dataNode[$namespaceId] === NS_DATASETS
? -1
: dataNode[$namespaceId];
match = child[$data] = new XmlObject(nsId, child.name);
if (this.emptyMerge) {
match[$consumed] = true;
}
dataNode[$appendChild](match);
// Don't bind the value in newly created node because it's empty.
this._setAndBind(child, match);
continue;
}
if (this.emptyMerge) {
match[$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[$getParent]()[$removeChild](node));
}