salve-annos
Version:
A fork with support for documentation of Salve, a Javascript library which implements a validator able to validate an XML document on the basis of a subset of RelaxNG.
257 lines • 10 kB
JavaScript
;
/**
* Implements a name resolver for handling namespace changes in XML.
* @author Louis-Dominique Dubeau
* @license MPL 2.0
* @copyright Mangalam Research Center for Buddhist Languages
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.DefaultNameResolver = void 0;
const ename_1 = require("./ename");
const name_resolver_1 = require("./name_resolver");
/**
* A name resolver for handling namespace changes in XML. This name
* resolver maintains mappings from namespace prefix to namespace URI.
*/
class DefaultNameResolver {
constructor(other) {
if (other !== undefined) {
this._contextStack = other._contextStack.slice();
}
else {
// Both namespaces defined at:
// http://www.w3.org/TR/REC-xml-names/#ns-decl
// Skip definePrefix for these initial values.
this._contextStack = [{
forward: new Map([["xml", name_resolver_1.XML1_NAMESPACE],
["xmlns", name_resolver_1.XMLNS_NAMESPACE]]),
backwards: new Map([[name_resolver_1.XML1_NAMESPACE, ["xml"]],
[name_resolver_1.XMLNS_NAMESPACE, ["xmlns"]]]),
}];
}
}
/**
* Makes a deep copy.
*
* @returns A deep copy of the resolver.
*/
clone() {
return new DefaultNameResolver(this);
}
/**
* Defines a (prefix, URI) mapping.
*
* @param prefix The namespace prefix to associate with the URI.
*
* @param uri The namespace URI associated with the prefix.
*/
definePrefix(prefix, uri) {
// http://www.w3.org/TR/REC-xml-names/#ns-decl
if (prefix === "xmlns") {
throw new Error("trying to define 'xmlns' but the XML Namespaces " +
"standard stipulates that 'xmlns' cannot be " +
"declared (= \"defined\")");
}
if (prefix === "xml" && uri !== name_resolver_1.XML1_NAMESPACE) {
throw new Error("trying to define 'xml' to an incorrect URI");
}
const top = this._contextStack[this._contextStack.length - 1];
top.forward.set(prefix, uri);
let prefixes = top.backwards.get(uri);
if (prefixes === undefined) {
prefixes = [prefix];
top.backwards.set(uri, prefixes);
}
// This ensure that the default namespace is given priority when
// unresolving names.
else if (prefix === "") {
prefixes.unshift("");
}
else {
prefixes.push(prefix);
}
}
/**
* This method is called to indicate the start of a new context. Contexts
* enable this class to support namespace redeclarations. In XML, each start
* tag can potentially redefine a prefix that was already defined by an
* ancestor. When using this class, such redefinition must appear in a new
* context, otherwise it would merely overwrite the old definition.
*
* See also [[enterContextWithMapping]], which is preferable if you already
* know the bindings you need to initialize the context with.
*
* At creation, a [[NameResolver]] has a default context already
* created. There is no need to create it and it is not possible to leave it.
*/
enterContext() {
this._contextStack.push({
forward: new Map(),
backwards: new Map(),
});
}
/**
* Enter a new context, and immediately populate it with bindings. If you
* already have a binding map, then using this method is preferable to using
* [[enterContext]] because it is faster than doing [[enterContext]] followed
* by a series of calls to [[definePrefix]].
*
* @param mapping The mapping with which to initialize the context.
*/
enterContextWithMapping(mapping) {
// http://www.w3.org/TR/REC-xml-names/#ns-decl
if (mapping.xmlns !== undefined) {
throw new Error("trying to define 'xmlns' but the XML Namespaces " +
"standard stipulates that 'xmlns' cannot be " +
"declared (= \"defined\")");
}
const xmlMapping = mapping.xml;
if (xmlMapping !== undefined && xmlMapping !== name_resolver_1.XML1_NAMESPACE) {
throw new Error("trying to define 'xml' to an incorrect URI");
}
const forward = new Map();
const backwards = new Map();
for (const prefix of Object.keys(mapping)) {
const uri = mapping[prefix];
forward.set(prefix, uri);
let prefixes = backwards.get(uri);
if (prefixes === undefined) {
prefixes = [prefix];
backwards.set(uri, prefixes);
}
// This ensure that the default namespace is given priority when
// unresolving names.
else if (prefix === "") {
prefixes.unshift("");
}
else {
prefixes.push(prefix);
}
}
this._contextStack.push({ forward, backwards });
}
/**
* This method is called to indicate the end of a context. Whatever context
* was in effect when the current context ends becomes effective.
*
* @throws {Error} If this method is called when there is no context created
* by [[enterContext]].
*/
leaveContext() {
if (this._contextStack.length > 1) {
this._contextStack.pop();
}
else {
throw new Error("trying to leave the default context");
}
}
/**
* Resolves a qualified name to an expanded name. A qualified name is an XML
* name optionally prefixed by a namespace prefix. For instance, in ``<html
* xml:lang="en">``, "html" is a name without a prefix, and "xml:lang" is a
* name with the "xml" prefix. An expanded name is a (URI, name) pair.
*
* @param name The name to resolve.
*
* @param attribute Whether this name appears as an attribute.
*
* @throws {Error} If the name is malformed. For instance, a name with two
* colons would be malformed.
*
* @returns The expanded name, or ``undefined`` if the name cannot be
* resolved.
*/
resolveName(name, attribute = false) {
const colon = name.indexOf(":");
let prefix;
let local;
if (colon === -1) {
if (attribute) { // Attribute in undefined namespace
return new ename_1.EName("", name);
}
// We are searching for the default namespace currently in effect.
prefix = "";
local = name;
}
else {
prefix = name.substr(0, colon);
local = name.substr(colon + 1);
if (local.includes(":")) {
throw new Error("invalid name passed to resolveName");
}
}
// Search through the contexts.
for (let ix = this._contextStack.length - 1; ix >= 0; --ix) {
const context = this._contextStack[ix];
const uri = context.forward.get(prefix);
if (uri !== undefined) {
return new ename_1.EName(uri, local);
}
}
// If we get here uri is necessarily undefined.
return (prefix === "") ? new ename_1.EName("", local) : undefined;
}
/**
* Unresolves an expanded name to a qualified name. An expanded name is a
* (URI, name) pair. Note that if we execute:
*
* <pre>
* var nameResolver = new NameResolver();
* var ename = nameResolver.resolveName(qname);
* var qname2 = nameResolver.unresolveName(ename.ns, ename.name);
* </pre>
*
* then ``qname === qname2`` is not necessarily true. This would happen if two
* prefixes map to the same URI. In such case the prefix provided in the
* return value is arbitrarily chosen.
*
* @param uri The URI part of the expanded name. An empty string is
* valid, and basically means "no namespace". This occurs for unprefixed
* attributes but could also happen if the default namespace is undeclared.
*
* @param name The name part.
*
* @returns The qualified name that corresponds to the expanded name, or
* ``undefined`` if it cannot be resolved.
*/
unresolveName(uri, name) {
if (uri === "") {
return name;
}
// Search through the contexts.
let prefixes;
for (let cIx = this._contextStack.length - 1; (prefixes === undefined) && (cIx >= 0); --cIx) {
prefixes = this._contextStack[cIx].backwards.get(uri);
}
if (prefixes === undefined) {
return undefined;
}
const pre = prefixes[0];
return (pre !== "") ? `${pre}:${name}` : name;
}
/**
* Returns a prefix that, in the current context, is mapped to the URI
* specified. Note that this function will return the first prefix that
* satisfies the requirement, starting from the innermost context.
*
* @param uri A URI for which to get a prefix.
*
* @returns A prefix that maps to this URI. Undefined if there is no prefix
* available.
*/
prefixFromURI(uri) {
let prefixes;
for (let cIx = this._contextStack.length - 1; (prefixes === undefined) && (cIx >= 0); --cIx) {
prefixes = this._contextStack[cIx].backwards.get(uri);
}
if (prefixes === undefined) {
return undefined;
}
return prefixes[0];
}
}
exports.DefaultNameResolver = DefaultNameResolver;
// LocalWords: unprefixed nameResolver pre definePrefix Unresolves qname vm
// LocalWords: redeclarations newID ename lang html NameResolver Mangalam uri
// LocalWords: xmlns URI Dubeau resolveName xml MPL unresolving namespace
//# sourceMappingURL=default_name_resolver.js.map