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.

183 lines 6.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.step15 = step15; /** * Simplification step 15. * @author Louis-Dominique Dubeau * @license MPL 2.0 * @copyright 2013, 2014 Mangalam Research Center for Buddhist Languages */ const parser_1 = require("../parser"); const schema_validation_1 = require("../schema-validation"); const util_1 = require("./util"); class GrammarNode { constructor(id, grammar) { this.id = id; this.grammar = grammar; this.childGrammars = []; this.defines = []; this.refs = []; this.parentRefs = []; this.refNames = new Set(); this.parentRefNames = new Set(); this.defineNames = new Set(); } } function gatherGrammars(el, state) { const { local } = el; const { stack } = state; const top = stack[stack.length - 1]; let pop = false; switch (local) { case "ref": top.refs.push(el); top.refNames.add(el.mustGetAttribute("name")); break; case "define": top.defines.push(el); top.defineNames.add(el.mustGetAttribute("name")); break; case "grammar": pop = true; const thisGrammar = new GrammarNode(++state.latestId, el); stack.push(thisGrammar); if (top !== undefined) { top.childGrammars.push(thisGrammar); } break; case "parentRef": top.parentRefs.push(el); top.parentRefNames.add(el.mustGetAttribute("name")); break; default: if (state.root === null) { stack.push(new GrammarNode(++state.latestId, el)); pop = true; } } if (state.root === null) { // We have to acquire it from stack[0] and not from the variable top. state.root = stack[0]; } for (const child of el.children) { if (!(0, parser_1.isElement)(child)) { continue; } gatherGrammars(child, state); } if (pop) { stack.pop(); } } function transformGrammars(multiple, root, parent, grammar) { for (const name of grammar.refNames) { if (!grammar.defineNames.has(name)) { throw new schema_validation_1.SchemaValidationError(`dangling ref: ${name}`); } } if (parent === null && grammar.parentRefNames.size !== 0) { throw new schema_validation_1.SchemaValidationError("top-level grammar contains parentRef!"); } for (const name of grammar.parentRefNames) { // The test above ensures parent is not null. // tslint:disable-next-line:no-non-null-assertion if (!parent.defineNames.has(name)) { throw new schema_validation_1.SchemaValidationError(`dangling parentRef: ${name}`); } } const toRename = grammar.defines.concat(grammar.refs, ...grammar.childGrammars.map(x => x.parentRefs)); const suffix = `-gr-${grammar.id}`; // Make all names unique globally. for (const el of toRename) { el.setAttribute("name", el.mustGetAttribute("name") + suffix); } // Move the ``define`` elements to the root grammar. We do this on the root // grammar too so that the ``define`` elements are moved after ``start``. root.grammar.appendChildren(grammar.defines); for (const child of grammar.childGrammars) { transformGrammars(multiple, root, grammar, child); } // Rename all parentRef elements to ref elements. for (const parentRef of grammar.parentRefs) { parentRef.local = "ref"; } const start = grammar.grammar.children[0]; if (start.local !== "start") { throw new Error("there should be a single start element in the grammar!"); } if (grammar !== root) { // Remove the remaining ``grammar`` and ``start`` elements. grammar.grammar.parent.replaceChildWith(grammar.grammar, start.children[0]); } } /** * Implements step 15 of the XSL pipeline. Namely: * * - Rename each ``define`` element so as to make it unique across the * schema. We do this by giving a unique id to each ``grammar`` element, which * is the number of ``grammar`` elements before it, in document reading order, * plus 1. Then we add to ``define/@name`` the string ``-gr-{id}`` where * ``{id}`` is the grammar's id of the grammar to which the ``define`` * belongs. NOTE: this pattern was selected to avoid a clash with step 16, * which creates new ``define`` elements. * * - Rename each ``ref`` and ``parentRef`` to preserve the references the * establish to ``define`` elements. * * - Create a top level ``grammar/start`` structure, if necessary. * * - Move all ``define`` elements to the top ``grammar``. * * - Rename all ``parentRef`` elements to ``ref``. * * - Replace all ``grammar/start`` elements with the expression contained * therein, except for the top level ``grammar/start``. * * @param el The tree to process. It is modified in-place. * * @returns The new tree root. */ function step15(el) { let root = el; if (el.local !== "grammar") { root = parser_1.Element.makeElement("grammar", [parser_1.Element.makeElement("start", [el])]); root.setXMLNS(util_1.RELAXNG_URI); el.removeAttribute("xmlns"); } const state = { latestId: 0, root: null, stack: [], }; gatherGrammars(root, state); const multiple = state.latestId !== 1; // tslint:disable-next-line:no-non-null-assertion const grammar = state.root; if (multiple) { transformGrammars(multiple, grammar, null, grammar); } else { // If we have only a single grammar, we can reduce the work to this. for (const name of grammar.refNames) { if (!grammar.defineNames.has(name)) { throw new schema_validation_1.SchemaValidationError(`dangling ref: ${name}`); } } if (grammar.parentRefNames.size !== 0) { throw new schema_validation_1.SchemaValidationError("top-level grammar contains parentRef!"); } let start = grammar.grammar.children[0]; if (start.local !== "start") { // The first element is not start. Move the defines around. grammar.grammar.appendChildren(grammar.defines); start = grammar.grammar.children[0]; if (start.local !== "start") { // Somehow it did not work! throw new Error("there should be a single start element in the \ grammar!"); } } } return root; } //# sourceMappingURL=step15.js.map