Merge pull request #1573 from hapifhir/2024-03-gg-valueset-validation

2024 03 gg valueset validation
This commit is contained in:
Grahame Grieve 2024-03-14 18:00:50 +11:00 committed by GitHub
commit 6babf380c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 583 additions and 91 deletions

View File

@ -0,0 +1,308 @@
package org.hl7.fhir.convertors.misc;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.utilities.IniFile;
import org.hl7.fhir.utilities.VersionUtilities;
public class OIDAssigner {
public static void main(String[] args) throws Exception {
new OIDAssigner().execute(args[0], args[1], args[2]);
}
private void execute(String oidSource, String folder, String version) {
IniFile oids = new IniFile(oidSource);
File f = new File(folder);
process(oids, f, version);
}
private void process(IniFile oids, File folder, String version) {
for (File f : folder.listFiles()) {
if (f.isDirectory()) {
process(oids, f, version);
} else if (f.getName().endsWith(".xml")) {
processFile(oids, f, version, FhirFormat.XML);
} else if (f.getName().endsWith(".json")) {
processFile(oids, f, version, FhirFormat.JSON);
}
}
}
private void processFile(IniFile oids, File f, String version, FhirFormat fmt) {
switch (VersionUtilities.getMajMin(version)) {
case "1.0" : processFileR2(oids, f, fmt);
case "3.0" : processFileR3(oids, f, fmt);
case "4.0" : processFileR4(oids, f, fmt);
case "4.3" : processFileR4B(oids, f, fmt);
case "5.0" : processFileR5(oids, f, fmt);
}
}
private void processFileR2(IniFile oids, File f, FhirFormat fmt) {
org.hl7.fhir.dstu2.formats.IParser parser = fmt == FhirFormat.JSON ? new org.hl7.fhir.dstu2.formats.JsonParser() : new org.hl7.fhir.dstu2.formats.XmlParser();
try {
boolean save = false;
org.hl7.fhir.dstu2.model.Resource r = parser.parse(new FileInputStream(f));
if (r instanceof org.hl7.fhir.dstu2.model.ValueSet) {
org.hl7.fhir.dstu2.model.ValueSet vs = (org.hl7.fhir.dstu2.model.ValueSet) r;
boolean hasOid = isOid(vs.getIdentifier());
if (!hasOid) {
String oid = getOid(oids, "ValueSet", vs.getUrl());
vs.setIdentifier(new org.hl7.fhir.dstu2.model.Identifier().setSystem("urn:ietf:rfc:3986").setValue("urn:oid:"+oid));
save = true;
}
}
if (r instanceof org.hl7.fhir.dstu2.model.ConceptMap) {
org.hl7.fhir.dstu2.model.ConceptMap cm = (org.hl7.fhir.dstu2.model.ConceptMap) r;
boolean hasOid = isOid(cm.getIdentifier());
if (!hasOid) {
String oid = getOid(oids, "ConceptMap", cm.getUrl());
cm.setIdentifier(new org.hl7.fhir.dstu2.model.Identifier().setSystem("urn:ietf:rfc:3986").setValue("urn:oid:"+oid));
save = true;
}
}
if (r instanceof org.hl7.fhir.dstu2.model.StructureDefinition) {
org.hl7.fhir.dstu2.model.StructureDefinition sd = (org.hl7.fhir.dstu2.model.StructureDefinition) r;
boolean hasOid = false;
for (org.hl7.fhir.dstu2.model.Identifier id : sd.getIdentifier()) {
if (isOid(id)) {
hasOid = true;
}
}
if (!hasOid) {
String oid = getOid(oids, "StructureDefinition", sd.getUrl());
sd.getIdentifier().add(new org.hl7.fhir.dstu2.model.Identifier().setSystem("urn:ietf:rfc:3986").setValue("urn:oid:"+oid));
save = true;
}
}
if (save) {
parser.setOutputStyle(org.hl7.fhir.dstu2.formats.IParser.OutputStyle.PRETTY).compose(new FileOutputStream(f), r);
}
} catch (Exception e) {
System.out.println("Erro processing "+f.getAbsolutePath()+": "+e.getMessage());
}
}
private void processFileR3(IniFile oids, File f, FhirFormat fmt) {
org.hl7.fhir.dstu3.formats.IParser parser = fmt == FhirFormat.JSON ? new org.hl7.fhir.dstu3.formats.JsonParser() : new org.hl7.fhir.dstu3.formats.XmlParser();
try {
boolean save = false;
org.hl7.fhir.dstu3.model.Resource r = parser.parse(new FileInputStream(f));
if (r instanceof org.hl7.fhir.dstu3.model.CodeSystem) {
org.hl7.fhir.dstu3.model.CodeSystem cs = (org.hl7.fhir.dstu3.model.CodeSystem) r;
boolean hasOid = isOid(cs.getIdentifier());
if (!hasOid) {
String oid = getOid(oids, "CodeSystem", cs.getUrl());
cs.setIdentifier(new org.hl7.fhir.dstu3.model.Identifier().setSystem("urn:ietf:rfc:3986").setValue("urn:oid:"+oid));
save = true;
}
}
if (r instanceof org.hl7.fhir.dstu3.model.ValueSet) {
org.hl7.fhir.dstu3.model.ValueSet vs = (org.hl7.fhir.dstu3.model.ValueSet) r;
boolean hasOid = false;
for (org.hl7.fhir.dstu3.model.Identifier id : vs.getIdentifier()) {
if (isOid(id)) {
hasOid = true;
}
}
if (!hasOid) {
String oid = getOid(oids, "ValueSet", vs.getUrl());
vs.getIdentifier().add(new org.hl7.fhir.dstu3.model.Identifier().setSystem("urn:ietf:rfc:3986").setValue("urn:oid:"+oid));
save = true;
}
}
if (r instanceof org.hl7.fhir.dstu3.model.ConceptMap) {
org.hl7.fhir.dstu3.model.ConceptMap cm = (org.hl7.fhir.dstu3.model.ConceptMap) r;
boolean hasOid = isOid(cm.getIdentifier());
if (!hasOid) {
String oid = getOid(oids, "ConceptMap", cm.getUrl());
cm.setIdentifier(new org.hl7.fhir.dstu3.model.Identifier().setSystem("urn:ietf:rfc:3986").setValue("urn:oid:"+oid));
save = true;
}
}
if (r instanceof org.hl7.fhir.dstu3.model.StructureDefinition) {
org.hl7.fhir.dstu3.model.StructureDefinition sd = (org.hl7.fhir.dstu3.model.StructureDefinition) r;
boolean hasOid = false;
for (org.hl7.fhir.dstu3.model.Identifier id : sd.getIdentifier()) {
if (isOid(id)) {
hasOid = true;
}
}
if (!hasOid) {
String oid = getOid(oids, "StructureDefinition", sd.getUrl());
sd.getIdentifier().add(new org.hl7.fhir.dstu3.model.Identifier().setSystem("urn:ietf:rfc:3986").setValue("urn:oid:"+oid));
save = true;
}
}
if (save) {
parser.setOutputStyle(org.hl7.fhir.dstu3.formats.IParser.OutputStyle.PRETTY).compose(new FileOutputStream(f), r);
}
} catch (Exception e) {
System.out.println("Erro processing "+f.getAbsolutePath()+": "+e.getMessage());
}
}
private void processFileR4(IniFile oids, File f, FhirFormat fmt) {
org.hl7.fhir.r4.formats.IParser parser = fmt == FhirFormat.JSON ? new org.hl7.fhir.r4.formats.JsonParser() : new org.hl7.fhir.r4.formats.XmlParser();
try {
boolean save = false;
org.hl7.fhir.r4.model.Resource r = parser.parse(new FileInputStream(f));
if (r instanceof org.hl7.fhir.r4.model.CodeSystem) {
org.hl7.fhir.r4.model.CodeSystem cs = (org.hl7.fhir.r4.model.CodeSystem) r;
boolean hasOid = false;
for (org.hl7.fhir.r4.model.Identifier id : cs.getIdentifier()) {
if (isOid(id)) {
hasOid = true;
}
}
if (!hasOid) {
String oid = getOid(oids, "CodeSystem", cs.getUrl());
cs.getIdentifier().add(new org.hl7.fhir.r4.model.Identifier().setSystem("urn:ietf:rfc:3986").setValue("urn:oid:"+oid));
save = true;
}
}
if (r instanceof org.hl7.fhir.r4.model.ValueSet) {
org.hl7.fhir.r4.model.ValueSet vs = (org.hl7.fhir.r4.model.ValueSet) r;
boolean hasOid = false;
for (org.hl7.fhir.r4.model.Identifier id : vs.getIdentifier()) {
if (isOid(id)) {
hasOid = true;
}
}
if (!hasOid) {
String oid = getOid(oids, "ValueSet", vs.getUrl());
vs.getIdentifier().add(new org.hl7.fhir.r4.model.Identifier().setSystem("urn:ietf:rfc:3986").setValue("urn:oid:"+oid));
save = true;
}
}
if (r instanceof org.hl7.fhir.r4.model.ConceptMap) {
org.hl7.fhir.r4.model.ConceptMap cm = (org.hl7.fhir.r4.model.ConceptMap) r;
boolean hasOid = isOid(cm.getIdentifier());
if (!hasOid) {
String oid = getOid(oids, "ConceptMap", cm.getUrl());
cm.setIdentifier(new org.hl7.fhir.r4.model.Identifier().setSystem("urn:ietf:rfc:3986").setValue("urn:oid:"+oid));
save = true;
}
}
if (r instanceof org.hl7.fhir.r4.model.StructureDefinition) {
org.hl7.fhir.r4.model.StructureDefinition sd = (org.hl7.fhir.r4.model.StructureDefinition) r;
boolean hasOid = false;
for (org.hl7.fhir.r4.model.Identifier id : sd.getIdentifier()) {
if (isOid(id)) {
hasOid = true;
}
}
if (!hasOid) {
String oid = getOid(oids, "StructureDefinition", sd.getUrl());
sd.getIdentifier().add(new org.hl7.fhir.r4.model.Identifier().setSystem("urn:ietf:rfc:3986").setValue("urn:oid:"+oid));
save = true;
}
}
if (save) {
parser.setOutputStyle(org.hl7.fhir.r4.formats.IParser.OutputStyle.PRETTY).compose(new FileOutputStream(f), r);
}
} catch (Exception e) {
System.out.println("Erro processing "+f.getAbsolutePath()+": "+e.getMessage());
}
}
private void processFileR4B(IniFile oids, File f, FhirFormat fmt) {
org.hl7.fhir.r4b.formats.IParser parser = fmt == FhirFormat.JSON ? new org.hl7.fhir.r4b.formats.JsonParser() : new org.hl7.fhir.r4b.formats.XmlParser();
try {
boolean save = false;
org.hl7.fhir.r4b.model.Resource r = parser.parse(new FileInputStream(f));
if (r instanceof org.hl7.fhir.r4b.model.CanonicalResource) {
org.hl7.fhir.r4b.model.CanonicalResource cs = (org.hl7.fhir.r4b.model.CanonicalResource) r;
boolean hasOid = false;
for (org.hl7.fhir.r4b.model.Identifier id : cs.getIdentifier()) {
if (isOid(id)) {
hasOid = true;
}
}
if (!hasOid) {
String oid = getOid(oids, r.fhirType(), cs.getUrl());
cs.getIdentifier().add(new org.hl7.fhir.r4b.model.Identifier().setSystem("urn:ietf:rfc:3986").setValue("urn:oid:"+oid));
save = true;
}
}
if (save) {
parser.setOutputStyle(org.hl7.fhir.r4b.formats.IParser.OutputStyle.PRETTY).compose(new FileOutputStream(f), r);
}
} catch (Exception e) {
System.out.println("Erro processing "+f.getAbsolutePath()+": "+e.getMessage());
}
}
private void processFileR5(IniFile oids, File f, FhirFormat fmt) {
org.hl7.fhir.r5.formats.IParser parser = fmt == FhirFormat.JSON ? new org.hl7.fhir.r5.formats.JsonParser() : new org.hl7.fhir.r5.formats.XmlParser();
try {
boolean save = false;
org.hl7.fhir.r5.model.Resource r = parser.parse(new FileInputStream(f));
if (r instanceof org.hl7.fhir.r5.model.CanonicalResource) {
org.hl7.fhir.r5.model.CanonicalResource cs = (org.hl7.fhir.r5.model.CanonicalResource) r;
boolean hasOid = false;
for (org.hl7.fhir.r5.model.Identifier id : cs.getIdentifier()) {
if (isOid(id)) {
hasOid = true;
}
}
if (!hasOid) {
String oid = getOid(oids, r.fhirType(), cs.getUrl());
cs.getIdentifier().add(new org.hl7.fhir.r5.model.Identifier().setSystem("urn:ietf:rfc:3986").setValue("urn:oid:"+oid));
save = true;
}
}
if (save) {
parser.setOutputStyle(org.hl7.fhir.r5.formats.IParser.OutputStyle.PRETTY).compose(new FileOutputStream(f), r);
}
} catch (Exception e) {
System.out.println("Erro processing "+f.getAbsolutePath()+": "+e.getMessage());
}
}
private boolean isOid(org.hl7.fhir.dstu2.model.Identifier id) {
return "urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:");
}
private boolean isOid(org.hl7.fhir.dstu3.model.Identifier id) {
return "urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:");
}
private boolean isOid(org.hl7.fhir.r4.model.Identifier id) {
return "urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:");
}
private boolean isOid(org.hl7.fhir.r4b.model.Identifier id) {
return "urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:");
}
private boolean isOid(org.hl7.fhir.r5.model.Identifier id) {
return "urn:ietf:rfc:3986".equals(id.getSystem()) && id.hasValue() && id.getValue().startsWith("urn:oid:");
}
private String getOid(IniFile oids, String rt, String url) {
String root = oids.getStringProperty("Roots", rt);
if (root == null) {
throw new Error("no OID.ini entry for "+rt);
}
String oid = oids.getStringProperty(rt, url);
if (oid != null) {
return oid;
}
int key = oids.getIntegerProperty("Key", rt);
key++;
oid = root+"."+key;
oids.setIntegerProperty("Key", rt, key, null);
oids.setStringProperty(rt, url, oid, null);
oids.save();
return oid;
}
}

