/* 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 = ` 7 foobar http://a.b.c forbidden enabled http://d.e.f http://g.h.i foobar `; 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 = ` 7 http://a.b.c http://c.b.a `; 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 parse CDATA when needed", function () { const xml = ` `; const root = new XFAParser().parse(xml); const exdata = searchNode(root, root, "foo")[0]; const body = exdata[$dump]().$content[$dump](); const expected = { $name: "body", attributes: {}, children: [ { $content: "hello", $name: "span", attributes: {}, children: [] }, ], }; expect(body).toEqual(expected); }); it("should parse a xfa document and apply some prototypes", function () { const xml = ` `; 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 and apply some prototypes through usehref", function () { const xml = ` `; 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 = ` `; 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 ", ].join("") ); }); it("should parse a xfa document and apply some prototypes with cycle", function () { const xml = ` `; 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 = ` `; 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 = ` `; 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 = ` `; 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 = ` `; 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 = ` 1 Giant Slingshot 1 250.00 250.00 2 Road Runner Bait, large bag 5 12.00 60.00 310.00 24.80 334.80 `; 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 = ` 1 Giant Slingshot 1 250.00 250.00 2 Road Runner Bait, large bag 5 12.00 60.00 310.00 24.80 334.80 `; 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 = ` foo bar `; 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 = ` xyz `; 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 a basic binding and create a non-existing node", function () { const xml = ` `; const root = new XFAParser().parse(xml); const binder = new Binder(root); const form = binder.bind(); const data = binder.getData(); expect( searchNode(form, form, "A.B.D.value.text")[0][$dump]().$content ).toBe("foobar"); const expected = { $name: "A", attributes: {}, children: [ { $name: "B", attributes: {}, children: [ { $name: "C", attributes: {}, children: [], }, { $name: "D", attributes: {}, children: [], }, ], }, ], }; expect(searchNode(data, data, "A")[0][$dump]()).toEqual(expected); }); it("should make a basic binding and create a non-existing node with namespaceId equal to -1", function () { const xml = ` `; const root = new XFAParser().parse(xml); const binder = new Binder(root); const form = binder.bind(); const data = binder.getData(); expect( searchNode(form, form, "A.B.D.value.text")[0][$dump]().$content ).toBe("foobar"); // Created nodes mustn't belong to xfa:datasets namespace. const expected = { $name: "A", $ns: -1, attributes: {}, children: [ { $name: "B", $ns: -1, attributes: {}, children: [ { $name: "C", $ns: -1, attributes: {}, children: [], }, { $name: "D", $ns: -1, attributes: {}, children: [], }, ], }, ], }; expect(searchNode(data, data, "A")[0][$dump](/* hasNS */ true)).toEqual( expected ); }); it("should make another basic binding", function () { const xml = ` Jack Spratt 99 Candlestick Lane London UK SW1 `; 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 = ` Jack Spratt 99 Candlestick Lane London UK SW1 `; 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 = ` Jack Spratt
7 99 Candlestick Lane London
`; 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 = ` Abott Costello `; 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 = ` 1 `; 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 = ` foo
Give the name!
`; 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 = `
MC
`; 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 and bind items with a ref", function () { const xml = `
VISA MC
`; const root = new XFAParser().parse(xml); const form = new Binder(root).bind(); expect( searchNode(form, form, "subform.CardName.value.text").map(x => x[$text]() ) ).toEqual(["VISA"]); 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 = `
item1
item2
`; 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 = `
item1
item2
`; 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"]); }); it("should make binding and create nodes in data with some bind tag", function () { const xml = ` `; const root = new XFAParser().parse(xml); const binder = new Binder(root); binder.bind(); const data = binder.getData(); const expected = { $name: "root", children: [ { $name: "root", children: [ { $name: "foo", children: [], attributes: {}, }, { $name: "bar", children: [], attributes: {}, }, { $name: "bar", children: [], attributes: {}, }, { $name: "bar", children: [], attributes: {}, }, ], attributes: {}, }, ], attributes: {}, }; expect(searchNode(data, data, "root")[0][$dump]()).toEqual(expected); }); it("should make a binding with a bindItems", function () { const xml = ` 1 2 3 4 5 `; const root = new XFAParser().parse(xml); const form = new Binder(root).bind(); expect( searchNode(form, form, "A.B.C.items[0].text[*]").map( x => x[$dump]().$content ) ).toEqual(["1", "2", "3", "4", "5"]); expect( searchNode(form, form, "A.B.C.items[1].text[*]").map( x => x[$dump]().$content ) ).toEqual(["a", "b", "c", "d", "e"]); }); }); it("should make a binding with a element in an area", function () { const xml = ` foobar `; const root = new XFAParser().parse(xml); const form = new Binder(root).bind(); expect(searchNode(form, form, "A..B..text")[0][$dump]().$content).toBe( "foobar" ); }); });