11573ddd16
- attribute 'use' was already implemented but not usehref - in general, usehref should make reference to current document - add support for SOM expressions in use and usehref to search a node. - get prototype for all nodes if any.
194 lines
5.0 KiB
JavaScript
194 lines
5.0 KiB
JavaScript
/* 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,
|
|
$finalize,
|
|
$ids,
|
|
$nsAttributes,
|
|
$onChild,
|
|
$resolvePrototypes,
|
|
$root,
|
|
XFAObject,
|
|
} from "./xfa_object.js";
|
|
import { NamespaceSetUp } from "./setup.js";
|
|
import { Template } from "./template.js";
|
|
import { UnknownNamespace } from "./unknown.js";
|
|
import { warn } from "../../shared/util.js";
|
|
|
|
class Root extends XFAObject {
|
|
constructor(ids) {
|
|
super(-1, "root", Object.create(null));
|
|
this.element = null;
|
|
this[$ids] = ids;
|
|
}
|
|
|
|
[$onChild](child) {
|
|
this.element = child;
|
|
return true;
|
|
}
|
|
|
|
[$finalize]() {
|
|
super[$finalize]();
|
|
if (this.element.template instanceof Template) {
|
|
// Set the root element in $ids using a symbol in order
|
|
// to avoid conflict with real IDs.
|
|
this[$ids].set($root, this.element);
|
|
|
|
this.element.template[$resolvePrototypes](this[$ids]);
|
|
this.element.template[$ids] = this[$ids];
|
|
}
|
|
}
|
|
}
|
|
|
|
class Empty extends XFAObject {
|
|
constructor() {
|
|
super(-1, "", Object.create(null));
|
|
}
|
|
|
|
[$onChild](_) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
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(ids) {
|
|
return new Root(ids);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
if (attributes.hasOwnProperty($nsAttributes)) {
|
|
// Only support xfa-data namespace.
|
|
const dataTemplate = NamespaceSetUp.datasets;
|
|
const nsAttrs = attributes[$nsAttributes];
|
|
let xfaAttrs = null;
|
|
for (const [ns, attrs] of Object.entries(nsAttrs)) {
|
|
const nsToUse = this._getNamespaceToUse(ns);
|
|
if (nsToUse === dataTemplate) {
|
|
xfaAttrs = { xfa: attrs };
|
|
break;
|
|
}
|
|
}
|
|
if (xfaAttrs) {
|
|
attributes[$nsAttributes] = xfaAttrs;
|
|
} else {
|
|
delete attributes[$nsAttributes];
|
|
}
|
|
}
|
|
|
|
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 };
|