From bd1b90b9a5c672e71630aac4e310c194855c1a4a Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 13 Nov 2023 15:00:53 +1100 Subject: [PATCH] Add support for CCDA .hasTemplateIdOf(canonical) --- .../org/hl7/fhir/r5/context/TypeManager.java | 9 +++ .../main/java/org/hl7/fhir/r5/model/Base.java | 12 ++++ .../org/hl7/fhir/r5/model/ExpressionNode.java | 4 +- .../org/hl7/fhir/r5/utils/FHIRPathEngine.java | 60 +++++++++++++++++-- .../org/hl7/fhir/r5/test/FHIRPathTests.java | 25 ++++++-- 5 files changed, 99 insertions(+), 11 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/TypeManager.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/TypeManager.java index c35f397fa..8450989cd 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/TypeManager.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/context/TypeManager.java @@ -63,6 +63,15 @@ public class TypeManager { } 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) { primitiveNames.add(sd.getType()); } else if (sd.getKind() == StructureDefinitionKind.COMPLEXTYPE) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base.java index e644230cb..40ed9bed5 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/Base.java @@ -339,6 +339,18 @@ public abstract class Base implements Serializable, IBase, IElement { 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 { if (name.equals("*")) { List children = new ArrayList(); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ExpressionNode.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ExpressionNode.java index d4e3b697e..9f58432ed 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ExpressionNode.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/model/ExpressionNode.java @@ -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, Comparable, hasTemplateIdOf; public static Function fromCode(String name) { if (name.equals("empty")) return Function.Empty; @@ -157,6 +157,7 @@ public class ExpressionNode { if (name.equals("lowBoundary")) return Function.LowBoundary; if (name.equals("highBoundary")) return Function.HighBoundary; if (name.equals("precision")) return Function.Precision; + if (name.equals("hasTemplateIdOf")) return Function.hasTemplateIdOf; return null; } @@ -260,6 +261,7 @@ public class ExpressionNode { case LowBoundary: return "lowBoundary"; case HighBoundary: return "highBoundary"; case Precision: return "precision"; + case hasTemplateIdOf: return "hasTemplateIdOf"; default: return "?custom?"; } } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java index 25f6b78b8..016167966 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/FHIRPathEngine.java @@ -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.Kind; 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.Property; 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 HighBoundary: return checkParamCount(lexer, location, exp, 0, 1); 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()); } return false; @@ -3141,7 +3142,7 @@ public class FHIRPathEngine { } } else { while (sd != null) { - if (sd.getType().equals(exp.getName())) { + if (sd.getType().equals(exp.getName()) || sd.getTypeTail().equals(exp.getName())) { result.add(item); break; } @@ -3627,7 +3628,10 @@ public class FHIRPathEngine { checkContextContinuous(focus, exp.getFunction().toCode(), exp); 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 : { 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 HighBoundary : return funcHighBoundary(context, focus, exp); case Precision : return funcPrecision(context, focus, exp); - + case hasTemplateIdOf: return funcHasTemplateIdOf(context, focus, exp); + case Custom: { List> params = new ArrayList>(); @@ -3890,6 +3895,53 @@ public class FHIRPathEngine { } } + private List funcHasTemplateIdOf(ExecutionContext context, List focus, ExpressionNode exp) { + List result = new ArrayList(); + List 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 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 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 funcSqrt(ExecutionContext context, List focus, ExpressionNode expr) { if (focus.size() != 1) { throw makeExceptionPlural(focus.size(), expr, I18nConstants.FHIRPATH_FOCUS, "sqrt", focus.size()); diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/FHIRPathTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/FHIRPathTests.java index 88de6464e..3e15b4e50 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/FHIRPathTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/FHIRPathTests.java @@ -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.FHIRPathUtilityClasses.FunctionDetails; 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.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; @@ -102,11 +104,20 @@ public class FHIRPathTests { } private static FHIRPathEngine fp; - private final Map resources = new HashMap(); + private final Map resources = new HashMap(); @BeforeAll - public static void setUp() { - fp = new FHIRPathEngine(TestingUtilities.getSharedWorkerContext()); + public static void setUp() throws FileNotFoundException, FHIRException, IOException { + 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 data() throws ParserConfigurationException, SAXException, IOException { @@ -168,7 +179,7 @@ public class FHIRPathTests { fail = TestResultType.EXECUTION; }; fp.setAllowPolymorphicNames("lenient/polymorphics".equals(test.getAttribute("mode"))); - Resource res = null; + Base res = null; List outcome = new ArrayList(); @@ -187,7 +198,9 @@ public class FHIRPathTests { if (!Utilities.noString(input)) { res = resources.get(input); 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)); } else { res = new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", input)); @@ -200,7 +213,7 @@ public class FHIRPathTests { if (Utilities.noString(input)) { fp.check(null, null, node); } 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)); } catch (Exception e) {