UNPKG

esvit-node-schematron

Version:

A pure-JS Schematron implementation for Node, browsers and CLI

415 lines (332 loc) 14.7 kB
import { evaluateXPathToString, evaluateXPathToBoolean, evaluateXPath, evaluateXPathToNodes } from 'fontoxpath'; export { registerCustomXPathFunction } from 'fontoxpath'; import fs from 'fs'; import { sync } from 'slimdom-sax-parser'; function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); } function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } function _createForOfIteratorHelperLoose(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (it) return (it = it.call(o)).next.bind(it); if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; return function () { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var Namespace = /*#__PURE__*/function () { function Namespace(prefix, uri) { this.prefix = prefix; this.uri = uri; } Namespace.fromJson = function fromJson(json) { return new Namespace(json.prefix, json.uri); }; return Namespace; }(); Namespace.QUERY = "map {\n\t\t\"prefix\": @prefix/string(),\n\t\t\"uri\": @uri/string()\n\t}"; var Result = /*#__PURE__*/function () { function Result( // pattern: Pattern, // phase?: Phase, // rule: Rule, context, assert, message) { // this.pattern = pattern; // this.phase = phase; // this.rule = rule; this.assertId = assert.id; this.isReport = assert.isReport; this.context = context; this.message = message; } var _proto = Result.prototype; _proto.toJson = function toJson() { return { assertId: this.assertId, isReport: this.isReport, context: this.context.outerHTML, message: this.message }; }; return Result; }(); var Assert = /*#__PURE__*/function () { function Assert(id, test, message, isReport) { this.id = id; this.test = test; this.message = message; this.isReport = isReport; } var _proto = Assert.prototype; _proto.createMessageString = function createMessageString(contextNode, variables, fontoxpathOptions, chunks) { return chunks.map(function (chunk) { if (typeof chunk === 'string') { return chunk; } // <sch:name /> if (chunk.$type === 'name') { return evaluateXPathToString('name(' + (chunk.path || '') + ')', contextNode, null, variables, fontoxpathOptions); } // <sch:value-of /> if (chunk.$type === 'value-of') { return evaluateXPathToString(chunk.select, contextNode, null, variables, fontoxpathOptions); } console.log(chunk); throw new Error('Unsupported element in <sch:message>'); }).join(''); }; _proto.validateNode = function validateNode(context, variables, fontoxpathOptions) { var outcome = evaluateXPathToBoolean(this.test, context, null, variables, fontoxpathOptions); return !this.isReport && outcome || this.isReport && !outcome ? null : new Result(context, this, this.createMessageString(context, variables, fontoxpathOptions, this.message)); }; Assert.fromJson = function fromJson(json) { return new Assert(json.id, json.test, json.message, json.isReport); }; return Assert; }(); Assert.QUERY = "map {\n\t\t'id': if (@id) then string(@id) else (),\n\t\t'test': @test/string(),\n\t\t'message': array { (./text()|./element())/local:json(.) },\n\t\t'isReport': boolean(local-name() = 'report')\n\t}"; var Variable = /*#__PURE__*/function () { function Variable(name, value) { this.name = name.trim(); this.value = value; } Variable.reduceVariables = function reduceVariables(context, variables, fontoxpathOptions, initial) { return variables.reduce(function (mapping, variable) { var _Object$assign; return Object.assign(mapping, (_Object$assign = {}, _Object$assign[variable.name] = variable.value ? evaluateXPath(variable.value, context, null, mapping, undefined, fontoxpathOptions) : context, _Object$assign)); }, initial || {}); }; Variable.fromJson = function fromJson(json) { return new Variable(json.name, json.value); }; return Variable; }(); Variable.QUERY = "map {\n\t\t'name': @name/string(),\n\t\t'value': @value/string()\n\t}"; var Rule = /*#__PURE__*/function () { function Rule(context, variables, asserts) { this.context = context; this.variables = variables; this.asserts = asserts; } var _proto = Rule.prototype; _proto.validateNode = function validateNode(context, parentVariables, fontoxpathOptions) { var variables = Variable.reduceVariables(context, this.variables, fontoxpathOptions, _extends({}, parentVariables)); return this.asserts.map(function (assert) { return assert.validateNode(context, variables, fontoxpathOptions); }).filter(function (result) { return result !== null; }); }; Rule.fromJson = function fromJson(json) { var variables = json.variables.map(function (rule) { return Variable.fromJson(rule); }); var asserts = json.asserts.map(function (rule) { return Assert.fromJson(rule); }); return new Rule(json.context.trim(), variables, asserts); }; return Rule; }(); Rule.QUERY = "map {\n\t\t'context': @context/string(),\n\t\t'variables': array { ./(sch:let|sch:param)/" + Variable.QUERY + "},\n\t\t'asserts': array{ ./(sch:report|sch:assert)/" + Assert.QUERY + "}\n\t}"; var Pattern = /*#__PURE__*/function () { function Pattern(id, rules, variables) { this.id = id; this.rules = rules; this.variables = variables; } var _proto = Pattern.prototype; _proto.validateDocument = function validateDocument(documentDom, parentVariables, fontoxpathOptions) { var _this = this; var variables = Variable.reduceVariables(documentDom, this.variables, fontoxpathOptions, _extends({}, parentVariables)); var ruleContexts = this.rules.map(function (rule) { return evaluateXPathToNodes('//(' + rule.context + ')', documentDom, null, variables, fontoxpathOptions); }); var flattenValidationResults = function flattenValidationResults(results, node) { var ruleIndex = ruleContexts.findIndex(function (context) { return context.includes(node); }); var rule = ruleIndex >= 0 ? _this.rules[ruleIndex] : null; if (rule) { results.splice.apply(results, [results.length, 0].concat(rule.validateNode(node, variables, fontoxpathOptions))); } return Array.from(node.childNodes).reduce(flattenValidationResults, results); }; return Array.from(documentDom.childNodes).reduce(flattenValidationResults, []); }; Pattern.fromJson = function fromJson(json) { return new Pattern(json.id, json.rules.map(function (obj) { return Rule.fromJson(obj); }), json.variables.map(function (obj) { return Variable.fromJson(obj); })); }; return Pattern; }(); Pattern.QUERY = "map {\n\t\t'id': @id/string(),\n\t\t'rules': array{ ./sch:rule/" + Rule.QUERY + "},\n\t\t'variables': array { ./sch:let/" + Variable.QUERY + "}\n\t}"; var Phase = /*#__PURE__*/function () { function Phase(id, active, variables) { this.id = id; this.active = active; this.variables = variables; } Phase.fromJson = function fromJson(json) { return new Phase(json.id, json.active, json.variables.map(function (rule) { return Variable.fromJson(rule); })); }; return Phase; }(); Phase.QUERY = "map {\n\t\t\"id\": @id/string(),\n\t\t\"active\": array { ./sch:active/@pattern/string() },\n\t\t'variables': array { ./sch:let/" + Variable.QUERY + "}\n\t}"; var Schema = /*#__PURE__*/function () { function Schema(title, defaultPhase, variables, phases, patterns, namespaces) { this.title = title; this.defaultPhase = defaultPhase; this.variables = variables; this.phases = phases; this.patterns = patterns; this.namespaces = namespaces; } var _proto = Schema.prototype; _proto.validateString = function validateString(documentXmlString, options) { // Typescript casting slimdom.Document to Document, which are the same return this.validateDocument(sync(documentXmlString), options); }; _proto.validateDocument = function validateDocument(documentDom, options) { var _this = this; var _ref = options || {}, phaseId = _ref.phaseId, debug = _ref.debug; if (!phaseId) { phaseId = '#DEFAULT'; } if (phaseId === '#DEFAULT') { phaseId = this.defaultPhase || '#ALL'; } var fontoxpathOptions = { namespaceResolver: this.getNamespaceUriForPrefix.bind(this), debug: debug }; var variables = Variable.reduceVariables(documentDom, this.variables, fontoxpathOptions, {}); if (phaseId === '#ALL') { return this.patterns.reduce(function (results, pattern) { return results.concat(pattern.validateDocument(documentDom, variables, fontoxpathOptions)); }, []); } var phase = this.phases.find(function (phase) { return phase.id === phaseId; }); var phaseVariables = Variable.reduceVariables(documentDom, (phase == null ? void 0 : phase.variables) || [], fontoxpathOptions, _extends({}, variables)); return (phase == null ? void 0 : phase.active.map(function (patternId) { return _this.patterns.find(function (pattern) { return pattern.id === patternId; }); }).reduce(function (results, pattern) { return results.concat((pattern == null ? void 0 : pattern.validateDocument(documentDom, phaseVariables, fontoxpathOptions)) || []); }, [])) || []; } // TODO more optimally store the namespace prefix/uri mapping. Right now its modeled as an array because there // is a list of <ns> elements that are not really guaranteed to use unique prefixes. ; _proto.getNamespaceUriForPrefix = function getNamespaceUriForPrefix(prefix) { if (!prefix) { return null; } var ns = this.namespaces.find(function (ns) { return ns.prefix === prefix; }); if (!ns) { throw new Error("Namespace prefix \"" + prefix + "\" could not be resolved to an URI using <sch:ns>"); } return ns.uri; }; Schema.getElementsByTagName = function getElementsByTagName(node, tagName) { var elements = []; // Helper function to recursively traverse the DOM tree function traverse(currentNode) { if (currentNode.nodeType === 1 && currentNode.tagName === tagName) { elements.push(currentNode); } for (var _iterator = _createForOfIteratorHelperLoose(currentNode.childNodes), _step; !(_step = _iterator()).done;) { var child = _step.value; traverse(child); } } traverse(node); // Start traversing from the given node return elements; }; Schema.processIncludes = function processIncludes(schematronDom, options) { if (options === void 0) { options = {}; } var includeElements = this.getElementsByTagName(schematronDom, 'include'); for (var i = 0; i < includeElements.length; i++) { var includeElement = includeElements[i]; var href = includeElement.getAttribute('href'); if (href) { var _includeElement$paren, _includeElement$paren2; var includedContent = fs.readFileSync(options.resourceDir + "/" + href, 'utf-8'); var includedDom = sync(includedContent); Schema.processIncludes(includedDom); (_includeElement$paren = includeElement.parentNode) == null ? void 0 : _includeElement$paren.insertBefore(includedDom.documentElement, includeElement); (_includeElement$paren2 = includeElement.parentNode) == null ? void 0 : _includeElement$paren2.removeChild(includeElement); } } }; Schema.fromJson = function fromJson(json) { return new Schema(json.title, json.defaultPhase, json.variables.map(function (obj) { return Variable.fromJson(obj); }), json.phases.map(function (obj) { return Phase.fromJson(obj); }), json.patterns.map(function (obj) { return Pattern.fromJson(obj); }), json.namespaces.map(function (obj) { return Namespace.fromJson(obj); })); }; Schema.fromDomToJson = function fromDomToJson(schematronDom) { return evaluateXPath(Schema.QUERY, schematronDom, null, {}, undefined, { language: evaluateXPath.XQUERY_3_1_LANGUAGE }); }; Schema.fromDom = function fromDom(schematronDom) { return Schema.fromJson(Schema.fromDomToJson(schematronDom)); }; Schema.fromString = function fromString(schematronXmlString, options) { var schematronDom = sync(schematronXmlString); Schema.processIncludes(schematronDom, options); return Schema.fromDom(schematronDom); }; return Schema; }(); Schema.QUERY = "\n\t\tdeclare namespace sch = 'http://purl.oclc.org/dsdl/schematron';\n\n\t\tdeclare function local:json($node as node()) {\n\t\t\tif ($node[self::text()])\n\t\t\t\tthen $node/string()\n\t\t\telse\n\t\t\tmap:merge((\n\t\t\t\tmap:entry('$type', $node/local-name()),\n\t\t\t\tfor $attr in $node/@*\n\t\t\t\t\treturn map:entry($attr/name(), $attr/string())\n\t\t\t))\n\t\t};\n\n\t\tlet $context := /*[1]\n\t\treturn map {\n\t\t\t'title': $context/@title/string(),\n\t\t\t'defaultPhase': $context/@defaultPhase/string(),\n\t\t\t'phases': array { $context/sch:phase/" + Phase.QUERY + "},\n\t\t\t'patterns': array { $context/sch:pattern/" + Pattern.QUERY + "},\n\t\t\t'variables': array { $context/sch:let/" + Variable.QUERY + "},\n\t\t\t'namespaces': array { $context/sch:ns/" + Namespace.QUERY + "}\n\t\t}\n\t"; export { Schema }; //# sourceMappingURL=esvit-node-schematron.esm.js.map