From 7e0554afe2b5594962a13020bcfb4bad346ccb70 Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Wed, 3 Feb 2021 18:55:56 +0100 Subject: [PATCH] XFA -- Add attributes and children in XFAObject - in order to evaluate SOM expressions nodes and their attributes must be checked in the same order as in the xml; - add an object XFAObjectArray with a parameter max to handle multiple children with the same name. --- src/core/xfa/config.js | 21 +++---- src/core/xfa/xdp.js | 3 +- src/core/xfa/xfa_object.js | 109 +++++++++++++++++++++++++++++++------ 3 files changed, 105 insertions(+), 28 deletions(-) diff --git a/src/core/xfa/config.js b/src/core/xfa/config.js index 8d8819d78..16ded14f8 100644 --- a/src/core/xfa/config.js +++ b/src/core/xfa/config.js @@ -19,6 +19,7 @@ import { OptionObject, StringObject, XFAObject, + XFAObjectArray, } from "./xfa_object.js"; const CONFIG_NS_ID = NamespaceIds.config.id; @@ -31,7 +32,7 @@ class Acrobat extends XFAObject { this.common = null; this.validate = null; this.validateApprovalSignatures = null; - this.submitUrl = []; + this.submitUrl = new XFAObjectArray(); } } @@ -60,7 +61,7 @@ class Config extends XFAObject { this.acrobat = null; this.present = null; this.trace = null; - this.agent = []; + this.agent = new XFAObjectArray(); } } @@ -87,14 +88,14 @@ class Present extends XFAObject { this.script = null; this.validate = null; this.xdp = null; - this.driver = []; - this.labelPrinter = []; - this.pcl = []; - this.pdf = []; - this.ps = []; - this.submitUrl = []; - this.webClient = []; - this.zpl = []; + this.driver = new XFAObjectArray(); + this.labelPrinter = new XFAObjectArray(); + this.pcl = new XFAObjectArray(); + this.pdf = new XFAObjectArray(); + this.ps = new XFAObjectArray(); + this.submitUrl = new XFAObjectArray(); + this.webClient = new XFAObjectArray(); + this.zpl = new XFAObjectArray(); } } diff --git a/src/core/xfa/xdp.js b/src/core/xfa/xdp.js index 69c110b23..a4e201b36 100644 --- a/src/core/xfa/xdp.js +++ b/src/core/xfa/xdp.js @@ -19,6 +19,7 @@ import { $nodeName, $onChildCheck, XFAObject, + XFAObjectArray, } from "./xfa_object.js"; const XDP_NS_ID = NamespaceIds.xdp.id; @@ -32,7 +33,7 @@ class Xdp extends XFAObject { this.connectionSet = null; this.datasets = null; this.localeSet = null; - this.stylesheet = []; + this.stylesheet = new XFAObjectArray(); this.template = null; } diff --git a/src/core/xfa/xfa_object.js b/src/core/xfa/xfa_object.js index 8077dbbb2..71eb255bd 100644 --- a/src/core/xfa/xfa_object.js +++ b/src/core/xfa/xfa_object.js @@ -14,7 +14,7 @@ */ import { getInteger, getKeyword } from "./utils.js"; -import { warn } from "../../shared/util.js"; +import { shadow, warn } from "../../shared/util.js"; // We use these symbols to avoid name conflict between tags // and properties/methods names. @@ -23,15 +23,22 @@ const $cleanup = Symbol(); const $content = Symbol("content"); const $dump = Symbol(); const $finalize = Symbol(); +const $getChildren = Symbol(); const $isTransparent = Symbol(); +const $lastAttribute = Symbol(); const $namespaceId = Symbol("namespaceId"); const $nodeName = Symbol("nodeName"); const $onChild = Symbol(); const $onChildCheck = Symbol(); const $onText = Symbol(); +const $text = Symbol(); +const _attributes = Symbol(); +const _attributeNames = Symbol(); +const _children = Symbol(); const _defaultValue = Symbol(); const _hasChildren = Symbol(); +const _max = Symbol(); const _options = Symbol(); const _parent = Symbol(); const _validator = Symbol(); @@ -42,6 +49,7 @@ class XFAObject { this[$nodeName] = name; this[_hasChildren] = hasChildren; this[_parent] = null; + this[_children] = []; } [$onChild](child) { @@ -51,12 +59,15 @@ class XFAObject { const name = child[$nodeName]; const node = this[name]; - if (Array.isArray(node)) { - node.push(child); - child[_parent] = this; + if (node instanceof XFAObjectArray) { + if (node.push(child)) { + child[_parent] = this; + this[_children].push(child); + } } else if (node === null) { this[name] = child; child[_parent] = this; + this[_children].push(child); } else { warn(`XFA - node "${this[$nodeName]}" accepts only one child: ${name}`); } @@ -85,6 +96,37 @@ class XFAObject { return this.name === ""; } + [$lastAttribute]() { + return ""; + } + + get [_attributeNames]() { + // Lazily get attributes names + const proto = Object.getPrototypeOf(this); + if (!proto._attributes) { + const attributes = (proto._attributes = new Set()); + for (const name of Object.getOwnPropertyNames(this)) { + if ( + this[name] === null || + this[name] instanceof XFAObject || + this[name] instanceof XFAObjectArray + ) { + break; + } + attributes.add(name); + } + } + return shadow(this, _attributeNames, proto._attributes); + } + + [$getChildren](name = null) { + if (!name) { + return this[_children]; + } + + return this[_children].filter(c => c[$nodeName] === name); + } + [$dump]() { const dumped = Object.create(null); if (this[$content]) { @@ -93,17 +135,14 @@ class XFAObject { for (const name of Object.getOwnPropertyNames(this)) { const value = this[name]; - if (value === null || (Array.isArray(value) && value.length === 0)) { + if (value === null) { continue; } if (value instanceof XFAObject) { dumped[name] = value[$dump](); - } else if (Array.isArray(value)) { - if (value[0] instanceof XFAObject) { - dumped[name] = - value.length === 1 ? value[0][$dump]() : value.map(x => x[$dump]()); - } else { - dumped[name] = value; + } else if (value instanceof XFAObjectArray) { + if (!value.isEmpty()) { + dumped[name] = value.dump(); } } else { dumped[name] = value; @@ -114,13 +153,41 @@ class XFAObject { } } +class XFAObjectArray { + constructor(max = Infinity) { + this[_max] = max; + this[_children] = []; + } + + push(child) { + const len = this[_children].length; + if (len <= this[_max]) { + this[_children].push(child); + return true; + } + warn( + `XFA - node "${child[$nodeName]}" accepts no more than ${this[_max]} children` + ); + return false; + } + + isEmpty() { + return this[_children].length === 0; + } + + dump() { + return this[_children].length === 1 + ? this[_children][0][$dump]() + : this[_children].map(x => x[$dump]()); + } +} + class XmlObject extends XFAObject { constructor(nsId, name, attributes = Object.create(null)) { super(nsId, name); this[$content] = ""; if (name !== "#text") { - this.attributes = attributes; - this.children = []; + this[_attributes] = attributes; } } @@ -129,9 +196,9 @@ class XmlObject extends XFAObject { const node = new XmlObject(this[$namespaceId], "#text"); node[$content] = this[$content]; this[$content] = ""; - this.children.push(node); + this[_children].push(node); } - this.children.push(child); + this[_children].push(child); } [$onText](str) { @@ -139,13 +206,20 @@ class XmlObject extends XFAObject { } [$finalize]() { - if (this[$content] && this.children.length > 0) { + if (this[$content] && this[_children].length > 0) { const node = new XmlObject(this[$namespaceId], "#text"); node[$content] = this[$content]; - this.children.push(node); + this[_children].push(node); delete this[$content]; } } + + [$text]() { + if (this[_children].length === 0) { + return this[$content]; + } + return this[_children].map(c => c[$text]()).join(""); + } } class ContentObject extends XFAObject { @@ -240,5 +314,6 @@ export { OptionObject, StringObject, XFAObject, + XFAObjectArray, XmlObject, };