XFA - Add support for prototypes (#12979)
- specifications: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.364.2157&rep=rep1&type=pdf#page=225&zoom=auto,-207,784 - add a clone method on nodes in order to be able to clone a proto; - support ids in template namespace; - prevent from cycle when applying protos.
This commit is contained in:
parent
4619b1b568
commit
0fa9976268
@ -14,19 +14,37 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { $buildXFAObject, NamespaceIds } from "./namespaces.js";
|
import { $buildXFAObject, NamespaceIds } from "./namespaces.js";
|
||||||
import { $cleanup, $onChild, XFAObject } from "./xfa_object.js";
|
import {
|
||||||
|
$cleanup,
|
||||||
|
$finalize,
|
||||||
|
$onChild,
|
||||||
|
$resolvePrototypes,
|
||||||
|
XFAObject,
|
||||||
|
} from "./xfa_object.js";
|
||||||
import { NamespaceSetUp } from "./setup.js";
|
import { NamespaceSetUp } from "./setup.js";
|
||||||
|
import { Template } from "./template.js";
|
||||||
import { UnknownNamespace } from "./unknown.js";
|
import { UnknownNamespace } from "./unknown.js";
|
||||||
import { warn } from "../../shared/util.js";
|
import { warn } from "../../shared/util.js";
|
||||||
|
|
||||||
|
const _ids = Symbol();
|
||||||
|
|
||||||
class Root extends XFAObject {
|
class Root extends XFAObject {
|
||||||
constructor() {
|
constructor(ids) {
|
||||||
super(-1, "root", Object.create(null));
|
super(-1, "root", Object.create(null));
|
||||||
this.element = null;
|
this.element = null;
|
||||||
|
this[_ids] = ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
[$onChild](child) {
|
[$onChild](child) {
|
||||||
this.element = child;
|
this.element = child;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[$finalize]() {
|
||||||
|
super[$finalize]();
|
||||||
|
if (this.element.template instanceof Template) {
|
||||||
|
this.element.template[$resolvePrototypes](this[_ids]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,7 +53,9 @@ class Empty extends XFAObject {
|
|||||||
super(-1, "", Object.create(null));
|
super(-1, "", Object.create(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
[$onChild](_) {}
|
[$onChild](_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Builder {
|
class Builder {
|
||||||
@ -51,8 +71,8 @@ class Builder {
|
|||||||
this._currentNamespace = new UnknownNamespace(++this._nextNsId);
|
this._currentNamespace = new UnknownNamespace(++this._nextNsId);
|
||||||
}
|
}
|
||||||
|
|
||||||
buildRoot() {
|
buildRoot(ids) {
|
||||||
return new Root();
|
return new Root(ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
build({ nsPrefix, name, attributes, namespace, prefixes }) {
|
build({ nsPrefix, name, attributes, namespace, prefixes }) {
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { $clean, $finalize, $onChild, $onText } from "./xfa_object.js";
|
import { $clean, $finalize, $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";
|
||||||
@ -23,7 +23,8 @@ class XFAParser extends XMLParserBase {
|
|||||||
super();
|
super();
|
||||||
this._builder = new Builder();
|
this._builder = new Builder();
|
||||||
this._stack = [];
|
this._stack = [];
|
||||||
this._current = this._builder.buildRoot();
|
this._ids = new Map();
|
||||||
|
this._current = this._builder.buildRoot(this._ids);
|
||||||
this._errorCode = XMLParserErrorCode.NoError;
|
this._errorCode = XMLParserErrorCode.NoError;
|
||||||
this._whiteRegex = /^\s+$/;
|
this._whiteRegex = /^\s+$/;
|
||||||
}
|
}
|
||||||
@ -35,6 +36,8 @@ class XFAParser extends XMLParserBase {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._current[$finalize]();
|
||||||
|
|
||||||
return this._current.element;
|
return this._current.element;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,7 +104,9 @@ class XFAParser extends XMLParserBase {
|
|||||||
if (isEmpty) {
|
if (isEmpty) {
|
||||||
// No children: just push the node into its parent.
|
// No children: just push the node into its parent.
|
||||||
node[$finalize]();
|
node[$finalize]();
|
||||||
this._current[$onChild](node);
|
if (this._current[$onChild](node)) {
|
||||||
|
node[$setId](this._ids);
|
||||||
|
}
|
||||||
node[$clean](this._builder);
|
node[$clean](this._builder);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -114,7 +119,9 @@ class XFAParser extends XMLParserBase {
|
|||||||
const node = this._current;
|
const node = this._current;
|
||||||
node[$finalize]();
|
node[$finalize]();
|
||||||
this._current = this._stack.pop();
|
this._current = this._stack.pop();
|
||||||
this._current[$onChild](node);
|
if (this._current[$onChild](node)) {
|
||||||
|
node[$setId](this._ids);
|
||||||
|
}
|
||||||
node[$clean](this._builder);
|
node[$clean](this._builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ import {
|
|||||||
$namespaceId,
|
$namespaceId,
|
||||||
$nodeName,
|
$nodeName,
|
||||||
$onChild,
|
$onChild,
|
||||||
|
$setSetAttributes,
|
||||||
ContentObject,
|
ContentObject,
|
||||||
Option01,
|
Option01,
|
||||||
OptionObject,
|
OptionObject,
|
||||||
@ -1071,9 +1072,15 @@ class ExData extends ContentObject {
|
|||||||
child[$namespaceId] === NamespaceIds.xhtml.id
|
child[$namespaceId] === NamespaceIds.xhtml.id
|
||||||
) {
|
) {
|
||||||
this[$content] = child;
|
this[$content] = child;
|
||||||
} else if (this.contentType === "text/xml") {
|
return true;
|
||||||
this[$content] = child;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.contentType === "text/xml") {
|
||||||
|
this[$content] = child;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2531,9 +2538,10 @@ class Text extends ContentObject {
|
|||||||
[$onChild](child) {
|
[$onChild](child) {
|
||||||
if (child[$namespaceId] === NamespaceIds.xhtml.id) {
|
if (child[$namespaceId] === NamespaceIds.xhtml.id) {
|
||||||
this[$content] = child;
|
this[$content] = child;
|
||||||
} else {
|
return true;
|
||||||
warn(`XFA - Invalid content in Text: ${child[$nodeName]}.`);
|
|
||||||
}
|
}
|
||||||
|
warn(`XFA - Invalid content in Text: ${child[$nodeName]}.`);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2757,7 +2765,9 @@ class Variables extends XFAObject {
|
|||||||
class TemplateNamespace {
|
class TemplateNamespace {
|
||||||
static [$buildXFAObject](name, attributes) {
|
static [$buildXFAObject](name, attributes) {
|
||||||
if (TemplateNamespace.hasOwnProperty(name)) {
|
if (TemplateNamespace.hasOwnProperty(name)) {
|
||||||
return TemplateNamespace[name](attributes);
|
const node = TemplateNamespace[name](attributes);
|
||||||
|
node[$setSetAttributes](attributes);
|
||||||
|
return node;
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -3215,4 +3225,4 @@ class TemplateNamespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { TemplateNamespace };
|
export { Template, TemplateNamespace };
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
import { getInteger, getKeyword } from "./utils.js";
|
import { getInteger, getKeyword } from "./utils.js";
|
||||||
import { shadow, warn } from "../../shared/util.js";
|
import { shadow, warn } from "../../shared/util.js";
|
||||||
|
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.
|
||||||
@ -31,16 +32,25 @@ const $nodeName = Symbol("nodeName");
|
|||||||
const $onChild = Symbol();
|
const $onChild = Symbol();
|
||||||
const $onChildCheck = Symbol();
|
const $onChildCheck = Symbol();
|
||||||
const $onText = Symbol();
|
const $onText = Symbol();
|
||||||
|
const $resolvePrototypes = Symbol();
|
||||||
|
const $setId = Symbol();
|
||||||
|
const $setSetAttributes = Symbol();
|
||||||
const $text = Symbol();
|
const $text = Symbol();
|
||||||
|
|
||||||
|
const _applyPrototype = Symbol();
|
||||||
const _attributes = Symbol();
|
const _attributes = Symbol();
|
||||||
const _attributeNames = Symbol();
|
const _attributeNames = Symbol();
|
||||||
const _children = Symbol();
|
const _children = Symbol();
|
||||||
|
const _clone = Symbol();
|
||||||
|
const _cloneAttribute = Symbol();
|
||||||
const _defaultValue = Symbol();
|
const _defaultValue = Symbol();
|
||||||
|
const _getPrototype = 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();
|
||||||
|
const _setAttributes = Symbol();
|
||||||
const _validator = Symbol();
|
const _validator = Symbol();
|
||||||
|
|
||||||
class XFAObject {
|
class XFAObject {
|
||||||
@ -54,23 +64,27 @@ class XFAObject {
|
|||||||
|
|
||||||
[$onChild](child) {
|
[$onChild](child) {
|
||||||
if (!this[_hasChildren] || !this[$onChildCheck](child)) {
|
if (!this[_hasChildren] || !this[$onChildCheck](child)) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const name = child[$nodeName];
|
const name = child[$nodeName];
|
||||||
const node = this[name];
|
const node = this[name];
|
||||||
|
|
||||||
if (node instanceof XFAObjectArray) {
|
if (node instanceof XFAObjectArray) {
|
||||||
if (node.push(child)) {
|
if (node.push(child)) {
|
||||||
child[_parent] = this;
|
child[_parent] = this;
|
||||||
this[_children].push(child);
|
this[_children].push(child);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
} else if (node === null) {
|
} else if (node === null) {
|
||||||
this[name] = child;
|
this[name] = child;
|
||||||
child[_parent] = this;
|
child[_parent] = this;
|
||||||
this[_children].push(child);
|
this[_children].push(child);
|
||||||
} else {
|
return true;
|
||||||
warn(`XFA - node "${this[$nodeName]}" accepts only one child: ${name}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
warn(`XFA - node "${this[$nodeName]}" has already enough "${name}"!`);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
[$onChildCheck](child) {
|
[$onChildCheck](child) {
|
||||||
@ -80,6 +94,12 @@ class XFAObject {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$setId](ids) {
|
||||||
|
if (this.id && this[$namespaceId] === NamespaceIds.template.id) {
|
||||||
|
ids.set(this.id, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[$onText](_) {}
|
[$onText](_) {}
|
||||||
|
|
||||||
[$finalize]() {}
|
[$finalize]() {}
|
||||||
@ -151,6 +171,198 @@ class XFAObject {
|
|||||||
|
|
||||||
return dumped;
|
return dumped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[$setSetAttributes](attributes) {
|
||||||
|
if (attributes.use || attributes.id) {
|
||||||
|
// Just keep set attributes because this node uses a proto or is a proto.
|
||||||
|
this[_setAttributes] = new Set(Object.keys(attributes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get attribute names which have been set in the proto but not in this.
|
||||||
|
*/
|
||||||
|
[_getUnsetAttributes](protoAttributes) {
|
||||||
|
const allAttr = this[_attributeNames];
|
||||||
|
const setAttr = this[_setAttributes];
|
||||||
|
return [...protoAttributes].filter(x => allAttr.has(x) && !setAttr.has(x));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the node with properties coming from a prototype and apply
|
||||||
|
* this function recursivly to all children.
|
||||||
|
*/
|
||||||
|
[$resolvePrototypes](ids, ancestors = new Set()) {
|
||||||
|
for (const child of this[_children]) {
|
||||||
|
const proto = child[_getPrototype](ids, ancestors);
|
||||||
|
if (proto) {
|
||||||
|
// _applyPrototype will apply $resolvePrototypes with correct ancestors
|
||||||
|
// to avoid infinite loop.
|
||||||
|
child[_applyPrototype](proto, ids, ancestors);
|
||||||
|
} else {
|
||||||
|
child[$resolvePrototypes](ids, ancestors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[_getPrototype](ids, ancestors) {
|
||||||
|
const { use } = this;
|
||||||
|
if (use && use.startsWith("#")) {
|
||||||
|
const id = use.slice(1);
|
||||||
|
const proto = ids.get(id);
|
||||||
|
this.use = "";
|
||||||
|
if (!proto) {
|
||||||
|
warn(`XFA - Invalid prototype id: ${id}.`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (proto[$nodeName] !== this[$nodeName]) {
|
||||||
|
warn(
|
||||||
|
`XFA - Incompatible prototype: ${proto[$nodeName]} !== ${this[$nodeName]}.`
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ancestors.has(proto)) {
|
||||||
|
// We've a cycle so break it.
|
||||||
|
warn(`XFA - Cycle detected in prototypes use.`);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ancestors.add(proto);
|
||||||
|
// The prototype can have a "use" attribute itself.
|
||||||
|
const protoProto = proto[_getPrototype](ids, ancestors);
|
||||||
|
if (!protoProto) {
|
||||||
|
ancestors.delete(proto);
|
||||||
|
return proto;
|
||||||
|
}
|
||||||
|
|
||||||
|
proto[_applyPrototype](protoProto, ids, ancestors);
|
||||||
|
ancestors.delete(proto);
|
||||||
|
|
||||||
|
return proto;
|
||||||
|
}
|
||||||
|
// TODO: handle SOM expressions.
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[_applyPrototype](proto, ids, ancestors) {
|
||||||
|
if (ancestors.has(proto)) {
|
||||||
|
// We've a cycle so break it.
|
||||||
|
warn(`XFA - Cycle detected in prototypes use.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this[$content] && proto[$content]) {
|
||||||
|
this[$content] = proto[$content];
|
||||||
|
}
|
||||||
|
|
||||||
|
const newAncestors = new Set(ancestors);
|
||||||
|
newAncestors.add(proto);
|
||||||
|
|
||||||
|
for (const unsetAttrName of this[_getUnsetAttributes](
|
||||||
|
proto[_setAttributes]
|
||||||
|
)) {
|
||||||
|
this[unsetAttrName] = proto[unsetAttrName];
|
||||||
|
if (this[_setAttributes]) {
|
||||||
|
this[_setAttributes].add(unsetAttrName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const name of Object.getOwnPropertyNames(this)) {
|
||||||
|
if (this[_attributeNames].has(name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const value = this[name];
|
||||||
|
const protoValue = proto[name];
|
||||||
|
|
||||||
|
if (value instanceof XFAObjectArray) {
|
||||||
|
for (const child of value[_children]) {
|
||||||
|
child[$resolvePrototypes](ids, ancestors);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (
|
||||||
|
let i = value[_children].length, ii = protoValue[_children].length;
|
||||||
|
i < ii;
|
||||||
|
i++
|
||||||
|
) {
|
||||||
|
const child = proto[_children][i][_clone]();
|
||||||
|
if (value.push(child)) {
|
||||||
|
child[_parent] = this;
|
||||||
|
this[_children].push(child);
|
||||||
|
child[$resolvePrototypes](ids, newAncestors);
|
||||||
|
} else {
|
||||||
|
// No need to continue: other nodes will be rejected.
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value !== null) {
|
||||||
|
value[$resolvePrototypes](ids, ancestors);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (protoValue !== null) {
|
||||||
|
const child = protoValue[_clone]();
|
||||||
|
child[_parent] = this;
|
||||||
|
this[name] = child;
|
||||||
|
this[_children].push(child);
|
||||||
|
child[$resolvePrototypes](ids, newAncestors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static [_cloneAttribute](obj) {
|
||||||
|
if (Array.isArray(obj)) {
|
||||||
|
return obj.map(x => XFAObject[_cloneAttribute](x));
|
||||||
|
}
|
||||||
|
if (obj instanceof Object) {
|
||||||
|
return Object.assign({}, obj);
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
[_clone]() {
|
||||||
|
const clone = Object.create(Object.getPrototypeOf(this));
|
||||||
|
for (const $symbol of Object.getOwnPropertySymbols(this)) {
|
||||||
|
try {
|
||||||
|
clone[$symbol] = this[$symbol];
|
||||||
|
} catch (_) {
|
||||||
|
shadow(clone, $symbol, this[$symbol]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clone[_children] = [];
|
||||||
|
|
||||||
|
for (const name of Object.getOwnPropertyNames(this)) {
|
||||||
|
if (this[_attributeNames].has(name)) {
|
||||||
|
clone[name] = XFAObject[_cloneAttribute](this[name]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const value = this[name];
|
||||||
|
if (value instanceof XFAObjectArray) {
|
||||||
|
clone[name] = new XFAObjectArray(value[_max]);
|
||||||
|
} else {
|
||||||
|
clone[name] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const child of this[_children]) {
|
||||||
|
const name = child[$nodeName];
|
||||||
|
const clonedChild = child[_clone]();
|
||||||
|
clone[_children].push(clonedChild);
|
||||||
|
clonedChild[_parent] = clone;
|
||||||
|
if (clone[name] === null) {
|
||||||
|
clone[name] = clonedChild;
|
||||||
|
} else {
|
||||||
|
clone[name][_children].push(clonedChild);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class XFAObjectArray {
|
class XFAObjectArray {
|
||||||
@ -180,6 +392,12 @@ class XFAObjectArray {
|
|||||||
? this[_children][0][$dump]()
|
? this[_children][0][$dump]()
|
||||||
: this[_children].map(x => x[$dump]());
|
: this[_children].map(x => x[$dump]());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[_clone]() {
|
||||||
|
const clone = new XFAObjectArray(this[_max]);
|
||||||
|
clone[_children] = this[_children].map(c => c[_clone]());
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class XmlObject extends XFAObject {
|
class XmlObject extends XFAObject {
|
||||||
@ -198,7 +416,9 @@ class XmlObject extends XFAObject {
|
|||||||
this[$content] = "";
|
this[$content] = "";
|
||||||
this[_children].push(node);
|
this[_children].push(node);
|
||||||
}
|
}
|
||||||
|
child[_parent] = this;
|
||||||
this[_children].push(child);
|
this[_children].push(child);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
[$onText](str) {
|
[$onText](str) {
|
||||||
@ -308,6 +528,9 @@ export {
|
|||||||
$onChild,
|
$onChild,
|
||||||
$onChildCheck,
|
$onChildCheck,
|
||||||
$onText,
|
$onText,
|
||||||
|
$resolvePrototypes,
|
||||||
|
$setId,
|
||||||
|
$setSetAttributes,
|
||||||
$text,
|
$text,
|
||||||
ContentObject,
|
ContentObject,
|
||||||
IntegerObject,
|
IntegerObject,
|
||||||
|
@ -248,6 +248,54 @@ describe("XFAParser", function () {
|
|||||||
expect(root[$dump]()).toEqual(expected);
|
expect(root[$dump]()).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should parse a xfa document and apply some prototypes", 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>
|
||||||
|
<proto>
|
||||||
|
<font id="id1" typeface="Foo" size="123pt" weight="bold" posture="italic">
|
||||||
|
<fill>
|
||||||
|
<color value="1,2,3"/>
|
||||||
|
</fill>
|
||||||
|
</font>
|
||||||
|
</proto>
|
||||||
|
<field>
|
||||||
|
<font use="#id1"/>
|
||||||
|
</field>
|
||||||
|
<field>
|
||||||
|
<font use="#id1" size="456pt" weight="bold" posture="normal">
|
||||||
|
<fill>
|
||||||
|
<color value="4,5,6"/>
|
||||||
|
</fill>
|
||||||
|
<extras id="id2"/>
|
||||||
|
</font>
|
||||||
|
</field>
|
||||||
|
</subform>
|
||||||
|
</template>
|
||||||
|
</xdp:xdp>
|
||||||
|
`;
|
||||||
|
const root = new XFAParser().parse(xml)[$dump]();
|
||||||
|
let font = root.template.subform.field[0].font;
|
||||||
|
expect(font.typeface).toEqual("Foo");
|
||||||
|
expect(font.overline).toEqual(0);
|
||||||
|
expect(font.size).toEqual({ value: 123, unit: "pt" });
|
||||||
|
expect(font.weight).toEqual("bold");
|
||||||
|
expect(font.posture).toEqual("italic");
|
||||||
|
expect(font.fill.color.value).toEqual({ r: 1, g: 2, b: 3 });
|
||||||
|
expect(font.extras).toEqual(undefined);
|
||||||
|
|
||||||
|
font = root.template.subform.field[1].font;
|
||||||
|
expect(font.typeface).toEqual("Foo");
|
||||||
|
expect(font.overline).toEqual(0);
|
||||||
|
expect(font.size).toEqual({ value: 456, unit: "pt" });
|
||||||
|
expect(font.weight).toEqual("bold");
|
||||||
|
expect(font.posture).toEqual("normal");
|
||||||
|
expect(font.fill.color.value).toEqual({ r: 4, g: 5, b: 6 });
|
||||||
|
expect(font.extras.id).toEqual("id2");
|
||||||
|
});
|
||||||
|
|
||||||
it("should parse a xfa document with xhtml", function () {
|
it("should parse a xfa document with xhtml", function () {
|
||||||
const xml = `
|
const xml = `
|
||||||
<?xml version="1.0"?>
|
<?xml version="1.0"?>
|
||||||
@ -280,5 +328,92 @@ describe("XFAParser", function () {
|
|||||||
].join("")
|
].join("")
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should parse a xfa document and apply some prototypes with cycle", 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>
|
||||||
|
<proto>
|
||||||
|
<subform id="id1">
|
||||||
|
<subform use="#id1"/>
|
||||||
|
</subform>
|
||||||
|
</proto>
|
||||||
|
</subform>
|
||||||
|
<subform use="#id1"/>
|
||||||
|
</template>
|
||||||
|
</xdp:xdp>
|
||||||
|
`;
|
||||||
|
const root = new XFAParser().parse(xml)[$dump]();
|
||||||
|
const subform = root.template.subform[1];
|
||||||
|
|
||||||
|
expect(subform.id).toEqual("id1");
|
||||||
|
expect(subform.subform.id).toEqual("id1");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should parse a xfa document and apply some nested prototypes", 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>
|
||||||
|
<proto>
|
||||||
|
<color id="RED" value="7, 8, 9"/>
|
||||||
|
<font id="HELV" typeface="helvetica" size="31pt" weight="normal" posture="italic"> </font>
|
||||||
|
<font id="HELV-RED" use="#HELV">
|
||||||
|
<fill>
|
||||||
|
<color use="#RED"/>
|
||||||
|
</fill>
|
||||||
|
</font>
|
||||||
|
</proto>
|
||||||
|
<field>
|
||||||
|
<font use="#HELV-RED"/>
|
||||||
|
</field>
|
||||||
|
</subform>
|
||||||
|
</template>
|
||||||
|
</xdp:xdp>
|
||||||
|
`;
|
||||||
|
const root = new XFAParser().parse(xml)[$dump]();
|
||||||
|
const font = root.template.subform.field.font;
|
||||||
|
|
||||||
|
expect(font.typeface).toEqual("helvetica");
|
||||||
|
expect(font.overline).toEqual(0);
|
||||||
|
expect(font.size).toEqual({ value: 31, unit: "pt" });
|
||||||
|
expect(font.weight).toEqual("normal");
|
||||||
|
expect(font.posture).toEqual("italic");
|
||||||
|
expect(font.fill.color.value).toEqual({ r: 7, g: 8, b: 9 });
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should parse a xfa document and apply a prototype with content", 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>
|
||||||
|
<proto>
|
||||||
|
<text id="TEXT">default TEXT</text>
|
||||||
|
</proto>
|
||||||
|
<field>
|
||||||
|
<value>
|
||||||
|
<text use="#TEXT"></text>
|
||||||
|
</value>
|
||||||
|
</field>
|
||||||
|
<field>
|
||||||
|
<value>
|
||||||
|
<text use="#TEXT">Overriding text</text>
|
||||||
|
</value>
|
||||||
|
</field>
|
||||||
|
</subform>
|
||||||
|
</template>
|
||||||
|
</xdp:xdp>
|
||||||
|
`;
|
||||||
|
const root = new XFAParser().parse(xml)[$dump]();
|
||||||
|
let field = root.template.subform.field[0];
|
||||||
|
expect(field.value.text.$content).toEqual("default TEXT");
|
||||||
|
|
||||||
|
field = root.template.subform.field[1];
|
||||||
|
expect(field.value.text.$content).toEqual("Overriding text");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user