From 57ae3a5a76e64c5737327bb202de2612c2bd9d6f Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Fri, 3 Sep 2021 15:43:19 +0200 Subject: [PATCH] XFA - Created data node mustn't belong to datasets namespace - when some named nodes in the template don't have their counterpart in datasets we create some nodes: the main node mustn't belong to the datasets namespace because it doesn't make sense and Acrobat Reader isn't able to read pdf with such nodes. - so created nodes under a datasets node have a namespaceId set to -1 and consequently when serialized no namespace prefix will appear. --- src/core/xfa/bind.js | 17 +++++++--- src/core/xfa/som.js | 5 ++- src/core/xfa/xfa_object.js | 7 +++-- test/unit/xfa_parser_spec.js | 61 ++++++++++++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 8 deletions(-) diff --git a/src/core/xfa/bind.js b/src/core/xfa/bind.js index 3864f6bd5185d..3cf7bed0da585 100644 --- a/src/core/xfa/bind.js +++ b/src/core/xfa/bind.js @@ -45,6 +45,8 @@ import { createDataNode, searchNode } from "./som.js"; import { NamespaceIds } from "./namespaces.js"; import { warn } from "../../shared/util.js"; +const NS_DATASETS = NamespaceIds.datasets.id; + function createText(content) { const node = new Text({}); node[$content] = content; @@ -501,8 +503,12 @@ class Binder { if (dataChildren.length > 0) { this._bindOccurrences(child, [dataChildren[0]], null); } else if (this.emptyMerge) { + const nsId = + dataNode[$namespaceId] === NS_DATASETS + ? -1 + : dataNode[$namespaceId]; const dataChild = (child[$data] = new XmlObject( - dataNode[$namespaceId], + nsId, child.name || "root" )); dataNode[$appendChild](dataChild); @@ -625,10 +631,11 @@ class Binder { if (!match) { // We're in matchTemplate mode so create a node in data to reflect // what we've in template. - match = child[$data] = new XmlObject( - dataNode[$namespaceId], - child.name - ); + const nsId = + dataNode[$namespaceId] === NS_DATASETS + ? -1 + : dataNode[$namespaceId]; + match = child[$data] = new XmlObject(nsId, child.name); if (this.emptyMerge) { match[$consumed] = true; } diff --git a/src/core/xfa/som.js b/src/core/xfa/som.js index 515b69671edca..6955292b1598d 100644 --- a/src/core/xfa/som.js +++ b/src/core/xfa/som.js @@ -23,6 +23,7 @@ import { XFAObjectArray, XmlObject, } from "./xfa_object.js"; +import { NamespaceIds } from "./namespaces.js"; import { warn } from "../../shared/util.js"; const namePattern = /^[^.[]+/; @@ -51,6 +52,7 @@ const shortcuts = new Map([ ]); const somCache = new WeakMap(); +const NS_DATASETS = NamespaceIds.datasets.id; function parseIndex(index) { index = index.trim(); @@ -261,7 +263,8 @@ function createNodes(root, path) { let node = null; for (const { name, index } of path) { for (let i = 0, ii = !isFinite(index) ? 0 : index; i <= ii; i++) { - node = new XmlObject(root[$namespaceId], name); + const nsId = root[$namespaceId] === NS_DATASETS ? -1 : root[$namespaceId]; + node = new XmlObject(nsId, name); root[$appendChild](node); } diff --git a/src/core/xfa/xfa_object.js b/src/core/xfa/xfa_object.js index e6bf5ed1617f6..785950e8c61f7 100644 --- a/src/core/xfa/xfa_object.js +++ b/src/core/xfa/xfa_object.js @@ -976,8 +976,11 @@ class XmlObject extends XFAObject { this[$content] = value.toString(); } - [$dump]() { + [$dump](hasNS = false) { const dumped = Object.create(null); + if (hasNS) { + dumped.$ns = this[$namespaceId]; + } if (this[$content]) { dumped.$content = this[$content]; } @@ -985,7 +988,7 @@ class XmlObject extends XFAObject { dumped.children = []; for (const child of this[_children]) { - dumped.children.push(child[$dump]()); + dumped.children.push(child[$dump](hasNS)); } dumped.attributes = Object.create(null); diff --git a/test/unit/xfa_parser_spec.js b/test/unit/xfa_parser_spec.js index 8ef28682243f5..37edaba2b8a8a 100644 --- a/test/unit/xfa_parser_spec.js +++ b/test/unit/xfa_parser_spec.js @@ -841,6 +841,67 @@ describe("XFAParser", function () { 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 = `