- I thought it was possible to rely on browser layout engine to handle layout stuff but it isn't possible - mainly because when a contentArea overflows, we must continue to layout in the next contentArea - when no more contentArea is available then we must go to the next page... - we must handle breakBefore and breakAfter which allows to "break" the layout to go to the next container - Sometimes some containers don't provide their dimensions so we must compute them in order to know where to put them in their parents but to compute those dimensions we need to layout the container itself... - See top of file layout.js for more explanations about layout. - fix few bugs in other places I met during my work on layout.
1152 lines
34 KiB
JavaScript
1152 lines
34 KiB
JavaScript
/* Copyright 2020 Mozilla Foundation
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import {
|
|
$dump,
|
|
$getChildren,
|
|
$getChildrenByClass,
|
|
$getChildrenByName,
|
|
$text,
|
|
} from "../../src/core/xfa/xfa_object.js";
|
|
import { Binder } from "../../src/core/xfa/bind.js";
|
|
import { searchNode } from "../../src/core/xfa/som.js";
|
|
import { XFAParser } from "../../src/core/xfa/parser.js";
|
|
|
|
describe("XFAParser", function () {
|
|
describe("Parse XFA", function () {
|
|
it("should parse a xfa document and create an object to represent it", function () {
|
|
const xml = `
|
|
<?xml version="1.0"?>
|
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/" uuid="1234" invalid="foo">
|
|
<config xmlns="http://www.xfa.org/schema/xci/3.1/">
|
|
<present>
|
|
<pdf name="hello">
|
|
<adobeExtensionLevel>
|
|
7
|
|
</adobeExtensionLevel>
|
|
</pdf>
|
|
<invalid><a>foobar</a></invalid>
|
|
</present>
|
|
<acrobat>
|
|
<submitUrl>http://a.b.c</submitUrl>
|
|
<acrobat7>
|
|
<dynamicRender>
|
|
forbidden
|
|
</dynamicRender>
|
|
</acrobat7>
|
|
<autoSave>enabled</autoSave>
|
|
<submitUrl>
|
|
http://d.e.f
|
|
</submitUrl>
|
|
<submitUrl>http://g.h.i</submitUrl>
|
|
<validate>foobar</validate>
|
|
</acrobat>
|
|
</config>
|
|
<template baseProfile="full" xmlns="http://www.xfa.org/schema/xfa-template/3.3">
|
|
<extras>
|
|
<float>1.23</float>
|
|
<boolean>1</boolean>
|
|
<integer>314</integer>
|
|
<float>2.71</float>
|
|
</extras>
|
|
<subform>
|
|
<proto>
|
|
<area x="hello" y="-3.14in" relevant="-foo +bar" />
|
|
<color value="111, 222, 123" />
|
|
<color value="111, abc, 123" />
|
|
<medium imagingBBox="1,2in,3.4cm,5.67px" />
|
|
<medium imagingBBox="1,2in,-3cm,4px" />
|
|
</proto>
|
|
</subform>
|
|
</template>
|
|
</xdp:xdp>
|
|
`;
|
|
const attributes = {
|
|
id: "",
|
|
name: "",
|
|
use: "",
|
|
usehref: "",
|
|
};
|
|
const mediumAttributes = {
|
|
id: "",
|
|
long: 0,
|
|
orientation: "portrait",
|
|
short: 0,
|
|
stock: "",
|
|
trayIn: "auto",
|
|
trayOut: "auto",
|
|
use: "",
|
|
usehref: "",
|
|
};
|
|
const colorAttributes = {
|
|
cSpace: "SRGB",
|
|
id: "",
|
|
use: "",
|
|
usehref: "",
|
|
};
|
|
const root = new XFAParser().parse(xml);
|
|
const expected = {
|
|
uuid: "1234",
|
|
timeStamp: "",
|
|
template: {
|
|
baseProfile: "full",
|
|
extras: {
|
|
...attributes,
|
|
float: [
|
|
{ ...attributes, $content: 1.23 },
|
|
{ ...attributes, $content: 2.71 },
|
|
],
|
|
boolean: { ...attributes, $content: 1 },
|
|
integer: { ...attributes, $content: 314 },
|
|
},
|
|
subform: {
|
|
access: "open",
|
|
allowMacro: 0,
|
|
anchorType: "topLeft",
|
|
colSpan: 1,
|
|
columnWidths: [0],
|
|
h: "",
|
|
hAlign: "left",
|
|
id: "",
|
|
layout: "position",
|
|
locale: "",
|
|
maxH: 0,
|
|
maxW: 0,
|
|
mergeMode: "consumeData",
|
|
minH: 0,
|
|
minW: 0,
|
|
name: "",
|
|
presence: "visible",
|
|
relevant: [],
|
|
restoreState: "manual",
|
|
scope: "name",
|
|
use: "",
|
|
usehref: "",
|
|
w: "",
|
|
x: 0,
|
|
y: 0,
|
|
proto: {
|
|
area: {
|
|
...attributes,
|
|
colSpan: 1,
|
|
x: 0,
|
|
y: -226.08,
|
|
relevant: [
|
|
{ excluded: true, viewname: "foo" },
|
|
{ excluded: false, viewname: "bar" },
|
|
],
|
|
},
|
|
color: [
|
|
{
|
|
...colorAttributes,
|
|
value: { r: 111, g: 222, b: 123 },
|
|
},
|
|
{
|
|
...colorAttributes,
|
|
value: { r: 111, g: 0, b: 123 },
|
|
},
|
|
],
|
|
medium: [
|
|
{
|
|
...mediumAttributes,
|
|
imagingBBox: {
|
|
x: 1,
|
|
y: 144,
|
|
width: 96.3779527559055,
|
|
height: 5.67,
|
|
},
|
|
},
|
|
{
|
|
...mediumAttributes,
|
|
imagingBBox: {
|
|
x: -1,
|
|
y: -1,
|
|
width: -1,
|
|
height: -1,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
},
|
|
},
|
|
config: {
|
|
acrobat: {
|
|
acrobat7: {
|
|
dynamicRender: {
|
|
$content: "forbidden",
|
|
},
|
|
},
|
|
autoSave: {
|
|
$content: "enabled",
|
|
},
|
|
validate: {
|
|
$content: "preSubmit",
|
|
},
|
|
submitUrl: [
|
|
{
|
|
$content: "http://a.b.c",
|
|
},
|
|
{
|
|
$content: "http://d.e.f",
|
|
},
|
|
{
|
|
$content: "http://g.h.i",
|
|
},
|
|
],
|
|
},
|
|
present: {
|
|
pdf: {
|
|
name: "hello",
|
|
adobeExtensionLevel: {
|
|
$content: 7,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
expect(root[$dump]()).toEqual(expected);
|
|
});
|
|
|
|
it("should parse a xfa document and check namespaces", function () {
|
|
const xml = `
|
|
<?xml version="1.0"?>
|
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
|
<config xmlns:foo="http:/www.foo.com" xmlns="http://www.xfa.org/schema/xci/3.1/">
|
|
<present xmlns="http://www.mozilla.org">
|
|
<pdf name="hello">
|
|
<adobeExtensionLevel>
|
|
7
|
|
</adobeExtensionLevel>
|
|
</pdf>
|
|
</present>
|
|
<acrobat>
|
|
<foo:submitUrl>http://a.b.c</foo:submitUrl>
|
|
<submitUrl>http://c.b.a</submitUrl>
|
|
</acrobat>
|
|
</config>
|
|
<template baseProfile="full" xmlns="http://www.allizom.org">
|
|
<extras>
|
|
<float>1.23</float>
|
|
</extras>
|
|
</template>
|
|
</xdp:xdp>
|
|
`;
|
|
const root = new XFAParser().parse(xml);
|
|
const expected = {
|
|
uuid: "",
|
|
timeStamp: "",
|
|
config: {
|
|
acrobat: {
|
|
submitUrl: { $content: "http://c.b.a" },
|
|
},
|
|
},
|
|
};
|
|
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(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"?>
|
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
|
<template xmlns="http://www.xfa.org/schema/xfa-template/3.3">
|
|
<extras>
|
|
<text>
|
|
<body xmlns="http://www.w3.org/1999/xhtml">
|
|
<p style="foo: bar; text-indent:0.5in; line-height:11px;bar:foo;tab-stop: left 0.5in">
|
|
The first line of this paragraph is indented a half-inch.<br/>
|
|
Successive lines are not indented.<br/>
|
|
This is the last line of the paragraph.<br/>
|
|
</p>
|
|
</body>
|
|
</text>
|
|
</extras>
|
|
</template>
|
|
</xdp:xdp>
|
|
`;
|
|
const root = new XFAParser().parse(xml)[$dump]();
|
|
const p = root.template.extras.text.$content[$getChildren]()[0];
|
|
expect(p.style).toEqual(
|
|
"text-indent:0.5in;line-height:11px;tab-stop:left 0.5in"
|
|
);
|
|
expect(p[$text]()).toEqual(
|
|
[
|
|
" The first line of this paragraph is indented a half-inch.\n",
|
|
" Successive lines are not indented.\n",
|
|
" This is the last line of the paragraph.\n \n",
|
|
].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(31);
|
|
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");
|
|
});
|
|
});
|
|
|
|
describe("Search in XFA", function () {
|
|
it("should search some nodes in a template object", 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 name="Receipt" id="l">
|
|
<subform id="m">
|
|
<field name="Description" id="a"> </field>
|
|
<field name="Units" id="b"> </field>
|
|
<field name="Unit_Price" id="c"> </field>
|
|
<field name="Total_Price" id="d"> </field>
|
|
</subform>
|
|
<subform id="n">
|
|
<field name="Description" id="e"> </field>
|
|
<field name="Units" id="f"> </field>
|
|
<field name="Unit_Price" id="g"> </field>
|
|
<field name="Total_Price" id="h"> </field>
|
|
</subform>
|
|
<subform name="foo" id="o">
|
|
<field name="Description" id="p"> </field>
|
|
<field name="Units" id="q"> </field>
|
|
<field name="Unit_Price" id="r"> </field>
|
|
<field name="Total_Price" id="s"> </field>
|
|
</subform>
|
|
<field name="Sub_Total" id="i"> </field>
|
|
<field name="Tax" id="j"> </field>
|
|
<field name="Total_Price" id="k"> </field>
|
|
</subform>
|
|
</template>
|
|
</xdp:xdp>
|
|
`;
|
|
const root = new XFAParser().parse(xml);
|
|
|
|
let found = root[$getChildrenByName]("subform", true);
|
|
expect(found.map(x => x.id)).toEqual(["l", "m", "n", "o"]);
|
|
|
|
found = root[$getChildrenByName]("Total_Price", true);
|
|
expect(found.map(x => x.id)).toEqual(["d", "h", "s", "k"]);
|
|
|
|
found = root.template[$getChildrenByName]("Receipt", false);
|
|
const receipt = found[0];
|
|
|
|
found = receipt[$getChildrenByName]("Total_Price", false);
|
|
expect(found.map(x => x.id)).toEqual(["d", "h", "k"]);
|
|
|
|
expect(receipt[$getChildrenByClass]("name")).toEqual("Receipt");
|
|
const subforms = receipt[$getChildrenByClass]("subform");
|
|
expect(subforms.children.map(x => x.id)).toEqual(["m", "n", "o"]);
|
|
});
|
|
|
|
it("should search some nodes in a template object using SOM", 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 name="Receipt" id="l">
|
|
<subform id="m">
|
|
<field name="Description" id="a"> </field>
|
|
<field name="Units" id="b"> </field>
|
|
<field name="Unit_Price" id="c"> </field>
|
|
<field name="Total_Price" id="d"> </field>
|
|
</subform>
|
|
<subform id="n">
|
|
<field name="Description" id="e"> </field>
|
|
<field name="Units" id="f"> </field>
|
|
<field name="Unit_Price" id="g"> </field>
|
|
<field name="Total_Price" id="h"> </field>
|
|
</subform>
|
|
<subform name="foo" id="o">
|
|
<field name="Description" id="p"> </field>
|
|
<field name="Units" id="q"> </field>
|
|
<field name="Unit_Price" id="r"> </field>
|
|
<field name="Total_Price" id="s"> </field>
|
|
</subform>
|
|
<field name="Sub_Total" id="i"> </field>
|
|
<field name="Tax" id="j"> </field>
|
|
<field name="Total_Price" id="k"> </field>
|
|
</subform>
|
|
</template>
|
|
</xdp:xdp>
|
|
`;
|
|
const root = new XFAParser().parse(xml);
|
|
expect(
|
|
searchNode(root, null, "$template..Description.id")[0][$text]()
|
|
).toBe("a");
|
|
expect(
|
|
searchNode(root, null, "$template..Description.id")[0][$text]()
|
|
).toBe("a");
|
|
expect(
|
|
searchNode(root, null, "$template..Description[0].id")[0][$text]()
|
|
).toBe("a");
|
|
expect(
|
|
searchNode(root, null, "$template..Description[1].id")[0][$text]()
|
|
).toBe("e");
|
|
expect(
|
|
searchNode(root, null, "$template..Description[2].id")[0][$text]()
|
|
).toBe("p");
|
|
expect(searchNode(root, null, "$template.Receipt.id")[0][$text]()).toBe(
|
|
"l"
|
|
);
|
|
expect(
|
|
searchNode(root, null, "$template.Receipt.Description[1].id")[0][
|
|
$text
|
|
]()
|
|
).toBe("e");
|
|
expect(searchNode(root, null, "$template.Receipt.Description[2]")).toBe(
|
|
null
|
|
);
|
|
expect(
|
|
searchNode(root, null, "$template.Receipt.foo.Description.id")[0][
|
|
$text
|
|
]()
|
|
).toBe("p");
|
|
expect(
|
|
searchNode(root, null, "$template.#subform.Sub_Total.id")[0][$text]()
|
|
).toBe("i");
|
|
expect(
|
|
searchNode(root, null, "$template.#subform.Units.id")[0][$text]()
|
|
).toBe("b");
|
|
expect(
|
|
searchNode(root, null, "$template.#subform.Units.parent.id")[0][$text]()
|
|
).toBe("m");
|
|
});
|
|
|
|
it("should search some nodes in a datasets object", function () {
|
|
const xml = `
|
|
<?xml version="1.0"?>
|
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
|
<xfa:data>
|
|
<Receipt>
|
|
<Page>1</Page>
|
|
<Detail PartNo="GS001">
|
|
<Description>Giant Slingshot</Description>
|
|
<Units>1</Units>
|
|
<Unit_Price>250.00</Unit_Price>
|
|
<Total_Price>250.00</Total_Price>
|
|
</Detail>
|
|
<Page>2</Page>
|
|
<Detail PartNo="RRB-LB">
|
|
<Description>Road Runner Bait, large bag</Description>
|
|
<Units>5</Units>
|
|
<Unit_Price>12.00</Unit_Price>
|
|
<Total_Price>60.00</Total_Price>
|
|
</Detail>
|
|
<Sub_Total>310.00</Sub_Total>
|
|
<Tax>24.80</Tax>
|
|
<Total_Price>334.80</Total_Price>
|
|
</Receipt>
|
|
</xfa:data>
|
|
</xfa:datasets>
|
|
</xdp:xdp>
|
|
`;
|
|
const root = new XFAParser().parse(xml);
|
|
const data = root.datasets.data;
|
|
|
|
let found = data[$getChildrenByName]("Description", true);
|
|
expect(found.map(x => x[$text]())).toEqual([
|
|
"Giant Slingshot",
|
|
"Road Runner Bait, large bag",
|
|
]);
|
|
|
|
found = data[$getChildrenByName]("Total_Price", true);
|
|
expect(found.map(x => x[$text]())).toEqual(["250.00", "60.00", "334.80"]);
|
|
});
|
|
|
|
it("should search some nodes using SOM from a non-root node", function () {
|
|
const xml = `
|
|
<?xml version="1.0"?>
|
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
|
<xfa:data>
|
|
<Receipt>
|
|
<Page>1</Page>
|
|
<Detail PartNo="GS001">
|
|
<Description>Giant Slingshot</Description>
|
|
<Units>1</Units>
|
|
<Unit_Price>250.00</Unit_Price>
|
|
<Total_Price>250.00</Total_Price>
|
|
</Detail>
|
|
<Page>2</Page>
|
|
<Detail PartNo="RRB-LB">
|
|
<Description>Road Runner Bait, large bag</Description>
|
|
<Units>5</Units>
|
|
<Unit_Price>12.00</Unit_Price>
|
|
<Total_Price>60.00</Total_Price>
|
|
</Detail>
|
|
<Sub_Total>310.00</Sub_Total>
|
|
<Tax>24.80</Tax>
|
|
<Total_Price>334.80</Total_Price>
|
|
</Receipt>
|
|
</xfa:data>
|
|
</xfa:datasets>
|
|
</xdp:xdp>
|
|
`;
|
|
const root = new XFAParser().parse(xml);
|
|
const [receipt] = root.datasets.data[$getChildren]("Receipt");
|
|
expect(
|
|
searchNode(root, receipt, "Detail[*].Total_Price").map(x => x[$text]())
|
|
).toEqual(["250.00", "60.00"]);
|
|
|
|
const [units] = searchNode(root, receipt, "Detail[1].Units");
|
|
expect(units[$text]()).toBe("5");
|
|
|
|
let [found] = searchNode(root, units, "Total_Price");
|
|
expect(found[$text]()).toBe("60.00");
|
|
|
|
found = searchNode(root, units, "Total_Pric");
|
|
expect(found).toEqual(null);
|
|
});
|
|
|
|
it("should search some nodes in a datasets object using SOM", function () {
|
|
const xml = `
|
|
<?xml version="1.0"?>
|
|
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
|
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
|
<xfa:data>
|
|
<Receipt Detail="Acme">
|
|
<Detail>foo</Detail>
|
|
<Detail>bar</Detail>
|
|
</Receipt>
|
|
</xfa:data>
|
|
</xfa:datasets>
|
|
</xdp:xdp>
|
|
`;
|
|
const root = new XFAParser().parse(xml);
|
|
expect(searchNode(root, null, "$data.Receipt.Detail")[0][$text]()).toBe(
|
|
"Acme"
|
|
);
|
|
expect(
|
|
searchNode(root, null, "$data.Receipt.Detail[0]")[0][$text]()
|
|
).toBe("Acme");
|
|
expect(
|
|
searchNode(root, null, "$data.Receipt.Detail[1]")[0][$text]()
|
|
).toBe("foo");
|
|
expect(
|
|
searchNode(root, null, "$data.Receipt.Detail[2]")[0][$text]()
|
|
).toBe("bar");
|
|
});
|
|
});
|
|
|
|
describe("Bind data into form", function () {
|
|
it("should make a basic binding", 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 name="A">
|
|
<subform name="B">
|
|
<field name="C">
|
|
</field>
|
|
<field name="D">
|
|
</field>
|
|
</subform>
|
|
</subform>
|
|
</template>
|
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
|
<xfa:data>
|
|
<A>
|
|
<C>xyz</C>
|
|
</A>
|
|
</xfa:data>
|
|
</xfa:datasets>
|
|
</xdp:xdp>
|
|
`;
|
|
const root = new XFAParser().parse(xml);
|
|
const form = new Binder(root).bind();
|
|
|
|
expect(
|
|
searchNode(form, form, "A.B.C.value.text")[0][$dump]().$content
|
|
).toBe("xyz");
|
|
});
|
|
|
|
it("should make another basic binding", 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 name="registration">
|
|
<field name="first"> </field>
|
|
<field name="last"> </field>
|
|
<field name="apt"> </field>
|
|
<field name="street"> </field>
|
|
<field name="city"> </field>
|
|
<field name="country"> </field>
|
|
<field name="postalcode"/>
|
|
</subform>
|
|
</template>
|
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
|
<xfa:data>
|
|
<registration>
|
|
<first>Jack</first>
|
|
<last>Spratt</last>
|
|
<apt/>
|
|
<street>99 Candlestick Lane</street>
|
|
<city>London</city>
|
|
<country>UK</country>
|
|
<postalcode>SW1</postalcode>
|
|
</registration>
|
|
</xfa:data>
|
|
</xfa:datasets>
|
|
</xdp:xdp>
|
|
`;
|
|
const root = new XFAParser().parse(xml);
|
|
const form = new Binder(root).bind();
|
|
|
|
expect(
|
|
searchNode(form, form, "registration.first..text")[0][$dump]().$content
|
|
).toBe("Jack");
|
|
expect(
|
|
searchNode(form, form, "registration.last..text")[0][$dump]().$content
|
|
).toBe("Spratt");
|
|
expect(
|
|
searchNode(form, form, "registration.apt..text")[0][$dump]().$content
|
|
).toBe(undefined);
|
|
expect(
|
|
searchNode(form, form, "registration.street..text")[0][$dump]().$content
|
|
).toBe("99 Candlestick Lane");
|
|
expect(
|
|
searchNode(form, form, "registration.city..text")[0][$dump]().$content
|
|
).toBe("London");
|
|
expect(
|
|
searchNode(form, form, "registration.country..text")[0][$dump]()
|
|
.$content
|
|
).toBe("UK");
|
|
expect(
|
|
searchNode(form, form, "registration.postalcode..text")[0][$dump]()
|
|
.$content
|
|
).toBe("SW1");
|
|
});
|
|
|
|
it("should make basic binding with extra subform", 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 name="registration">
|
|
<field name="first"> </field>
|
|
<field name="last"> </field>
|
|
<subform name="address">
|
|
<field name="apt"> </field>
|
|
<field name="street"> </field>
|
|
<field name="city"> </field>
|
|
<field name="country"> </field>
|
|
<field name="postalcode"> </field>
|
|
</subform>
|
|
</subform>
|
|
</template>
|
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
|
<xfa:data>
|
|
<registration>
|
|
<first>Jack</first>
|
|
<last>Spratt</last>
|
|
<apt/>
|
|
<street>99 Candlestick Lane</street>
|
|
<city>London</city>
|
|
<country>UK</country>
|
|
<postalcode>SW1</postalcode>
|
|
</registration>
|
|
</xfa:data>
|
|
</xfa:datasets>
|
|
</xdp:xdp>
|
|
`;
|
|
const root = new XFAParser().parse(xml);
|
|
const form = new Binder(root).bind();
|
|
|
|
expect(
|
|
searchNode(form, form, "registration..first..text")[0][$dump]().$content
|
|
).toBe("Jack");
|
|
expect(
|
|
searchNode(form, form, "registration..last..text")[0][$dump]().$content
|
|
).toBe("Spratt");
|
|
expect(
|
|
searchNode(form, form, "registration..apt..text")[0][$dump]().$content
|
|
).toBe(undefined);
|
|
expect(
|
|
searchNode(form, form, "registration..street..text")[0][$dump]()
|
|
.$content
|
|
).toBe("99 Candlestick Lane");
|
|
expect(
|
|
searchNode(form, form, "registration..city..text")[0][$dump]().$content
|
|
).toBe("London");
|
|
expect(
|
|
searchNode(form, form, "registration..country..text")[0][$dump]()
|
|
.$content
|
|
).toBe("UK");
|
|
expect(
|
|
searchNode(form, form, "registration..postalcode..text")[0][$dump]()
|
|
.$content
|
|
).toBe("SW1");
|
|
});
|
|
|
|
it("should make basic binding with extra subform", 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 name="registration" mergeMode="consumeData">
|
|
<subform name="address">
|
|
<field name="first"/>
|
|
<field name="last"/>
|
|
<field name="apt"/>
|
|
<field name="street"/>
|
|
<field name="city"/>
|
|
</subform>
|
|
</subform>
|
|
</template>
|
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
|
<xfa:data>
|
|
<registration>
|
|
<first>Jack</first>
|
|
<last>Spratt</last>
|
|
<address>
|
|
<apt>7</apt>
|
|
<street>99 Candlestick Lane</street>
|
|
<city>London</city>
|
|
</address>
|
|
</registration>
|
|
</xfa:data>
|
|
</xfa:datasets>
|
|
</xdp:xdp>
|
|
`;
|
|
const root = new XFAParser().parse(xml);
|
|
const form = new Binder(root).bind();
|
|
|
|
expect(
|
|
searchNode(form, form, "registration..first..text")[0][$dump]().$content
|
|
).toBe("Jack");
|
|
expect(
|
|
searchNode(form, form, "registration..last..text")[0][$dump]().$content
|
|
).toBe("Spratt");
|
|
expect(
|
|
searchNode(form, form, "registration..apt..text")[0][$dump]().$content
|
|
).toBe("7");
|
|
expect(
|
|
searchNode(form, form, "registration..street..text")[0][$dump]()
|
|
.$content
|
|
).toBe("99 Candlestick Lane");
|
|
expect(
|
|
searchNode(form, form, "registration..city..text")[0][$dump]().$content
|
|
).toBe("London");
|
|
});
|
|
|
|
it("should make basic binding with same names in different parts", 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 name="application" mergeMode="consumeData">
|
|
<subform name="sponsor">
|
|
<field name="lastname"> </field>
|
|
<!-- sponsor's last name -->
|
|
</subform>
|
|
<field name="lastname"> </field>
|
|
<!-- applicant's last name -->
|
|
</subform>
|
|
</template>
|
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
|
<xfa:data>
|
|
<application>
|
|
<lastname>Abott</lastname>
|
|
<sponsor>
|
|
<lastname>Costello</lastname>
|
|
</sponsor>
|
|
</application>
|
|
</xfa:data>
|
|
</xfa:datasets>
|
|
</xdp:xdp>
|
|
`;
|
|
const root = new XFAParser().parse(xml);
|
|
const form = new Binder(root).bind();
|
|
|
|
expect(
|
|
searchNode(form, form, "application.sponsor.lastname..text")[0][$dump]()
|
|
.$content
|
|
).toBe("Costello");
|
|
expect(
|
|
searchNode(form, form, "application.lastname..text")[0][$dump]()
|
|
.$content
|
|
).toBe("Abott");
|
|
});
|
|
|
|
it("should make binding and create nodes in data", 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 name="root" mergeMode="matchTemplate">
|
|
<subform name="A">
|
|
<field name="a"/>
|
|
<field name="b"/>
|
|
<subform name="B">
|
|
<field name="c"/>
|
|
<field name="d"/>
|
|
<subform name="C">
|
|
<field name="e"/>
|
|
<field name="f"/>
|
|
</subform>
|
|
</subform>
|
|
</subform>
|
|
</subform>
|
|
</template>
|
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
|
<xfa:data>
|
|
<root>
|
|
<A>
|
|
<b>1</b>
|
|
</A>
|
|
</root>
|
|
</xfa:data>
|
|
</xfa:datasets>
|
|
</xdp:xdp>
|
|
`;
|
|
const root = new XFAParser().parse(xml);
|
|
const binder = new Binder(root);
|
|
const form = binder.bind();
|
|
const data = binder.getData();
|
|
|
|
expect(searchNode(form, form, "root..b..text")[0][$dump]().$content).toBe(
|
|
"1"
|
|
);
|
|
expect(searchNode(data, data, "root.A.a")[0][$dump]().$name).toBe("a");
|
|
expect(searchNode(data, data, "root.A.B.c")[0][$dump]().$name).toBe("c");
|
|
expect(searchNode(data, data, "root.A.B.d")[0][$dump]().$name).toBe("d");
|
|
expect(searchNode(data, data, "root.A.B.C.e")[0][$dump]().$name).toBe(
|
|
"e"
|
|
);
|
|
expect(searchNode(data, data, "root.A.B.C.f")[0][$dump]().$name).toBe(
|
|
"f"
|
|
);
|
|
});
|
|
|
|
it("should make binding and set properties", 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 name="Id">
|
|
<field name="LastName">
|
|
<setProperty ref="$data.Main.Style.NameFont" target="font.typeface"/>
|
|
<setProperty ref="$data.Main.Style.NameSize" target="font.size"/>
|
|
<setProperty ref="$data.Main.Help.LastName" target="assist.toolTip"/>
|
|
<font></font>
|
|
<assist>
|
|
<toolTip>
|
|
</toolTip>
|
|
</assist>
|
|
</field>
|
|
</subform>
|
|
</template>
|
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
|
<xfa:data>
|
|
<Id>
|
|
<LastName>foo</LastName>
|
|
</Id>
|
|
<Main>
|
|
<Style>
|
|
<NameFont>myfont</NameFont>
|
|
<NameSize>123.4pt</NameSize>
|
|
</Style>
|
|
<Help>
|
|
<LastName>Give the name!</LastName>
|
|
</Help>
|
|
</Main>
|
|
</xfa:data>
|
|
</xfa:datasets>
|
|
</xdp:xdp>
|
|
`;
|
|
const root = new XFAParser().parse(xml);
|
|
const form = new Binder(root).bind();
|
|
|
|
expect(
|
|
searchNode(form, form, "Id.LastName..text")[0][$dump]().$content
|
|
).toBe("foo");
|
|
expect(
|
|
searchNode(form, form, "Id.LastName.font.typeface")[0][$text]()
|
|
).toBe("myfont");
|
|
expect(
|
|
searchNode(form, form, "Id.LastName.font.size")[0][$text]()
|
|
).toEqual(123.4);
|
|
expect(
|
|
searchNode(form, form, "Id.LastName.assist.toolTip")[0][$dump]()
|
|
.$content
|
|
).toBe("Give the name!");
|
|
});
|
|
|
|
it("should make binding and bind items", 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 name="main">
|
|
<field name="CardName">
|
|
<bindItems ref="$data.main.ccs.cc[*]" labelRef="uiname" valueRef="token"/>
|
|
<ui>
|
|
<choiceList/>
|
|
</ui>
|
|
</field>
|
|
</subform>
|
|
</template>
|
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
|
<xfa:data>
|
|
<main>
|
|
<ccs>
|
|
<cc uiname="Visa" token="VISA"/>
|
|
<cc uiname="Mastercard" token="MC"/>
|
|
<cc uiname="American Express" token="AMEX"/>
|
|
</ccs>
|
|
<CardName>MC</CardName>
|
|
</main>
|
|
</xfa:data>
|
|
</xfa:datasets>
|
|
</xdp:xdp>
|
|
`;
|
|
const root = new XFAParser().parse(xml);
|
|
const form = new Binder(root).bind();
|
|
expect(
|
|
searchNode(form, form, "subform.CardName.items[*].text[*]").map(x =>
|
|
x[$text]()
|
|
)
|
|
).toEqual([
|
|
"Visa",
|
|
"Mastercard",
|
|
"American Express",
|
|
"VISA",
|
|
"MC",
|
|
"AMEX",
|
|
]);
|
|
});
|
|
|
|
it("should make binding with occurrences in consumeData mode", 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 name="root" mergeMode="consumeData">
|
|
<subform name="section" id="section1">
|
|
<occur min="0" max="-1"/>
|
|
<bind match="dataRef" ref="$.section[*]"/>
|
|
<field name="line-item"/>
|
|
</subform>
|
|
<subform name="section" id="section2">
|
|
<occur min="0" max="-1"/>
|
|
<bind match="dataRef" ref="$.section[*]"/>
|
|
<field name="line-item"/>
|
|
</subform>
|
|
</subform>
|
|
</template>
|
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
|
<xfa:data>
|
|
<root>
|
|
<section>
|
|
<line-item>item1</line-item>
|
|
</section>
|
|
<section>
|
|
<line-item>item2</line-item>
|
|
</section>
|
|
</root>
|
|
</xfa:data>
|
|
</xfa:datasets>
|
|
</xdp:xdp>
|
|
`;
|
|
const root = new XFAParser().parse(xml);
|
|
const form = new Binder(root).bind();
|
|
|
|
expect(
|
|
searchNode(form, form, "root.section[*].id").map(x => x[$text]())
|
|
).toEqual(["section1", "section1"]);
|
|
|
|
expect(
|
|
searchNode(form, form, "root.section[*].line-item..text").map(x =>
|
|
x[$text]()
|
|
)
|
|
).toEqual(["item1", "item2"]);
|
|
});
|
|
|
|
it("should make binding with occurrences in matchTemplate mode", 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 name="root" mergeMode="matchTemplate">
|
|
<subform name="section" id="section1">
|
|
<occur min="0" max="-1"/>
|
|
<bind match="dataRef" ref="$.section[*]"/>
|
|
<field name="line-item"/>
|
|
</subform>
|
|
<subform name="section" id="section2">
|
|
<occur min="0" max="-1"/>
|
|
<bind match="dataRef" ref="$.section[*]"/>
|
|
<field name="line-item"/>
|
|
</subform>
|
|
</subform>
|
|
</template>
|
|
<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
|
|
<xfa:data>
|
|
<root>
|
|
<section>
|
|
<line-item>item1</line-item>
|
|
</section>
|
|
<section>
|
|
<line-item>item2</line-item>
|
|
</section>
|
|
</root>
|
|
</xfa:data>
|
|
</xfa:datasets>
|
|
</xdp:xdp>
|
|
`;
|
|
const root = new XFAParser().parse(xml);
|
|
const form = new Binder(root).bind();
|
|
|
|
expect(
|
|
searchNode(form, form, "root.section[*].id").map(x => x[$text]())
|
|
).toEqual(["section1", "section1", "section2", "section2"]);
|
|
|
|
expect(
|
|
searchNode(form, form, "root.section[*].line-item..text").map(x =>
|
|
x[$text]()
|
|
)
|
|
).toEqual(["item1", "item2", "item1", "item2"]);
|
|
});
|
|
});
|
|
});
|