Add support for CCDA .hasTemplateIdOf(canonical)

This commit is contained in:
Grahame Grieve 2023-11-13 15:00:53 +11:00
parent e56dfb8693
commit bd1b90b9a5
5 changed files with 99 additions and 11 deletions

View File

@ -63,6 +63,15 @@ public class TypeManager {
} }
types.add(sd); types.add(sd);
} }
if (Utilities.isAbsoluteUrl(type)) {
type = sd.getTypeTail();
types = typeDefinitions.get(type);
if (types == null) {
types = new HashSet<>();
typeDefinitions.put(type, types);
}
types.add(sd);
}
if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) { if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) {
primitiveNames.add(sd.getType()); primitiveNames.add(sd.getType());
} else if (sd.getKind() == StructureDefinitionKind.COMPLEXTYPE) { } else if (sd.getKind() == StructureDefinitionKind.COMPLEXTYPE) {

View File

@ -339,6 +339,18 @@ public abstract class Base implements Serializable, IBase, IElement {
return result; return result;
} }
public Base getChildValueByName(String name) {
Property p = getChildByName(name);
if (p != null && p.hasValues()) {
if (p.getValues().size() > 1) {
throw new Error("Too manye values for "+name+" found");
} else {
return p.getValues().get(0);
}
}
return null;
}
public Base[] listChildrenByName(String name, boolean checkValid) throws FHIRException { public Base[] listChildrenByName(String name, boolean checkValid) throws FHIRException {
if (name.equals("*")) { if (name.equals("*")) {
List<Property> children = new ArrayList<Property>(); List<Property> children = new ArrayList<Property>();

View File

@ -56,7 +56,7 @@ public class ExpressionNode {
Encode, Decode, Escape, Unescape, Trim, Split, Join, LowBoundary, HighBoundary, Precision, Encode, Decode, Escape, Unescape, Trim, Split, Join, LowBoundary, HighBoundary, Precision,
// Local extensions to FHIRPath // Local extensions to FHIRPath
HtmlChecks1, HtmlChecks2, AliasAs, Alias, Comparable; HtmlChecks1, HtmlChecks2, AliasAs, Alias, Comparable, hasTemplateIdOf;
public static Function fromCode(String name) { public static Function fromCode(String name) {
if (name.equals("empty")) return Function.Empty; if (name.equals("empty")) return Function.Empty;
@ -157,6 +157,7 @@ public class ExpressionNode {
if (name.equals("lowBoundary")) return Function.LowBoundary; if (name.equals("lowBoundary")) return Function.LowBoundary;
if (name.equals("highBoundary")) return Function.HighBoundary; if (name.equals("highBoundary")) return Function.HighBoundary;
if (name.equals("precision")) return Function.Precision; if (name.equals("precision")) return Function.Precision;
if (name.equals("hasTemplateIdOf")) return Function.hasTemplateIdOf;
return null; return null;
} }
@ -260,6 +261,7 @@ public class ExpressionNode {
case LowBoundary: return "lowBoundary"; case LowBoundary: return "lowBoundary";
case HighBoundary: return "highBoundary"; case HighBoundary: return "highBoundary";
case Precision: return "precision"; case Precision: return "precision";
case hasTemplateIdOf: return "hasTemplateIdOf";
default: return "?custom?"; default: return "?custom?";
} }
} }

View File

@ -47,6 +47,7 @@ import org.hl7.fhir.r5.model.ExpressionNode.CollectionStatus;
import org.hl7.fhir.r5.model.ExpressionNode.Function; import org.hl7.fhir.r5.model.ExpressionNode.Function;
import org.hl7.fhir.r5.model.ExpressionNode.Kind; import org.hl7.fhir.r5.model.ExpressionNode.Kind;
import org.hl7.fhir.r5.model.ExpressionNode.Operation; import org.hl7.fhir.r5.model.ExpressionNode.Operation;
import org.hl7.fhir.r5.model.Identifier;
import org.hl7.fhir.r5.model.IntegerType; import org.hl7.fhir.r5.model.IntegerType;
import org.hl7.fhir.r5.model.Property; import org.hl7.fhir.r5.model.Property;
import org.hl7.fhir.r5.model.Property.PropertyMatcher; import org.hl7.fhir.r5.model.Property.PropertyMatcher;
@ -1436,7 +1437,7 @@ public class FHIRPathEngine {
case LowBoundary: return checkParamCount(lexer, location, exp, 0, 1); case LowBoundary: return checkParamCount(lexer, location, exp, 0, 1);
case HighBoundary: return checkParamCount(lexer, location, exp, 0, 1); case HighBoundary: return checkParamCount(lexer, location, exp, 0, 1);
case Precision: return checkParamCount(lexer, location, exp, 0); case Precision: return checkParamCount(lexer, location, exp, 0);
case hasTemplateIdOf: return checkParamCount(lexer, location, exp, 1);
case Custom: return checkParamCount(lexer, location, exp, details.getMinParameters(), details.getMaxParameters()); case Custom: return checkParamCount(lexer, location, exp, details.getMinParameters(), details.getMaxParameters());
} }
return false; return false;
@ -3141,7 +3142,7 @@ public class FHIRPathEngine {
} }
} else { } else {
while (sd != null) { while (sd != null) {
if (sd.getType().equals(exp.getName())) { if (sd.getType().equals(exp.getName()) || sd.getTypeTail().equals(exp.getName())) {
result.add(item); result.add(item);
break; break;
} }
@ -3627,7 +3628,10 @@ public class FHIRPathEngine {
checkContextContinuous(focus, exp.getFunction().toCode(), exp); checkContextContinuous(focus, exp.getFunction().toCode(), exp);
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer); return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer);
} }
case hasTemplateIdOf: {
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
}
case Custom : { case Custom : {
return hostServices.checkFunction(this, context.appInfo,exp.getName(), focus, paramTypes); return hostServices.checkFunction(this, context.appInfo,exp.getName(), focus, paramTypes);
} }
@ -3862,7 +3866,8 @@ public class FHIRPathEngine {
case LowBoundary : return funcLowBoundary(context, focus, exp); case LowBoundary : return funcLowBoundary(context, focus, exp);
case HighBoundary : return funcHighBoundary(context, focus, exp); case HighBoundary : return funcHighBoundary(context, focus, exp);
case Precision : return funcPrecision(context, focus, exp); case Precision : return funcPrecision(context, focus, exp);
case hasTemplateIdOf: return funcHasTemplateIdOf(context, focus, exp);
case Custom: { case Custom: {
List<List<Base>> params = new ArrayList<List<Base>>(); List<List<Base>> params = new ArrayList<List<Base>>();
@ -3890,6 +3895,53 @@ public class FHIRPathEngine {
} }
} }
private List<Base> funcHasTemplateIdOf(ExecutionContext context, List<Base> focus, ExpressionNode exp) {
List<Base> result = new ArrayList<Base>();
List<Base> swb = execute(context, focus, exp.getParameters().get(0), true);
String sw = convertToString(swb);
StructureDefinition sd = this.worker.fetchResource(StructureDefinition.class, sw);
if (focus.size() == 1 && sd != null) {
boolean found = false;
for (Identifier id : sd.getIdentifier()) {
if (id.getValue().startsWith("urn:hl7ii:")) {
String[] p = id.getValue().split("\\:");
if (p.length == 4) {
found = found || hasTemplateId(focus.get(0), p[2], p[3]);
}
} else if (id.getValue().startsWith("urn:oid:")) {
found = found || hasTemplateId(focus.get(0), id.getValue().substring(8));
}
}
result.add(new BooleanType(found));
}
return result;
}
private boolean hasTemplateId(Base base, String rv) {
List<Base> templateIds = base.listChildrenByName("templateId");
for (Base templateId : templateIds) {
Base root = templateId.getChildValueByName("root");
Base extension = templateId.getChildValueByName("extension");
if (extension == null && root != null && rv.equals(root.primitiveValue())) {
return true;
}
}
return false;
}
private boolean hasTemplateId(Base base, String rv, String ev) {
List<Base> templateIds = base.listChildrenByName("templateId");
for (Base templateId : templateIds) {
Base root = templateId.getChildValueByName("root");
Base extension = templateId.getChildValueByName("extension");
if (extension != null && ev.equals(extension.primitiveValue()) && root != null && rv.equals(root.primitiveValue())) {
return true;
}
}
return false;
}
private List<Base> funcSqrt(ExecutionContext context, List<Base> focus, ExpressionNode expr) { private List<Base> funcSqrt(ExecutionContext context, List<Base> focus, ExpressionNode expr) {
if (focus.size() != 1) { if (focus.size() != 1) {
throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "sqrt", focus.size()); throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "sqrt", focus.size());

View File

@ -22,6 +22,8 @@ import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext; import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext;
import org.hl7.fhir.r5.utils.FHIRPathUtilityClasses.FunctionDetails; import org.hl7.fhir.r5.utils.FHIRPathUtilityClasses.FunctionDetails;
import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
import org.hl7.fhir.utilities.npm.NpmPackage;
import org.hl7.fhir.utilities.xml.XMLUtil; import org.hl7.fhir.utilities.xml.XMLUtil;
import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeAll;
@ -102,11 +104,20 @@ public class FHIRPathTests {
} }
private static FHIRPathEngine fp; private static FHIRPathEngine fp;
private final Map<String, Resource> resources = new HashMap<String, Resource>(); private final Map<String, Base> resources = new HashMap<String, Base>();
@BeforeAll @BeforeAll
public static void setUp() { public static void setUp() throws FileNotFoundException, FHIRException, IOException {
fp = new FHIRPathEngine(TestingUtilities.getSharedWorkerContext()); if (!TestingUtilities.getSharedWorkerContext().hasPackage("hl7.cda.us.ccda", null)) {
FilesystemPackageCacheManager pcm = new FilesystemPackageCacheManager(true);
NpmPackage npm = pcm.loadPackage("hl7.cda.uv.core");
TestingUtilities.getSharedWorkerContext().loadFromPackage(npm, null);
npm = pcm.loadPackage("hl7.cda.us.ccda");
TestingUtilities.getSharedWorkerContext().loadFromPackage(npm, null);
}
if (fp == null) {
fp = new FHIRPathEngine(TestingUtilities.getSharedWorkerContext());
}
} }
public static Stream<Arguments> data() throws ParserConfigurationException, SAXException, IOException { public static Stream<Arguments> data() throws ParserConfigurationException, SAXException, IOException {
@ -168,7 +179,7 @@ public class FHIRPathTests {
fail = TestResultType.EXECUTION; fail = TestResultType.EXECUTION;
}; };
fp.setAllowPolymorphicNames("lenient/polymorphics".equals(test.getAttribute("mode"))); fp.setAllowPolymorphicNames("lenient/polymorphics".equals(test.getAttribute("mode")));
Resource res = null; Base res = null;
List<Base> outcome = new ArrayList<Base>(); List<Base> outcome = new ArrayList<Base>();
@ -187,7 +198,9 @@ public class FHIRPathTests {
if (!Utilities.noString(input)) { if (!Utilities.noString(input)) {
res = resources.get(input); res = resources.get(input);
if (res == null) { if (res == null) {
if (input.endsWith(".json")) { if ("cda".equals(test.getAttribute("mode"))) {
res = Manager.makeParser(fp.getWorker(), FhirFormat.XML).parseSingle(TestingUtilities.loadTestResourceStream("r5", input), null);
} else if (input.endsWith(".json")) {
res = new JsonParser().parse(TestingUtilities.loadTestResourceStream("r5", input)); res = new JsonParser().parse(TestingUtilities.loadTestResourceStream("r5", input));
} else { } else {
res = new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", input)); res = new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", input));
@ -200,7 +213,7 @@ public class FHIRPathTests {
if (Utilities.noString(input)) { if (Utilities.noString(input)) {
fp.check(null, null, node); fp.check(null, null, node);
} else { } else {
fp.check(res, res.getResourceType().toString(), res.getResourceType().toString(), node); fp.check(res, res.fhirType(), res.fhirType(), node);
} }
Assertions.assertTrue(fail != TestResultType.SEMANTICS, String.format("Expected exception didn't occur checking %s", expression)); Assertions.assertTrue(fail != TestResultType.SEMANTICS, String.format("Expected exception didn't occur checking %s", expression));
} catch (Exception e) { } catch (Exception e) {