XFA - Add a parser for XFA files

- the parser is base on a class extending XMLParserBase
 - it handle xml namespaces:
   * each namespace is assocated with a builder
   * builder builds nodes belonging to the namespace
   * when a node is inserted in the parent namespace compatibility is checked (if required)
 - to avoid name collision between xml names and object properties, use Symbol.
This commit is contained in:
Calixte Denizet 2021-02-01 13:44:03 +01:00
parent c92011e093
commit 0ff5cd7eb5
14 changed files with 1049 additions and 10 deletions

View File

@ -1431,7 +1431,7 @@ function buildLib(defines, dir) {
return merge([
gulp.src(
[
"src/{core,display,shared}/*.js",
"src/{core,display,shared}/**/*.js",
"!src/shared/{cffStandardStrings,fonts_utils}.js",
"src/{pdf,pdf.worker}.js",
],

148
src/core/xfa/builder.js Normal file
View File

@ -0,0 +1,148 @@
/* Copyright 2021 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 { $buildXFAObject, NamespaceIds } from "./namespaces.js";
import { $cleanup, $onChild, XFAObject } from "./xfa_object.js";
import { NamespaceSetUp } from "./setup.js";
import { UnknownNamespace } from "./unknown.js";
import { warn } from "../../shared/util.js";
class Root extends XFAObject {
constructor() {
super(-1, "root", Object.create(null));
this.element = null;
}
[$onChild](child) {
this.element = child;
}
}
class Empty extends XFAObject {
constructor() {
super(-1, "", Object.create(null));
}
[$onChild](_) {}
}
class Builder {
constructor() {
this._namespaceStack = [];
// Each prefix has its own stack
this._namespacePrefixes = new Map();
this._namespaces = new Map();
this._nextNsId = Math.max(
...Object.values(NamespaceIds).map(({ id }) => id)
);
this._currentNamespace = new UnknownNamespace(++this._nextNsId);
}
buildRoot() {
return new Root();
}
build({ nsPrefix, name, attributes, namespace, prefixes }) {
const hasNamespaceDef = namespace !== null;
if (hasNamespaceDef) {
// Define the current namespace to use.
this._namespaceStack.push(this._currentNamespace);
this._currentNamespace = this._searchNamespace(namespace);
}
if (prefixes) {
// The xml node may have namespace prefix definitions
this._addNamespacePrefix(prefixes);
}
const namespaceToUse = this._getNamespaceToUse(nsPrefix);
const node =
(namespaceToUse && namespaceToUse[$buildXFAObject](name, attributes)) ||
new Empty();
// In case the node has some namespace things,
// we must pop the different stacks.
if (hasNamespaceDef || prefixes) {
node[$cleanup] = {
hasNamespace: hasNamespaceDef,
prefixes,
};
}
return node;
}
_searchNamespace(nsName) {
let ns = this._namespaces.get(nsName);
if (ns) {
return ns;
}
for (const [name, { check }] of Object.entries(NamespaceIds)) {
if (check(nsName)) {
ns = NamespaceSetUp[name];
if (ns) {
this._namespaces.set(nsName, ns);
return ns;
}
// The namespace is known but not handled.
break;
}
}
ns = new UnknownNamespace(++this._nextNsId);
this._namespaces.set(nsName, ns);
return ns;
}
_addNamespacePrefix(prefixes) {
for (const { prefix, value } of prefixes) {
const namespace = this._searchNamespace(value);
let prefixStack = this._namespacePrefixes.get(prefix);
if (!prefixStack) {
prefixStack = [];
this._namespacePrefixes.set(prefix, prefixStack);
}
prefixStack.push(namespace);
}
}
_getNamespaceToUse(prefix) {
if (!prefix) {
return this._currentNamespace;
}
const prefixStack = this._namespacePrefixes.get(prefix);
if (prefixStack && prefixStack.length > 0) {
return prefixStack[prefixStack.length - 1];
}
warn(`Unknown namespace prefix: ${prefix}.`);
return null;
}
clean(data) {
const { hasNamespace, prefixes } = data;
if (hasNamespace) {
this._currentNamespace = this._namespaceStack.pop();
}
if (prefixes) {
prefixes.forEach(({ prefix }) => {
this._namespacePrefixes.get(prefix).pop();
});
}
}
}
export { Builder };

193
src/core/xfa/config.js Normal file
View File

@ -0,0 +1,193 @@
/* Copyright 2021 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 { $buildXFAObject, NamespaceIds } from "./namespaces.js";
import {
IntegerObject,
OptionObject,
StringObject,
XFAObject,
} from "./xfa_object.js";
const CONFIG_NS_ID = NamespaceIds.config.id;
class Acrobat extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "acrobat", /* hasChildren = */ true);
this.acrobat7 = null;
this.autoSave = null;
this.common = null;
this.validate = null;
this.validateApprovalSignatures = null;
this.submitUrl = [];
}
}
class Acrobat7 extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "acrobat7", /* hasChildren = */ true);
this.dynamicRender = null;
}
}
class AdobeExtensionLevel extends IntegerObject {
constructor(attributes) {
super(CONFIG_NS_ID, "adobeExtensionLevel", 0, n => n >= 1 && n <= 8);
}
}
class AutoSave extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "autoSave", ["disabled", "enabled"]);
}
}
class Config extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "config", /* hasChildren = */ true);
this.acrobat = null;
this.present = null;
this.trace = null;
this.agent = [];
}
}
class DynamicRender extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "dynamicRender", ["forbidden", "required"]);
}
}
class Present extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "present", /* hasChildren = */ true);
this.behaviorOverride = null;
this.cache = null;
this.common = null;
this.copies = null;
this.destination = null;
this.incrementalMerge = null;
this.layout = null;
this.output = null;
this.overprint = null;
this.pagination = null;
this.paginationOverride = null;
this.script = null;
this.validate = null;
this.xdp = null;
this.driver = [];
this.labelPrinter = [];
this.pcl = [];
this.pdf = [];
this.ps = [];
this.submitUrl = [];
this.webClient = [];
this.zpl = [];
}
}
class Pdf extends XFAObject {
constructor(attributes) {
super(CONFIG_NS_ID, "pdf", /* hasChildren = */ true);
this.name = attributes.name || "";
this.adobeExtensionLevel = null;
this.batchOutput = null;
this.compression = null;
this.creator = null;
this.encryption = null;
this.fontInfo = null;
this.interactive = null;
this.linearized = null;
this.openAction = null;
this.pdfa = null;
this.producer = null;
this.renderPolicy = null;
this.scriptModel = null;
this.silentPrint = null;
this.submitFormat = null;
this.tagged = null;
this.version = null;
this.viewerPreferences = null;
this.xdc = null;
}
}
class SubmitUrl extends StringObject {
constructor(attributes) {
super(CONFIG_NS_ID, "submitUrl");
}
}
class Validate extends OptionObject {
constructor(attributes) {
super(CONFIG_NS_ID, "validate", [
"preSubmit",
"prePrint",
"preExecute",
"preSave",
]);
}
}
class ConfigNamespace {
static [$buildXFAObject](name, attributes) {
if (ConfigNamespace.hasOwnProperty(name)) {
return ConfigNamespace[name](attributes);
}
return undefined;
}
static acrobat(attrs) {
return new Acrobat(attrs);
}
static acrobat7(attrs) {
return new Acrobat7(attrs);
}
static adobeExtensionLevel(attrs) {
return new AdobeExtensionLevel(attrs);
}
static autoSave(attrs) {
return new AutoSave(attrs);
}
static config(attrs) {
return new Config(attrs);
}
static dynamicRender(attrs) {
return new DynamicRender(attrs);
}
static pdf(attrs) {
return new Pdf(attrs);
}
static present(attrs) {
return new Present(attrs);
}
static submitUrl(attrs) {
return new SubmitUrl(attrs);
}
static validate(attrs) {
return new Validate(attrs);
}
}
export { ConfigNamespace };

