UNPKG

lforms

Version:

[LForms](http://lhncbc.github.io/lforms/), a.k.a. LHC-Forms, is a feature-rich, open-source Web Component that creates input forms, based on definition files, for Web-based applications. In addition to its native form-definition format, it partially sup

1,606 lines (1,578 loc) 2.53 MB
/******/ (function() { // webpackBootstrap /******/ var __webpack_modules__ = ([ /* 0 */, /* 1 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ LOINC_URI: function() { return /* binding */ LOINC_URI; } /* harmony export */ }); // Definitions for things needed by both importing and exporting. let LOINC_URI = 'http://loinc.org'; /***/ }), /* 2 */ /***/ (function(module, __unused_webpack_exports, __webpack_require__) { // This is fhirpath interpreter // everything starts at evaluate function, // which is passed fhirpath AST and resource. // // We reduce/eval recursively each node in AST // passing the context and current data // // each AST node has eval function, which should be registered in evalTable // and named after node type // if node needs to eval father it's children it has to call `doEval` function // // most of nodes do function or operator invocation at the end // // For invocation's and operator's there is one lookup table - // invocationTable and two helper functions doInvoke and infixInvoke for // operators // 1. operator or function is looked up in table // 2. using signature (in .arity property) unpack parameters // 3. check params types // 4. do call function // 5. wrap result by util.arraify // // if function is nullable // and one of parameters is empty/null - function will not be invoked and empty // result returned // // Not solved problem is overloading functions by types - for example + operator defined // for strings and numbers // we can make dispatching params type dependent - let see const { version } = __webpack_require__(3); const parser = __webpack_require__(4); const util = __webpack_require__(53); __webpack_require__(69); const constants = __webpack_require__(70); let engine = {}; // the object with all FHIRPath functions and operations let existence = __webpack_require__(71); let filtering = __webpack_require__(72); let aggregate = __webpack_require__(76); let supplements = __webpack_require__(79); let combining = __webpack_require__(80); let misc = __webpack_require__(75); let equality = __webpack_require__(78); let collections = __webpack_require__(81); let math = __webpack_require__(77); let strings = __webpack_require__(82); let navigation = __webpack_require__(83); let datetime = __webpack_require__(84); let additional = __webpack_require__(85); let logic = __webpack_require__(87); const types = __webpack_require__(54); const { FP_Date, FP_DateTime, FP_Time, FP_Quantity, FP_Type, ResourceNode, TypeInfo } = types; let makeResNode = ResourceNode.makeResNode; const Terminologies = __webpack_require__(86); const Factory = __webpack_require__(88); // * fn: handler // * arity: is index map with type signature // if type is in array (like [Boolean]) - this means // function accepts value of this type or empty value {} // * nullable: means propagate empty result, i.e. instead // calling function if one of params is empty return empty engine.invocationTable = { memberOf: { fn: additional.memberOf, arity: { 1: ['String'] } }, empty: { fn: existence.emptyFn }, not: { fn: existence.notFn }, exists: { fn: existence.existsMacro, arity: { 0: [], 1: ["Expr"] } }, all: { fn: existence.allMacro, arity: { 1: ["Expr"] } }, allTrue: { fn: existence.allTrueFn }, anyTrue: { fn: existence.anyTrueFn }, allFalse: { fn: existence.allFalseFn }, anyFalse: { fn: existence.anyFalseFn }, subsetOf: { fn: existence.subsetOfFn, arity: { 1: ["AnyAtRoot"] } }, supersetOf: { fn: existence.supersetOfFn, arity: { 1: ["AnyAtRoot"] } }, isDistinct: { fn: existence.isDistinctFn }, distinct: { fn: filtering.distinctFn }, count: { fn: aggregate.countFn }, where: { fn: filtering.whereMacro, arity: { 1: ["Expr"] } }, extension: { fn: filtering.extension, arity: { 1: ["String"] } }, select: { fn: filtering.selectMacro, arity: { 1: ["Expr"] } }, aggregate: { fn: aggregate.aggregateMacro, arity: { 1: ["Expr"], 2: ["Expr", "AnyAtRoot"] } }, sum: { fn: aggregate.sumFn }, min: { fn: aggregate.minFn }, max: { fn: aggregate.maxFn }, avg: { fn: aggregate.avgFn }, weight: { fn: supplements.weight }, ordinal: { fn: supplements.weight }, single: { fn: filtering.singleFn }, first: { fn: filtering.firstFn }, last: { fn: filtering.lastFn }, type: { fn: types.typeFn, arity: { 0: [] } }, ofType: { fn: filtering.ofTypeFn, arity: { 1: ["TypeSpecifier"] } }, is: { fn: types.isFn, arity: { 1: ["TypeSpecifier"] } }, as: { fn: types.asFn, arity: { 1: ["TypeSpecifier"] } }, tail: { fn: filtering.tailFn }, take: { fn: filtering.takeFn, arity: { 1: ["Integer"] } }, skip: { fn: filtering.skipFn, arity: { 1: ["Integer"] } }, combine: { fn: combining.combineFn, arity: { 1: ["AnyAtRoot"] } }, union: { fn: combining.union, arity: { 1: ["AnyAtRoot"] } }, intersect: { fn: combining.intersect, arity: { 1: ["AnyAtRoot"] } }, exclude: { fn: combining.exclude, arity: { 1: ["AnyAtRoot"] } }, iif: { fn: misc.iifMacro, arity: { 2: ["Expr", "Expr"], 3: ["Expr", "Expr", "Expr"] } }, trace: { fn: misc.traceFn, arity: { 1: ["String"], 2: ["String", "Expr"] } }, defineVariable: { fn: misc.defineVariable, arity: { 1: ["String"], 2: ["String", "Expr"] } }, toInteger: { fn: misc.toInteger }, toDecimal: { fn: misc.toDecimal }, toString: { fn: misc.toString }, toDate: { fn: misc.toDate }, toDateTime: { fn: misc.toDateTime }, toTime: { fn: misc.toTime }, toBoolean: { fn: misc.toBoolean }, toQuantity: { fn: misc.toQuantity, arity: { 0: [], 1: ["String"] } }, hasValue: { fn: misc.hasValueFn }, getValue: { fn: misc.getValueFn }, convertsToBoolean: { fn: misc.createConvertsToFn(misc.toBoolean, 'boolean') }, convertsToInteger: { fn: misc.createConvertsToFn(misc.toInteger, 'number') }, convertsToDecimal: { fn: misc.createConvertsToFn(misc.toDecimal, 'number') }, convertsToString: { fn: misc.createConvertsToFn(misc.toString, 'string') }, convertsToDate: { fn: misc.createConvertsToFn(misc.toDate, FP_Date) }, convertsToDateTime: { fn: misc.createConvertsToFn(misc.toDateTime, FP_DateTime) }, convertsToTime: { fn: misc.createConvertsToFn(misc.toTime, FP_Time) }, convertsToQuantity: { fn: misc.createConvertsToFn(misc.toQuantity, FP_Quantity) }, indexOf: { fn: strings.indexOf, arity: { 1: ["String"] } }, substring: { fn: strings.substring, arity: { 1: ["Integer"], 2: ["Integer", "Integer"] } }, startsWith: { fn: strings.startsWith, arity: { 1: ["String"] } }, endsWith: { fn: strings.endsWith, arity: { 1: ["String"] } }, contains: { fn: strings.containsFn, arity: { 1: ["String"] } }, upper: { fn: strings.upper }, lower: { fn: strings.lower }, replace: { fn: strings.replace, arity: { 2: ["String", "String"] } }, matches: { fn: strings.matches, arity: { 1: ["String"] } }, replaceMatches: { fn: strings.replaceMatches, arity: { 2: ["String", "String"] } }, length: { fn: strings.length }, toChars: { fn: strings.toChars }, join: { fn: strings.joinFn, arity: { 0: [], 1: ["String"] } }, split: { fn: strings.splitFn, arity: { 1: ["String"] } }, trim: { fn: strings.trimFn }, encode: { fn: strings.encodeFn, arity: { 1: ["String"] } }, decode: { fn: strings.decodeFn, arity: { 1: ["String"] } }, abs: { fn: math.abs }, ceiling: { fn: math.ceiling }, exp: { fn: math.exp }, floor: { fn: math.floor }, ln: { fn: math.ln }, log: { fn: math.log, arity: { 1: ["Number"] }, nullable: true }, power: { fn: math.power, arity: { 1: ["Number"] }, nullable: true }, round: { fn: math.round, arity: { 0: [], 1: ["Number"] } }, sqrt: { fn: math.sqrt }, truncate: { fn: math.truncate }, now: { fn: datetime.now }, today: { fn: datetime.today }, timeOfDay: { fn: datetime.timeOfDay }, repeat: { fn: filtering.repeatMacro, arity: { 1: ["Expr"] } }, children: { fn: navigation.children }, descendants: { fn: navigation.descendants }, "|": { fn: combining.union, arity: { 2: ["Any", "Any"] } }, "=": { fn: equality.equal, arity: { 2: ["Any", "Any"] }, nullable: true }, "!=": { fn: equality.unequal, arity: { 2: ["Any", "Any"] }, nullable: true }, "~": { fn: equality.equival, arity: { 2: ["Any", "Any"] } }, "!~": { fn: equality.unequival, arity: { 2: ["Any", "Any"] } }, "<": { fn: equality.lt, arity: { 2: ["Any", "Any"] }, nullable: true }, ">": { fn: equality.gt, arity: { 2: ["Any", "Any"] }, nullable: true }, "<=": { fn: equality.lte, arity: { 2: ["Any", "Any"] }, nullable: true }, ">=": { fn: equality.gte, arity: { 2: ["Any", "Any"] }, nullable: true }, "containsOp": { fn: collections.contains, arity: { 2: ["Any", "Any"] } }, "inOp": { fn: collections.in, arity: { 2: ["Any", "Any"] } }, "isOp": { fn: types.isFn, arity: { 2: ["Any", "TypeSpecifier"] } }, "asOp": { fn: types.asFn, arity: { 2: ["Any", "TypeSpecifier"] } }, "&": { fn: math.amp, arity: { 2: ["String", "String"] } }, "+": { fn: math.plus, arity: { 2: ["Any", "Any"] }, nullable: true }, "-": { fn: math.minus, arity: { 2: ["Any", "Any"] }, nullable: true }, "*": { fn: math.mul, arity: { 2: ["Any", "Any"] }, nullable: true }, "/": { fn: math.div, arity: { 2: ["Any", "Any"] }, nullable: true }, "mod": { fn: math.mod, arity: { 2: ["Number", "Number"] }, nullable: true }, "div": { fn: math.intdiv, arity: { 2: ["Number", "Number"] }, nullable: true }, "or": { fn: logic.orOp, arity: { 2: [["Boolean"], ["Boolean"]] } }, "and": { fn: logic.andOp, arity: { 2: [["Boolean"], ["Boolean"]] } }, "xor": { fn: logic.xorOp, arity: { 2: [["Boolean"], ["Boolean"]] } }, "implies": { fn: logic.impliesOp, arity: { 2: [["Boolean"], ["Boolean"]] } } }; engine.InvocationExpression = function (ctx, parentData, node) { return node.children.reduce(function (acc, ch) { return engine.doEval(ctx, acc, ch); }, parentData); }; engine.TermExpression = function (ctx, parentData, node) { if (parentData) { parentData = parentData.map(x => { if (x instanceof Object && x.resourceType) { return makeResNode(x, null, null, null, null, ctx.model); } return x; }); } return engine.doEval(ctx, parentData, node.children[0]); }; engine.PolarityExpression = function (ctx, parentData, node) { var sign = node.terminalNodeText[0]; // either - or + per grammar var rtn = engine.doEval(ctx, parentData, node.children[0]); if (rtn.length !== 1) { // not yet in spec, but per Bryn Rhodes throw new Error('Unary ' + sign + ' can only be applied to an individual number or Quantity.'); } if (rtn[0] instanceof FP_Quantity) { if (sign === '-') { rtn[0] = new FP_Quantity(-rtn[0].value, rtn[0].unit); } } else if (typeof rtn[0] === 'number' && !isNaN(rtn[0])) { if (sign === '-') { rtn[0] = -rtn[0]; } } else { throw new Error('Unary ' + sign + ' can only be applied to a number or Quantity.'); } return rtn; }; engine.TypeSpecifier = function (ctx, parentData, node) { let namespace, name; const identifiers = node.text.split('.').map(i => i.replace(/(^`|`$)/g, "")); switch (identifiers.length) { case 2: [namespace, name] = identifiers; break; case 1: [name] = identifiers; break; default: throw new Error("Expected TypeSpecifier node, got " + JSON.stringify(node)); } const typeInfo = new TypeInfo({ namespace, name }); if (!typeInfo.isValid(ctx.model)) { throw new Error('"' + typeInfo + '" cannot be resolved to a valid type identifier'); } return typeInfo; }; engine.ExternalConstantTerm = function (ctx, parentData, node) { let varName; const extConstant = node.children[0]; // externalConstant(variable name) is defined in the grammar as: // '%' ( identifier | STRING ) if (extConstant.terminalNodeText.length === 2) { // if the variable name is a STRING varName = getStringLiteralVal(extConstant.terminalNodeText[1]); } else { // otherwise, it is an identifier varName = getIdentifierVal(extConstant.children[0].text); } let value; // Check the user-defined environment variables first as the user can override // the "context" variable like we do in unit tests. In this case, the user // environment variable can replace the system environment variable in "processedVars". // If the user-defined environment variable has been processed, we don't need to process it again. if (varName in ctx.vars && !ctx.processedUserVarNames.has(varName)) { // Restore the ResourceNodes for the top-level objects of the environment // variables. The nested objects will be converted to ResourceNodes // in the MemberInvocation method. value = ctx.vars[varName]; if (Array.isArray(value)) { value = value.map(i => i?.__path__ ? makeResNode(i, i.__path__.parentResNode, i.__path__.path, null, i.__path__.fhirNodeDataType, i.__path__.model) : i?.resourceType ? makeResNode(i, null, null, null, null, ctx.model) : i); } else { value = value?.__path__ ? makeResNode(value, value.__path__.parentResNode, value.__path__.path, null, value.__path__.fhirNodeDataType, value.__path__.model) : value?.resourceType ? makeResNode(value, null, null, null, null, ctx.model) : value; } ctx.processedVars[varName] = value; ctx.processedUserVarNames.add(varName); } else if (varName in ctx.processedVars) { // "processedVars" are variables with ready-to-use values that have already // been converted to ResourceNodes if necessary. value = ctx.processedVars[varName]; } else if (ctx.definedVars && varName in ctx.definedVars) { // "definedVars" are variables defined with the "defineVariable" function. value = ctx.definedVars[varName]; } else { throw new Error("Attempting to access an undefined environment variable: " + varName); } // For convenience, all variable values could be passed in without their array // wrapper. However, when evaluating, we need to put the array back in. return value === undefined || value === null ? [] : value instanceof Array ? value : [value]; }; engine.LiteralTerm = function (ctx, parentData, node) { var term = node.children[0]; if (term) { return engine.doEval(ctx, parentData, term); } else { return [node.text]; } }; engine.StringLiteral = function (ctx, parentData, node) { return [getStringLiteralVal(node.text)]; }; /** * Removes the beginning and ending single-quotes and replaces string escape * sequences. * @param {string} str - string literal * @return {string} */ function getStringLiteralVal(str) { return str.replace(/(^'|'$)/g, "").replace(/\\(u\d{4}|.)/g, function (match, submatch) { switch (match) { case '\\r': return '\r'; case '\\n': return "\n"; case '\\t': return '\t'; case '\\f': return '\f'; default: if (submatch.length > 1) return String.fromCharCode('0x' + submatch.slice(1));else return submatch; } }); } engine.BooleanLiteral = function (ctx, parentData, node) { if (node.text === "true") { return [true]; } else { return [false]; } }; engine.QuantityLiteral = function (ctx, parentData, node) { var valueNode = node.children[0]; var value = Number(valueNode.terminalNodeText[0]); var unitNode = valueNode.children[0]; var unit = unitNode.terminalNodeText[0]; // Sometimes the unit is in a child node of the child if (!unit && unitNode.children) unit = unitNode.children[0].terminalNodeText[0]; return [new FP_Quantity(value, unit)]; }; engine.DateTimeLiteral = function (ctx, parentData, node) { var dateStr = node.text.slice(1); // Remove the @ return [new FP_DateTime(dateStr)]; }; engine.TimeLiteral = function (ctx, parentData, node) { var timeStr = node.text.slice(1); // Remove the @ return [new FP_Time(timeStr)]; }; engine.NumberLiteral = function (ctx, parentData, node) { return [Number(node.text)]; }; engine.Identifier = function (ctx, parentData, node) { return [getIdentifierVal(node.text)]; }; /** * Removes the beginning and ending back-quotes. * @param {string} str - identifier string * @return {string} */ function getIdentifierVal(str) { return str.replace(/(^`|`$)/g, ""); } engine.InvocationTerm = function (ctx, parentData, node) { return engine.doEval(ctx, parentData, node.children[0]); }; engine.MemberInvocation = function (ctx, parentData, node) { const key = engine.doEval(ctx, parentData, node.children[0])[0]; const model = ctx.model; if (parentData) { return parentData.reduce(function (acc, res) { res = makeResNode(res, null, res.__path__?.path, null, res.__path__?.fhirNodeDataType, model); if (res.data?.resourceType === key) { acc.push(res); } else { util.pushFn(acc, util.makeChildResNodes(res, key, model)); } return acc; }, []); } else { return []; } }; engine.IndexerExpression = function (ctx, parentData, node) { const coll_node = node.children[0]; const idx_node = node.children[1]; var coll = engine.doEval(ctx, parentData, coll_node); var idx = engine.doEval(ctx, parentData, idx_node); if (util.isEmpty(idx)) { return []; } var idxNum = parseInt(idx[0]); if (coll && util.isSome(idxNum) && coll.length > idxNum && idxNum >= 0) { return [coll[idxNum]]; } else { return []; } }; engine.Functn = function (ctx, parentData, node) { return node.children.map(function (x) { return engine.doEval(ctx, parentData, x); }); }; engine.realizeParams = function (ctx, parentData, args) { if (args && args[0] && args[0].children) { return args[0].children.map(function (x) { return engine.doEval(ctx, parentData, x); }); } else { return []; } }; function makeParam(ctx, parentData, type, param) { if (type === "Expr") { return function (data) { const $this = util.arraify(data); let ctxExpr = { ...ctx, $this }; if (ctx.definedVars) { // Each parameter subexpression needs its own set of defined variables // (cloned from the parent context). This way, the changes to the variables // are isolated in the subexpression. ctxExpr.definedVars = { ...ctx.definedVars }; } return engine.doEval(ctxExpr, $this, param); }; } if (type === "AnyAtRoot") { const $this = ctx.$this || ctx.dataRoot; let ctxExpr = { ...ctx, $this }; if (ctx.definedVars) { // Each parameter subexpression needs its own set of defined variables // (cloned from the parent context). This way, the changes to the variables // are isolated in the subexpression. ctxExpr.definedVars = { ...ctx.definedVars }; } return engine.doEval(ctxExpr, $this, param); } if (type === "Identifier") { if (param.type === "TermExpression") { return param.text; } else { throw new Error("Expected identifier node, got " + JSON.stringify(param)); } } if (type === "TypeSpecifier") { return engine.TypeSpecifier(ctx, parentData, param); } let res; if (type === 'AnySingletonAtRoot') { const $this = ctx.$this || ctx.dataRoot; let ctxExpr = { ...ctx, $this }; if (ctx.definedVars) { // Each parameter subexpression needs its own set of defined variables // (cloned from the parent context). This way, the changes to the variables // are isolated in the subexpression. ctxExpr.definedVars = { ...ctx.definedVars }; } res = engine.doEval(ctxExpr, $this, param); } else { let ctxExpr = { ...ctx }; if (ctx.definedVars) { // Each parameter subexpression needs its own set of defined variables // (cloned from the parent context). This way, the changes to the variables // are isolated in the subexpression. ctxExpr.definedVars = { ...ctx.definedVars }; } res = engine.doEval(ctxExpr, parentData, param); if (type === "Any") { return res; } if (Array.isArray(type)) { if (res.length === 0) { return []; } else { type = type[0]; } } } return res instanceof Promise ? res.then(r => misc.singleton(r, type)) : misc.singleton(res, type); } function doInvoke(ctx, fnName, data, rawParams) { var invoc = ctx.userInvocationTable && Object.prototype.hasOwnProperty.call(ctx.userInvocationTable, fnName) && ctx.userInvocationTable?.[fnName] || engine.invocationTable[fnName] || data.length === 1 && data[0]?.invocationTable?.[fnName]; var res; if (invoc) { if (!invoc.arity) { if (!rawParams) { res = invoc.fn.call(ctx, data); return util.resolveAndArraify(res); } else { throw new Error(fnName + " expects no params"); } } else { var paramsNumber = rawParams ? rawParams.length : 0; var argTypes = invoc.arity[paramsNumber]; if (argTypes) { var params = []; for (var i = 0; i < paramsNumber; i++) { var tp = argTypes[i]; var pr = rawParams[i]; params.push(makeParam(ctx, data, tp, pr)); } params.unshift(data); if (invoc.nullable) { if (params.some(isNullable)) { return []; } } if (params.some(p => p instanceof Promise)) { return Promise.all(params).then(p => { res = invoc.fn.apply(ctx, p); return util.resolveAndArraify(res); }); } res = invoc.fn.apply(ctx, params); return util.resolveAndArraify(res); } else { console.log(fnName + " wrong arity: got " + paramsNumber); return []; } } } else { throw new Error("Not implemented: " + fnName); } } function isNullable(x) { return x === null || x === undefined || util.isEmpty(x); } function infixInvoke(ctx, fnName, data, rawParams) { var invoc = engine.invocationTable[fnName]; if (invoc && invoc.fn) { var paramsNumber = rawParams ? rawParams.length : 0; if (paramsNumber !== 2) { throw new Error("Infix invoke should have arity 2"); } var argTypes = invoc.arity[paramsNumber]; if (argTypes) { var params = []; for (var i = 0; i < paramsNumber; i++) { var tp = argTypes[i]; var pr = rawParams[i]; params.push(makeParam(ctx, data, tp, pr)); } if (invoc.nullable) { if (params.some(isNullable)) { return []; } } if (params.some(p => p instanceof Promise)) { return Promise.all(params).then(p => { var res = invoc.fn.apply(ctx, p); return util.arraify(res); }); } var res = invoc.fn.apply(ctx, params); return util.arraify(res); } else { console.log(fnName + " wrong arity: got " + paramsNumber); return []; } } else { throw new Error("Not impl " + fnName); } } engine.FunctionInvocation = function (ctx, parentData, node) { var args = engine.doEval(ctx, parentData, node.children[0]); const fnName = args[0]; args.shift(); var rawParams = args && args[0] && args[0].children; return doInvoke(ctx, fnName, parentData, rawParams); }; engine.ParamList = function (ctx, parentData, node) { // we do not eval param list because sometimes it should be passed as // lambda/macro (for example in case of where(...) return node; }; engine.UnionExpression = function (ctx, parentData, node) { return infixInvoke(ctx, '|', parentData, node.children); }; engine.ThisInvocation = function (ctx) { return ctx.$this; }; engine.TotalInvocation = function (ctx) { return util.arraify(ctx.$total); }; engine.IndexInvocation = function (ctx) { return util.arraify(ctx.$index); }; engine.OpExpression = function (ctx, parentData, node) { var op = node.terminalNodeText[0]; return infixInvoke(ctx, op, parentData, node.children); }; engine.AliasOpExpression = function (map) { return function (ctx, parentData, node) { var op = node.terminalNodeText[0]; var alias = map[op]; if (!alias) { throw new Error("Do not know how to alias " + op + " by " + JSON.stringify(map)); } return infixInvoke(ctx, alias, parentData, node.children); }; }; engine.NullLiteral = function () { return []; }; engine.ParenthesizedTerm = function (ctx, parentData, node) { return engine.doEval(ctx, parentData, node.children[0]); }; engine.evalTable = { // not every evaluator is listed if they are defined on engine BooleanLiteral: engine.BooleanLiteral, EqualityExpression: engine.OpExpression, FunctionInvocation: engine.FunctionInvocation, Functn: engine.Functn, Identifier: engine.Identifier, IndexerExpression: engine.IndexerExpression, InequalityExpression: engine.OpExpression, InvocationExpression: engine.InvocationExpression, AdditiveExpression: engine.OpExpression, MultiplicativeExpression: engine.OpExpression, TypeExpression: engine.AliasOpExpression({ "is": "isOp", "as": "asOp" }), MembershipExpression: engine.AliasOpExpression({ "contains": "containsOp", "in": "inOp" }), NullLiteral: engine.NullLiteral, EntireExpression: engine.InvocationTerm, InvocationTerm: engine.InvocationTerm, LiteralTerm: engine.LiteralTerm, MemberInvocation: engine.MemberInvocation, NumberLiteral: engine.NumberLiteral, ParamList: engine.ParamList, ParenthesizedTerm: engine.ParenthesizedTerm, StringLiteral: engine.StringLiteral, TermExpression: engine.TermExpression, ThisInvocation: engine.ThisInvocation, TotalInvocation: engine.TotalInvocation, IndexInvocation: engine.IndexInvocation, UnionExpression: engine.UnionExpression, OrExpression: engine.OpExpression, ImpliesExpression: engine.OpExpression, AndExpression: engine.OpExpression, XorExpression: engine.OpExpression }; engine.doEval = function (ctx, parentData, node) { if (parentData instanceof Promise) { return parentData.then(p => engine.doEvalSync(ctx, p, node)); } else { return engine.doEvalSync(ctx, parentData, node); } }; engine.doEvalSync = function (ctx, parentData, node) { const evaluator = engine.evalTable[node.type] || engine[node.type]; if (evaluator) { return evaluator.call(engine, ctx, parentData, node); } else { throw new Error("No " + node.type + " evaluator "); } }; function parse(path) { return parser.parse(path); } /** * Applies the given parsed FHIRPath expression to the given resource, * returning the result of doEval. * @param {(object|object[])} resource - FHIR resource, bundle as js object or array of resources * This resource will be modified by this function to add type information. * @param {object} parsedPath - a special object created by the parser that describes the structure of a fhirpath expression. * @param {object} envVars - a hash of variable name/value pairs. * @param {object} model - The "model" data object specific to a domain, e.g. R4. * For example, you could pass in the result of require("fhirpath/fhir-context/r4"); * @param {object} options - additional options: * @param {boolean} [options.resolveInternalTypes] - whether values of internal * types should be converted to strings, true by default. * @param {function} [options.traceFn] - An optional trace function to call when tracing. * @param {object} [options.userInvocationTable] - a user invocation table used * to replace any existing or define new functions. * @param {boolean|string} [options.async] - defines how to support asynchronous functions: * false or similar to false, e.g. undefined, null, or 0 (default) - throw an exception; * true or similar to true - return Promise only for asynchronous functions; * "always" - return Promise always. * @param {string} [options.terminologyUrl] - a URL that points to a FHIR * RESTful API that is used to create %terminologies that implements * the Terminology Service API. * @param {AbortSignal} [options.signal] - an AbortSignal object that allows you * to abort the asynchronous FHIRPath expression evaluation. */ function applyParsedPath(resource, parsedPath, envVars, model, options) { constants.reset(); let dataRoot = util.arraify(resource).map(i => i?.__path__ ? makeResNode(i, i.__path__.parentResNode, i.__path__.path, null, i.__path__.fhirNodeDataType, model) : i?.resourceType ? makeResNode(i, null, null, null, null, model) : i); // doEval takes a "ctx" object, and we store things in that as we parse, so we // need to put user-provided variable data in a sub-object, ctx.vars. // Set up default standard variables, and allow override from the variables. // However, we'll keep our own copy of dataRoot for internal processing. let ctx = { dataRoot, processedVars: { ucum: 'http://unitsofmeasure.org', context: dataRoot }, processedUserVarNames: new Set(), vars: envVars || {}, model }; if (options.traceFn) { ctx.customTraceFn = options.traceFn; } if (options.userInvocationTable) { ctx.userInvocationTable = options.userInvocationTable; } if (options.async) { ctx.async = options.async; } if (options.terminologyUrl) { ctx.processedVars.terminologies = new Terminologies(options.terminologyUrl); } ctx.processedVars.factory = Factory; if (options.signal) { ctx.signal = options.signal; if (!ctx.async) { throw new Error('The "signal" option is only supported for asynchronous functions.'); } if (ctx.signal.aborted) { throw new Error('Evaluation of the expression was aborted before it started.'); } } const res = engine.doEval(ctx, dataRoot, parsedPath.children[0]); return res instanceof Promise ? res.then(r => { if (ctx.signal?.aborted) { return Promise.reject(new DOMException('Evaluation of the expression was aborted.', 'AbortError')); } else { return prepareEvalResult(r, model, options); } }) : options.async === 'always' ? Promise.resolve(prepareEvalResult(res, model, options)) : prepareEvalResult(res, model, options); } /** * Prepares the result after evaluating an expression. * engine.doEval returns array of "ResourceNode" and/or "FP_Type" instances. * "ResourceNode" or "FP_Type" instances are not created for sub-items. * Resolves any internal "ResourceNode" instances to plain objects and if * options.resolveInternalTypes is true, resolve any internal "FP_Type" * instances to strings. * @param {Array} result - result of expression evaluation. * @param {object} model - The "model" data object specific to a domain, e.g. R4. * @param {object} options - additional options (see function "applyParsedPath"). * @return {Array} */ function prepareEvalResult(result, model, options) { return result.reduce((acc, n) => { // Path for the data extracted from the resource. let path; let fhirNodeDataType; let parentResNode; if (n instanceof ResourceNode) { path = n.path; fhirNodeDataType = n.fhirNodeDataType; parentResNode = n.parentResNode; } n = util.valData(n); if (n instanceof FP_Type) { if (options.resolveInternalTypes) { n = n.toString(); } } // Exclude nulls if (n != null) { // Add a hidden (non-enumerable) property with the path to the data extracted // from the resource. if (path && typeof n === 'object' && !n.__path__) { Object.defineProperty(n, '__path__', { value: { path, fhirNodeDataType, parentResNode, model } }); } acc.push(n); } return acc; }, []); } /** * Resolves any internal "FP_Type" instances in a result of FHIRPath expression * evaluation to standard JavaScript types. * @param {any} val - a result of FHIRPath expression evaluation * @returns {any} a new object with resolved values. */ function resolveInternalTypes(val) { if (Array.isArray(val)) { for (let i = 0, len = val.length; i < len; ++i) val[i] = resolveInternalTypes(val[i]); } else if (val instanceof FP_Type) { val = val.toString(); } else if (typeof val === 'object') { for (let k of Object.keys(val)) val[k] = resolveInternalTypes(val[k]); } return val; } /** * Evaluates the "path" FHIRPath expression on the given resource or part of the resource, * using data from "context" for variables mentioned in the "path" expression. * @param {(object|object[])} fhirData - FHIR resource, part of a resource (in this case * path.base should be provided), bundle as js object or array of resources. * This object/array will be modified by this function to add type information. * @param {string|object} path - string with FHIRPath expression, sample 'Patient.name.given', * or object, if fhirData represents the part of the FHIR resource: * @param {string} path.base - base path in resource from which fhirData was extracted * @param {string} path.expression - FHIRPath expression relative to path.base * @param {object} [envVars] - a hash of variable name/value pairs. * @param {object} [model] - The "model" data object specific to a domain, e.g. R4. * For example, you could pass in the result of require("fhirpath/fhir-context/r4"); * @param {object} [options] - additional options: * @param {boolean} [options.resolveInternalTypes] - whether values of internal * types should be converted to standard JavaScript types (true by default). * If false is passed, this conversion can be done later by calling * resolveInternalTypes(). * @param {function} [options.traceFn] - An optional trace function to call when tracing. * @param {object} [options.userInvocationTable] - a user invocation table used * to replace any existing or define new functions. * @param {boolean|string} [options.async] - defines how to support asynchronous functions: * false or similar to false, e.g. undefined, null, or 0 (default) - throw an exception, * true or similar to true - return Promise, only for asynchronous functions, * "always" - return Promise always. * @param {string} [options.terminologyUrl] - a URL that points to a FHIR * RESTful API that is used to create %terminologies that implements * the Terminology Service API. * @param {AbortSignal} [options.signal] - an AbortSignal object that allows you * to abort the asynchronous FHIRPath expression evaluation. */ function evaluate(fhirData, path, envVars, model, options) { return compile(path, model, options)(fhirData, envVars); } /** * Returns a function that takes a resource or part of the resource and an * optional context hash (see "evaluate"), and returns the result of evaluating * the given FHIRPath expression on that resource. The advantage of this * function over "evaluate" is that if you have multiple resources, the given * FHIRPath expression will only be parsed once. * @param {string|object} path - string with FHIRPath expression to be parsed or object: * @param {string} path.base - base path in resource from which a part of * the resource was extracted * @param {string} path.expression - FHIRPath expression relative to path.base * @param {object} [model] - The "model" data object specific to a domain, e.g. R4. * For example, you could pass in the result of require("fhirpath/fhir-context/r4"); * @param {object} [options] - additional options: * @param {boolean} [options.resolveInternalTypes] - whether values of internal * types should be converted to strings, true by default. * @param {function} [options.traceFn] - An optional trace function to call when tracing. * @param {object} [options.userInvocationTable] - a user invocation table used * to replace any existing or define new functions. * @param {boolean|string} [options.async] - defines how to support asynchronous functions: * false or similar to false, e.g. undefined, null, or 0 (default) - throw an exception, * true or similar to true - return Promise, only for asynchronous functions, * "always" - return Promise always. * @param {string} [options.terminologyUrl] - a URL that points to a FHIR * RESTful API that is used to create %terminologies that implements * the Terminology Service API. * @param {AbortSignal} [options.signal] - an AbortSignal object that allows you * to abort the asynchronous FHIRPath expression evaluation. Passing a signal * to compile() whose result is used more than once will cause abortion * problems. */ function compile(path, model, options) { options = { resolveInternalTypes: true, ...options }; const userInvocationTable = options.userInvocationTable; if (userInvocationTable) { options.userInvocationTable = Object.keys(userInvocationTable).reduce((invocationTable, fnName) => { if (userInvocationTable[fnName].internalStructures) { invocationTable[fnName] = userInvocationTable[fnName]; } else { invocationTable[fnName] = { ...userInvocationTable[fnName], fn: (...args) => { return userInvocationTable[fnName].fn.apply( // When we check Array.isArray(arg), we are checking if the // singleton function has been called. An alternative to this is // to check that the type of the argument is Integer, Boolean, // Number, or String. this, args.map(arg => Array.isArray(arg) ? arg.map(item => util.valData(item)) : arg)); } }; } return invocationTable; }, {}); } if (typeof path === 'object') { const node = parse(path.expression); return function (fhirData, envVars, additionalOptions) { if (path.base) { let basePath = model.pathsDefinedElsewhere[path.base] || path.base; const baseFhirNodeDataType = model && model.path2Type[basePath]; basePath = baseFhirNodeDataType === 'BackboneElement' || baseFhirNodeDataType === 'Element' ? basePath : baseFhirNodeDataType || basePath; fhirData = makeResNode(fhirData, null, basePath, null, baseFhirNodeDataType, model); } const actualOptions = additionalOptions ? { ...options, ...additionalOptions } : options; return applyParsedPath(fhirData, node, envVars, model, actualOptions); }; } else { const node = parse(path); return function (fhirData, envVars, additionalOptions) { const actualOptions = additionalOptions ? { ...options, ...additionalOptions } : options; return applyParsedPath(fhirData, node, envVars, model, actualOptions); }; } } /** * Returns the type of each element in fhirpathResult array which was obtained * from evaluate() with option resolveInternalTypes=false. * @param {any} fhirpathResult - a result of FHIRPath expression evaluation. * @returns {string[]} an array of types, e.g. ['FHIR.Quantity', 'FHIR.date', 'System.String']. */ function typesFn(fhirpathResult) { return util.arraify(fhirpathResult).map(value => { const ti = TypeInfo.fromValue(value?.__path__ ? new ResourceNode(value, value.__path__?.parentResNode, value.__path__?.path, null, value.__path__?.fhirNodeDataType, value.__path__.model) : value); return `${ti.namespace}.${ti.name}`; }); } module.exports = { version, parse, compile, evaluate, resolveInternalTypes, types: typesFn, // Might as well export the UCUM library, since we are using it. ucumUtils: (__webpack_require__(60).UcumLhcUtils).getInstance(), // Utility functions that can be used to implement custom functions util }; /***/ }), /* 3 */ /***/ (function(module) { "use strict"; module.exports = /*#__PURE__*/JSON.parse('{"name":"fhirpath","version":"3.18.0","description":"A FHIRPath engine","main":"src/fhirpath.js","types":"src/fhirpath.d.ts","dependencies":{"@lhncbc/ucum-lhc":"^5.0.0","antlr4":"~4.9.3","commander":"^2.18.0","date-fns":"^1.30.1","js-yaml":"^3.13.1"},"devDependencies":{"@babel/core":"^7.21.4","@babel/eslint-parser":"^7.17.0","@babel/preset-env":"^7.16.11","babel-loader":"^8.2.3","benny":"github:caderek/benny#0ad058d3c7ef0b488a8fe9ae3519159fc7f36bb6","bestzip":"^2.2.0","copy-webpack-plugin":"^12.0.2","cypress":"^13.7.2","eslint":"^8.10.0","fhir":"^4.10.3","grunt":"^1.5.2","grunt-cli":"^1.4.3","grunt-text-replace":"^0.4.0","jasmine-spec-reporter":"^4.2.1","jest":"^29.7.0","jit-grunt":"^0.10.0","lodash":"^4.17.21","open":"^8.4.0","rimraf":"^3.0.0","tmp":"0.0.33","tsd":"^0.31.1","webpack":"^5.11.1","webpack-bundle-analyzer":"^4.4.2","webpack-cli":"^4.9.1","xml2js":"^0.5.0","yargs":"^15.1.0"},"engines":{"node":">=8.9.0"},"tsd":{"directory":"test/typescript"},"scripts":{"preinstall":"node bin/install-demo.js","postinstall":"echo \\"Building the Benny package based on a pull request which fixes an issue with \'statusShift\'... \\" && (cd node_modules/benny && npm i && npm run build > /dev/null) || echo \\"Building the Benny package is completed.\\"","generateParser":"cd src/parser; rimraf ./generated/*; java -Xmx500M -cp \\"../../antlr-4.9.3-complete.jar:$CLASSPATH\\" org.antlr.v4.Tool -o generated -Dlanguage=JavaScript FHIRPath.g4; grunt updateParserRequirements","build":"cd browser-build && webpack && rimraf fhirpath.zip && bestzip fhirpath.zip LICENSE.md fhirpath.min.js fhirpath.r5.min.js fhirpath.r4.min.js fhirpath.stu3.min.js fhirpath.dstu2.min.js && rimraf LICENSE.md","test:unit":"node --use_strict node_modules/.bin/jest && TZ=America/New_York node --use_strict node_modules/.bin/jest && TZ=Europe/Paris node --use_strict node_modules/.bin/jest","test:unit:debug":"echo \'open chrome chrome://inspect/\' && node --inspect node_modules/.bin/jest --runInBand","build:demo":"npm run build && cd demo && npm run build","test:e2e":"npm run build:demo && cypress run","test:tsd":"tsd","test":"npm run lint && npm run test:tsd && npm run test:unit && npm run test:e2e && echo \\"For tests specific to IE 11, open browser-build/test/index.html in IE 11, and confirm that the tests on that page pass.\\"","lint":"eslint src/parser/index.js src/*.js converter/","compare-performance":"node ./test/benchmark.js"},"bin":{"fhirpath":"bin/fhirpath"},"files":["CHANGELOG.md","bin","fhir-context","src"],"repository":"github:HL7/fhirpath.js","license":"SEE LICENSE in LICENSE.md"}'); /***/ }), /* 4 */ /***/ (function(module, __unused_webpack_exports, __webpack_require__) { const antlr4 = __webpack_require__(5); const Lexer = __webpack_require__(50); const Parser = __webpack_require__(51); const Listener = __webpack_require__(52); class ErrorListener extends antlr4.error.ErrorListener { constructor(errors) { super(); this.errors = errors; } syntaxError(rec, sym, line, col, msg, e) { this.errors.push([rec, sym, line, col, msg, e]); } } var parse = function (path) { var chars = new antlr4.InputStream(path); var lexer = new Lexer(chars); var tokens = new antlr4.CommonTokenStream(lexer); var parser = new Parser(tokens); parser.buildParseTrees = true; var errors = []; var listener = new ErrorListener(errors); lexer.removeErrorListeners(); lexer.addErrorListener(listener); parser.removeErrorListeners(); parser.addErrorListener(listener); var tree = parser.entireExpression(); class PathListener extends Listener { constructor() { super(); } } var ast = {}; var node; var parentStack = [ast]; for (let p of Object.getOwnPropertyNames(Listener.prototype)) { if (p.startsWith("enter")) { PathListener.prototype[p] = function (ctx) { let parentNode = parentStack[parentStack.length - 1]; let nodeType = p.slice(5); // remove "enter" node = { type: nodeType }; node.text = ctx.getText(); if (!parentNode.children) parentNode.children = []; parentNode.children.push(node); parentStack.push(node); // Also collect this node's terminal nodes, if any. Terminal nodes are // not walked with the rest of the tree, but include things like "+" and // "-", which we need. node.terminalNodeText = []; for (let c of ctx.children) { // Test for node type "TerminalNodeImpl". Minimized code no longer // has the original function names, so we can't rely on // c.constructor.name. It appears the TerminalNodeImpl is the only // node with a "symbol" property, so test for that. if (c.symbol) node.terminalNodeText.push(c.getText()); } }; } else if (p.startsWith("exit")) { PathListener.prototype[p] = function () { parentStack.pop(); }; } } var printer = new PathListener(); antlr4.tree.ParseTreeWalker.DEFAULT.walk(printer, tree); if (errors.length > 0) { let errMsgs = []; for (let i = 0, len = errors.length; i < len; ++i) { let err = errors[i]; let msg = "line: " + err[2] + "; column: " + err[3] + "; message: " + err[4]; errMsgs.push(msg); } var e = new Error(errMsgs.join("\n")); e.errors = errors; throw e; } return ast; }; module.exports = { parse: parse }; /***/ }), /* 5 */ /***/ (function(__unused_webpack_module, exports, __webpack_require__) { // This is a modified version of antr4's index.js, in which // the "require" statements of two unused classes are commented out // to avoid introducing a dependency on Node.js' "fs" package. /* Copyright (c) 2012-2017 The ANTLR Project. All rights reserved. * Use of this file is governed by the BSD 3-clause license that * can be found in the LICENSE.txt file in the project root. */ exports.atn = __webpack_require__(6); exports.codepointat = __webpack_require__(37); exports.dfa = __webpack_require__(38); exports.fromcodepoint = __webpack_require__(41); exports.tree = __webpack_require__(42); exports.error = __webpack_require__(43); exports.Token = __webpack_require__(10).Token; // Commented out to avoid the problem with 'fs' during the webpack build // exports.CharStreams = require('antlr4/src/antlr4/CharStreams'); exports.CommonToken = __webpack_require__(10).CommonToken; exports.InputStream = __webpack_require__(46); // Commented out to avoid the problem with 'fs' during the webpack build // exports.FileStream = require('antlr4/src/antlr4/FileStream'); exports.CommonTokenStream = __webpack_require__(47); exports.Lexer = __webpack_require__(25); exports.Parser = __webpack_require__(49); var pc = __webpack_require__(16); exports.PredictionContextCache = pc.PredictionContextCache; exports.ParserRuleContext = __webpack_require__(36); exports.Interval = __webpack_require__(14).Interval; exports.IntervalSet = __webpack_require__(14).IntervalSet; exports.Utils = __webpack_require__(9); exports.LL1Analyzer = __webpack_require__(8).LL1Analyzer; /***/ }), /* 6 */ /***/ (function(__unused_webpack_module, exports, __webpack_require__) { /* Copyright (c) 2012-2017 The ANTLR Project. All rights reserved. * Use of this file is governed by the BSD 3-clause license that * can be found in the LICENSE.txt file in the project root. */ exports.ATN = __webpack_require__(7); exports.ATNDeserializer = __webpack_require__(20); exports.LexerATNSimulator = __webpack_require__(24); exports.ParserATNSimulator = __webpack_require__(34); exports.PredictionMode = __webpack_require__(35); /***/ }), /* 7 */ /***/ (function(module, __unused_webpack_exports, __webpack_require__) { /* Copyright (c) 2012-2017 The ANTLR Project. All rights reserved. * Use of this file is governed by the BSD 3-clause license that * can be found in the LICENSE.txt file in the project root. */ const LL1Analyzer = __webpack_require__(8); const { IntervalSet } = __webpack_require__(14); const { Token } = __webpack_require__(10); class ATN { constructor(grammarType, maxTokenType) { /** * Used for runtime deserialization of ATNs from strings * The type of the ATN. */ this.grammarType = grammarType; // The maximum value for any symbol recognized by a transition in the ATN. this.maxTokenType = maxTokenType; this.states = []; /** * Each subrule/rule is a decision point and we must track them so we * can go back later and build DFA p