View File

@ -388,7 +388,8 @@ public class ContextUtilities implements ProfileKnowledgeProvider {
public List<String> getConcreteResources() {
if (concreteResourceNames == null) {
concreteResourceNames.addAll(Utilities.sorted(concreteResourceNameSet));
concreteResourceNames = new ArrayList<>();
concreteResourceNames.addAll(Utilities.sorted(getConcreteResourceSet()));
}
return concreteResourceNames;
}

View File

@ -3253,7 +3253,7 @@ public class FHIRPathEngine {
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
}
case SubsetOf : {
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, focus);
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, focus.toUnordered());
return new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Boolean);
}
case SupersetOf : {

View File

@ -405,6 +405,16 @@ public class TypeDetails {
result.types.addAll(types);
return result;
}
public TypeDetails toOrdered() {
TypeDetails result = new TypeDetails(CollectionStatus.ORDERED);
result.types.addAll(types);
return result;
}
public TypeDetails toUnordered() {
TypeDetails result = new TypeDetails(CollectionStatus.UNORDERED);
result.types.addAll(types);
return result;
}
public CollectionStatus getCollectionStatus() {
return collectionStatus;
}

View File

@ -8629,6 +8629,17 @@ public class ConceptMap extends MetadataResource {
}
public List<ConceptMapGroupComponent> getGroups(String su) {
List<ConceptMapGroupComponent> res = new ArrayList<>();
for (ConceptMapGroupComponent g : getGroup()) {
if (su.equals(g.getSource())) {
res.add(g);
}
}
return res;
}
// end addition

View File

@ -159,10 +159,15 @@ public class DataRenderer extends Renderer implements CodeResolver {
parts[0] = parts[0].substring(0, parts[0].indexOf("."));
}
StructureDefinition p = getContext().getWorker().fetchResource(StructureDefinition.class, parts[0]);
if (p == null)
if (p == null) {
p = getContext().getWorker().fetchTypeDefinition(parts[0]);
if (p == null)
}
if (context.getTypeMap().containsKey(parts[0])) {
p = getContext().getWorker().fetchTypeDefinition(context.getTypeMap().get(parts[0]));
}
if (p == null) {
p = getContext().getWorker().fetchResource(StructureDefinition.class, link);
}
if (p != null) {
if ("Extension".equals(p.getType())) {
path = null;
@ -175,8 +180,9 @@ public class DataRenderer extends Renderer implements CodeResolver {
if (url == null) {
url = p.getUserString("filename");
}
} else
} else {
throw new DefinitionException("Unable to resolve markdown link "+link);
}
text = left+"["+link+"]("+url+(path == null ? "" : "#"+path)+")"+right;
}
@ -1201,6 +1207,11 @@ public class DataRenderer extends Renderer implements CodeResolver {
protected String displayIdentifier(Identifier ii) {
String s = Utilities.noString(ii.getValue()) ? "?ngen-9?" : ii.getValue();
if ("urn:ietf:rfc:3986".equals(ii.getSystem()) && s.startsWith("urn:oid:")) {
s = "OID:"+s.substring(8);
} else if ("urn:ietf:rfc:3986".equals(ii.getSystem()) && s.startsWith("urn:uuid:")) {
s = "UUID:"+s.substring(9);
} else {
NamingSystem ns = context.getContext().getNSUrlMap().get(ii.getSystem());
if (ns != null) {
s = ns.present()+"#"+s;
@ -1215,6 +1226,7 @@ public class DataRenderer extends Renderer implements CodeResolver {
} else if (ii.hasSystem()) {
s = ii.getSystem()+"#"+s;
}
}
if (ii.hasUse() || ii.hasPeriod()) {
s = s + "\u00A0(";

View File

@ -216,6 +216,7 @@ public class RenderingContext {
private Map<KnownLinkType, String> links = new HashMap<>();
private Map<String, String> namedLinks = new HashMap<>();
private boolean addName = false;
private Map<String, String> typeMap = new HashMap<>(); // type aliases that can be resolved in Markdown type links (mainly for cross-version usage)
/**
*
@ -281,6 +282,7 @@ public class RenderingContext {
res.changeVersion = changeVersion;
res.terminologyServiceOptions = terminologyServiceOptions.copy();
res.typeMap.putAll(typeMap);
return res;
}
@ -739,5 +741,9 @@ public class RenderingContext {
return this;
}
public Map<String, String> getTypeMap() {
return typeMap;
}
}

View File

@ -1,6 +1,7 @@
package org.hl7.fhir.r5.terminologies;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
@ -20,6 +21,7 @@ import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent;
import org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship;
import org.hl7.fhir.r5.terminologies.ConceptMapUtilities.ConceptMapElementSorter;
import org.hl7.fhir.r5.terminologies.ConceptMapUtilities.ElementMappingPair;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.r5.model.Identifier;
import org.hl7.fhir.r5.model.Meta;
import org.hl7.fhir.r5.model.UriType;
@ -220,9 +222,9 @@ public class ConceptMapUtilities {
res.setUrl(url);
for (ConceptMap cm : sequence) {
if (res.hasTargetScope() && src.hasTargetScope()) {
if (!cm.getSourceScope().equals(cm.getTargetScope())) {
throw new Error("Mismatch between seqeuntial concept maps: ");
if (res.hasTargetScope() && cm.hasTargetScope()) {
if (!cm.getSourceScope().primitiveValue().equals(res.getTargetScope().primitiveValue())) {
throw new Error("Mismatch between sequential concept maps: target was "+res.getTargetScope()+" and source is "+cm.getSourceScope());
} else {
res.setTargetScope(cm.getTargetScope());
}
@ -613,4 +615,71 @@ public class ConceptMapUtilities {
return code.getSystem().equals(grp.getSource()) || (code.getSystem()+"|"+code.getVersion()).equals(grp.getSource());
}
public static List<String> translateCode(String name, String defaultValue, ConceptMap... cmList) {
List<String> res = translateCode(name, cmList);
if (res.isEmpty()) {
res.add(defaultValue);
}
return res;
}
public static List<String> translateCode(String name, ConceptMap... cmList) {
List<String> res = new ArrayList<>();
res.add(name);
for (ConceptMap cm : cmList) {
res = translateCodes(res, cm);
}
return res;
}
private static List<String> translateCodes(List<String> codes, ConceptMap cm) {
List<String> res = new ArrayList<>();
for (ConceptMapGroupComponent g : cm.getGroup()) {
for (SourceElementComponent e : g.getElement()) {
if (Utilities.existsInList(e.getCode(), codes)) {
for (TargetElementComponent t : e.getTarget()) {
if (t.getRelationship() == ConceptMapRelationship.EQUIVALENT || t.getRelationship() == ConceptMapRelationship.RELATEDTO ||
t.getRelationship() == ConceptMapRelationship.SOURCEISBROADERTHANTARGET ||t.getRelationship() == ConceptMapRelationship.SOURCEISNARROWERTHANTARGET) {
res.add(t.getCode());
}
}
}
}
}
return res;
}
public static List<Coding> translateCoding(Coding code, ConceptMap... cmList) {
List<Coding> res = new ArrayList<>();
for (ConceptMap cm : cmList) {
res = translateCodings(res, cm);
}
return res;
}
private static List<Coding> translateCodings(List<Coding> codes, ConceptMap cm) {
List<Coding> res = new ArrayList<>();
for (ConceptMapGroupComponent g : cm.getGroup()) {
for (SourceElementComponent e : g.getElement()) {
if (hasCode(g.getSource(), e.getCode(), codes)) {
for (TargetElementComponent t : e.getTarget()) {
if (t.getRelationship() == ConceptMapRelationship.EQUIVALENT || t.getRelationship() == ConceptMapRelationship.RELATEDTO ||
t.getRelationship() == ConceptMapRelationship.SOURCEISBROADERTHANTARGET ||t.getRelationship() == ConceptMapRelationship.SOURCEISNARROWERTHANTARGET) {
res.add(new Coding().setSystem(g.getTarget()).setCode((t.getCode())));
}
}
}
}
}
return res;
}
private static boolean hasCode(String system, String code, List<Coding> codes) {
for (Coding c : codes) {
if (system.equals(c.getSystem()) && code.equals(c.getCode())) {
return true;
}
}
return false;
}
}

View File

@ -9,6 +9,7 @@ import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent;
import org.hl7.fhir.r5.model.CodeSystem.PropertyType;
import org.hl7.fhir.r5.model.Enumerations.FilterOperator;
import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent;
import org.hl7.fhir.utilities.Utilities;
public class KnownPropertyFilter extends ConceptFilter {
@ -31,10 +32,10 @@ public class KnownPropertyFilter extends ConceptFilter {
case EQUAL: return filter.getValue().equals(v);
case EXISTS: throw fail("not supported yet: "+filter.getOp().toCode());
case GENERALIZES: throw fail("not supported yet: "+filter.getOp().toCode());
case IN: throw fail("not supported yet: "+filter.getOp().toCode());
case IN: return Utilities.existsInListTrimmed(v, filter.getValue().split("\\,"));
case ISA: throw fail("not supported yet: "+filter.getOp().toCode());
case ISNOTA: throw fail("not supported yet: "+filter.getOp().toCode());
case NOTIN: throw fail("not supported yet: "+filter.getOp().toCode());
case NOTIN: return Utilities.existsInListTrimmed(v, filter.getValue().split("\\,"));
case NULL: throw fail("not supported yet: "+filter.getOp().toCode());
case REGEX: throw fail("not supported yet: "+filter.getOp().toCode());
default:

View File

@ -9,6 +9,7 @@ import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent;
import org.hl7.fhir.r5.model.CodeSystem.PropertyType;
import org.hl7.fhir.r5.model.Enumerations.FilterOperator;
import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent;
import org.hl7.fhir.utilities.Utilities;
public class PropertyFilter extends ConceptFilter {
@ -31,17 +32,17 @@ public class PropertyFilter extends ConceptFilter {
case EQUAL: return filter.getValue().equals(v);
case EXISTS: throw fail("not supported yet: "+filter.getOp().toCode());
case GENERALIZES: throw fail("not supported yet: "+filter.getOp().toCode());
case IN: throw fail("not supported yet: "+filter.getOp().toCode());
case IN: return Utilities.existsInListTrimmed(v, filter.getValue().split("\\,"));
case ISA: throw fail("not supported yet: "+filter.getOp().toCode());
case ISNOTA: throw fail("not supported yet: "+filter.getOp().toCode());
case NOTIN: throw fail("not supported yet: "+filter.getOp().toCode());
case NOTIN: return !Utilities.existsInListTrimmed(v, filter.getValue().split("\\,"));
case NULL: throw fail("not supported yet: "+filter.getOp().toCode());
case REGEX: throw fail("not supported yet: "+filter.getOp().toCode());
default:
throw fail("Shouldn't get here");
}
} else {
return false;
return filter.getOp() == FilterOperator.NOTIN;
}
}

View File

@ -680,8 +680,6 @@ public class ValueSetExpander extends ValueSetProcessBase {
if (debug) {
e.printStackTrace();
}
// well, we couldn't expand, so we'll return an interface to a checker that can check membership of the set
// that might fail too, but it might not, later.
return new ValueSetExpansionOutcome(e.getMessage(), TerminologyServiceErrorClass.UNKNOWN, allErrors, e instanceof EFhirClientException || e instanceof TerminologyServiceException);
}
}

View File

@ -1395,6 +1395,36 @@ public class ValueSetValidator extends ValueSetProcessBase {
}
d = CodeSystemUtilities.getProperty(cs, code, f.getProperty());
return d != null && d.primitiveValue() != null && d.primitiveValue().matches(f.getValue());
case IN:
if (f.getValue() == null) {
return false;
}
String[] values = f.getValue().split("\\,");
d = CodeSystemUtilities.getProperty(cs, code, f.getProperty());
if (d != null) {
String v = d.primitiveValue();
for (String value : values) {
if (v != null && v.equals(value.trim())) {
return true;
}
}
}
return false;
case NOTIN:
if (f.getValue() == null) {
return true;
}
values = f.getValue().split("\\,");
d = CodeSystemUtilities.getProperty(cs, code, f.getProperty());
if (d != null) {
String v = d.primitiveValue();
for (String value : values) {
if (v != null && v.equals(value.trim())) {
return false;
}
}
}
return true;
default:
System.out.println("todo: handle property filters with op = "+f.getOp());
throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_HANDLE_SYSTEM__PROPERTY_FILTER_WITH_OP__, cs.getUrl(), f.getOp()));

View File

@ -52,6 +52,7 @@ import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.apache.commons.compress.compressors.gzip.GzipCompressorOutputStream;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.ContactDetail;
import org.hl7.fhir.r5.model.ContactPoint;
import org.hl7.fhir.r5.model.ContactPoint.ContactPointSystem;
@ -263,7 +264,10 @@ public class NPMPackageGenerator {
packageManifest.add("fhirVersion", fv);
packageManifest.add("date", dt);
packageManifest.add("name", ig.getPackageId());
if (ig.hasJurisdiction() && ig.getJurisdiction().size() == 1 && ig.getJurisdictionFirstRep().getCoding().size() == 1) {
Coding c = ig.getJurisdictionFirstRep().getCodingFirstRep();
packageManifest.add("jurisdiction", c.getSystem()+"#"+c.getCode());
}
}

View File

@ -933,6 +933,15 @@ public class Utilities {
return false;
}
public static boolean existsInListTrimmed(String value, String... array) {
if (value == null)
return false;
for (String s : array)
if (value.equals(s.trim()))
return true;
return false;
}
public static boolean existsInList(int value, int... array) {
for (int i : array)
if (value == i)

View File

@ -300,8 +300,8 @@ public class VersionUtilities {
} else if (Utilities.charCount(version, '.') == 2) {
String[] p = version.split("\\.");
return p[0]+"."+p[1];
} else if (Utilities.existsInList(version, "R2", "R2B", "R3", "R4", "R4B", "R5", "R6")) {
switch (version) {
} else if (Utilities.existsInList(version.toUpperCase(), "R2", "R2B", "R3", "R4", "R4B", "R5", "R6")) {
switch (version.toUpperCase()) {
case "R2": return "1.0";
case "R2B": return "1.4";
case "R3": return "3.0";

View File

@ -1098,6 +1098,9 @@ public class I18nConstants {
public static final String VALUESET_BAD_FILTER_VALUE_CODED = "VALUESET_BAD_FILTER_VALUE_CODED";
public static final String VALUESET_BAD_FILTER_VALUE_CODED_INVALID = "VALUESET_BAD_FILTER_VALUE_CODED_INVALID";
public static final String VALUESET_BAD_FILTER_OP = "VALUESET_BAD_FILTER_OP";
public static final String VALUESET_BAD_FILTER_VALUE_HAS_COMMA = "VALUESET_BAD_FILTER_VALUE_HAS_COMMA";
public static final String VALUESET_BAD_FILTER_VALUE_VALID_REGEX = "VALUESET_BAD_FILTER_VALUE_VALID_REGEX";
public static final String VALUESET_BAD_PROPERTY_NO_REGEX = "VALUESET_BAD_PROPERTY_NO_REGEX";
}

View File

@ -1128,7 +1128,7 @@ VALUESET_INCLUDE_CS_SUPPLEMENT = The value set references CodeSystem ''{0}'' whi
VALUESET_INCLUDE_CSVER_SUPPLEMENT = The value set references CodeSystem ''{0}'' version ''{2}'' which is a supplement. It must reference the underlying CodeSystem ''{1}'' and use the http://hl7.org/fhir/StructureDefinition/valueset-supplement extension for the supplement
CODESYSTEM_SUPP_NO_DISPLAY = This display (''{0}'') differs from that defined by the base code system (''{1}''). Both displays claim to be 'the "primary designation" for the same language (''{2}''), and the correct interpretation of this is undefined
CODESYSTEM_NOT_CONTAINED = CodeSystems are referred to directly from Coding.system, so it's generally best for them not to be contained resources
CODESYSTEM_THO_CHECK = Most code systems defined in HL7 IGs will need to move to THO later during the process. Consider giving this code system a THO URL now (See https://confluence.hl7.org/display/TSMG/Terminology+Play+Book)
CODESYSTEM_THO_CHECK = Most code systems defined in HL7 IGs will need to move to THO later during the process. Consider giving this code system a THO URL now (See https://confluence.hl7.org/display/TSMG/Terminology+Play+Book, and/or talk to TSMG)
TYPE_SPECIFIC_CHECKS_DT_CANONICAL_MULTIPLE_POSSIBLE_VERSIONS = There are multiple different potential matches for the url ''{0}''. It might be a good idea to fix to the correct version to reduce the likelihood of a wrong version being selected by an implementation/implementer. Using version ''{1}'', found versions: {2}
ABSTRACT_CODE_NOT_ALLOWED = Code ''{0}#{1}'' is abstract, and not allowed in this context
CODESYSTEM_PROPERTY_DUPLICATE_URI = A property is already defined with the URI ''{0}''
@ -1156,3 +1156,7 @@ VALUESET_BAD_FILTER_VALUE_VALID_CODE = The value for a filter based on property
VALUESET_BAD_FILTER_VALUE_CODED = The value for a filter based on property ''{0}'' must be in the format system(|version)#code, not ''{1}''
VALUESET_BAD_FILTER_VALUE_CODED_INVALID = The value for a filter based on property ''{0}'' is ''{1}'' which is not a valid code ({2})
VALUESET_BAD_FILTER_OP = The operation ''{0}'' is not allowed for property ''{1}''. Allowed ops: {2}
VALUESET_BAD_FILTER_VALUE_HAS_COMMA = The filter value has a comma, but the operation is different to 'in' and 'not-in', so the comma will be interpreted as part of the {0} value
VALUESET_BAD_FILTER_VALUE_VALID_REGEX = The value for a filter based on property ''{0}'' should be a valid regex, not ''{1}'' (err = ''{2}'')
VALUESET_BAD_PROPERTY_NO_REGEX = Cannot apply a regex filter to the property ''{0}'' (usually regex filters are applied to the codes, or a named property of the code system)

View File

@ -5,6 +5,8 @@ import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.CodeSystem.CodeSystemFilterComponent;
@ -59,41 +61,46 @@ public class ValueSetValidator extends BaseValidator {
}
public enum CodeValidationRule {
Warning, Error, None
}
public class PropertyValidationRules {
boolean repeating;
PropertyFilterType type;
EnumSet<PropertyOperation> ops;
protected PropertyValidationRules(boolean repeating, PropertyFilterType type, PropertyOperation... ops) {
private PropertyFilterType type;
private CodeValidationRule codeValidation;
private EnumSet<PropertyOperation> ops;
protected PropertyValidationRules(PropertyFilterType type, CodeValidationRule codeValidation, PropertyOperation... ops) {
super();
this.repeating = repeating;
this.type = type;
this.codeValidation = codeValidation;
this.ops = EnumSet.noneOf(PropertyOperation.class);
for (PropertyOperation op : ops) {
this.ops.add(op);
}
}
public PropertyValidationRules(boolean repeating, PropertyFilterType type, EnumSet<PropertyOperation> ops) {
public PropertyValidationRules(PropertyFilterType type, CodeValidationRule codeValidation, EnumSet<PropertyOperation> ops) {
super();
this.repeating = repeating;
this.type = type;
this.codeValidation = codeValidation;
this.ops = ops;
}
public boolean isRepeating() {
return repeating;
}
public PropertyFilterType getType() {
return type;
}
public EnumSet<PropertyOperation> getOps() {
return ops;
}
public CodeValidationRule getCodeValidation() {
return codeValidation;
}
}
public enum PropertyFilterType {
Boolean, Integer, Decimal, Code, DateTime, ValidCode, Coding
Boolean, Integer, Decimal, Code, DateTime, Coding
}
private static final int TOO_MANY_CODES_TO_VALIDATE = 1000;
@ -422,13 +429,22 @@ public class ValueSetValidator extends BaseValidator {
}
if ("exists".equals(op)) {
ok = checkFilterValue(errors, stack, system, version, ok, property, value, PropertyFilterType.Boolean) && ok;
} else if (rules.isRepeating()) {
ok = checkFilterValue(errors, stack, system, version, ok, property, op, value, PropertyFilterType.Boolean, null) && ok;
} else if ("regex".equals(op)) {
String err = null;
try {
Pattern.compile(value);
} catch (PatternSyntaxException e) {
err = e.getMessage();
}
ok = rule(errors, "2024-03-09", IssueType.INVALID, stack, err == null, I18nConstants.VALUESET_BAD_FILTER_VALUE_VALID_REGEX, property, value, err) && ok;
ok = rule(errors, "2024-03-09", IssueType.INVALID, stack, !"concept".equals(property), I18nConstants.VALUESET_BAD_PROPERTY_NO_REGEX, property) && ok;
} else if (Utilities.existsInList(op, "in", "not-in")) {
for (String v : value.split("\\,")) {
ok = checkFilterValue(errors, stack, system, version, ok, property, v, rules.getType()) && ok;
ok = checkFilterValue(errors, stack, system, version, ok, property, op, v, rules.getType(), rules.getCodeValidation()) && ok;
}
} else {
ok = checkFilterValue(errors, stack, system, version, ok, property, value, rules.getType()) && ok;
ok = checkFilterValue(errors, stack, system, version, ok, property, op, value, rules.getType(), rules.getCodeValidation()) && ok;
}
}
}
@ -454,8 +470,11 @@ public class ValueSetValidator extends BaseValidator {
return false;
}
private boolean checkFilterValue(List<ValidationMessage> errors, NodeStack stack, String system, String version,boolean ok, String property, String value, PropertyFilterType type) {
private boolean checkFilterValue(List<ValidationMessage> errors, NodeStack stack, String system, String version,boolean ok, String property, String op, String value, PropertyFilterType type, CodeValidationRule cr) {
if (type != null) {
if (!Utilities.existsInList(op, "in", "not-in")) {
hint(errors, "2024-03-09", IssueType.INVALID, stack.getLiteralPath(), !value.contains(","), I18nConstants.VALUESET_BAD_FILTER_VALUE_HAS_COMMA, type.toString());
}
switch (type) {
case Boolean:
ok = rule(errors, "2024-03-09", IssueType.INVALID, stack,
@ -466,6 +485,14 @@ public class ValueSetValidator extends BaseValidator {
ok = rule(errors, "2024-03-09", IssueType.INVALID, stack,
value.trim().equals(value),
I18nConstants.VALUESET_BAD_FILTER_VALUE_CODE, property, value) && ok;
if (cr == CodeValidationRule.Error || cr == CodeValidationRule.Warning) {
ValidationResult vr = context.validateCode(baseOptions, system, version, value, null);
if (cr == CodeValidationRule.Error) {
ok = rule(errors, "2024-03-09", IssueType.INVALID, stack.getLiteralPath(), vr.isOk(), I18nConstants.VALUESET_BAD_FILTER_VALUE_VALID_CODE, property, value, system, vr.getMessage()) && ok;
} else {
warning(errors, "2024-03-09", IssueType.INVALID, stack.getLiteralPath(), vr.isOk(), I18nConstants.VALUESET_BAD_FILTER_VALUE_VALID_CODE, property, value, system, vr.getMessage());
}
}
break;
case DateTime:
ok = rule(errors, "2024-03-09", IssueType.INVALID, stack.getLiteralPath(),
@ -482,18 +509,12 @@ public class ValueSetValidator extends BaseValidator {
Utilities.isInteger(value),
I18nConstants.VALUESET_BAD_FILTER_VALUE_INTEGER, property, value) && ok;
break;
case ValidCode :
ValidationResult vr = context.validateCode(baseOptions, system, version, value, null);
ok = rule(errors, "2024-03-09", IssueType.INVALID, stack.getLiteralPath(),
vr.isOk(),
I18nConstants.VALUESET_BAD_FILTER_VALUE_VALID_CODE, property, value, system, vr.getMessage()) && ok;
break;
case Coding :
Coding code = Coding.fromLiteral(value);
if (code == null) {
ok = rule(errors, "2024-03-09", IssueType.INVALID, stack, false, I18nConstants.VALUESET_BAD_FILTER_VALUE_CODED, property, value) && ok;
} else {
vr = context.validateCode(baseOptions, code, null);
ValidationResult vr = context.validateCode(baseOptions, code, null);
ok = rule(errors, "2024-03-09", IssueType.INVALID, stack, vr.isOk(), I18nConstants.VALUESET_BAD_FILTER_VALUE_CODED_INVALID, property, value, vr.getMessage()) && ok;
}
break;
@ -521,12 +542,16 @@ public class ValueSetValidator extends BaseValidator {
if (property.equals(p.getCode())) {
if (p.getType() != null) {
switch (p.getType()) {
case BOOLEAN: return new PropertyValidationRules(false, PropertyFilterType.Boolean, ops);
case CODE: return new PropertyValidationRules(false, PropertyFilterType.ValidCode, ops);
case CODING: return new PropertyValidationRules(false, PropertyFilterType.Coding, ops);
case DATETIME: return new PropertyValidationRules(false, PropertyFilterType.DateTime, ops);
case DECIMAL: return new PropertyValidationRules(false, PropertyFilterType.Decimal, ops);
case INTEGER: return new PropertyValidationRules(false, PropertyFilterType.Integer, ops);
case BOOLEAN: return new PropertyValidationRules(PropertyFilterType.Boolean, null, ops);
case CODE:
// the definitions say " a code that identifies a concept defined in the code system" -> ValidCode.
// but many people have ignored that and defined a property as 'code' because it's from a list of values that are otherwise undefined
boolean external = !forPublication || cs.getWebPath() == null || Utilities.isAbsoluteUrl(cs.getWebPath());
return new PropertyValidationRules(PropertyFilterType.Code, external ? CodeValidationRule.Warning : CodeValidationRule.Error, ops); // valid code... the definitions say that, but people were missing that in the pastm
case CODING: return new PropertyValidationRules(PropertyFilterType.Coding, null, ops);
case DATETIME: return new PropertyValidationRules(PropertyFilterType.DateTime, null, ops);
case DECIMAL: return new PropertyValidationRules(PropertyFilterType.Decimal, null, ops);
case INTEGER: return new PropertyValidationRules(PropertyFilterType.Integer, null, ops);
case STRING: return null;
}
}
@ -535,51 +560,51 @@ public class ValueSetValidator extends BaseValidator {
}
switch (property) {
case "concept" : return new PropertyValidationRules(false, PropertyFilterType.ValidCode, addToOps(ops, PropertyOperation.Equals, PropertyOperation.In, PropertyOperation.IsA, PropertyOperation.DescendentOf, PropertyOperation.DescendentLeaf, PropertyOperation.IsNotA, PropertyOperation.NotIn));
case "code" : return new PropertyValidationRules(false, PropertyFilterType.ValidCode, addToOps(ops, PropertyOperation.Equals, PropertyOperation.RegEx));
case "status" : return new PropertyValidationRules(false, PropertyFilterType.Code, ops);
case "inactive" : return new PropertyValidationRules(false, PropertyFilterType.Boolean, ops);
case "effectiveDate" : return new PropertyValidationRules(false, PropertyFilterType.DateTime, ops);
case "deprecationDate" : return new PropertyValidationRules(false, PropertyFilterType.DateTime, ops);
case "retirementDate" : return new PropertyValidationRules(false, PropertyFilterType.DateTime, ops);
case "notSelectable" : return new PropertyValidationRules(false, PropertyFilterType.Boolean, ops);
case "parent" : return new PropertyValidationRules(false, PropertyFilterType.ValidCode, ops);
case "child" : return new PropertyValidationRules(false, PropertyFilterType.ValidCode, ops);
case "partOf" : return new PropertyValidationRules(false, PropertyFilterType.ValidCode, ops);
case "synonym" : return new PropertyValidationRules(false, PropertyFilterType.Code, ops);
case "concept" : return new PropertyValidationRules(PropertyFilterType.Code, CodeValidationRule.Error, addToOps(ops, PropertyOperation.Equals, PropertyOperation.In, PropertyOperation.IsA, PropertyOperation.DescendentOf, PropertyOperation.DescendentLeaf, PropertyOperation.IsNotA, PropertyOperation.NotIn));
case "code" : return new PropertyValidationRules(PropertyFilterType.Code, CodeValidationRule.Error, addToOps(ops, PropertyOperation.Equals, PropertyOperation.RegEx));
case "status" : return new PropertyValidationRules(PropertyFilterType.Code, CodeValidationRule.None, ops);
case "inactive" : return new PropertyValidationRules(PropertyFilterType.Boolean,null, ops);
case "effectiveDate" : return new PropertyValidationRules(PropertyFilterType.DateTime, null, ops);
case "deprecationDate" : return new PropertyValidationRules(PropertyFilterType.DateTime, null, ops);
case "retirementDate" : return new PropertyValidationRules(PropertyFilterType.DateTime, null, ops);
case "notSelectable" : return new PropertyValidationRules(PropertyFilterType.Boolean, null, ops);
case "parent" : return new PropertyValidationRules(PropertyFilterType.Code, CodeValidationRule.Error, ops);
case "child" : return new PropertyValidationRules(PropertyFilterType.Code, CodeValidationRule.Error, ops);
case "partOf" : return new PropertyValidationRules(PropertyFilterType.Code, CodeValidationRule.Error, ops);
case "synonym" : return new PropertyValidationRules(PropertyFilterType.Code, CodeValidationRule.None, ops); // ? none?
case "comment" : return null;
case "itemWeight" : return new PropertyValidationRules(false, PropertyFilterType.Decimal, ops);
case "itemWeight" : return new PropertyValidationRules(PropertyFilterType.Decimal, null, ops);
}
switch (system) {
case "http://loinc.org" :
if (Utilities.existsInList(property, "copyright", "STATUS", "CLASS", "CONSUMER_NAME", "ORDER_OBS", "DOCUMENT_SECTION")) {
return new PropertyValidationRules(false, PropertyFilterType.Code);
if (Utilities.existsInList(property, "copyright", "STATUS", "CLASS", "CONSUMER_NAME", "ORDER_OBS", "DOCUMENT_SECTION", "SCALE_TYP")) {
return new PropertyValidationRules(PropertyFilterType.Code, CodeValidationRule.None);
} else if ("CLASSTYPE".equals(property)) {
return new PropertyValidationRules(false, PropertyFilterType.Integer, addToOps(ops, PropertyOperation.Equals, PropertyOperation.In));
return new PropertyValidationRules(PropertyFilterType.Integer, null, addToOps(ops, PropertyOperation.Equals, PropertyOperation.In));
} else {
return new PropertyValidationRules(false, PropertyFilterType.ValidCode, addToOps(ops, PropertyOperation.Equals, PropertyOperation.In));
return new PropertyValidationRules(PropertyFilterType.Code, CodeValidationRule.Error, addToOps(ops, PropertyOperation.Equals, PropertyOperation.In));
}
case "http://snomed.info/sct":
switch (property) {
case "constraint": return null; // for now
case "expressions": return new PropertyValidationRules(false, PropertyFilterType.Boolean, addToOps(ops, PropertyOperation.Equals, PropertyOperation.In));
case "expressions": return new PropertyValidationRules(PropertyFilterType.Boolean, null, addToOps(ops, PropertyOperation.Equals, PropertyOperation.In));
default:
return new PropertyValidationRules(false, PropertyFilterType.ValidCode, addToOps(ops, PropertyOperation.Equals, PropertyOperation.In));
return new PropertyValidationRules(PropertyFilterType.Code, CodeValidationRule.Error, addToOps(ops, PropertyOperation.Equals, PropertyOperation.In));
}
case "http://www.nlm.nih.gov/research/umls/rxnorm" : return new PropertyValidationRules(false, PropertyFilterType.Code, ops);
case "http://unitsofmeasure.org" : return new PropertyValidationRules(false, PropertyFilterType.Code, ops);
case "http://www.nlm.nih.gov/research/umls/rxnorm" : return new PropertyValidationRules(PropertyFilterType.Code, CodeValidationRule.None, ops);
case "http://unitsofmeasure.org" : return new PropertyValidationRules(PropertyFilterType.Code, CodeValidationRule.None, ops);
case "http://www.ama-assn.org/go/cpt" :
switch (property) {
case "modifier": return new PropertyValidationRules(false, PropertyFilterType.Boolean, ops);
case "kind" : return new PropertyValidationRules(true, PropertyFilterType.Code, ops); // for now
case "modified": return new PropertyValidationRules(false, PropertyFilterType.Boolean, ops);
case "modifier": return new PropertyValidationRules(PropertyFilterType.Boolean, null, ops);
case "kind" : return new PropertyValidationRules(PropertyFilterType.Code, CodeValidationRule.None, ops); // for now
case "modified": return new PropertyValidationRules(PropertyFilterType.Boolean, null, ops);
case "code" : return null;
case "telemedicine": return new PropertyValidationRules(false, PropertyFilterType.Boolean, ops);
case "orthopox" : return new PropertyValidationRules(false, PropertyFilterType.Boolean, ops);
case "telemedicine": return new PropertyValidationRules(PropertyFilterType.Boolean, null, ops);
case "orthopox" : return new PropertyValidationRules(PropertyFilterType.Boolean,null, ops);
}
}
if (ops != null) {
return new PropertyValidationRules(false, null, ops);
return new PropertyValidationRules(null, null, ops);
} else {
return null;
}

View File

@ -20,7 +20,7 @@
<properties>
<guava_version>32.0.1-jre</guava_version>
<hapi_fhir_version>6.4.1</hapi_fhir_version>
<validator_test_case_version>1.5.1</validator_test_case_version>
<validator_test_case_version>1.5.2-SNAPSHOT</validator_test_case_version>
<jackson_version>2.16.0</jackson_version>
<junit_jupiter_version>5.9.2</junit_jupiter_version>
<junit_platform_launcher_version>1.8.2</junit_platform_launcher_version>