Revert "update R4B FHIRpath implementation"

This reverts commit 3abd19cc4d.
This commit is contained in:
Grahame Grieve 2023-06-21 09:19:19 +10:00
parent 661a0a6919
commit 3f6ec1da47
3 changed files with 62 additions and 163 deletions

View File

@ -56,7 +56,7 @@ public class ExpressionNode {
Encode, Decode, Escape, Unescape, Trim, Split, Join, LowBoundary, HighBoundary, Precision,
// Local extensions to FHIRPath
HtmlChecks1, HtmlChecks2, AliasAs, Alias, Comparable;
HtmlChecks1, HtmlChecks2, AliasAs, Alias;
public static Function fromCode(String name) {
if (name.equals("empty")) return Function.Empty;
@ -118,7 +118,6 @@ public class ExpressionNode {
if (name.equals("htmlChecks")) return Function.HtmlChecks1;
if (name.equals("htmlchecks")) return Function.HtmlChecks1; // support change of care from R3
if (name.equals("htmlChecks2")) return Function.HtmlChecks2;
if (name.equals("comparable")) return Function.Comparable;
if (name.equals("encode")) return Function.Encode;
if (name.equals("decode")) return Function.Decode;
if (name.equals("escape")) return Function.Escape;
@ -153,7 +152,7 @@ public class ExpressionNode {
if (name.equals("ln")) return Function.Ln;
if (name.equals("log")) return Function.Log;
if (name.equals("power")) return Function.Power;
if (name.equals("truncate")) return Function.Truncate;
if (name.equals("truncate")) return Function.Truncate;
if (name.equals("lowBoundary")) return Function.LowBoundary;
if (name.equals("highBoundary")) return Function.HighBoundary;
if (name.equals("precision")) return Function.Precision;
@ -227,7 +226,6 @@ public class ExpressionNode {
case Join : return "join";
case HtmlChecks1 : return "htmlChecks";
case HtmlChecks2 : return "htmlChecks2";
case Comparable : return "comparable";
case OfType : return "ofType";
case Type : return "type";
case ToInteger : return "toInteger";

View File

@ -49,15 +49,15 @@ import org.hl7.fhir.utilities.Utilities;
public class TypeDetails {
public static final String FHIR_NS = "http://hl7.org/fhir/StructureDefinition/";
public static final String FP_NS = "http://hl7.org/fhirpath/";
public static final String FP_String = "http://hl7.org/fhirpath/System.String";
public static final String FP_Boolean = "http://hl7.org/fhirpath/System.Boolean";
public static final String FP_Integer = "http://hl7.org/fhirpath/System.Integer";
public static final String FP_Decimal = "http://hl7.org/fhirpath/System.Decimal";
public static final String FP_Quantity = "http://hl7.org/fhirpath/System.Quantity";
public static final String FP_DateTime = "http://hl7.org/fhirpath/System.DateTime";
public static final String FP_Time = "http://hl7.org/fhirpath/System.Time";
public static final String FP_SimpleTypeInfo = "http://hl7.org/fhirpath/System.SimpleTypeInfo";
public static final String FP_ClassInfo = "http://hl7.org/fhirpath/System.ClassInfo";
public static final String FP_String = "http://hl7.org/fhirpath/String";
public static final String FP_Boolean = "http://hl7.org/fhirpath/Boolean";
public static final String FP_Integer = "http://hl7.org/fhirpath/Integer";
public static final String FP_Decimal = "http://hl7.org/fhirpath/Decimal";
public static final String FP_Quantity = "http://hl7.org/fhirpath/Quantity";
public static final String FP_DateTime = "http://hl7.org/fhirpath/DateTime";
public static final String FP_Time = "http://hl7.org/fhirpath/Time";
public static final String FP_SimpleTypeInfo = "http://hl7.org/fhirpath/SimpleTypeInfo";
public static final String FP_ClassInfo = "http://hl7.org/fhirpath/ClassInfo";
public static final Set<String> FP_NUMBERS = new HashSet<String>(Arrays.asList(FP_Integer, FP_Decimal));
public static class ProfiledType {
@ -187,7 +187,7 @@ public class TypeDetails {
if (typesContains(t))
return true;
if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) {
t = FP_NS+"System."+Utilities.capitalize(n);
t = FP_NS+Utilities.capitalize(n);
if (typesContains(t))
return true;
}
@ -224,7 +224,7 @@ public class TypeDetails {
if (url.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
String code = url.substring(40);
if (Utilities.existsInList(code, "string", "boolean", "integer", "decimal", "dateTime", "time", "Quantity"))
return FP_NS+"System."+Utilities.capitalize(code);
return FP_NS+Utilities.capitalize(code);
}
return null;
}
@ -299,7 +299,7 @@ public class TypeDetails {
if (typesContains(t))
return true;
if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "date", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) {
t = FP_NS+"System."+Utilities.capitalize(n);
t = FP_NS+Utilities.capitalize(n);
if (typesContains(t))
return true;
}
@ -312,7 +312,7 @@ public class TypeDetails {
if (typesContains(t))
return true;
if (Utilities.existsInList(n, "boolean", "string", "integer", "decimal", "Quantity", "dateTime", "time", "ClassInfo", "SimpleTypeInfo")) {
t = FP_NS+"System."+Utilities.capitalize(n);
t = FP_NS+Utilities.capitalize(n);
if (typesContains(t))
return true;
}
@ -360,8 +360,4 @@ public class TypeDetails {
}
public boolean matches(TypeDetails other) {
return collectionStatus == other.collectionStatus && types.equals(other.types);
}
}

View File

@ -59,7 +59,6 @@ import org.hl7.fhir.r4b.model.ValueSet;
import org.hl7.fhir.r4b.utils.FHIRLexer.FHIRLexerException;
import org.hl7.fhir.r4b.utils.FHIRPathEngine.IEvaluationContext.FunctionDetails;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.MarkDownProcessor;
import org.hl7.fhir.utilities.MergedList;
import org.hl7.fhir.utilities.MergedList.MergeNode;
import org.hl7.fhir.utilities.SourceLocation;
@ -596,7 +595,7 @@ public class FHIRPathEngine {
}
}
return executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, true, false);
return executeType(new ExecutionTypeContext(appContext, resourceType, types, types), types, expr, true);
}
private FHIRException makeExceptionPlural(Integer num, ExpressionNode holder, String constName, Object... args) {
@ -645,13 +644,13 @@ public class FHIRPathEngine {
}
}
return executeType(new ExecutionTypeContext(appContext, sd.getUrl(), types, types), types, expr, true, false);
return executeType(new ExecutionTypeContext(appContext, sd.getUrl(), types, types), types, expr, true);
}
public TypeDetails check(Object appContext, StructureDefinition sd, ExpressionNode expr) throws FHIRLexerException, PathEngineException, DefinitionException {
// if context is a path that refers to a type, do that conversion now
TypeDetails types = null; // this is a special case; the first path reference will have to resolve to something in the context
return executeType(new ExecutionTypeContext(appContext, sd == null ? null : sd.getUrl(), null, types), types, expr, true, false);
return executeType(new ExecutionTypeContext(appContext, sd == null ? null : sd.getUrl(), null, types), types, expr, true);
}
public TypeDetails check(Object appContext, String resourceType, String context, String expr) throws FHIRLexerException, PathEngineException, DefinitionException {
@ -1401,7 +1400,6 @@ public class FHIRPathEngine {
case Join: return checkParamCount(lexer, location, exp, 1);
case HtmlChecks1: return checkParamCount(lexer, location, exp, 0);
case HtmlChecks2: return checkParamCount(lexer, location, exp, 0);
case Comparable: return checkParamCount(lexer, location, exp, 1);
case ToInteger: return checkParamCount(lexer, location, exp, 0);
case ToDecimal: return checkParamCount(lexer, location, exp, 0);
case ToString: return checkParamCount(lexer, location, exp, 0);
@ -1545,7 +1543,7 @@ public class FHIRPathEngine {
return new TypeDetails(CollectionStatus.SINGLETON, exp.getName());
}
private TypeDetails executeType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry, boolean canBeNone) throws PathEngineException, DefinitionException {
private TypeDetails executeType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean atEntry) throws PathEngineException, DefinitionException {
TypeDetails result = new TypeDetails(null);
switch (exp.getKind()) {
case Name:
@ -1561,17 +1559,13 @@ public class FHIRPathEngine {
for (String s : focus.getTypes()) {
result.update(executeType(s, exp, atEntry));
}
if (result.hasNoTypes()) {
if (!canBeNone) {
throw makeException(exp, I18nConstants.FHIRPATH_UNKNOWN_NAME, exp.getName(), focus.describe());
} else {
// return result;
}
if (result.hasNoTypes()) {
throw makeException(exp, I18nConstants.FHIRPATH_UNKNOWN_NAME, exp.getName(), focus.describe());
}
}
break;
case Function:
result.update(evaluateFunctionType(context, focus, exp, canBeNone));
result.update(evaluateFunctionType(context, focus, exp));
break;
case Unary:
result.addType(TypeDetails.FP_Integer);
@ -1582,12 +1576,12 @@ public class FHIRPathEngine {
result.update(resolveConstantType(context, exp.getConstant(), exp));
break;
case Group:
result.update(executeType(context, focus, exp.getGroup(), atEntry, canBeNone));
result.update(executeType(context, focus, exp.getGroup(), atEntry));
}
exp.setTypes(result);
if (exp.getInner() != null) {
result = executeType(context, result, exp.getInner(), false, false);
result = executeType(context, result, exp.getInner(), false);
}
if (exp.isProximal() && exp.getOperation() != null) {
@ -1598,7 +1592,7 @@ public class FHIRPathEngine {
if (last.getOperation() == Operation.Is || last.getOperation() == Operation.As) {
work = executeTypeName(context, focus, next, atEntry);
} else {
work = executeType(context, focus, next, atEntry, canBeNone);
work = executeType(context, focus, next, atEntry);
}
result = operateTypes(result, last.getOperation(), work, last);
last = next;
@ -3109,36 +3103,21 @@ public class FHIRPathEngine {
}
private void evaluateParameters(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, List<TypeDetails> paramTypes, boolean canBeNone) {
int i = 0;
for (ExpressionNode expr : exp.getParameters()) {
if (isExpressionParameter(exp, i)) {
paramTypes.add(executeType(changeThis(context, focus), focus, expr, true, canBeNone));
} else {
paramTypes.add(executeType(context, context.thisItem, expr, true, canBeNone));
}
i++;
}
}
@SuppressWarnings("unchecked")
private TypeDetails evaluateFunctionType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp, boolean canBeNone) throws PathEngineException, DefinitionException {
private TypeDetails evaluateFunctionType(ExecutionTypeContext context, TypeDetails focus, ExpressionNode exp) throws PathEngineException, DefinitionException {
List<TypeDetails> paramTypes = new ArrayList<TypeDetails>();
if (exp.getFunction() == Function.Is || exp.getFunction() == Function.As || exp.getFunction() == Function.OfType) {
paramTypes.add(new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
} else if (exp.getFunction() == Function.Repeat && exp.getParameters().size() == 1) {
TypeDetails base = focus;
boolean changed = false;
do {
evaluateParameters(context, base, exp, paramTypes, true);
changed = !paramTypes.get(0).matches(base);
if (changed) {
base = paramTypes.get(0);
paramTypes.clear();
}
} while (changed);
} else {
evaluateParameters(context, focus, exp, paramTypes, false);
int i = 0;
for (ExpressionNode expr : exp.getParameters()) {
if (isExpressionParameter(exp, i)) {
paramTypes.add(executeType(changeThis(context, focus), focus, expr, true));
} else {
paramTypes.add(executeType(context, context.thisItem, expr, true));
}
i++;
}
}
switch (exp.getFunction()) {
case Empty :
@ -3166,11 +3145,11 @@ public class FHIRPathEngine {
case Where :
return focus;
case Select :
return paramTypes.get(0);
return anything(focus.getCollectionStatus());
case All :
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
case Repeat :
return paramTypes.get(0);
return anything(focus.getCollectionStatus());
case Aggregate :
return anything(focus.getCollectionStatus());
case Item : {
@ -3333,7 +3312,7 @@ public class FHIRPathEngine {
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_DateTime);
case Resolve : {
checkContextReference(focus, "resolve", exp);
return new TypeDetails(CollectionStatus.ORDERED, "DomainResource");
return new TypeDetails(CollectionStatus.SINGLETON, "DomainResource");
}
case Extension : {
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
@ -3353,8 +3332,6 @@ public class FHIRPathEngine {
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
case HtmlChecks2 :
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
case Comparable :
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
case Alias :
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
return anything(CollectionStatus.SINGLETON);
@ -3661,7 +3638,6 @@ public class FHIRPathEngine {
case Alias : return funcAlias(context, focus, exp);
case HtmlChecks1 : return funcHtmlChecks1(context, focus, exp);
case HtmlChecks2 : return funcHtmlChecks2(context, focus, exp);
case Comparable : return funcComparable(context, focus, exp);
case ToInteger : return funcToInteger(context, focus, exp);
case ToDecimal : return funcToDecimal(context, focus, exp);
case ToString : return funcToString(context, focus, exp);
@ -3901,10 +3877,7 @@ public class FHIRPathEngine {
}
private List<Base> funcLowBoundary(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
if (focus.size() == 0) {
return makeNull();
}
if (focus.size() > 1) {
if (focus.size() != 1) {
throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "lowBoundary", focus.size());
}
int precision = 0;
@ -3927,11 +3900,6 @@ public class FHIRPathEngine {
result.add(new DateTimeType(Utilities.lowBoundaryForDate(base.primitiveValue(), precision == 0 ? 17 : precision)));
} else if (base.hasType("time")) {
result.add(new TimeType(Utilities.lowBoundaryForTime(base.primitiveValue(), precision == 0 ? 9 : precision)));
} else if (base.hasType("Quantity")) {
String value = getNamedValue(base, "value");
Base v = base; // .copy() - big issue
v.setProperty("value", new DecimalType(Utilities.lowBoundaryForDecimal(value, precision == 0 ? 8 : precision)));
result.add(v);
} else {
makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date");
}
@ -3939,10 +3907,7 @@ public class FHIRPathEngine {
}
private List<Base> funcHighBoundary(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
if (focus.size() == 0) {
return makeNull();
}
if (focus.size() > 1) {
if (focus.size() != 1) {
throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "highBoundary", focus.size());
}
int precision = 0;
@ -3965,11 +3930,6 @@ public class FHIRPathEngine {
result.add(new DateTimeType(Utilities.highBoundaryForDate(base.primitiveValue(), precision == 0 ? 17 : precision)));
} else if (base.hasType("time")) {
result.add(new TimeType(Utilities.highBoundaryForTime(base.primitiveValue(), precision == 0 ? 9 : precision)));
} else if (base.hasType("Quantity")) {
String value = getNamedValue(base, "value");
Base v = base; // .copy() - big issue
v.setProperty("value", new DecimalType(Utilities.highBoundaryForDecimal(value, precision == 0 ? 8 : precision)));
result.add(v);
} else {
makeException(expr, I18nConstants.FHIRPATH_WRONG_PARAM_TYPE, "sqrt", "(focus)", base.fhirType(), "decimal or date");
}
@ -4091,10 +4051,6 @@ public class FHIRPathEngine {
result.add(new StringType(Utilities.escapeXml(cnt)));
} else if ("json".equals(param)) {
result.add(new StringType(Utilities.escapeJson(cnt)));
} else if ("url".equals(param)) {
result.add(new StringType(Utilities.URLEncode(cnt)));
} else if ("md".equals(param)) {
result.add(new StringType(MarkDownProcessor.makeStringSafeAsMarkdown(cnt)));
}
}
@ -4112,10 +4068,6 @@ public class FHIRPathEngine {
result.add(new StringType(Utilities.unescapeXml(cnt)));
} else if ("json".equals(param)) {
result.add(new StringType(Utilities.unescapeJson(cnt)));
} else if ("url".equals(param)) {
result.add(new StringType(Utilities.URLDecode(cnt)));
} else if ("md".equals(param)) {
result.add(new StringType(MarkDownProcessor.makeMarkdownForString(cnt)));
}
}
@ -4138,8 +4090,7 @@ public class FHIRPathEngine {
List<Base> result = new ArrayList<Base>();
if (focus.size() == 1) {
String cnt = focus.get(0).primitiveValue();
String[] sl = Pattern.compile(param, Pattern.LITERAL).split(cnt);
for (String s : sl) {
for (String s : cnt.split(param)) {
result.add(new StringType(s));
}
}
@ -4149,14 +4100,9 @@ public class FHIRPathEngine {
private List<Base> funcJoin(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
String param = nl.get(0).primitiveValue();
String param2 = param;
if (exp.getParameters().size() == 2) {
nl = execute(context, focus, exp.getParameters().get(1), true);
param2 = nl.get(0).primitiveValue();
}
List<Base> result = new ArrayList<Base>();
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(param, param2);
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(param);
for (Base i : focus) {
b.append(i.primitiveValue());
}
@ -4218,47 +4164,6 @@ public class FHIRPathEngine {
return false;
}
private List<Base> funcComparable(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
if (focus.size() != 1 || !(focus.get(0).fhirType().equals("Quantity"))) {
return makeBoolean(false);
}
List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
if (nl.size() != 1 || !(nl.get(0).fhirType().equals("Quantity"))) {
return makeBoolean(false);
}
String s1 = getNamedValue(focus.get(0), "system");
String u1 = getNamedValue(focus.get(0), "code");
String s2 = getNamedValue(nl.get(0), "system");
String u2 = getNamedValue(nl.get(0), "code");
if (s1 == null || s2 == null || !s1.equals(s2)) {
return makeBoolean(false);
}
if (u1 == null || u2 == null) {
return makeBoolean(false);
}
if (u1.equals(u2)) {
return makeBoolean(true);
}
if (s1.equals("http://unitsofmeasure.org") && worker.getUcumService() != null) {
try {
return makeBoolean(worker.getUcumService().isComparable(u1, u2));
} catch (UcumException e) {
return makeBoolean(false);
}
} else {
return makeBoolean(false);
}
}
private String getNamedValue(Base base, String name) {
Property p = base.getChildByName(name);
if (p.hasValues() && p.getValues().size() == 1) {
return p.getValues().get(0).primitiveValue();
}
return null;
}
private boolean checkHtmlNames(XhtmlNode node) {
if (node.getNodeType() == NodeType.Comment) {
@ -4959,11 +4864,10 @@ public class FHIRPathEngine {
if (s != null) {
Base res = null;
if (s.startsWith("#")) {
String t = s.substring(1);
Property p = context.rootResource.getChildByName("contained");
if (p != null) {
for (Base c : p.getValues()) {
if (t.equals(c.getIdBase())) {
if (chompHash(s).equals(chompHash(c.getIdBase()))) {
res = c;
break;
}
@ -4985,6 +4889,17 @@ public class FHIRPathEngine {
return result;
}
/**
* Strips a leading hashmark (#) if present at the start of a string
*/
private String chompHash(String theId) {
String retVal = theId;
while (retVal.startsWith("#")) {
retVal = retVal.substring(1);
}
return retVal;
}
private List<Base> funcExtension(ExecutionContext context, List<Base> focus, ExpressionNode exp) throws FHIRException {
List<Base> result = new ArrayList<Base>();
List<Base> nl = execute(context, focus, exp.getParameters().get(0), true);
@ -5710,17 +5625,16 @@ public class FHIRPathEngine {
}
if (type.equals("http://hl7.org/fhir/StructureDefinition/xhtml")) {
return;
}
}
if (type.startsWith(Constants.NS_SYSTEM_TYPE)) {
return;
}
if (type.equals(TypeDetails.FP_SimpleTypeInfo)) {
getSimpleTypeChildTypesByName(name, result);
} else if (type.equals(TypeDetails.FP_ClassInfo)) {
getClassInfoChildTypesByName(name, result);
} else {
if (type.startsWith(Constants.NS_SYSTEM_TYPE)) {
return;
}
String url = null;
if (type.contains("#")) {
url = type.substring(0, type.indexOf("#"));
@ -5730,11 +5644,7 @@ public class FHIRPathEngine {
String tail = "";
StructureDefinition sd = worker.fetchResource(StructureDefinition.class, url);
if (sd == null) {
if (url.startsWith(TypeDetails.FP_NS)) {
return;
} else {
throw makeException(expr, I18nConstants.FHIRPATH_UNKNOWN_TYPE, url, "getChildTypesByName");
}
throw makeException(expr, I18nConstants.FHIRPATH_NO_TYPE, url, "getChildTypesByName");
}
List<StructureDefinition> sdl = new ArrayList<StructureDefinition>();
ElementDefinitionMatch m = null;
@ -5817,7 +5727,7 @@ public class FHIRPathEngine {
for (TypeRefComponent t : ed.getDefinition().getType()) {
if (Utilities.noString(t.getCode())) {
if (Utilities.existsInList(ed.getDefinition().getId(), "Element.id", "Extension.url") || Utilities.existsInList(ed.getDefinition().getBase().getPath(), "Resource.id", "Element.id", "Extension.url")) {
result.addType(TypeDetails.FP_NS, "System.String");
result.addType(TypeDetails.FP_NS, "string");
}
break; // throw new PathEngineException("Illegal reference to primitive value attribute @ "+path);
}
@ -5916,12 +5826,7 @@ public class FHIRPathEngine {
}
private boolean isAbstractType(List<TypeRefComponent> list) {
if (list.size() != 1) {
return false;
} else {
StructureDefinition sd = worker.fetchTypeDefinition(list.get(0).getCode());
return sd != null && sd.getAbstract();
}
return list.size() != 1 ? true : Utilities.existsInList(list.get(0).getCode(), "Element", "BackboneElement", "Resource", "DomainResource");
}
private boolean hasType(ElementDefinition ed, String s) {