UNPKG

fhir

Version:

Library that assists in handling FHIR resources. Supports serialization between JSON and XML, validation and FhirPath evaluation.

283 lines 11.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FhirPath = void 0; const parseConformance_1 = require("./parseConformance"); class FhirPath { constructor(resources, parser) { this.operators = ['=', '!', '&', '<', '>', '~']; this.resources = resources instanceof Array ? resources : [resources]; this.parser = parser ? parser : new parseConformance_1.ParseConformance(true); } findClosingParenIndex(string, startIndex) { let parenLevel = 0; for (let i = startIndex; i < string.length; i++) { if (string[i] === '(') { parenLevel++; } else if (string[i] === ')') { if (parenLevel > 0) { parenLevel--; } else { return i; } } } } findClosingQuoteIndex(string, startIndex) { for (let i = startIndex; i < string.length; i++) { if (string[i] === '\'') { if (string[i - 1] === '\\') { continue; } return i; } } } internalResolve(reference) { const regex = /^([A-z]+)\/(.+?)$/; const match = reference.trim().match(regex); function find(resources, resourceType, id) { for (let i = 0; i < resources.length; i++) { const resource = resources[i]; if (resource.resourceType === 'Bundle') { const childResources = resource.entry.map(e => e.resource); const found = find(childResources, resourceType, id); if (found) { return found; } } if (resource.resourceType.toLowerCase() !== resourceType.toLowerCase()) { continue; } if (resource.id.toLowerCase() !== id.toLowerCase()) { continue; } return resource; } } if (match) { const found = find(this.resources, match[1], match[2]); if (found) { return found; } } return this.resolve(reference); } resolve(reference) { return; } getResourceTypes() { const self = this; const keys = Object.keys(this.parser.parsedStructureDefinitions); return keys.filter(key => self.parser.parsedStructureDefinitions[key]._kind === 'resource'); } parse(fhirPath) { const statements = []; let ns = {}; const fhirPathSplit = fhirPath.split('.'); const resourceTypes = this.getResourceTypes(); if (fhirPathSplit.length > 0 && resourceTypes.indexOf(fhirPathSplit[0]) >= 0) { ns.resourceType = fhirPathSplit[0]; fhirPath = fhirPath.substring(fhirPathSplit[0].length + 1); } for (let i = 0; i < fhirPath.length; i++) { const char = fhirPath[i]; if (char === '\'') { if (i === 0) { const closingQuoteIndex = this.findClosingQuoteIndex(fhirPath, i + 1); ns.value = fhirPath.substring(i + 1, closingQuoteIndex); i = closingQuoteIndex; } } else if (char === '(') { if (ns.path && ns.path.length > 0) { const fn = { name: ns.path.pop().toLowerCase() }; ns.path.push(fn); const closingParenIndex = this.findClosingParenIndex(fhirPath, i + 1); const fnParams = fhirPath.substring(i + 1, closingParenIndex); fn.params = this.parse(fnParams); i = closingParenIndex; } } else if (char === '\'') { } else if (char === ' ') { } else if (this.operators.indexOf(char) >= 0) { const left = ns; let rightPath = fhirPath.substring(i + 1); let operator = char; if (this.operators.indexOf(rightPath[0]) >= 0) { operator += rightPath[0]; rightPath = rightPath.substring(1); } ns = { left: left, right: this.parse(rightPath.trim())[0], op: operator }; if (ns.left.path && ns.left.path.length > 0) { ns.left.path[ns.left.path.length - 1] = ns.left.path[ns.left.path.length - 1].trim(); } if (ns.right.path && ns.right.path.length > 0) { ns.right.path[ns.right.path.length - 1] = ns.right.path[ns.right.path.length - 1].trim(); } break; } else if (char === '.') { ns.path.push(''); } else { if (!ns.hasOwnProperty('path')) { ns.path = ['']; } ns.path[ns.path.length - 1] += char; } } statements.push(ns); return statements; } getValue(current, paths) { if (current === undefined || current == null) { return current; } if (!paths || paths.length === 0) { return current; } const nextPath = paths[0]; const nextPaths = paths.slice(1); if (current instanceof Array) { if (typeof nextPath === 'string') { let ret = []; nextPaths.unshift(nextPath); for (let i = 0; i < current.length; i++) { const currentRet = this.getValue(current[i], nextPaths); if (currentRet instanceof Array) { ret = ret.concat(currentRet); } else if (currentRet !== undefined && currentRet !== null) { ret.push(currentRet); } } return ret; } else if (nextPath.name === 'first') { return this.getValue(current[0], nextPaths); } else if (nextPath.name === 'last') { return this.getValue(current[current.length - 1], nextPaths); } else if (nextPath.name === 'where') { if (!nextPath.params || nextPath.params.length === 0) { throw new Error('Expected .where() to have a parameter'); } const filtered = []; for (let i = 0; i < current.length; i++) { const paramsClone = JSON.parse(JSON.stringify(nextPath.params)); const results = this.internalEvaluate(current[i], paramsClone); if (typeof results === 'boolean' && results === true) { filtered.push(current[i]); } else if (results instanceof Array && results.length === 1 && results[0]) { filtered.push(current[i]); } } return this.getValue(filtered, nextPaths); } else { throw new Error('Unsupported function for arrays ' + nextPath.name); } } else { if (typeof nextPath === 'string') { return this.getValue(current[nextPath], nextPaths); } else if (nextPath.name === 'resolve') { const reference = typeof current === 'string' ? current : current.reference; const resource = this.internalResolve(reference); return this.getValue(resource, nextPaths); } else if (nextPath.name === 'startswith') { if (!nextPath.params || nextPath.params.length !== 1) { throw new Error('Expected a single parameter to startsWith()'); } if (typeof current !== 'string') { throw new Error('startsWith() must be used on string types'); } const paramValue = nextPath.params[0].value || this.getValue(current, nextPath.params[0].path); if (!paramValue || current.indexOf(paramValue) !== 0) { return false; } return true; } else { throw new Error('Unsupported function for objects ' + nextPath.name); } } } internalEvaluate(resource, statements) { let ret = []; for (let i = 0; i < statements.length; i++) { const statement = statements[i]; if (statement.path) { statement.value = this.getValue(resource, statement.path); } if (statement.left && statement.left.path) { statement.left.value = this.getValue(resource, statement.left.path); } if (statement.right && statement.right.path) { statement.right.value = this.getValue(resource, statement.right.path); } if (statement.op) { if (!statement.left || !statement.right) { return false; } switch (statement.op) { case '=': case '==': return statement.left.value === statement.right.value; case '!=': return statement.left.value !== statement.right.value; } } else { if (statement.value instanceof Array) { ret = ret.concat(statement.value); } else { ret.push(statement.value); } } } return ret; } shouldReturnArray(statements) { if (statements.length === 1) { const statementHasWhereFn = (statements[0].path || []).filter(nextPath => nextPath.name === 'where'); if (statementHasWhereFn.length > 0) { return true; } } return false; } evaluate(fhirPath) { if (!fhirPath) { return; } const statements = this.parse(fhirPath); let ret = []; for (let r = 0; r < this.resources.length; r++) { const resource = this.resources[r]; ret = ret.concat(this.internalEvaluate(resource, statements)); } if (this.resources.length === 1 && ret.length === 1 && !this.shouldReturnArray(statements)) { return ret[0]; } return ret; } } exports.FhirPath = FhirPath; //# sourceMappingURL=fhirPath.js.map