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,752 lines (1,733 loc) • 2.28 MB
JavaScript
/******/ (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 = 'STU3',
fhirVersionNum = '3.0';
/* harmony default export */ __webpack_exports__["default"] = ({
fhirVersion: fhirVersion,
//Needed by lfData for fhirpath, etc.
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)(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 a