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:
parent
c92011e093
commit
0ff5cd7eb5
@ -1431,7 +1431,7 @@ function buildLib(defines, dir) {
|
|||||||
return merge([
|
return merge([
|
||||||
gulp.src(
|
gulp.src(
|
||||||
[
|
[
|
||||||
"src/{core,display,shared}/*.js",
|
"src/{core,display,shared}/**/*.js",
|
||||||
"!src/shared/{cffStandardStrings,fonts_utils}.js",
|
"!src/shared/{cffStandardStrings,fonts_utils}.js",
|
||||||
"src/{pdf,pdf.worker}.js",
|
"src/{pdf,pdf.worker}.js",
|
||||||
],
|
],
|
||||||
|
148
src/core/xfa/builder.js
Normal file
148
src/core/xfa/builder.js
Normal 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
193
src/core/xfa/config.js
Normal 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 };
|
81
src/core/xfa/namespaces.js
Normal file
81
src/core/xfa/namespaces.js
Normal 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
126
src/core/xfa/parser.js
Normal 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
24
src/core/xfa/setup.js
Normal 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
29
src/core/xfa/unknown.js
Normal 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
47
src/core/xfa/utils.js
Normal 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
58
src/core/xfa/xdp.js
Normal 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
244
src/core/xfa/xfa_object.js
Normal 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,
|
||||||
|
};
|
@ -63,6 +63,8 @@ class XMLParserBase {
|
|||||||
return "&";
|
return "&";
|
||||||
case "quot":
|
case "quot":
|
||||||
return '"';
|
return '"';
|
||||||
|
case "apos":
|
||||||
|
return "'";
|
||||||
}
|
}
|
||||||
return this.onResolveEntity(entity);
|
return this.onResolveEntity(entity);
|
||||||
});
|
});
|
||||||
@ -455,14 +457,6 @@ class SimpleXMLParser extends XMLParserBase {
|
|||||||
return { documentElement };
|
return { documentElement };
|
||||||
}
|
}
|
||||||
|
|
||||||
onResolveEntity(name) {
|
|
||||||
switch (name) {
|
|
||||||
case "apos":
|
|
||||||
return "'";
|
|
||||||
}
|
|
||||||
return super.onResolveEntity(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
onText(text) {
|
onText(text) {
|
||||||
if (isWhitespaceString(text)) {
|
if (isWhitespaceString(text)) {
|
||||||
return;
|
return;
|
||||||
@ -509,4 +503,4 @@ class SimpleXMLParser extends XMLParserBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { SimpleDOMNode, SimpleXMLParser };
|
export { SimpleDOMNode, SimpleXMLParser, XMLParserBase, XMLParserErrorCode };
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
"unicode_spec.js",
|
"unicode_spec.js",
|
||||||
"util_spec.js",
|
"util_spec.js",
|
||||||
"writer_spec.js",
|
"writer_spec.js",
|
||||||
|
"xfa_parser_spec.js",
|
||||||
"xml_spec.js"
|
"xml_spec.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -85,6 +85,7 @@ async function initializePDFJS(callback) {
|
|||||||
"pdfjs-test/unit/unicode_spec.js",
|
"pdfjs-test/unit/unicode_spec.js",
|
||||||
"pdfjs-test/unit/util_spec.js",
|
"pdfjs-test/unit/util_spec.js",
|
||||||
"pdfjs-test/unit/writer_spec.js",
|
"pdfjs-test/unit/writer_spec.js",
|
||||||
|
"pdfjs-test/unit/xfa_parser_spec.js",
|
||||||
"pdfjs-test/unit/xml_spec.js",
|
"pdfjs-test/unit/xml_spec.js",
|
||||||
].map(function (moduleName) {
|
].map(function (moduleName) {
|
||||||
// eslint-disable-next-line no-unsanitized/method
|
// eslint-disable-next-line no-unsanitized/method
|
||||||
|
93
test/unit/xfa_parser_spec.js
Normal file
93
test/unit/xfa_parser_spec.js
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user