View File

@ -0,0 +1,81 @@
/* Copyright 2021 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.
*/
const $buildXFAObject = Symbol();
const NamespaceIds = {
config: {
id: 0,
check: ns => ns.startsWith("http://www.xfa.org/schema/xci/"),
},
connectionSet: {
id: 1,
check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-connection-set/"),
},
datasets: {
id: 2,
check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-data/"),
},
form: {
id: 3,
check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-form/"),
},
localeSet: {
id: 4,
check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-locale-set/"),
},
pdf: {
id: 5,
check: ns => ns === "http://ns.adobe.com/xdp/pdf/",
},
signature: {
id: 6,
check: ns => ns === "http://www.w3.org/2000/09/xmldsig#",
},
sourceSet: {
id: 7,
check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-source-set/"),
},
stylesheet: {
id: 8,
check: ns => ns === "http://www.w3.org/1999/XSL/Transform",
},
template: {
id: 9,
check: ns => ns.startsWith("http://www.xfa.org/schema/xfa-template/"),
},
xdc: {
id: 10,
check: ns => ns.startsWith("http://www.xfa.org/schema/xdc/"),
},
xdp: {
id: 11,
check: ns => ns === "http://ns.adobe.com/xdp/",
},
xfdf: {
id: 12,
check: ns => ns === "http://ns.adobe.com/xfdf/",
},
xhtml: {
id: 13,
check: ns => ns === "http://www.w3.org/1999/xhtml",
},
xmpmeta: {
id: 14,
check: ns => ns === "http://ns.adobe.com/xmpmeta/",
},
};
export { $buildXFAObject, NamespaceIds };

