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,750 lines (1,732 loc) 2.42 MB
/******/ (function() { // webpackBootstrap /******/ var __webpack_modules__ = ([ /* 0 */, /* 1 */ /***/ (function(__unused_webpack___webpack_module__, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ copyStrict: function() { return /* binding */ copyStrict; }, /* harmony export */ createCopier: function() { return /* binding */ createCopier; }, /* harmony export */ createStrictCopier: function() { return /* binding */ createStrictCopier; }, /* harmony export */ "default": function() { return /* binding */ index; } /* harmony export */ }); var toStringFunction = Function.prototype.toString; var create = Object.create; var toStringObject = Object.prototype.toString; /** * @classdesc Fallback cache for when WeakMap is not natively supported */ var LegacyCache = /** @class */function () { function LegacyCache() { this._keys = []; this._values = []; } LegacyCache.prototype.has = function (key) { return !!~this._keys.indexOf(key); }; LegacyCache.prototype.get = function (key) { return this._values[this._keys.indexOf(key)]; }; LegacyCache.prototype.set = function (key, value) { this._keys.push(key); this._values.push(value); }; return LegacyCache; }(); function createCacheLegacy() { return new LegacyCache(); } function createCacheModern() { return new WeakMap(); } /** * Get a new cache object to prevent circular references. */ var createCache = typeof WeakMap !== 'undefined' ? createCacheModern : createCacheLegacy; /** * Get an empty version of the object with the same prototype it has. */ function getCleanClone(prototype) { if (!prototype) { return create(null); } var Constructor = prototype.constructor; if (Constructor === Object) { return prototype === Object.prototype ? {} : create(prototype); } if (Constructor && ~toStringFunction.call(Constructor).indexOf('[native code]')) { try { return new Constructor(); } catch (_a) {} } return create(prototype); } function getRegExpFlagsLegacy(regExp) { var flags = ''; if (regExp.global) { flags += 'g'; } if (regExp.ignoreCase) { flags += 'i'; } if (regExp.multiline) { flags += 'm'; } if (regExp.unicode) { flags += 'u'; } if (regExp.sticky) { flags += 'y'; } return flags; } function getRegExpFlagsModern(regExp) { return regExp.flags; } /** * Get the flags to apply to the copied regexp. */ var getRegExpFlags = /test/g.flags === 'g' ? getRegExpFlagsModern : getRegExpFlagsLegacy; function getTagLegacy(value) { var type = toStringObject.call(value); return type.substring(8, type.length - 1); } function getTagModern(value) { return value[Symbol.toStringTag] || getTagLegacy(value); } /** * Get the tag of the value passed, so that the correct copier can be used. */ var getTag = typeof Symbol !== 'undefined' ? getTagModern : getTagLegacy; var defineProperty = Object.defineProperty, getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor, getOwnPropertyNames = Object.getOwnPropertyNames, getOwnPropertySymbols = Object.getOwnPropertySymbols; var _a = Object.prototype, hasOwnProperty = _a.hasOwnProperty, propertyIsEnumerable = _a.propertyIsEnumerable; var SUPPORTS_SYMBOL = typeof getOwnPropertySymbols === 'function'; function getStrictPropertiesModern(object) { return getOwnPropertyNames(object).concat(getOwnPropertySymbols(object)); } /** * Get the properites used when copying objects strictly. This includes both keys and symbols. */ var getStrictProperties = SUPPORTS_SYMBOL ? getStrictPropertiesModern : getOwnPropertyNames; /** * Striclty copy all properties contained on the object. */ function copyOwnPropertiesStrict(value, clone, state) { var properties = getStrictProperties(value); for (var index = 0, length_1 = properties.length, property = void 0, descriptor = void 0; index < length_1; ++index) { property = properties[index]; if (property === 'callee' || property === 'caller') { continue; } descriptor = getOwnPropertyDescriptor(value, property); if (!descriptor) { // In extra edge cases where the property descriptor cannot be retrived, fall back to // the loose assignment. clone[property] = state.copier(value[property], state); continue; } // Only clone the value if actually a value, not a getter / setter. if (!descriptor.get && !descriptor.set) { descriptor.value = state.copier(descriptor.value, state); } try { defineProperty(clone, property, descriptor); } catch (error) { // Tee above can fail on node in edge cases, so fall back to the loose assignment. clone[property] = descriptor.value; } } return clone; } /** * Deeply copy the indexed values in the array. */ function copyArrayLoose(array, state) { var clone = new state.Constructor(); // set in the cache immediately to be able to reuse the object recursively state.cache.set(array, clone); for (var index = 0, length_2 = array.length; index < length_2; ++index) { clone[index] = state.copier(array[index], state); } return clone; } /** * Deeply copy the indexed values in the array, as well as any custom properties. */ function copyArrayStrict(array, state) { var clone = new state.Constructor(); // set in the cache immediately to be able to reuse the object recursively state.cache.set(array, clone); return copyOwnPropertiesStrict(array, clone, state); } /** * Copy the contents of the ArrayBuffer. */ function copyArrayBuffer(arrayBuffer, _state) { return arrayBuffer.slice(0); } /** * Create a new Blob with the contents of the original. */ function copyBlob(blob, _state) { return blob.slice(0, blob.size, blob.type); } /** * Create a new DataView with the contents of the original. */ function copyDataView(dataView, state) { return new state.Constructor(copyArrayBuffer(dataView.buffer)); } /** * Create a new Date based on the time of the original. */ function copyDate(date, state) { return new state.Constructor(date.getTime()); } /** * Deeply copy the keys and values of the original. */ function copyMapLoose(map, state) { var clone = new state.Constructor(); // set in the cache immediately to be able to reuse the object recursively state.cache.set(map, clone); map.forEach(function (value, key) { clone.set(key, state.copier(value, state)); }); return clone; } /** * Deeply copy the keys and values of the original, as well as any custom properties. */ function copyMapStrict(map, state) { return copyOwnPropertiesStrict(map, copyMapLoose(map, state), state); } function copyObjectLooseLegacy(object, state) { var clone = getCleanClone(state.prototype); // set in the cache immediately to be able to reuse the object recursively state.cache.set(object, clone); for (var key in object) { if (hasOwnProperty.call(object, key)) { clone[key] = state.copier(object[key], state); } } return clone; } function copyObjectLooseModern(object, state) { var clone = getCleanClone(state.prototype); // set in the cache immediately to be able to reuse the object recursively state.cache.set(object, clone); for (var key in object) { if (hasOwnProperty.call(object, key)) { clone[key] = state.copier(object[key], state); } } var symbols = getOwnPropertySymbols(object); for (var index = 0, length_3 = symbols.length, symbol = void 0; index < length_3; ++index) { symbol = symbols[index]; if (propertyIsEnumerable.call(object, symbol)) { clone[symbol] = state.copier(object[symbol], state); } } return clone; } /** * Deeply copy the properties (keys and symbols) and values of the original. */ var copyObjectLoose = SUPPORTS_SYMBOL ? copyObjectLooseModern : copyObjectLooseLegacy; /** * Deeply copy the properties (keys and symbols) and values of the original, as well * as any hidden or non-enumerable properties. */ function copyObjectStrict(object, state) { var clone = getCleanClone(state.prototype); // set in the cache immediately to be able to reuse the object recursively state.cache.set(object, clone); return copyOwnPropertiesStrict(object, clone, state); } /** * Create a new primitive wrapper from the value of the original. */ function copyPrimitiveWrapper(primitiveObject, state) { return new state.Constructor(primitiveObject.valueOf()); } /** * Create a new RegExp based on the value and flags of the original. */ function copyRegExp(regExp, state) { var clone = new state.Constructor(regExp.source, getRegExpFlags(regExp)); clone.lastIndex = regExp.lastIndex; return clone; } /** * Return the original value (an identity function). * * @note * THis is used for objects that cannot be copied, such as WeakMap. */ function copySelf(value, _state) { return value; } /** * Deeply copy the values of the original. */ function copySetLoose(set, state) { var clone = new state.Constructor(); // set in the cache immediately to be able to reuse the object recursively state.cache.set(set, clone); set.forEach(function (value) { clone.add(state.copier(value, state)); }); return clone; } /** * Deeply copy the values of the original, as well as any custom properties. */ function copySetStrict(set, state) { return copyOwnPropertiesStrict(set, copySetLoose(set, state), state); } var isArray = Array.isArray; var assign = Object.assign; var getPrototypeOf = Object.getPrototypeOf || function (obj) { return obj.__proto__; }; var DEFAULT_LOOSE_OPTIONS = { array: copyArrayLoose, arrayBuffer: copyArrayBuffer, blob: copyBlob, dataView: copyDataView, date: copyDate, error: copySelf, map: copyMapLoose, object: copyObjectLoose, regExp: copyRegExp, set: copySetLoose }; var DEFAULT_STRICT_OPTIONS = assign({}, DEFAULT_LOOSE_OPTIONS, { array: copyArrayStrict, map: copyMapStrict, object: copyObjectStrict, set: copySetStrict }); /** * Get the copiers used for each specific object tag. */ function getTagSpecificCopiers(options) { return { Arguments: options.object, Array: options.array, ArrayBuffer: options.arrayBuffer, Blob: options.blob, Boolean: copyPrimitiveWrapper, DataView: options.dataView, Date: options.date, Error: options.error, Float32Array: options.arrayBuffer, Float64Array: options.arrayBuffer, Int8Array: options.arrayBuffer, Int16Array: options.arrayBuffer, Int32Array: options.arrayBuffer, Map: options.map, Number: copyPrimitiveWrapper, Object: options.object, Promise: copySelf, RegExp: options.regExp, Set: options.set, String: copyPrimitiveWrapper, WeakMap: copySelf, WeakSet: copySelf, Uint8Array: options.arrayBuffer, Uint8ClampedArray: options.arrayBuffer, Uint16Array: options.arrayBuffer, Uint32Array: options.arrayBuffer, Uint64Array: options.arrayBuffer }; } /** * Create a custom copier based on the object-specific copy methods passed. */ function createCopier(options) { var normalizedOptions = assign({}, DEFAULT_LOOSE_OPTIONS, options); var tagSpecificCopiers = getTagSpecificCopiers(normalizedOptions); var array = tagSpecificCopiers.Array, object = tagSpecificCopiers.Object; function copier(value, state) { state.prototype = state.Constructor = undefined; if (!value || typeof value !== 'object') { return value; } if (state.cache.has(value)) { return state.cache.get(value); } state.prototype = getPrototypeOf(value); state.Constructor = state.prototype && state.prototype.constructor; // plain objects if (!state.Constructor || state.Constructor === Object) { return object(value, state); } // arrays if (isArray(value)) { return array(value, state); } var tagSpecificCopier = tagSpecificCopiers[getTag(value)]; if (tagSpecificCopier) { return tagSpecificCopier(value, state); } return typeof value.then === 'function' ? value : object(value, state); } return function copy(value) { return copier(value, { Constructor: undefined, cache: createCache(), copier: copier, prototype: undefined }); }; } /** * Create a custom copier based on the object-specific copy methods passed, defaulting to the * same internals as `copyStrict`. */ function createStrictCopier(options) { return createCopier(assign({}, DEFAULT_STRICT_OPTIONS, options)); } /** * Copy an value deeply as much as possible, where strict recreation of object properties * are maintained. All properties (including non-enumerable ones) are copied with their * original property descriptors on both objects and arrays. */ var copyStrict = createStrictCopier({}); /** * Copy an value deeply as much as possible. */ var index = createCopier({}); /***/ }), /* 2 */ /***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); let fhirVersion = 'R4B', sdcVersion = '3.0', fhirVersionNum = '4.3'; /* harmony default export */ __webpack_exports__["default"] = ({ fhirVersion: fhirVersion, //Needed by lfData for fhirpath, etc. SDCVersion: sdcVersion, QProfile: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire|' + sdcVersion, QRProfile: 'http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaireresponse|' + sdcVersion, stdQProfile: 'http://hl7.org/fhir/' + fhirVersionNum + '/StructureDefinition/Questionnaire', stdQRProfile: 'http://hl7.org/fhir/' + fhirVersionNum + '/StructureDefinition/QuestionnaireResponse' }); /***/ }), /* 3 */ /***/ (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'; /***/ }), /* 4 */ /***/ (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__(5); const parser = __webpack_require__(6); const util = __webpack_require__(55); __webpack_require__(71); const constants = __webpack_require__(72); let engine = {}; // the object with all FHIRPath functions and operations let existence = __webpack_require__(73); let filtering = __webpack_require__(74); let aggregate = __webpack_require__(78); let supplements = __webpack_require__(81); let combining = __webpack_require__(82); let misc = __webpack_require__(77); let equality = __webpack_require__(80); let collections = __webpack_require__(83); let math = __webpack_require__(79); let strings = __webpack_require__(84); let navigation = __webpack_require__(85); let datetime = __webpack_require__(86); let additional = __webpack_require__(87); let logic = __webpack_require__(89); const types = __webpack_require__(56); const { FP_Date, FP_DateTime, FP_Time, FP_Quantity, FP_Type, ResourceNode, TypeInfo } = types; let makeResNode = ResourceNode.makeResNode; const Terminologies = __webpack_require__(88); const Factory = __webpack_require__(90); // * 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