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([
|
||||
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
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 "&";
|
||||
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 };
|
||||
|
@ -39,6 +39,7 @@
|
||||
"unicode_spec.js",
|
||||
"util_spec.js",
|
||||
"writer_spec.js",
|
||||
"xfa_parser_spec.js",
|
||||
"xml_spec.js"
|
||||
]
|
||||
}
|
||||
|
@ -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
|
||||
|
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