Merge pull request #13473 from calixteman/usehref
XFA - Implement usehref support
This commit is contained in:
commit
e0676ec298
@ -21,6 +21,7 @@ import {
|
||||
$nsAttributes,
|
||||
$onChild,
|
||||
$resolvePrototypes,
|
||||
$root,
|
||||
XFAObject,
|
||||
} from "./xfa_object.js";
|
||||
import { NamespaceSetUp } from "./setup.js";
|
||||
@ -43,6 +44,10 @@ class Root extends XFAObject {
|
||||
[$finalize]() {
|
||||
super[$finalize]();
|
||||
if (this.element.template instanceof Template) {
|
||||
// Set the root element in $ids using a symbol in order
|
||||
// to avoid conflict with real IDs.
|
||||
this[$ids].set($root, this.element);
|
||||
|
||||
this.element.template[$resolvePrototypes](this[$ids]);
|
||||
this.element.template[$ids] = this[$ids];
|
||||
}
|
||||
|
@ -3866,9 +3866,6 @@ class Subform extends XFAObject {
|
||||
}
|
||||
|
||||
[$toHTML](availableSpace) {
|
||||
if (this.name === "helpText") {
|
||||
return HTMLResult.EMPTY;
|
||||
}
|
||||
if (this[$extra] && this[$extra].afterBreakAfter) {
|
||||
const ret = this[$extra].afterBreakAfter;
|
||||
delete this[$extra];
|
||||
@ -3890,8 +3887,6 @@ class Subform extends XFAObject {
|
||||
);
|
||||
}
|
||||
|
||||
// TODO: implement usehref (probably in bind.js).
|
||||
|
||||
// TODO: incomplete.
|
||||
fixDimensions(this);
|
||||
const children = [];
|
||||
|
@ -16,6 +16,7 @@
|
||||
import { getInteger, getKeyword, HTMLResult } from "./utils.js";
|
||||
import { shadow, warn } from "../../shared/util.js";
|
||||
import { NamespaceIds } from "./namespaces.js";
|
||||
import { searchNode } from "./som.js";
|
||||
|
||||
// We use these symbols to avoid name conflict between tags
|
||||
// and properties/methods names.
|
||||
@ -61,6 +62,7 @@ const $onChild = Symbol();
|
||||
const $onChildCheck = Symbol();
|
||||
const $onText = Symbol();
|
||||
const $removeChild = Symbol();
|
||||
const $root = Symbol("root");
|
||||
const $resolvePrototypes = Symbol();
|
||||
const $searchNode = Symbol();
|
||||
const $setId = Symbol();
|
||||
@ -85,6 +87,7 @@ const _hasChildren = Symbol();
|
||||
const _max = Symbol();
|
||||
const _options = Symbol();
|
||||
const _parent = Symbol("parent");
|
||||
const _resolvePrototypesHelper = Symbol();
|
||||
const _setAttributes = Symbol();
|
||||
const _validator = Symbol();
|
||||
|
||||
@ -192,8 +195,14 @@ class XFAObject {
|
||||
this[_children].splice(i, 0, child);
|
||||
}
|
||||
|
||||
/**
|
||||
* If true the element is transparent when searching a node using
|
||||
* a SOM expression which means that looking for "foo.bar" in
|
||||
* <... name="foo"><toto><titi><... name="bar"></titi></toto>...
|
||||
* is fine because toto and titi are transparent.
|
||||
*/
|
||||
[$isTransparent]() {
|
||||
return this.name === "";
|
||||
return !this.name;
|
||||
}
|
||||
|
||||
[$lastAttribute]() {
|
||||
@ -343,10 +352,8 @@ class XFAObject {
|
||||
}
|
||||
|
||||
[$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));
|
||||
}
|
||||
// Just keep set attributes because it can be used in a proto.
|
||||
this[_setAttributes] = new Set(Object.keys(attributes));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -364,57 +371,103 @@ class XFAObject {
|
||||
*/
|
||||
[$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);
|
||||
}
|
||||
child[_resolvePrototypesHelper](ids, ancestors);
|
||||
}
|
||||
}
|
||||
|
||||
[_resolvePrototypesHelper](ids, ancestors) {
|
||||
const proto = this[_getPrototype](ids, ancestors);
|
||||
if (proto) {
|
||||
// _applyPrototype will apply $resolvePrototypes with correct ancestors
|
||||
// to avoid infinite loop.
|
||||
this[_applyPrototype](proto, ids, ancestors);
|
||||
} else {
|
||||
this[$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;
|
||||
}
|
||||
const { use, usehref } = this;
|
||||
if (!use && !usehref) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (proto[$nodeName] !== this[$nodeName]) {
|
||||
warn(
|
||||
`XFA - Incompatible prototype: ${proto[$nodeName]} !== ${this[$nodeName]}.`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
let proto = null;
|
||||
let somExpression = null;
|
||||
let id = null;
|
||||
let ref = use;
|
||||
|
||||
if (ancestors.has(proto)) {
|
||||
// We've a cycle so break it.
|
||||
warn(`XFA - Cycle detected in prototypes use.`);
|
||||
return null;
|
||||
// If usehref and use are non-empty then use usehref.
|
||||
if (usehref) {
|
||||
ref = usehref;
|
||||
// Href can be one of the following:
|
||||
// - #ID
|
||||
// - URI#ID
|
||||
// - #som(expression)
|
||||
// - URI#som(expression)
|
||||
// - URI
|
||||
// For now we don't handle URI other than "." (current document).
|
||||
if (usehref.startsWith("#som(") && usehref.endsWith(")")) {
|
||||
somExpression = usehref.slice("#som(".length, usehref.length - 1);
|
||||
} else if (usehref.startsWith(".#som(") && usehref.endsWith(")")) {
|
||||
somExpression = usehref.slice(".#som(".length, usehref.length - 1);
|
||||
} else if (usehref.startsWith("#")) {
|
||||
id = usehref.slice(1);
|
||||
} else if (usehref.startsWith(".#")) {
|
||||
id = usehref.slice(2);
|
||||
}
|
||||
} else if (use.startsWith("#")) {
|
||||
id = use.slice(1);
|
||||
} else {
|
||||
somExpression = use;
|
||||
}
|
||||
|
||||
ancestors.add(proto);
|
||||
// The prototype can have a "use" attribute itself.
|
||||
const protoProto = proto[_getPrototype](ids, ancestors);
|
||||
if (!protoProto) {
|
||||
ancestors.delete(proto);
|
||||
return proto;
|
||||
this.use = this.usehref = "";
|
||||
if (id) {
|
||||
proto = ids.get(id);
|
||||
} else {
|
||||
proto = searchNode(
|
||||
ids.get($root),
|
||||
this,
|
||||
somExpression,
|
||||
true /* = dotDotAllowed */,
|
||||
false /* = useCache */
|
||||
);
|
||||
if (proto) {
|
||||
proto = proto[0];
|
||||
}
|
||||
}
|
||||
|
||||
proto[_applyPrototype](protoProto, ids, ancestors);
|
||||
if (!proto) {
|
||||
warn(`XFA - Invalid prototype reference: ${ref}.`);
|
||||
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;
|
||||
}
|
||||
// TODO: handle SOM expressions.
|
||||
|
||||
return null;
|
||||
proto[_applyPrototype](protoProto, ids, ancestors);
|
||||
ancestors.delete(proto);
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
[_applyPrototype](proto, ids, ancestors) {
|
||||
@ -449,7 +502,7 @@ class XFAObject {
|
||||
|
||||
if (value instanceof XFAObjectArray) {
|
||||
for (const child of value[_children]) {
|
||||
child[$resolvePrototypes](ids, ancestors);
|
||||
child[_resolvePrototypesHelper](ids, ancestors);
|
||||
}
|
||||
|
||||
for (
|
||||
@ -461,7 +514,7 @@ class XFAObject {
|
||||
if (value.push(child)) {
|
||||
child[_parent] = this;
|
||||
this[_children].push(child);
|
||||
child[$resolvePrototypes](ids, newAncestors);
|
||||
child[_resolvePrototypesHelper](ids, ancestors);
|
||||
} else {
|
||||
// No need to continue: other nodes will be rejected.
|
||||
break;
|
||||
@ -472,6 +525,10 @@ class XFAObject {
|
||||
|
||||
if (value !== null) {
|
||||
value[$resolvePrototypes](ids, ancestors);
|
||||
if (protoValue) {
|
||||
// protoValue must be treated as a prototype for value.
|
||||
value[_applyPrototype](protoValue, ids, ancestors);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -480,7 +537,7 @@ class XFAObject {
|
||||
child[_parent] = this;
|
||||
this[name] = child;
|
||||
this[_children].push(child);
|
||||
child[$resolvePrototypes](ids, newAncestors);
|
||||
child[_resolvePrototypesHelper](ids, ancestors);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -924,6 +981,7 @@ export {
|
||||
$onText,
|
||||
$removeChild,
|
||||
$resolvePrototypes,
|
||||
$root,
|
||||
$searchNode,
|
||||
$setId,
|
||||
$setSetAttributes,
|
||||
|
@ -304,6 +304,56 @@ describe("XFAParser", function () {
|
||||
expect(font.extras.id).toEqual("id2");
|
||||
});
|
||||
|
||||
it("should parse a xfa document and apply some prototypes through usehref", 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>
|
||||
<draw name="foo">
|
||||
<font typeface="Foo" size="123pt" weight="bold" posture="italic">
|
||||
<fill>
|
||||
<color value="1,2,3"/>
|
||||
</fill>
|
||||
</font>
|
||||
</draw>
|
||||
</proto>
|
||||
<field>
|
||||
<font usehref=".#som($template.#subform.foo.#font)"/>
|
||||
</field>
|
||||
<field>
|
||||
<font usehref=".#som($template.#subform.foo.#font)" 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(123);
|
||||
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(456);
|
||||
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 () {
|
||||
const xml = `
|
||||
<?xml version="1.0"?>
|
||||
|
Loading…
Reference in New Issue
Block a user