/* 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/symbol_utils.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 parse CDATA when needed", 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>
      <field>
        <extras>
          <exData contentType="text/html" name="foo">
            <![CDATA[<body xmlns="http://www.w3.org/1999/xhtml">
              <span>hello</span></body>]]>
          </exData>
        </extra>
      </field>
    </subform>
  </template>
</xdp:xdp>
      `;
      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 = `
<?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 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"?>
<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 ",
        ].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 a basic binding and create a non-existing node", 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" mergeMode="matchTemplate">
      <subform name="B">
        <field name="C">
        </field>
        <field name="D">
          <value>
            <text>foobar</text>
          </value>
        </field>
      </subform>
    </subform>
  </template>
  <xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
    <xfa:data>
      <A>
      </A>
    </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, "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 = `
<?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">
          <value>
            <text>foobar</text>
          </value>
        </field>
      </subform>
    </subform>
  </template>
</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, "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 = `
<?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 (consumeData)", 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 and bind items with a ref", 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">
        <bind match="dataRef" ref="$data.main.value"/>
        <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>
        <value>VISA</value>
        <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.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 = `
<?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"]);
    });

    it("should make binding and create nodes in data with some bind tag", 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">
        <occur max="-1"/>
        <bind ref="$.root.foo[*]" match="dataRef"/>
      </subform>
      <subform name="B">
        <occur max="2"/>
        <bind ref="$.root.bar[2]" match="dataRef"/>
      </subform>
    </subform>
  </template>
  <xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
    <xfa:data>
      <root>
      </root>
    </xfa:data>
  </xfa:datasets>
</xdp:xdp>
      `;
      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 = `
<?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" mergeMode="matchTemplate">
      <subform name="B">
        <field name="C">
          <ui>
            <choicelist/>
          </ui>
          <bindItems ref="xfa.datasets.foo.bar[*]" labelRef="$" valueRef="oof"/>
        </field>
      </subform>
    </subform>
  </template>
  <xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
    <foo>
      <bar oof="a">1</bar>
      <bar oof="b">2</bar>
      <bar oof="c">3</bar>
      <bar oof="d">4</bar>
      <bar oof="e">5</bar>
    </foo>
    <xfa:data>
      <A><B></B></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.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 = `
<?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" mergeMode="matchTemplate">
      <area>
        <field name="B"/>
      </area>
    </subform>
  </template>
  <xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
    <xfa:data>
      <A><B>foobar</B></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..text")[0][$dump]().$content).toBe(
      "foobar"
    );
  });
});