126
src/core/xfa/parser.js Normal file
View File

@ -0,0 +1,126 @@
/* Copyright 2021 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 { $clean, $finalize, $onChild, $onText } from "./xfa_object.js";
import { XMLParserBase, XMLParserErrorCode } from "../../shared/xml_parser.js";
import { Builder } from "./builder.js";
import { warn } from "../../shared/util.js";
class XFAParser extends XMLParserBase {
constructor() {
super();
this._builder = new Builder();
this._stack = [];
this._current = this._builder.buildRoot();
this._errorCode = XMLParserErrorCode.NoError;
this._whiteRegex = /^\s+$/;
}
parse(data) {
this.parseXml(data);
if (this._errorCode !== XMLParserErrorCode.NoError) {
return undefined;
}
return this._current.element;
}
onText(text) {
if (this._whiteRegex.test(text)) {
return;
}
this._current[$onText](text);
}
onCdata(text) {
this._current[$onText](text);
}
_mkAttributes(attributes, tagName) {
// Transform attributes into an object and get out
// namespaces information.
let namespace = null;
let prefixes = null;
const attributeObj = Object.create(null);
for (const { name, value } of attributes) {
if (name === "xmlns") {
if (!namespace) {
namespace = value;
} else {
warn(`XFA - multiple namespace definition in <${tagName}>`);
}
} else if (name.startsWith("xmlns:")) {
const prefix = name.substring("xmlns:".length);
if (!prefixes) {
prefixes = [];
}
prefixes.push({ prefix, value });
} else {
attributeObj[name] = value;
}
}
return [namespace, prefixes, attributeObj];
}
_getNameAndPrefix(name) {
const i = name.indexOf(":");
if (i === -1) {
return [name, null];
}
return [name.substring(i + 1), name.substring(0, i)];
}
onBeginElement(tagName, attributes, isEmpty) {
const [namespace, prefixes, attributesObj] = this._mkAttributes(
attributes,
tagName
);
const [name, nsPrefix] = this._getNameAndPrefix(tagName);
const node = this._builder.build({
nsPrefix,
name,
attributes: attributesObj,
namespace,
prefixes,
});
if (isEmpty) {
// No children: just push the node into its parent.
node[$finalize]();
this._current[$onChild](node);
node[$clean](this._builder);
return;
}
this._stack.push(this._current);
this._current = node;
}
onEndElement(name) {
const node = this._current;
node[$finalize]();
this._current = this._stack.pop();
this._current[$onChild](node);
node[$clean](this._builder);
}
onError(code) {
this._errorCode = code;
}
}
export { XFAParser };

24
src/core/xfa/setup.js Normal file
View File

@ -0,0 +1,24 @@
/* Copyright 2021 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 { ConfigNamespace } from "./config.js";
import { XdpNamespace } from "./xdp.js";
const NamespaceSetUp = {
config: ConfigNamespace,
xdp: XdpNamespace,
};
export { NamespaceSetUp };

29
src/core/xfa/unknown.js Normal file
View File

@ -0,0 +1,29 @@
/* Copyright 2021 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 { $buildXFAObject } from "./namespaces.js";
import { XmlObject } from "./xfa_object.js";
class UnknownNamespace {
constructor(nsId) {
this.namespaceId = nsId;
}
[$buildXFAObject](name, attributes) {
return new XmlObject(this.namespaceId, name, attributes);
}
}
export { UnknownNamespace };

47
src/core/xfa/utils.js Normal file
View File

@ -0,0 +1,47 @@
/* Copyright 2021 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.
*/
function getInteger({ data, defaultValue, validate }) {
if (!data) {
return defaultValue;
}
data = data.trim();
const n = parseInt(data, 10);
if (!isNaN(n) && validate(n)) {
return n;
}
return defaultValue;
}
function getKeyword({ data, defaultValue, validate }) {
if (!data) {
return defaultValue;
}
data = data.trim();
if (validate(data)) {
return data;
}
return defaultValue;
}
function getStringOption(data, options) {
return getKeyword({
data,
defaultValue: options[0],
validate: k => options.includes(k),
});
}
export { getInteger, getKeyword, getStringOption };

