1
0
Fork 0
mirror of https://github.com/mozilla/pdf.js.git synced 2025-04-28 23:28:16 +02:00

XFA - Create Form DOM in merging template and data trees

- Spec: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.364.2157&rep=rep1&type=pdf#page=171;
  - support for the 2 ways of merging: consumeData and matchTemplate;
  - create additional nodes in template DOM when occur node allows it;
  - support for global values in data DOM.
This commit is contained in:
Calixte Denizet 2021-02-24 19:05:04 +01:00
parent 5e3af62d58
commit 3243672727
9 changed files with 1584 additions and 109 deletions

View file

@ -19,43 +19,57 @@ import { NamespaceIds } from "./namespaces.js";
// We use these symbols to avoid name conflict between tags
// and properties/methods names.
const $appendChild = Symbol();
const $clean = Symbol();
const $cleanup = Symbol();
const $clone = Symbol();
const $consumed = Symbol();
const $content = Symbol("content");
const $data = Symbol("data");
const $dump = Symbol();
const $finalize = Symbol();
const $isDataValue = Symbol();
const $getAttributeIt = Symbol();
const $getChildrenByClass = Symbol();
const $getChildrenByName = Symbol();
const $getChildrenByNameIt = Symbol();
const $getRealChildrenByNameIt = Symbol();
const $getChildren = 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 $lastAttribute = Symbol();
const $namespaceId = Symbol("namespaceId");
const $nodeName = Symbol("nodeName");
const $nsAttributes = Symbol();
const $onChild = Symbol();
const $onChildCheck = Symbol();
const $onText = Symbol();
const $removeChild = Symbol();
const $resolvePrototypes = Symbol();
const $setId = Symbol();
const $setSetAttributes = Symbol();
const $setValue = Symbol();
const $text = Symbol();
const _applyPrototype = Symbol();
const _attributes = Symbol();
const _attributeNames = Symbol();
const _children = Symbol();
const _clone = Symbol();
const _children = Symbol("_children");
const _cloneAttribute = Symbol();
const _dataValue = Symbol();
const _defaultValue = Symbol();
const _getPrototype = Symbol();
const _getUnsetAttributes = Symbol();
const _hasChildren = Symbol();
const _max = Symbol();
const _options = Symbol();
const _parent = Symbol();
const _parent = Symbol("parent");
const _setAttributes = Symbol();
const _validator = Symbol();
@ -78,18 +92,27 @@ class XFAObject {
if (node instanceof XFAObjectArray) {
if (node.push(child)) {
child[_parent] = this;
this[_children].push(child);
this[$appendChild](child);
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;
child[_parent] = this;
this[_children].push(child);
this[$appendChild](child);
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;
}
@ -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](_) {}
[$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]() {
return this.name === "";
}
@ -126,6 +178,13 @@ class XFAObject {
return "";
}
[$text]() {
if (this[_children].length === 0) {
return this[$content];
}
return this[_children].map(c => c[$text]()).join("");
}
get [_attributeNames]() {
// Lazily get attributes names
const proto = Object.getPrototypeOf(this);
@ -145,6 +204,17 @@ class XFAObject {
return shadow(this, _attributeNames, proto._attributes);
}
[$isDescendent](parent) {
let node = this;
while (node) {
if (node === parent) {
return true;
}
node = node[$getParent]();
}
return false;
}
[$getParent]() {
return this[_parent];
}
@ -297,7 +367,7 @@ class XFAObject {
i < ii;
i++
) {
const child = proto[_children][i][_clone]();
const child = proto[_children][i][$clone]();
if (value.push(child)) {
child[_parent] = this;
this[_children].push(child);
@ -316,7 +386,7 @@ class XFAObject {
}
if (protoValue !== null) {
const child = protoValue[_clone]();
const child = protoValue[$clone]();
child[_parent] = this;
this[name] = child;
this[_children].push(child);
@ -335,7 +405,7 @@ class XFAObject {
return obj;
}
[_clone]() {
[$clone]() {
const clone = Object.create(Object.getPrototypeOf(this));
for (const $symbol of Object.getOwnPropertySymbols(this)) {
try {
@ -361,7 +431,7 @@ class XFAObject {
for (const child of this[_children]) {
const name = child[$nodeName];
const clonedChild = child[_clone]();
const clonedChild = child[$clone]();
clone[_children].push(clonedChild);
clonedChild[_parent] = clone;
if (clone[name] === null) {
@ -444,15 +514,19 @@ class XFAObjectArray {
: this[_children].map(x => x[$dump]());
}
[_clone]() {
[$clone]() {
const clone = new XFAObjectArray(this[_max]);
clone[_children] = this[_children].map(c => c[_clone]());
clone[_children] = this[_children].map(c => c[$clone]());
return clone;
}
get children() {
return this[_children];
}
clear() {
this[_children].length = 0;
}
}
class XFAAttribute {
@ -460,6 +534,7 @@ class XFAAttribute {
this[_parent] = node;
this[$nodeName] = name;
this[$content] = value;
this[$consumed] = false;
}
[$getParent]() {
@ -473,27 +548,46 @@ class XFAAttribute {
[$text]() {
return this[$content];
}
[$isDescendent](parent) {
return this[_parent] === parent || this[_parent][$isDescendent](parent);
}
}
class XmlObject extends XFAObject {
constructor(nsId, name, attributes = null) {
constructor(nsId, name, attributes = {}) {
super(nsId, name);
this[$content] = "";
this[_dataValue] = null;
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) {
if (this[$content]) {
const node = new XmlObject(this[$namespaceId], "#text");
node[_parent] = this;
this[$appendChild](node);
node[$content] = this[$content];
this[$content] = "";
this[_children].push(node);
}
child[_parent] = this;
this[_children].push(child);
this[$appendChild](child);
return true;
}
@ -504,20 +598,12 @@ class XmlObject extends XFAObject {
[$finalize]() {
if (this[$content] && this[_children].length > 0) {
const node = new XmlObject(this[$namespaceId], "#text");
node[_parent] = this;
this[$appendChild](node);
node[$content] = this[$content];
this[_children].push(node);
delete this[$content];
}
}
[$text]() {
if (this[_children].length === 0) {
return this[$content];
}
return this[_children].map(c => c[$text]()).join("");
}
[$getChildren](name = null) {
if (!name) {
return this[_children];
@ -527,7 +613,7 @@ class XmlObject extends XFAObject {
}
[$getChildrenByClass](name) {
const value = this[_attributes][name];
const value = this[_attributes].get(name);
if (value !== undefined) {
return value;
}
@ -535,9 +621,9 @@ class XmlObject extends XFAObject {
}
*[$getChildrenByNameIt](name, allTransparent) {
const value = this[_attributes][name];
if (value !== undefined) {
yield new XFAAttribute(this, name, value);
const value = this[_attributes].get(name);
if (value) {
yield value;
}
for (const child of this[_children]) {
@ -551,19 +637,57 @@ class XmlObject extends XFAObject {
}
}
*[$getAttributeIt](name) {
const value = this[_attributes][name];
if (value !== undefined) {
yield new XFAAttribute(this, name, value);
*[$getAttributeIt](name, skipConsumed) {
const value = this[_attributes].get(name);
if (value && (!skipConsumed || !value[$consumed])) {
yield value;
}
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]() {
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 {
$appendChild,
$clean,
$cleanup,
$clone,
$consumed,
$content,
$data,
$dump,
$finalize,
$getAttributeIt,
@ -652,16 +780,26 @@ export {
$getChildrenByName,
$getChildrenByNameIt,
$getParent,
$getRealChildrenByNameIt,
$global,
$hasItem,
$hasSettableValue,
$indexOf,
$insertAt,
$isDataValue,
$isDescendent,
$isTransparent,
$namespaceId,
$nodeName,
$nsAttributes,
$onChild,
$onChildCheck,
$onText,
$removeChild,
$resolvePrototypes,
$setId,
$setSetAttributes,
$setValue,
$text,
ContentObject,
IntegerObject,