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.

623 lines 26.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); 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.InternalSimplifier = void 0; /** * A simplifier implemented in TypeScript (thus internal to Salve). * * @author Louis-Dominique Dubeau * @license MPL 2.0 * @copyright Mangalam Research Center for Buddhist Languages */ const saxes_1 = require("saxes"); const datatypes_1 = require("../../datatypes"); const default_name_resolver_1 = require("../../default_name_resolver"); const read_1 = require("../../json-format/read"); const patterns_1 = require("../../patterns"); const relaxng = __importStar(require("../../schemas/relaxng.json")); const parser_1 = require("../parser"); const schema_simplification_1 = require("../schema-simplification"); const schema_validation_1 = require("../schema-validation"); const simplifier = __importStar(require("../simplifier")); const util_1 = require("../simplifier/util"); const base_1 = require("./base"); const common_1 = require("./common"); function makeNamePattern(el) { switch (el.local) { case "name": return new patterns_1.Name(el.mustGetAttribute("ns"), el.text, el.documentation); case "choice": return new patterns_1.NameChoice(makeNamePattern(el.children[0]), makeNamePattern(el.children[1])); case "anyName": { const first = el.children[0]; return new patterns_1.AnyName(first !== undefined ? makeNamePattern(first) : undefined); } case "nsName": { const first = el.children[0]; return new patterns_1.NsName(el.mustGetAttribute("ns"), first !== undefined ? makeNamePattern(first) : undefined); } case "except": return makeNamePattern(el.children[0]); default: throw new Error(`unexpected element in name pattern ${el.local}`); } } class ProhibitedPath extends schema_validation_1.SchemaValidationError { constructor(path) { super(`prohibited path: ${path}`); } } class ProhibitedAttributePath extends schema_validation_1.SchemaValidationError { constructor(name) { super(`attribute//${name}`); } } class ProhibitedListPath extends schema_validation_1.SchemaValidationError { constructor(name) { super(`list//${name}`); } } class ProhibitedStartPath extends schema_validation_1.SchemaValidationError { constructor(name) { super(`start//${name}`); } } class ProhibitedDataExceptPath extends schema_validation_1.SchemaValidationError { constructor(name) { super(`data/except//${name}`); } } const EMPTY_ELEMENT_ARRAY = []; const EMPTY_NAME_ARRAY = []; const TEXT_RESULT = { contentType: 1 /* ContentType.COMPLEX */, occurringAttributeNames: EMPTY_NAME_ARRAY, occurringRefs: EMPTY_ELEMENT_ARRAY, occurringTexts: true, }; const EMPTY_RESULT = { contentType: 0 /* ContentType.EMPTY */, occurringAttributeNames: EMPTY_NAME_ARRAY, occurringRefs: EMPTY_ELEMENT_ARRAY, occurringTexts: false, }; const DATA_RESULT = { contentType: 2 /* ContentType.SIMPLE */, occurringAttributeNames: EMPTY_NAME_ARRAY, occurringRefs: EMPTY_ELEMENT_ARRAY, occurringTexts: false, }; const LIST_RESULT = DATA_RESULT; const VALUE_RESULT = DATA_RESULT; const NOT_ALLOWED_RESULT = { contentType: null, occurringAttributeNames: EMPTY_NAME_ARRAY, occurringRefs: EMPTY_ELEMENT_ARRAY, occurringTexts: false, }; const FORBIDDEN_IN_START = ["attribute", "data", "value", "text", "list", "group", "interleave", "oneOrMore", "empty"]; /** * Perform the final constraint checks, and record some information * for checkInterleaveRestriction. */ class GeneralChecker { constructor() { this.defineNameToElementNames = new Map(); this.typeWarnings = []; } check(el) { const { children } = el; const definesByName = new Map(); for (let ix = 1; ix < children.length; ++ix) { const child = children[ix]; definesByName.set(child.mustGetAttribute("name"), child); } this.definesByName = definesByName; const start = children[0]; const found = (0, util_1.findMultiDescendantsByLocalName)(start, FORBIDDEN_IN_START); for (const forbidden of FORBIDDEN_IN_START) { if (found[forbidden].length !== 0) { throw new ProhibitedStartPath(forbidden); } } const state = { inAttribute: false, inList: false, inDataExcept: false, inOneOrMore: false, inOneOrMoreGroup: false, inOneOrMoreInterleave: false, inInterlave: false, inGroup: false, }; // The first child of <grammar> is necessarily <start>. So we handle // start here. this._check(start.children[0], state); // The other children are necessarily <define>. for (let ix = 1; ix < children.length; ++ix) { // <define> elements necessarily have a single child which is an // <element>. const element = children[ix].children[0]; // The first child is the name class, which we do not need to walk. const pattern = element.children[1]; const { contentType } = this._check(pattern, state); if (contentType === null && pattern.local !== "notAllowed") { throw new schema_validation_1.SchemaValidationError(`definition ${el.mustGetAttribute("name")} violates the constraint \ on string values (section 7.2)`); } } } _check(el, state) { return this[`${el.local}Handler`] .call(this, el, state); } attributeHandler(el, state) { if (state.inOneOrMoreGroup) { throw new ProhibitedPath("oneOrMore//group//attribute"); } if (state.inOneOrMoreInterleave) { throw new ProhibitedPath("oneOrMore//interleave//attribute"); } if (state.inAttribute) { throw new ProhibitedAttributePath(el.local); } if (state.inList) { throw new ProhibitedListPath(el.local); } if (state.inDataExcept) { throw new ProhibitedDataExceptPath(el.local); } const [first, second] = el.children; const name = makeNamePattern(first); if (!state.inOneOrMore && !name.simple()) { throw new schema_validation_1.SchemaValidationError("an attribute with an infinite name \ class must be a descendant of oneOrMore (section 7.3)"); } // The first child is the name class, which we do not need to walk. this._check(second, Object.assign(Object.assign({}, state), { inAttribute: true })); return { contentType: 0 /* ContentType.EMPTY */, occurringAttributeNames: [name], occurringRefs: EMPTY_ELEMENT_ARRAY, occurringTexts: false, }; } oneOrMoreHandler(el, state) { if (state.inDataExcept) { throw new ProhibitedDataExceptPath(el.local); } const { contentType, occurringAttributeNames, occurringRefs, occurringTexts } = this._check(el.children[0], Object.assign(Object.assign({}, state), { inOneOrMore: true })); // The test would be // // ct !== null && groupable(ct, ct) // // but the only thing not groupable with itself is ContentType.SIMPLE // and if ct === null then forcibly ct !== ContentType.SIMPLE // is true so we can simplify to the following. return { contentType: contentType !== 2 /* ContentType.SIMPLE */ ? contentType : null, occurringAttributeNames, occurringRefs, occurringTexts, }; } groupHandler(el, state) { if (state.inDataExcept) { throw new ProhibitedDataExceptPath(el.local); } return this.groupInterleaveHandler(el.children, Object.assign(Object.assign({}, state), { inGroup: true, inOneOrMoreGroup: state.inOneOrMore }), false); } interleaveHandler(el, state) { if (state.inList) { throw new ProhibitedListPath(el.local); } if (state.inDataExcept) { throw new ProhibitedDataExceptPath(el.local); } return this.groupInterleaveHandler(el.children, Object.assign(Object.assign({}, state), { inInterlave: true, inOneOrMoreInterleave: state.inOneOrMore }), true); } choiceHandler(el, state) { const { contentType: firstCt, occurringAttributeNames: firstAttributes, occurringRefs: firstRefs, occurringTexts: firstTexts } = this._check(el.children[0], state); const { contentType: secondCt, occurringAttributeNames: secondAttributes, occurringRefs: secondRefs, occurringTexts: secondTexts } = this._check(el.children[1], state); return { contentType: firstCt !== null && secondCt !== null ? (firstCt > secondCt ? firstCt : secondCt) : null, occurringAttributeNames: firstAttributes.concat(secondAttributes), occurringRefs: firstRefs.concat(secondRefs), occurringTexts: firstTexts || secondTexts, }; } listHandler(el, state) { if (state.inList) { throw new ProhibitedListPath(el.local); } if (state.inDataExcept) { throw new ProhibitedDataExceptPath(el.local); } this._check(el.children[0], Object.assign(Object.assign({}, state), { inList: true })); return LIST_RESULT; } dataHandler(el, state) { const typeAttr = el.mustGetAttribute("type"); const libname = el.mustGetAttribute("datatypeLibrary"); const lib = datatypes_1.registry.find(libname); if (lib === undefined) { throw new datatypes_1.ValueValidationError(el.path, [new datatypes_1.ValueError(`unknown datatype library: ${libname}`)]); } const datatype = lib.types[typeAttr]; if (datatype === undefined) { throw new datatypes_1.ValueValidationError(el.path, [new datatypes_1.ValueError(`unknown datatype ${typeAttr} in \ ${(libname === "") ? "default library" : `library ${libname}`}`)]); } const { children } = el; if (children.length !== 0) { const last = children[children.length - 1]; // We only need to scan the possible except child, which is necessarily // last. const hasExcept = last.local === "except"; const limit = hasExcept ? children.length - 1 : children.length; // Running parseParams if we have no params is expensive. And if there are // no params, there's nothing to check so don't run parseParams without // params. if (limit > 0) { const params = []; for (let ix = 0; ix < limit; ++ix) { const child = children[ix]; params.push({ name: child.mustGetAttribute("name"), value: child.text, }); } datatype.parseParams(el.path, params); } if (hasExcept) { this._check(last.children[0], state.inDataExcept ? state : Object.assign(Object.assign({}, state), { inDataExcept: true })); } } // tslint:disable-next-line: no-http-string if (libname === "http://www.w3.org/2001/XMLSchema-datatypes" && (typeAttr === "ENTITY" || typeAttr === "ENTITIES")) { this.typeWarnings.push(`WARNING: ${el.path} uses the ${typeAttr} type in library \ ${libname}`); } return DATA_RESULT; } valueHandler(el, state) { const typeAttr = el.mustGetAttribute("type"); const libname = el.mustGetAttribute("datatypeLibrary"); let ns = el.mustGetAttribute("ns"); const lib = datatypes_1.registry.find(libname); if (lib === undefined) { throw new datatypes_1.ValueValidationError(el.path, [new datatypes_1.ValueError(`unknown datatype library: ${libname}`)]); } const datatype = lib.types[typeAttr]; if (datatype === undefined) { throw new datatypes_1.ValueValidationError(el.path, [new datatypes_1.ValueError(`unknown datatype ${typeAttr} in \ ${(libname === "") ? "default library" : `library ${libname}`}`)]); } let value = el.text; let context; if (datatype.needsContext) { // Change ns to the namespace we need. ns = (0, common_1.fromQNameToURI)(value, el); value = (0, common_1.localName)(value); el.setAttribute("ns", ns); el.replaceContent([new parser_1.Text(value)]); const nr = new default_name_resolver_1.DefaultNameResolver(); nr.definePrefix("", ns); context = { resolver: nr }; } datatype.parseValue(el.path, value, context); // tslint:disable-next-line: no-http-string if (libname === "http://www.w3.org/2001/XMLSchema-datatypes" && (typeAttr === "ENTITY" || typeAttr === "ENTITIES")) { this.typeWarnings.push(`WARNING: ${el.path} uses the ${typeAttr} type in library \ ${libname}`); } return VALUE_RESULT; } textHandler(el, state) { if (state.inList) { throw new ProhibitedListPath(el.local); } if (state.inDataExcept) { throw new ProhibitedDataExceptPath(el.local); } return TEXT_RESULT; } refHandler(el, state) { if (state.inList) { throw new ProhibitedListPath(el.local); } if (state.inAttribute) { throw new ProhibitedAttributePath(el.local); } if (state.inDataExcept) { throw new ProhibitedDataExceptPath(el.local); } return { contentType: 1 /* ContentType.COMPLEX */, occurringAttributeNames: EMPTY_NAME_ARRAY, occurringRefs: [el], occurringTexts: false, }; } emptyHandler(el, state) { if (state.inDataExcept) { throw new ProhibitedDataExceptPath(el.local); } return EMPTY_RESULT; } notAllowedHandler() { return NOT_ALLOWED_RESULT; } getElementNamesForDefine(name) { let pattern = this.defineNameToElementNames.get(name); if (pattern === undefined) { const def = this.definesByName.get(name); // tslint:disable-next-line:no-non-null-assertion const element = def.children[0]; const namePattern = element.children[0]; pattern = makeNamePattern(namePattern); this.defineNameToElementNames.set(name, pattern); } return pattern; } groupInterleaveHandler(children, newState, isInterleave) { const { contentType: firstCt, occurringAttributeNames: firstAttributes, occurringRefs: firstRefs, occurringTexts: firstTexts } = this._check(children[0], newState); const { contentType: secondCt, occurringAttributeNames: secondAttributes, occurringRefs: secondRefs, occurringTexts: secondTexts } = this._check(children[1], newState); for (const attr1 of firstAttributes) { for (const attr2 of secondAttributes) { if (attr1.intersects(attr2)) { throw new schema_validation_1.SchemaValidationError(`the name classes of two attributes in the same group or interleave intersect (section 7.3): ${attr1} and ${attr2}`); } } } if (isInterleave) { if (firstTexts && secondTexts) { throw new schema_validation_1.SchemaValidationError("text present in both patterns of an interleave (section 7.4)"); } for (const ref1 of firstRefs) { const name1 = this.getElementNamesForDefine(ref1.mustGetAttribute("name")); for (const ref2 of secondRefs) { const name2 = this.getElementNamesForDefine(ref2.mustGetAttribute("name")); if (name1.intersects(name2)) { throw new schema_validation_1.SchemaValidationError(`name classes of elements in both \ patterns of an interleave intersect (section 7.4): ${name1} and ${name2}`); } } } } // These tests combine the groupable(firstCt, secondCt) test together with // the requirement that we return the content type which is the greatest. let contentType; if (firstCt === 1 /* ContentType.COMPLEX */ && secondCt === 1 /* ContentType.COMPLEX */) { contentType = 1 /* ContentType.COMPLEX */; } else if (firstCt === 0 /* ContentType.EMPTY */) { contentType = secondCt; } else { contentType = (secondCt === 0 /* ContentType.EMPTY */) ? firstCt : null; } return { contentType, occurringAttributeNames: firstAttributes.concat(secondAttributes), occurringRefs: firstRefs.concat(secondRefs), occurringTexts: firstTexts || secondTexts, }; } } let cachedGrammar; function getGrammar() { if (cachedGrammar === undefined) { cachedGrammar = (0, read_1.readTreeFromJSON)(relaxng); } return cachedGrammar; } /** * A simplifier implemented in TypeScript (thus internal to Salve). */ class InternalSimplifier extends base_1.BaseSimplifier { constructor(options) { super(options); this.manifestPromises = []; if (options.timing) { options.verbose = true; } } parse(filePath, schemaResource, schemaText) { return __awaiter(this, void 0, void 0, function* () { const fileName = filePath.toString(); const saxesParser = new saxes_1.SaxesParser({ xmlns: true, position: false, fileName }); let validator; if (this.options.validate) { validator = new parser_1.Validator(getGrammar(), saxesParser); } const parser = new parser_1.BasicParser(saxesParser, validator); parser.saxesParser.write(schemaText); parser.saxesParser.close(); if (validator !== undefined) { if (validator.errors.length !== 0) { const message = validator.errors.map(x => x.toString()).join("\n"); throw new schema_validation_1.SchemaValidationError(message); } } if (this.options.createManifest) { const algo = this.options.manifestHashAlgorithm; if (typeof algo === "string") { this.manifestPromises.push((() => __awaiter(this, void 0, void 0, function* () { const digest = // tslint:disable-next-line:await-promise yield crypto.subtle.digest(algo, new TextEncoder().encode(schemaText)); const arr = new Uint8Array(digest); let hash = `${algo}-`; for (const x of arr) { const hex = x.toString(16); hash += x > 0xF ? hex : `0${hex}`; } return { filePath: fileName, hash }; }))()); } else { this.manifestPromises.push((() => __awaiter(this, void 0, void 0, function* () { const hash = yield algo(schemaResource); return { filePath: fileName, hash }; }))()); } } return parser.root; }); } stepStart(no) { this.stepTiming(); if (this.options.verbose) { // tslint:disable-next-line:no-console console.log(`Simplification step ${no}`); } } stepTiming() { if (!this.options.timing) { return; } if (this.lastStepStart !== undefined) { // tslint:disable-next-line:no-console console.log(`${Date.now() - this.lastStepStart}ms`); this.lastStepStart = undefined; } this.lastStepStart = Date.now(); } simplify(schemaPath) { return __awaiter(this, void 0, void 0, function* () { const schemaResource = yield this.options.resourceLoader.load(schemaPath); const schemaText = yield schemaResource.getText(); let startTime; if (this.options.verbose) { // tslint:disable-next-line:no-console console.log("Simplifying..."); if (this.options.timing) { startTime = Date.now(); } } let warnings = []; let tree; if (this.options.simplifyTo >= 1) { this.stepStart(1); tree = yield this.parse(schemaPath, schemaResource, schemaText); tree = yield simplifier.step1(schemaPath, tree, this.parse.bind(this)); } if (this.options.simplifyTo >= 4) { this.stepStart(4); tree = simplifier.step4(tree); } if (this.options.simplifyTo >= 6) { this.stepStart(6); tree = simplifier.step6(tree); } if (this.options.simplifyTo >= 9) { this.stepStart(9); tree = simplifier.step9(tree); } if (this.options.simplifyTo >= 10) { this.stepStart(10); tree = simplifier.step10(tree, this.options.validate); } if (this.options.simplifyTo >= 14) { this.stepStart(14); tree = simplifier.step14(tree); } if (this.options.simplifyTo >= 15) { this.stepStart(15); tree = simplifier.step15(tree); } if (this.options.simplifyTo >= 16) { this.stepStart(16); tree = simplifier.step16(tree); } if (this.options.simplifyTo >= 17) { this.stepStart(17); tree = simplifier.step17(tree); } if (this.options.simplifyTo >= 18) { this.stepStart(18); tree = simplifier.step18(tree); if (this.options.validate) { let checkStart; if (this.options.timing) { checkStart = Date.now(); } const checker = new GeneralChecker(); checker.check(tree); warnings = checker.typeWarnings; if (this.options.timing) { // tslint:disable-next-line:no-non-null-assertion no-console console.log(`Step 18 check delta: ${Date.now() - checkStart}`); } } } if (this.options.timing) { this.stepTiming(); // Output the last timing. // tslint:disable-next-line:no-non-null-assertion no-console console.log(`Simplification delta: ${Date.now() - startTime}`); } return { simplified: tree, warnings, manifest: yield Promise.all(this.manifestPromises), schemaText }; }); } } exports.InternalSimplifier = InternalSimplifier; InternalSimplifier.validates = true; InternalSimplifier.createsManifest = true; (0, schema_simplification_1.registerSimplifier)("internal", InternalSimplifier); //# sourceMappingURL=internal.js.map