58
src/core/xfa/xdp.js Normal file
View File

@ -0,0 +1,58 @@
/* Copyright 2021 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 { $buildXFAObject, NamespaceIds } from "./namespaces.js";
import {
$namespaceId,
$nodeName,
$onChildCheck,
XFAObject,
} from "./xfa_object.js";
const XDP_NS_ID = NamespaceIds.xdp.id;
class Xdp extends XFAObject {
constructor(attributes) {
super(XDP_NS_ID, "xdp", /* hasChildren = */ true);
this.uuid = attributes.uuid || "";
this.timeStamp = attributes.timeStamp || "";
this.config = null;
this.connectionSet = null;
this.datasets = null;
this.localeSet = null;
this.stylesheet = [];
this.template = null;
}
[$onChildCheck](child) {
const ns = NamespaceIds[child[$nodeName]];
return ns && child[$namespaceId] === ns.id;
}
}
class XdpNamespace {
static [$buildXFAObject](name, attributes) {
if (XdpNamespace.hasOwnProperty(name)) {
return XdpNamespace[name](attributes);
}
return undefined;
}
static xdp(attributes) {
return new Xdp(attributes);
}
}
export { XdpNamespace };

244
src/core/xfa/xfa_object.js Normal file
View File

@ -0,0 +1,244 @@
/* Copyright 2021 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 { getInteger, getKeyword } from "./utils.js";
import { warn } from "../../shared/util.js";
// We use these symbols to avoid name conflict between tags
// and properties/methods names.
const $clean = Symbol();
const $cleanup = Symbol();
const $content = Symbol("content");
const $dump = Symbol();
const $finalize = Symbol();
const $isTransparent = Symbol();
const $namespaceId = Symbol("namespaceId");
const $nodeName = Symbol("nodeName");
const $onChild = Symbol();
const $onChildCheck = Symbol();
const $onText = Symbol();
const _defaultValue = Symbol();
const _hasChildren = Symbol();
const _options = Symbol();
const _parent = Symbol();
const _validator = Symbol();
class XFAObject {
constructor(nsId, name, hasChildren = false) {
this[$namespaceId] = nsId;
this[$nodeName] = name;
this[_hasChildren] = hasChildren;
this[_parent] = null;
}
[$onChild](child) {
if (!this[_hasChildren] || !this[$onChildCheck](child)) {
return;
}
const name = child[$nodeName];
const node = this[name];
if (Array.isArray(node)) {
node.push(child);
child[_parent] = this;
} else if (node === null) {
this[name] = child;
child[_parent] = this;
} else {
warn(`XFA - node "${this[$nodeName]}" accepts only one child: ${name}`);
}
}
[$onChildCheck](child) {
return (
this.hasOwnProperty(child[$nodeName]) &&
child[$namespaceId] === this[$namespaceId]
);
}
[$onText](_) {}
[$finalize]() {}
[$clean](builder) {
delete this[_hasChildren];
if (this[$cleanup]) {
builder.clean(this[$cleanup]);
delete this[$cleanup];
}
}
[$isTransparent]() {
return this.name === "";
}
[$dump]() {
const dumped = Object.create(null);
if (this[$content]) {
dumped.$content = this[$content];
}
for (const name of Object.getOwnPropertyNames(this)) {
const value = this[name];
if (value === null || (Array.isArray(value) && value.length === 0)) {
continue;
}
if (value instanceof XFAObject) {
dumped[name] = value[$dump]();
} else if (Array.isArray(value)) {
if (value[0] instanceof XFAObject) {
dumped[name] =
value.length === 1 ? value[0][$dump]() : value.map(x => x[$dump]());
} else {
dumped[name] = value;
}
} else {
dumped[name] = value;
}
}
return dumped;
}
}
class XmlObject extends XFAObject {
constructor(nsId, name, attributes = Object.create(null)) {
super(nsId, name);
this[$content] = "";
if (name !== "#text") {
this.attributes = attributes;
this.children = [];
}
}
[$onChild](child) {
if (this[$content]) {
const node = new XmlObject(this[$namespaceId], "#text");
node[$content] = this[$content];
this[$content] = "";
this.children.push(node);
}
this.children.push(child);
}
[$onText](str) {
this[$content] += str;
}
[$finalize]() {
if (this[$content] && this.children.length > 0) {
const node = new XmlObject(this[$namespaceId], "#text");
node[$content] = this[$content];
this.children.push(node);
delete this[$content];
}
}
}
class ContentObject extends XFAObject {
constructor(nsId, name) {
super(nsId, name);
this[$content] = "";
}
[$onText](text) {
this[$content] += text;
}
[$finalize]() {}
}
class OptionObject extends ContentObject {
constructor(nsId, name, options) {
super(nsId, name);
this[_options] = options;
}
[$finalize]() {
this[$content] = getKeyword({
data: this[$content],
defaultValue: this[_options][0],
validate: k => this[_options].includes(k),
});
}
[$clean](builder) {
super[$clean](builder);
delete this[_options];
}
}
class StringObject extends ContentObject {
[$finalize]() {
this[$content] = this[$content].trim();
}
}
class IntegerObject extends ContentObject {
constructor(nsId, name, defaultValue, validator) {
super(nsId, name);
this[_defaultValue] = defaultValue;
this[_validator] = validator;
}
[$finalize]() {
this[$content] = getInteger({
data: this[$content],
defaultValue: this[_defaultValue],
validate: this[_validator],
});
}
[$clean](builder) {
super[$clean](builder);
delete this[_defaultValue];
delete this[_validator];
}
}
class Option01 extends IntegerObject {
constructor(nsId, name) {
super(nsId, name, 0, n => n === 1);
}
}
class Option10 extends IntegerObject {
constructor(nsId, name) {
super(nsId, name, 1, n => n === 0);
}
}
export {
$clean,
$cleanup,
$content,
$dump,
$finalize,
$isTransparent,
$namespaceId,
$nodeName,
$onChild,
$onChildCheck,
$onText,
ContentObject,
IntegerObject,
Option01,
Option10,
OptionObject,
StringObject,
XFAObject,
XmlObject,
};

View File

@ -63,6 +63,8 @@ class XMLParserBase {
return "&";
case "quot":
return '"';
case "apos":
return "'";
}
return this.onResolveEntity(entity);
});
@ -455,14 +457,6 @@ class SimpleXMLParser extends XMLParserBase {
return { documentElement };
}
onResolveEntity(name) {
switch (name) {
case "apos":
return "'";
}
return super.onResolveEntity(name);
}
onText(text) {
if (isWhitespaceString(text)) {
return;
@ -509,4 +503,4 @@ class SimpleXMLParser extends XMLParserBase {
}
}
export { SimpleDOMNode, SimpleXMLParser };
export { SimpleDOMNode, SimpleXMLParser, XMLParserBase, XMLParserErrorCode };

View File

@ -39,6 +39,7 @@
"unicode_spec.js",
"util_spec.js",
"writer_spec.js",
"xfa_parser_spec.js",
"xml_spec.js"
]
}

View File

@ -85,6 +85,7 @@ async function initializePDFJS(callback) {
"pdfjs-test/unit/unicode_spec.js",
"pdfjs-test/unit/util_spec.js",
"pdfjs-test/unit/writer_spec.js",
"pdfjs-test/unit/xfa_parser_spec.js",
"pdfjs-test/unit/xml_spec.js",
].map(function (moduleName) {
// eslint-disable-next-line no-unsanitized/method

View File

@ -0,0 +1,93 @@
/* 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 } from "../../src/core/xfa/xfa_object.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>
</xdp:xdp>
`;
const root = new XFAParser().parse(xml);
const expected = {
uuid: "1234",
timeStamp: "",
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);
});
});
});