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:
parent
5e3af62d58
commit
3243672727
9 changed files with 1584 additions and 109 deletions
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue