UNPKG

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.

222 lines 10.9 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.resolveURL = resolveURL; exports.step1 = step1; const parser_1 = require("../parser"); const schema_validation_1 = require("../schema-validation"); const util_1 = require("./util"); function resolveURL(base, tail) { return new URL(tail, base.toString()); } function loadFromElement(currentBase, seenURLs, el, parse) { return __awaiter(this, void 0, void 0, function* () { const resolved = resolveURL(currentBase, el.mustGetAttribute("href")); if (seenURLs.includes(resolved.toString())) { throw new schema_validation_1.SchemaValidationError(`detected an import loop: \ ${seenURLs.reverse().concat(resolved.toString()).join("\n")}`); } return { tree: yield parse(resolved), resolved }; }); } class Step1 { constructor(parser) { this.parser = parser; } walk(parentBase, seenURLs, el) { return __awaiter(this, void 0, void 0, function* () { // The XML parser we use immediately drops all *elements* which are not in // the RELAXNG_URI namespace so we don't have to remove them here. // We move all RNG nodes into the default namespace. el.prefix = ""; // At this point it is safe to drop all the attributes in the XML // namespace. "xml:base" in particular is no longer of any use. We do keep // namespace declarations, as they are used later for resolving QNames. const attrs = el.getRawAttributes(); let baseAttr; for (const name of Object.keys(attrs)) { const attr = attrs[name]; const { uri, prefix } = attr; if (name !== "xmlns" && uri !== "" && prefix !== "xmlns") { if (name === "xml:base") { baseAttr = attr.value; } delete attrs[name]; } else if (name === "name" || name === "type" || name === "combine") { attr.value = attr.value.trim(); } } const currentBase = baseAttr === undefined ? parentBase : resolveURL(parentBase, baseAttr); const { children, local } = el; if (children.length !== 0) { // We don't normalize text nodes in param or value. if (local === "param" || local === "value") { for (const child of children) { if ((0, parser_1.isElement)(child)) { yield this.walk(currentBase, seenURLs, child); } } } else { const newChildren = []; for (const child of children) { if ((0, parser_1.isElement)(child)) { const replace = yield this.walk(currentBase, seenURLs, child); newChildren.push(replace === null ? child : replace); continue; } const orig = child.text; const clean = orig.trim(); if (clean !== "") { // name gets the trimmed value newChildren.push(local === "name" && orig !== clean ? // We're triming text. new parser_1.Text(clean) : child); } // else we drop a node consisting entirely of whitespace } el.replaceContent(newChildren); } } const handler = this[local]; if (handler === undefined) { return el; } const replacement = yield handler.call(this, currentBase, seenURLs, el); return replacement === null ? el : replacement; }); } externalRef(currentBase, seenURLs, el) { return __awaiter(this, void 0, void 0, function* () { // tslint:disable-next-line:prefer-const let { tree: includedTree, resolved } = yield loadFromElement(currentBase, seenURLs, el, this.parser); includedTree = yield this.walk(resolved, [resolved.toString(), ...seenURLs], includedTree); const ns = el.getAttribute("ns"); const treeNs = includedTree.getAttribute("ns"); if (ns !== undefined && treeNs === undefined) { includedTree.setAttribute("ns", ns); } // See the comment in ``include`` below for the rational as to why we are // setting the value here. if (includedTree.getAttribute("datatypeLibrary") === undefined) { includedTree.setAttribute("datatypeLibrary", ""); } // If parent is null then we are at the root and we cannot remove the // element. if (el.parent !== undefined) { // By this point, the tree's default namespace is the Relax NG one. So we // can remove it to avoid redeclaring it. We don't want to do this though // if we're forming the root of the new tree. includedTree.removeAttribute("xmlns"); el.parent.replaceChildWith(el, includedTree); } return includedTree; }); } include(currentBase, seenURLs, el) { return __awaiter(this, void 0, void 0, function* () { const { tree: includedTree, resolved } = yield loadFromElement(currentBase, seenURLs, el, this.parser); yield this.walk(resolved, [resolved.toString(), ...seenURLs], includedTree); // By this point, the tree's default namespace is the Relax NG one. So we // can remove it to avoid redeclaring it. includedTree.removeAttribute("xmlns"); if (includedTree.local !== "grammar") { throw new schema_validation_1.SchemaValidationError("include does not point to a document " + "that has a grammar element as root"); } const { start: includeStarts, define: includeDefs } = (0, util_1.findMultiDescendantsByLocalName)(el, ["start", "define"]); const { start: grammarStarts, define: grammarDefs } = (0, util_1.findMultiDescendantsByLocalName)(includedTree, ["start", "define"]); if (includeStarts.length !== 0) { if (grammarStarts.length === 0) { throw new schema_validation_1.SchemaValidationError("include contains start element but grammar does not"); } for (const start of grammarStarts) { start.parent.removeChild(start); } } const includeDefsMap = (0, util_1.indexBy)(includeDefs, util_1.getName); const grammarDefsMap = (0, util_1.groupBy)(grammarDefs, util_1.getName); for (const key of includeDefsMap.keys()) { const defs = grammarDefsMap.get(key); if (defs === undefined) { throw new schema_validation_1.SchemaValidationError(`include has define with name ${name} which is not present in \ grammar`); } for (const def of defs) { def.parent.removeChild(def); } } el.local = "div"; el.removeAttribute("href"); includedTree.local = "div"; // // In the RNG specification, the propagation of datatypeLibrary elements is // an earlier processing step (4.3) than the resolution of external // references (4.5). This means that upon reading an external resource, an // algorithm that follows the spec to the letter would have to apply to the // resource the steps 4.1 to 4.4 before resolving any external references in // the newly loaded resource. // // Here we follow the order set in the XSL pipeline: we first resolve all // external references and then later we will propagate datatypeLibrary. // // This deivation from the spec requires that if datatypeLibrary is not set // on a imported element we MUST set it to the empty string. Otherwise, when // we do propagate datatypeLibrary, it would "cross" resource boundaries, so // to speak. This would be incorrect. // if (includedTree.getAttribute("datatypeLibrary") === undefined) { includedTree.setAttribute("datatypeLibrary", ""); } // Insert the grammar element (now named "div") into the include element // (also now named "div"). el.prependChild(includedTree); return null; }); } } /** * Modify the tree: * * - All references to external resources (``externalRef`` and ``include``) are * replaced by the contents of the references. It essentially "flattens" a * schema made of group of documents to a single document. * * - Remove text nodes that contain only white spaces. Text nodes in the * elements ``param`` and ``value`` are excluded. * * - Trim the text node in the elements named ``name``. * * - Also trim the values of the attributes ``name``, ``type`` and ``combine``. * * Note that step1 also subsumes what was step2 and step3 in the XSLT-based * transforms. * * @param documentBase The base URI of the tree being processed. * * @param tree The XML tree to process. * * @param parser A function through which we load and parse XML files. * * @returns A promise that resolves to the new tree root when processing is * done. */ function step1(documentBase, tree, parser) { return __awaiter(this, void 0, void 0, function* () { return new Step1(parser).walk(documentBase, [documentBase.toString()], tree); }); } //# sourceMappingURL=step1.js.map