Merge pull request #1149 from hapifhir/gg-202303-cm-sm-validation

Gg 202303 cm sm validation
This commit is contained in:
Grahame Grieve 2023-03-06 06:08:59 +11:00 committed by GitHub
commit 58ad99cfd3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 669 additions and 83 deletions

View File

@ -1,7 +1,11 @@
## Validator Changes
* no changes
* Add ConceptMap validation
* Add StructureMap validation
* Validate using type regex (had been omitted to now, mostly affects decimal validation)
## Other code changes
* no changes
* Various fixes and utilities to support StructureMap & ConceptMap validation
* Fix Observation.value conversion between R5 and other versions

View File

@ -1,6 +1,7 @@
package org.hl7.fhir.convertors.conv10_50.resources10_50;
import org.hl7.fhir.convertors.context.ConversionContext10_50;
import org.hl7.fhir.convertors.context.ConversionContext43_50;
import org.hl7.fhir.convertors.conv10_50.datatypes10_50.Reference10_50;
import org.hl7.fhir.convertors.conv10_50.datatypes10_50.complextypes10_50.CodeableConcept10_50;
import org.hl7.fhir.convertors.conv10_50.datatypes10_50.complextypes10_50.Identifier10_50;
@ -8,6 +9,7 @@ import org.hl7.fhir.convertors.conv10_50.datatypes10_50.complextypes10_50.Range1
import org.hl7.fhir.convertors.conv10_50.datatypes10_50.complextypes10_50.SimpleQuantity10_50;
import org.hl7.fhir.convertors.conv10_50.datatypes10_50.primitivetypes10_50.Instant10_50;
import org.hl7.fhir.convertors.conv10_50.datatypes10_50.primitivetypes10_50.String10_50;
import org.hl7.fhir.convertors.conv43_50.datatypes43_50.primitive43_50.String43_50;
import org.hl7.fhir.exceptions.FHIRException;
public class Observation10_50 {
@ -34,8 +36,15 @@ public class Observation10_50 {
if (src.hasIssuedElement())
tgt.setIssuedElement(Instant10_50.convertInstant(src.getIssuedElement()));
for (org.hl7.fhir.r5.model.Reference t : src.getPerformer()) tgt.addPerformer(Reference10_50.convertReference(t));
if (src.hasValue())
tgt.setValue(ConversionContext10_50.INSTANCE.getVersionConvertor_10_50().convertType(src.getValue()));
if (src.hasValue()) {
if (src.hasValueMarkdownType()) {
tgt.setValue(String10_50.convertMarkdownToString(src.getValueMarkdownType()));
} else {
tgt.setValue(ConversionContext10_50.INSTANCE.getVersionConvertor_10_50().convertType(src.getValue()));
}
}
if (src.hasDataAbsentReason())
tgt.setDataAbsentReason(CodeableConcept10_50.convertCodeableConcept(src.getDataAbsentReason()));
if (src.hasInterpretation())
@ -84,8 +93,15 @@ public class Observation10_50 {
tgt.setIssuedElement(Instant10_50.convertInstant(src.getIssuedElement()));
for (org.hl7.fhir.dstu2.model.Reference t : src.getPerformer())
tgt.addPerformer(Reference10_50.convertReference(t));
if (src.hasValue())
tgt.setValue(ConversionContext10_50.INSTANCE.getVersionConvertor_10_50().convertType(src.getValue()));
if (src.hasValue()) {
if (src.hasValueStringType()) {
tgt.setValue(String10_50.convertStringToMarkdown(src.getValueStringType()));
} else {
tgt.setValue(ConversionContext10_50.INSTANCE.getVersionConvertor_10_50().convertType(src.getValue()));
}
}
if (src.hasDataAbsentReason())
tgt.setDataAbsentReason(CodeableConcept10_50.convertCodeableConcept(src.getDataAbsentReason()));
if (src.hasInterpretation())

View File

@ -1,6 +1,7 @@
package org.hl7.fhir.convertors.conv30_50.resources30_50;
import org.hl7.fhir.convertors.context.ConversionContext30_50;
import org.hl7.fhir.convertors.context.ConversionContext43_50;
import org.hl7.fhir.convertors.conv30_50.datatypes30_50.Reference30_50;
import org.hl7.fhir.convertors.conv30_50.datatypes30_50.complextypes30_50.CodeableConcept30_50;
import org.hl7.fhir.convertors.conv30_50.datatypes30_50.complextypes30_50.Identifier30_50;
@ -8,6 +9,7 @@ import org.hl7.fhir.convertors.conv30_50.datatypes30_50.complextypes30_50.Range3
import org.hl7.fhir.convertors.conv30_50.datatypes30_50.complextypes30_50.SimpleQuantity30_50;
import org.hl7.fhir.convertors.conv30_50.datatypes30_50.primitivetypes30_50.Instant30_50;
import org.hl7.fhir.convertors.conv30_50.datatypes30_50.primitivetypes30_50.String30_50;
import org.hl7.fhir.convertors.conv43_50.datatypes43_50.primitive43_50.String43_50;
import org.hl7.fhir.exceptions.FHIRException;
public class Observation30_50 {
@ -35,8 +37,15 @@ public class Observation30_50 {
if (src.hasIssued())
tgt.setIssuedElement(Instant30_50.convertInstant(src.getIssuedElement()));
for (org.hl7.fhir.r5.model.Reference t : src.getPerformer()) tgt.addPerformer(Reference30_50.convertReference(t));
if (src.hasValue())
tgt.setValue(ConversionContext30_50.INSTANCE.getVersionConvertor_30_50().convertType(src.getValue()));
if (src.hasValue()) {
if (src.hasValueMarkdownType()) {
tgt.setValue(String30_50.convertMarkdownToString(src.getValueMarkdownType()));
} else {
tgt.setValue(ConversionContext30_50.INSTANCE.getVersionConvertor_30_50().convertType(src.getValue()));
}
}
if (src.hasDataAbsentReason())
tgt.setDataAbsentReason(CodeableConcept30_50.convertCodeableConcept(src.getDataAbsentReason()));
if (src.hasInterpretation())
@ -86,8 +95,15 @@ public class Observation30_50 {
tgt.setIssuedElement(Instant30_50.convertInstant(src.getIssuedElement()));
for (org.hl7.fhir.dstu3.model.Reference t : src.getPerformer())
tgt.addPerformer(Reference30_50.convertReference(t));
if (src.hasValue())
tgt.setValue(ConversionContext30_50.INSTANCE.getVersionConvertor_30_50().convertType(src.getValue()));
if (src.hasValue()) {
if (src.hasValueStringType()) {
tgt.setValue(String30_50.convertStringToMarkdown(src.getValueStringType()));
} else {
tgt.setValue(ConversionContext30_50.INSTANCE.getVersionConvertor_30_50().convertType(src.getValue()));
}
}
if (src.hasDataAbsentReason())
tgt.setDataAbsentReason(CodeableConcept30_50.convertCodeableConcept(src.getDataAbsentReason()));
if (src.hasInterpretation())

View File

@ -1,6 +1,8 @@
package org.hl7.fhir.convertors.conv43_50.resources43_50;
import org.hl7.fhir.convertors.context.ConversionContext40_50;
import org.hl7.fhir.convertors.context.ConversionContext43_50;
import org.hl7.fhir.convertors.conv40_50.datatypes40_50.primitive40_50.String40_50;
import org.hl7.fhir.convertors.conv43_50.datatypes43_50.general43_50.Annotation43_50;
import org.hl7.fhir.convertors.conv43_50.datatypes43_50.general43_50.CodeableConcept43_50;
import org.hl7.fhir.convertors.conv43_50.datatypes43_50.general43_50.Identifier43_50;
@ -67,8 +69,13 @@ public class Observation43_50 {
if (src.hasIssued())
tgt.setIssuedElement(Instant43_50.convertInstant(src.getIssuedElement()));
for (org.hl7.fhir.r4b.model.Reference t : src.getPerformer()) tgt.addPerformer(Reference43_50.convertReference(t));
if (src.hasValue())
tgt.setValue(ConversionContext43_50.INSTANCE.getVersionConvertor_43_50().convertType(src.getValue()));
if (src.hasValue()) {
if (src.hasValueStringType()) {
tgt.setValue(String43_50.convertStringToMarkdown(src.getValueStringType()));
} else {
tgt.setValue(ConversionContext43_50.INSTANCE.getVersionConvertor_43_50().convertType(src.getValue()));
}
}
if (src.hasDataAbsentReason())
tgt.setDataAbsentReason(CodeableConcept43_50.convertCodeableConcept(src.getDataAbsentReason()));
for (org.hl7.fhir.r4b.model.CodeableConcept t : src.getInterpretation())
@ -117,8 +124,13 @@ public class Observation43_50 {
if (src.hasIssued())
tgt.setIssuedElement(Instant43_50.convertInstant(src.getIssuedElement()));
for (org.hl7.fhir.r5.model.Reference t : src.getPerformer()) tgt.addPerformer(Reference43_50.convertReference(t));
if (src.hasValue())
tgt.setValue(ConversionContext43_50.INSTANCE.getVersionConvertor_43_50().convertType(src.getValue()));
if (src.hasValue()) {
if (src.hasValueMarkdownType()) {
tgt.setValue(String43_50.convertMarkdownToString(src.getValueMarkdownType()));
} else {
tgt.setValue(ConversionContext43_50.INSTANCE.getVersionConvertor_43_50().convertType(src.getValue()));
}
}
if (src.hasDataAbsentReason())
tgt.setDataAbsentReason(CodeableConcept43_50.convertCodeableConcept(src.getDataAbsentReason()));
for (org.hl7.fhir.r5.model.CodeableConcept t : src.getInterpretation())

View File

@ -12,8 +12,10 @@ import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.Element.SpecialElement;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.model.ExpressionNode;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupUnmappedMode;
import org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship;
import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
@ -78,6 +80,10 @@ public class FmlParser extends ParserBase {
}
}
lexer.setMetadataFormat(false);
if (!result.hasChild("status")) {
result.makeElement("status").setValue("draft");
}
while (lexer.hasToken("conceptmap"))
parseConceptMap(result, lexer);
@ -86,6 +92,9 @@ public class FmlParser extends ParserBase {
while (lexer.hasToken("imports"))
parseImports(result, lexer);
while (lexer.hasToken("conceptmap"))
parseConceptMap(result, lexer);
while (!lexer.done()) {
parseGroup(result, lexer);
}
@ -94,24 +103,22 @@ public class FmlParser extends ParserBase {
} catch (Exception e) {
logError("2023-02-24", -1, -1, "?", IssueType.INVALID, e.getMessage(), IssueSeverity.FATAL);
}
if (!result.hasChild("status")) {
result.makeElement("status").setValue("draft");
}
result.setIgnorePropertyOrder(true);
return result;
}
private void parseConceptMap(Element result, FHIRLexer lexer) throws FHIRLexerException {
private void parseConceptMap(Element structureMap, FHIRLexer lexer) throws FHIRLexerException {
lexer.token("conceptmap");
Element map = Manager.build(context, context.fetchTypeDefinition("ConceptMap"));
Element map = structureMap.makeElement("contained");
StructureDefinition sd = context.fetchTypeDefinition("ConceptMap");
map.updateProperty(new Property(context, sd.getSnapshot().getElement().get(0), sd), SpecialElement.fromProperty(map.getProperty()), map.getProperty());
map.setType("ConceptMap");
Element eid = map.makeElement("id").markLocation(lexer.getCurrentLocation());
String id = lexer.readConstant("map id");
if (id.startsWith("#"))
throw lexer.error("Concept Map identifier must start with #");
eid.setValue(id);
map.makeElement("status").setValue(PublicationStatus.DRAFT.toCode()); // todo: how to add this to the text format
result.makeElement("contained").setElement("resource", map);
map.makeElement("status").setValue(structureMap.getChildValue("status"));
lexer.token("{");
// lexer.token("source");
// map.setSource(new UriType(lexer.readConstant("source")));

View File

@ -47,6 +47,7 @@ import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CanonicalType;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent;
import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent;
import org.hl7.fhir.r5.model.CodeSystem.PropertyType;
@ -414,16 +415,6 @@ public class CodeSystemUtilities {
return cs;
}
public static boolean makeCSShareable(CodeSystem cs) {
if (!cs.hasMeta())
cs.setMeta(new Meta());
for (UriType t : cs.getMeta().getProfile())
if ("http://hl7.org/fhir/StructureDefinition/shareablecodesystem".equals(t.getValue()))
return false;
cs.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareablecodesystem"));
return true;
}
public static void setOID(CodeSystem cs, String oid) {
if (!oid.startsWith("urn:oid:"))
oid = "urn:oid:" + oid;
@ -678,5 +669,18 @@ public class CodeSystemUtilities {
public static boolean isNotCurrent(CodeSystem cs, ConceptDefinitionComponent c) {
return isInactive(cs, c) || isDeprecated(cs, c, false);
}
public static List<String> getDisplays(CodeSystem srcCS, ConceptDefinitionComponent cd) {
List<String> list = new ArrayList<>();
if (cd.hasDisplay()) {
list.add(cd.getDisplay());
}
for (ConceptDefinitionDesignationComponent d : cd.getDesignation()) {
if (!list.contains(d.getValue())) {
list.add(d.getValue());
}
}
return list;
}
}

View File

@ -1,7 +1,18 @@
package org.hl7.fhir.r5.terminologies;
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.r5.model.CanonicalType;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.ConceptMap;
import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent;
import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent;
import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent;
import org.hl7.fhir.r5.model.Identifier;
import org.hl7.fhir.r5.model.Meta;
import org.hl7.fhir.r5.model.UriType;
import org.hl7.fhir.r5.model.ValueSet;
public class ConceptMapUtilities {
@ -30,5 +41,48 @@ public class ConceptMapUtilities {
cm.addIdentifier().setSystem("urn:ietf:rfc:3986").setValue(oid);
}
public static boolean hasMappingForSource(ConceptMap cm, String system, String version, String code) {
for (ConceptMapGroupComponent grp : cm.getGroup()) {
if (system.equals(grp.getSource())) { // to do: version
for (SourceElementComponent e : grp.getElement()) {
if (code.equals(e.getCode())) {
return true; // doesn't matter if it's actually unmapped
}
}
}
}
return false;
}
public static List<Coding> listTargets(ConceptMap cm, List<String> systems) {
List<Coding> list = new ArrayList<>();
for (ConceptMapGroupComponent grp : cm.getGroup()) {
if (systems.isEmpty() || systems.contains(grp.getSource())) { // to do: version
for (SourceElementComponent e : grp.getElement()) {
for (TargetElementComponent t : e.getTarget()) {
if (t.hasCode()) {
list.add(new Coding(grp.getTarget(), t.getCode(), t.getDisplay()));
}
}
}
}
}
return list;
}
public static ConceptMap makeShareable(ConceptMap cm) {
if (!cm.hasExperimental()) {
cm.setExperimental(false);
}
if (!cm.hasMeta())
cm.setMeta(new Meta());
for (UriType t : cm.getMeta().getProfile())
if ("http://hl7.org/fhir/StructureDefinition/shareableconceptmap".equals(t.getValue()))
return cm;
cm.getMeta().getProfile().add(new CanonicalType("http://hl7.org/fhir/StructureDefinition/shareableconceptmap"));
return cm;
}
}

View File

@ -51,6 +51,7 @@ import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent;
import org.hl7.fhir.r5.model.CodeType;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent;
import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent;
@ -311,5 +312,21 @@ public class ValueSetUtilities {
}
}
public static boolean hasCodeInExpansion(ValueSet vs, Coding code) {
return hasCodeInExpansion(vs.getExpansion().getContains(), code);
}
private static boolean hasCodeInExpansion(List<ValueSetExpansionContainsComponent> list, Coding code) {
for (ValueSetExpansionContainsComponent c : list) {
if (c.getSystem().equals(code.getSystem()) && c.getCode().equals(code.getCode())) {
return true;
}
if (hasCodeInExpansion(c.getContains(), code)) {
return true;
}
}
return false;
}
}

View File

@ -660,6 +660,9 @@ public class StructureMapUtilities {
while (lexer.hasToken("imports"))
parseImports(result, lexer);
while (lexer.hasToken("conceptmap"))
parseConceptMap(result, lexer);
while (!lexer.done()) {
parseGroup(result, lexer);
}

View File

@ -527,6 +527,8 @@ public class I18nConstants {
public static final String TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH = "Type_Specific_Checks_DT_Primitive_Length";
public static final String TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_NOTEMPTY = "Type_Specific_Checks_DT_Primitive_NotEmpty";
public static final String TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX = "Type_Specific_Checks_DT_Primitive_Regex";
public static final String TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX_TYPE = "Type_Specific_Checks_DT_Primitive_Regex_Type";
public static final String TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX_EXCEPTION = "TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX_EXCEPTION";
public static final String TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_VALUEEXT = "Type_Specific_Checks_DT_Primitive_ValueExt";
public static final String TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_WS = "Type_Specific_Checks_DT_Primitive_WS";
public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_NO_ANNOTATIONS = "TYPE_SPECIFIC_CHECKS_DT_QTY_NO_ANNOTATIONS";
@ -657,6 +659,10 @@ public class I18nConstants {
public static final String CODESYSTEM_SHAREABLE_MISSING_HL7 = "CODESYSTEM_SHAREABLE_MISSING_HL7";
public static final String CODESYSTEM_SHAREABLE_EXTRA_MISSING_HL7 = "CODESYSTEM_SHAREABLE_EXTRA_MISSING_HL7";
public static final String CODESYSTEM_SHAREABLE_EXTRA_MISSING = "CODESYSTEM_SHAREABLE_EXTRA_MISSING";
public static final String CONCEPTMAP_SHAREABLE_MISSING = "CONCEPTMAP_SHAREABLE_MISSING";
public static final String CONCEPTMAP_SHAREABLE_MISSING_HL7 = "CONCEPTMAP_SHAREABLE_MISSING_HL7";
public static final String CONCEPTMAP_SHAREABLE_EXTRA_MISSING_HL7 = "CONCEPTMAP_SHAREABLE_EXTRA_MISSING_HL7";
public static final String CONCEPTMAP_SHAREABLE_EXTRA_MISSING = "CONCEPTMAP_SHAREABLE_EXTRA_MISSING";
public static final String MEASURE_SHAREABLE_MISSING = "MEASURE_SHAREABLE_MISSING";
public static final String MEASURE_SHAREABLE_MISSING_HL7 = "MEASURE_SHAREABLE_MISSING_HL7";
public static final String MEASURE_SHAREABLE_EXTRA_MISSING_HL7 = "MEASURE_SHAREABLE_EXTRA_MISSING_HL7";
@ -812,6 +818,32 @@ public class I18nConstants {
public static final String SM_SOURCE_TYPE_NOT_FOUND = "SM_SOURCE_TYPE_NOT_FOUND";
public static final String SM_TARGET_TYPE_NOT_FOUND = "SM_TARGET_TYPE_NOT_FOUND";
public static final String SM_MATCHING_RULEGROUP_NOT_FOUND = "SM_MATCHING_RULEGROUP_NOT_FOUND";
public static final String SM_TARGET_TRANSFORM_MISSING_PARAMS = "SM_TARGET_TRANSFORM_MISSING_PARAMS";
public static final String SM_TARGET_TRANSFORM_TRANSLATE_NO_PARAM = "SM_TARGET_TRANSFORM_TRANSLATE_NO_PARAM";
public static final String SM_TARGET_TRANSFORM_TRANSLATE_UNKNOWN_SOURCE = "SM_TARGET_TRANSFORM_TRANSLATE_UNKNOWN_SOURCE";
public static final String SM_TARGET_TRANSFORM_TRANSLATE_CM_NOT_FOUND = "SM_TARGET_TRANSFORM_TRANSLATE_CM_NOT_FOUND";
public static final String SM_TARGET_TRANSFORM_TRANSLATE_CM_BAD_MODE = "SM_TARGET_TRANSFORM_TRANSLATE_CM_BAD_MODE";
public static final String SM_TARGET_TRANSLATE_BINDING_SOURCE = "SM_TARGET_TRANSLATE_BINDING_SOURCE";
public static final String SM_TARGET_TRANSLATE_BINDING_VS_SOURCE = "SM_TARGET_TRANSLATE_BINDING_VS_SOURCE";
public static final String SM_TARGET_TRANSLATE_BINDING_VSE_SOURCE = "SM_TARGET_TRANSLATE_BINDING_VSE_SOURCE";
public static final String SM_TARGET_TRANSLATE_BINDING_SOURCE_UNMAPPED = "SM_TARGET_TRANSLATE_BINDING_SOURCE_UNMAPPED";
public static final String SM_TARGET_TRANSLATE_BINDING_TARGET = "SM_TARGET_TRANSLATE_BINDING_TARGET";
public static final String SM_TARGET_TRANSLATE_BINDING_VS_TARGET = "SM_TARGET_TRANSLATE_BINDING_VS_TARGET";
public static final String SM_TARGET_TRANSLATE_BINDING_VSE_TARGET = "SM_TARGET_TRANSLATE_BINDING_VSE_TARGET";
public static final String SM_TARGET_TRANSLATE_BINDING_TARGET_WRONG = "SM_TARGET_TRANSLATE_BINDING_TARGET_WRONG";
public static final String CONCEPTMAP_GROUP_SOURCE_MISSING = "CONCEPTMAP_GROUP_SOURCE_MISSING";
public static final String CONCEPTMAP_GROUP_SOURCE_UNKNOWN = "CONCEPTMAP_GROUP_SOURCE_UNKNOWN";
public static final String CONCEPTMAP_GROUP_TARGET_MISSING = "CONCEPTMAP_GROUP_TARGET_MISSING";
public static final String CONCEPTMAP_GROUP_TARGET_UNKNOWN = "CONCEPTMAP_GROUP_TARGET_UNKNOWN";
public static final String CONCEPTMAP_GROUP_SOURCE_CODE_INVALID = "CONCEPTMAP_GROUP_SOURCE_CODE_INVALID";
public static final String CONCEPTMAP_GROUP_SOURCE_DISPLAY_INVALID = "CONCEPTMAP_GROUP_SOURCE_DISPLAY_INVALID";
public static final String CONCEPTMAP_GROUP_TARGET_CODE_INVALID = "CONCEPTMAP_GROUP_TARGET_CODE_INVALID";
public static final String CONCEPTMAP_GROUP_TARGET_DISPLAY_INVALID = "CONCEPTMAP_GROUP_TARGET_DISPLAY_INVALID";
public static final String CONCEPTMAP_GROUP_TARGET_PROPERTY_INVALID = "CONCEPTMAP_GROUP_TARGET_PROPERTY_INVALID";
public static final String CONCEPTMAP_GROUP_TARGET_PROPERTY_TYPE_MISMATCH = "CONCEPTMAP_GROUP_TARGET_PROPERTY_TYPE_MISMATCH";
public static final String CONCEPTMAP_GROUP_TARGET_PROPERTY_TYPE_NO_SYSTEM = "CONCEPTMAP_GROUP_TARGET_PROPERTY_TYPE_NO_SYSTEM";
public static final String CONCEPTMAP_GROUP_TARGET_PROPERTY_CODE_INVALID = "CONCEPTMAP_GROUP_TARGET_PROPERTY_CODE_INVALID";
public static final String CONCEPTMAP_GROUP_TARGET_PROPERTY_TYPE_UNKNOWN_SYSTEM = "CONCEPTMAP_GROUP_TARGET_PROPERTY_TYPE_UNKNOWN_SYSTEM";
}

View File

@ -29,7 +29,7 @@ public class PackageHacker {
private static boolean useSecureReferences = false;
public static void main(String[] args) throws FileNotFoundException, IOException {
new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/5.0.0-draft-final/hl7.fhir.r5.search.tgz");
new PackageHacker().edit("/Users/grahamegrieve/web/hl7.org/fhir/us/vitals/2020Sep/package.tgz");
}
private void edit(String name) throws FileNotFoundException, IOException {
@ -42,14 +42,6 @@ public class PackageHacker {
System.out.println(nice(pck.getNpm()));
change(pck.getNpm());
// fixContent(pck.getFolders().get("package").getContent());
// if (pck.getFolders().containsKey("openapi")) {
// fixContent(pck.getFolders().get("openapi").getContent());
// }
// if (pck.getFolders().containsKey("xml")) {
// fixContent(pck.getFolders().get("xml").getContent());
// }
// fixExampleContent(pck.getFolders().get("example").getContent());
System.out.println("Revised Package");
System.out.println("=======================");
@ -79,35 +71,9 @@ public class PackageHacker {
}
private void change(JsonObject npm) throws FileNotFoundException, IOException {
// fixVersions(npm);
// fixVersions(npm, ver);
npm.remove("notForPublication");
// npm.remove("name");
// npm.add("name", "hl7.fhir.r5.search");
// npm.remove("title");
// npm.add("title", "FHIR 5.0.0-draft-final package : Server Search Parameters");
// npm.add("url", "https://hl7chile.cl/fhir/ig/CoreCL/1.7.0");
// npm.remove("name");
// npm.addProperty("name", "hl7.fhir.uv.smart-app-launch");
// npm.remove("canonical");
// npm.addProperty("canonical", "http://hl7.org/fhir/us/davinci-drug-formulary");
//// npm.remove("description");
//// npm.addProperty("description", "Group Wrapper that includes all the R4 packages");
// npm.remove("url");
// npm.addProperty("url", "http://hl7.org/fhir/R4B");
// npm.remove("homepage");
// npm.addProperty("homepage", "http://hl7.org/fhir/R4B");
// npm.remove("dependencies");
// JsonObject dep = new JsonObject();
// npm.add("dependencies", dep);
// dep.addProperty("hl7.fhir.r4.core", "4.0.1");
// dep.addProperty("ch.fhir.ig.ch-core", "2.0.0");
// dep.addProperty("ch.fhir.ig.ch-epr-term", "2.0.4");
// dep.addProperty("ch.fhir.ig.ch-emed","current");
// dep.addProperty("hl7.fhir.r4.examples", "4.0.1");
// dep.addProperty("hl7.fhir.r4.expansions", "4.0.1");
// dep.addProperty("hl7.fhir.r4.elements", "4.0.1");
// npm.addProperty("jurisdiction", "urn:iso:std:iso:3166#CL");
npm.set("name", "hl7.fhir.us.vitals");
}
private void fixVersionInContent(Map<String, byte[]> content) {

View File

@ -212,6 +212,8 @@ Type_Specific_Checks_DT_OID_Valid = OIDs must be valid ({0})
Type_Specific_Checks_DT_Primitive_Length = value is longer than permitted maximum length of {0}
Type_Specific_Checks_DT_Primitive_NotEmpty = value cannot be empty
Type_Specific_Checks_DT_Primitive_Regex = Element value ''{0}'' does not meet regex ''{1}''
TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX_EXCEPTION = "Exception evaluating regex ''{0}'' on type {1}: {2}
Type_Specific_Checks_DT_Primitive_Regex_Type = Element value ''{0}'' does not meet {1} regex ''{2}''
Type_Specific_Checks_DT_Primitive_ValueExt = Primitive types must have a value or must have child extensions
Type_Specific_Checks_DT_Primitive_WS = Primitive types should not only be whitespace
Type_Specific_Checks_DT_String_Length = value is longer than permitted maximum length of 1 MB (1048576 bytes)
@ -776,6 +778,10 @@ CODESYSTEM_SHAREABLE_MISSING = The ShareableCodeSystem profile says that the {0}
CODESYSTEM_SHAREABLE_EXTRA_MISSING = The ShareableCodeSystem profile recommends that the {0} element is populated, but it is not present. Published code systems SHOULD conform to the ShareableCodeSystem profile
CODESYSTEM_SHAREABLE_MISSING_HL7 = The ShareableCodeSystem profile says that the {0} element is mandatory, but it is not found. HL7 Published code systems SHALL conform to the ShareableCodeSystem profile
CODESYSTEM_SHAREABLE_EXTRA_MISSING_HL7 = The ShareableCodeSystem profile recommends that the {0} element is populated, but it is not found. HL7 Published code systems SHALL conform to the ShareableCodeSystem profile
CONCEPTMAP_SHAREABLE_MISSING = The ShareableConceptMap profile says that the {0} element is mandatory, but it is not present. Published concept maps SHOULD conform to the ShareableConceptMap profile
CONCEPTMAP_SHAREABLE_EXTRA_MISSING = The ShareableConceptMap profile recommends that the {0} element is populated, but it is not present. Published concept maps SHOULD conform to the ShareableConceptMap profile
CONCEPTMAP_SHAREABLE_MISSING_HL7 = The ShareableConceptMap profile says that the {0} element is mandatory, but it is not found. HL7 Published concept maps SHALL conform to the ShareableConceptMap profile
CONCEPTMAP_SHAREABLE_EXTRA_MISSING_HL7 = The ShareableConceptMap profile recommends that the {0} element is populated, but it is not found. HL7 Published concept maps SHALL conform to the ShareableConceptMap profile
MEASURE_SHAREABLE_MISSING = The ShareableMeasure profile says that the {0} element is mandatory, but it is not present. Published measures SHOULD conform to the ShareableMeasure profile
MEASURE_SHAREABLE_EXTRA_MISSING = The ShareableMeasure profile recommends that the {0} element is populated, but it is not present. Published measures SHOULD conform to the ShareableMeasure profile
MEASURE_SHAREABLE_MISSING_HL7 = The ShareableMeasure profile says that the {0} element is mandatory, but it is not found. HL7 Published measures SHALL conform to the ShareableMeasure profile
@ -849,7 +855,7 @@ SM_TARGET_PATH_MULTIPLE_MATCHES = The target path {0}.{1} refers to the path {2}
SM_SOURCE_TYPE_INVALID = The type {0} is not valid in this source context {1}. The possible types are [{2}]
SM_TARGET_TRANSFORM_PARAM_COUNT_RANGE = Transform {0} takes {1}-{2} parameter(s) but {3} were found
SM_TARGET_TRANSFORM_PARAM_COUNT_SINGLE = Transform {0} takes {1} parameter(s) but {2} were found
SM_TARGET_TRANSFORM_NOT_CHECKED = Transform {0} not checked yet
SM_TARGET_TRANSFORM_NOT_CHECKED = Transform {0} not checked dyet
SM_TARGET_NO_TRANSFORM_NO_CHECKED = When there is no transform, parameters can''t be provided
SM_TARGET_TRANSFORM_TYPE_UNPROCESSIBLE = The value of the type parameter could not be processed
SM_TARGET_TRANSFORM_PARAM_UNPROCESSIBLE = The parameter at index {0} could not be processed (type = {1})
@ -862,7 +868,31 @@ SM_ORPHAN_GROUP = This group is not called from within this mapping script, and
SM_SOURCE_TYPE_NOT_FOUND = No source type was found, so the default group for this implied dependent rule could not be determined
SM_TARGET_TYPE_NOT_FOUND = No target type was found, so the default group for this implied dependent rule could not be determined
SM_MATCHING_RULEGROUP_NOT_FOUND = Unable to find a default rule for the type pair source={0} and target={1}
SM_TARGET_TRANSFORM_MISSING_PARAMS = One or more parameters to the translate operation are missing (should be 3, was {0})
SM_TARGET_TRANSFORM_TRANSLATE_NO_PARAM = No value for the {0} parameter found
SM_TARGET_TRANSFORM_TRANSLATE_UNKNOWN_SOURCE = The source variable {0} is unknown
SM_TARGET_TRANSFORM_TRANSLATE_CM_NOT_FOUND = The map_uri ''{0}'' could not be resolved, so the map can't be checked
SM_TARGET_TRANSFORM_TRANSLATE_CM_BAD_MODE = The value ''{0}'' for the output parameter is incorrect
SM_TARGET_TRANSLATE_BINDING_SOURCE = The source variable does not have a required binding, so this concept map can''t be checked
SM_TARGET_TRANSLATE_BINDING_VS_SOURCE = The source variable refers to an unknown value set ''{0}'', so this concept map can''t be checked
SM_TARGET_TRANSLATE_BINDING_VSE_SOURCE = There was an error expanding the source value set, so this concept map can''t be checked: ''{0}''
SM_TARGET_TRANSLATE_BINDING_SOURCE_UNMAPPED = The source value set includes one or more codes that the map does not translate: {0}
SM_TARGET_TRANSLATE_BINDING_TARGET = The target variable does not have a required binding, so this concept map can''t be checked
SM_TARGET_TRANSLATE_BINDING_VS_TARGET = The target variable refers to an unknown value set ''{0}'', so this concept map can''t be checked
SM_TARGET_TRANSLATE_BINDING_VSE_TARGET = There was an error expanding the target value set, so this concept map can''t be checked: ''{0}''
SM_TARGET_TRANSLATE_BINDING_TARGET_WRONG = The map produces one or more codes that the target value set does not include: {0}
CONCEPTMAP_GROUP_SOURCE_MISSING = No Source Code System, so the source codes cannot be checked
CONCEPTMAP_GROUP_SOURCE_UNKNOWN = Unknown Source Code System, so the source codes cannot be checked
CONCEPTMAP_GROUP_TARGET_MISSING = No Target Code System, so the source codes cannot be checked
CONCEPTMAP_GROUP_TARGET_UNKNOWN = Unknown Target Code System, so the source codes cannot be checked
CONCEPTMAP_GROUP_SOURCE_CODE_INVALID = The source code ''{0}'' is not valid in the code system {1}
CONCEPTMAP_GROUP_SOURCE_DISPLAY_INVALID = The source display ''{0}'' is not valid. Possible codes {1}
CONCEPTMAP_GROUP_TARGET_CODE_INVALID =The target code ''{0}'' is not valid in the code system {1}
CONCEPTMAP_GROUP_TARGET_DISPLAY_INVALID = The target display ''{0}'' is not valid. Possible codes {1}
CONCEPTMAP_GROUP_TARGET_PROPERTY_INVALID = The property code ''{0}'' is not known
CONCEPTMAP_GROUP_TARGET_PROPERTY_TYPE_MISMATCH = The type of this property should be {1} not {0}
CONCEPTMAP_GROUP_TARGET_PROPERTY_TYPE_NO_SYSTEM = Since no system has been provided, a plain code cannot be used
CONCEPTMAP_GROUP_TARGET_PROPERTY_CODE_INVALID = The code {0} is invalid in the system {1}
CONCEPTMAP_GROUP_TARGET_PROPERTY_TYPE_UNKNOWN_SYSTEM = The system {0} is unknown, so code values can''t be checked

View File

@ -185,6 +185,7 @@ import org.hl7.fhir.validation.cli.model.HtmlInMarkdownCheck;
import org.hl7.fhir.validation.cli.utils.QuestionnaireMode;
import org.hl7.fhir.validation.instance.type.BundleValidator;
import org.hl7.fhir.validation.instance.type.CodeSystemValidator;
import org.hl7.fhir.validation.instance.type.ConceptMapValidator;
import org.hl7.fhir.validation.instance.type.MeasureValidator;
import org.hl7.fhir.validation.instance.type.QuestionnaireValidator;
import org.hl7.fhir.validation.instance.type.SearchParameterValidator;
@ -2288,6 +2289,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (regex != null) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, e.primitiveValue().matches(regex), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX, e.primitiveValue(), regex) && ok;
}
if (!"xhtml".equals(type)) {
if (securityChecks) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !containsHtmlTags(e.primitiveValue()), I18nConstants.SECURITY_STRING_CONTENT_ERROR) && ok;
@ -2304,9 +2306,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
String url = e.primitiveValue();
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !url.startsWith("oid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URI_OID) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !url.startsWith("uuid:"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URI_UUID) && ok;
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, url.equals(url.trim().replace(" ", ""))
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, url.equals(url.trim().replace(" ", ""))
// work around an old invalid example in a core package
|| "http://www.acme.com/identifiers/patient or urn:ietf:rfc:3986 if the Identifier.value itself is a full uri".equals(url), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URI_WS, url);
|| "http://www.acme.com/identifiers/patient or urn:ietf:rfc:3986 if the Identifier.value itself is a full uri".equals(url), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_URI_WS, url) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || url.length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !context.hasMaxLength() || context.getMaxLength() == 0 || e.primitiveValue().length() <= context.getMaxLength(), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_LENGTH, context.getMaxLength()) && ok;
@ -2429,6 +2431,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, v >= 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT0) && ok;
if (type.equals("positiveInt"))
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, v > 0, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INTEGER_LT1) && ok;
} else {
ok = false;
}
}
if (type.equals("integer64")) {
@ -2541,10 +2545,46 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
ok = checkFixedValue(errors, path, e, context.getPattern(), profile.getVersionedUrl(), context.getSliceName(), null, true) && ok;
}
if (ok && !Utilities.existsInList(e.fhirType(), "id", "base64Binary", "markdown")) { // ids get checked elsewhere
String regext = FHIRPathExpressionFixer.fixRegex(getRegexFromType(e.fhirType()));
if (regext != null) {
try {
boolean matches = e.primitiveValue().matches(regext);
if (!matches) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, matches, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX_TYPE, e.primitiveValue(), e.fhirType(), regext) && ok;
}
} catch (Throwable ex) {
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX_EXCEPTION, regext, e.fhirType(), ex.getMessage()) && ok;
}
}
}
// for nothing to check
return ok;
}
private String getRegexFromType(String fhirType) {
StructureDefinition sd = context.fetchTypeDefinition(fhirType);
if (sd != null) {
for (ElementDefinition ed : sd.getSnapshot().getElement()) {
if (ed.getPath().endsWith(".value")) {
String regex = ed.getExtensionString(ToolingExtensions.EXT_REGEX);
if (regex != null) {
return regex;
}
for (TypeRefComponent td : ed.getType()) {
regex = td.getExtensionString(ToolingExtensions.EXT_REGEX);
if (regex != null) {
return regex;
}
}
}
}
}
return null;
}
private boolean checkTypeValue(List<ValidationMessage> errors, String path, Element e, Element sd) {
String v = e.primitiveValue();
if (v == null) {
@ -3478,14 +3518,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
StructureDefinition sd = context.fetchResource(StructureDefinition.class, tp.getValue(), profile);
if (sd != null) {
types.add(sd.getType());
}
StructureDefinition sdF = sdFT;
while (sdF != null) {
if (sdF.getType().equals(sd.getType())) {
rok = true;
break;
StructureDefinition sdF = sdFT;
while (sdF != null) {
if (sdF.getType().equals(sd.getType())) {
rok = true;
break;
}
sdF = sdF.hasBaseDefinition() ? context.fetchResource(StructureDefinition.class, sdF.getBaseDefinition(), sdF) : null;
}
sdF = sdF.hasBaseDefinition() ? context.fetchResource(StructureDefinition.class, sdF.getBaseDefinition(), sdF) : null;
}
}
ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), path, types.isEmpty() || rok,
@ -4008,7 +4048,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return false;
else
lastWasSpace = true;
} else if (Character.isWhitespace(c))
} else if (Character.isWhitespace(c) || c == '\u00A0')
return false;
else
lastWasSpace = false;
@ -4997,6 +5037,8 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return validateCapabilityStatement(errors, element, stack);
} else if (element.getType().equals("CodeSystem")) {
return new CodeSystemValidator(context, timeTracker, this, xverManager, jurisdiction).validateCodeSystem(errors, element, stack, baseOptions.setLanguage(stack.getWorkingLang()));
} else if (element.getType().equals("ConceptMap")) {
return new ConceptMapValidator(context, timeTracker, this, xverManager, jurisdiction).validateConceptMap(errors, element, stack, baseOptions.setLanguage(stack.getWorkingLang()));
} else if (element.getType().equals("SearchParameter")) {
return new SearchParameterValidator(context, timeTracker, fpe, xverManager, jurisdiction).validateSearchParameter(errors, element, stack);
} else if (element.getType().equals("StructureDefinition")) {

View File

@ -0,0 +1,270 @@
package org.hl7.fhir.validation.instance.type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.CodeSystem.CodeSystemContentMode;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
import org.hl7.fhir.r5.utils.XVerExtensionManager;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import org.hl7.fhir.validation.BaseValidator;
import org.hl7.fhir.validation.TimeTracker;
import org.hl7.fhir.validation.instance.InstanceValidator;
import org.hl7.fhir.validation.instance.type.ConceptMapValidator.PropertyDefinition;
import org.hl7.fhir.validation.instance.utils.NodeStack;
public class ConceptMapValidator extends BaseValidator {
public class PropertyDefinition {
private String type;
private String system;
private CodeSystem cs;
protected PropertyDefinition(String type, String system, CodeSystem cs) {
super();
this.type = type;
this.system = system;
this.cs = cs;
}
public String getType() {
return type;
}
public String getSystem() {
return system;
}
public CodeSystem getCs() {
return cs;
}
}
private InstanceValidator parent;
public ConceptMapValidator(IWorkerContext context, TimeTracker timeTracker, InstanceValidator parent, XVerExtensionManager xverManager, Coding jurisdiction) {
super(context, xverManager);
source = Source.InstanceValidator;
this.timeTracker = timeTracker;
this.jurisdiction = jurisdiction;
this.parent = parent;
}
public boolean validateConceptMap(List<ValidationMessage> errors, Element cm, NodeStack stack, ValidationOptions options) {
boolean ok = true;
Map<String, PropertyDefinition> props = new HashMap<>();
Map<String, String> attribs = new HashMap<>();
if (VersionUtilities.isR5Plus(context.getVersion())) {
List<Element> properties = cm.getChildrenByName("property");
int ci = 0;
for (Element property : properties) {
String code = property.getChildValue("code");
String type = property.getChildValue("type");
String system = property.getChildValue("system");
CodeSystem cs = system != null ? context.fetchCodeSystem(system) : null;
ok = rule(errors, "2023-03-05", IssueType.REQUIRED, property.line(), property.col(), stack.push(property, ci, null, null).getLiteralPath(),
!"code".equals(type) || system != null, I18nConstants.CONCEPTMAP_GROUP_TARGET_PROPERTY_TYPE_NO_SYSTEM) && ok;
warning(errors, "2023-03-05", IssueType.REQUIRED, property.line(), property.col(), stack.push(property, ci, null, null).getLiteralPath(),
system == null || cs != null, I18nConstants.CONCEPTMAP_GROUP_TARGET_PROPERTY_TYPE_UNKNOWN_SYSTEM, system);
if (code != null) {
props.put(code, new PropertyDefinition(type, system, cs));
}
ci++;
}
List<Element> attributes = cm.getChildrenByName("additionalAttribute");
for (Element attribute : attributes) {
String code = attribute.getChildValue("code");
String type = attribute.getChildValue("type");
if (code != null) {
attribs.put(code, type);
}
}
}
List<Element> groups = cm.getChildrenByName("group");
int ci = 0;
for (Element group : groups) {
ok = validateGroup(errors, group, stack.push(group, ci, null, null), props, attribs) && ok;
ci++;
}
if (!stack.isContained()) {
ok = checkShareableConceptMap(errors, cm, stack) && ok;
}
return ok;
}
private boolean validateGroup(List<ValidationMessage> errors, Element grp, NodeStack stack, Map<String, PropertyDefinition> props, Map<String, String> attribs) {
boolean ok = true;
CodeSystem srcCS = null;
CodeSystem tgtCS = null;
Element e = grp.getNamedChild("source");
if (warning(errors, "2023-03-05", IssueType.REQUIRED, grp.line(), grp.col(), stack.getLiteralPath(), e != null, I18nConstants.CONCEPTMAP_GROUP_SOURCE_MISSING)) {
srcCS = context.fetchCodeSystem(e.getValue());
warning(errors, "2023-03-05", IssueType.NOTFOUND, grp.line(), grp.col(), stack.push(e, -1, null, null).getLiteralPath(), srcCS != null, I18nConstants.CONCEPTMAP_GROUP_SOURCE_UNKNOWN, e.getValue());
}
e = grp.getNamedChild("target");
if (warning(errors, "2023-03-05", IssueType.REQUIRED, grp.line(), grp.col(), stack.getLiteralPath(), e != null, I18nConstants.CONCEPTMAP_GROUP_TARGET_MISSING)) {
tgtCS = context.fetchCodeSystem(e.getValue());
warning(errors, "2023-03-05", IssueType.NOTFOUND, grp.line(), grp.col(), stack.push(e, -1, null, null).getLiteralPath(), tgtCS != null, I18nConstants.CONCEPTMAP_GROUP_TARGET_UNKNOWN, e.getValue());
}
List<Element> elements = grp.getChildrenByName("element");
int ci = 0;
for (Element element : elements) {
ok = validateGroupElement(errors, element, stack.push(element, ci, null, null), srcCS, tgtCS, props, attribs) && ok;
ci++;
}
return ok;
}
private boolean validateGroupElement(List<ValidationMessage> errors, Element src, NodeStack stack, CodeSystem srcCS, CodeSystem tgtCS, Map<String, PropertyDefinition> props, Map<String, String> attribs) {
boolean ok = true;
Element code = src.getNamedChild("code");
if (code != null && srcCS != null) {
String c = code.getValue();
ConceptDefinitionComponent cd = CodeSystemUtilities.getCode(srcCS, c);
if (warningOrError(srcCS.getContent() == CodeSystemContentMode.COMPLETE, errors, "2023-03-05", IssueType.REQUIRED, code.line(), code.col(), stack.push(code, -1, null, null).getLiteralPath(), cd != null, I18nConstants.CONCEPTMAP_GROUP_SOURCE_CODE_INVALID, c, srcCS.getVersionedUrl())) {
Element display = src.getNamedChild("display");
if (display != null) {
List<String> displays = CodeSystemUtilities.getDisplays(srcCS, cd);
ok = rule(errors, "2023-03-05", IssueType.REQUIRED, code.line(), code.col(), stack.push(code, -1, null, null).getLiteralPath(), displays.contains(display.getValue()), I18nConstants.CONCEPTMAP_GROUP_SOURCE_DISPLAY_INVALID, display.getValue(), displays) && ok;
}
} else {
ok = false;
}
}
List<Element> targets = src.getChildrenByName("target");
int ci = 0;
for (Element target : targets) {
ok = validateGroupElementTarget(errors, target, stack.push(target, ci, null, null), srcCS, tgtCS, props, attribs) && ok;
ci++;
}
return ok;
}
private boolean validateGroupElementTarget(List<ValidationMessage> errors, Element tgt, NodeStack stack, CodeSystem srcCS, CodeSystem tgtCS, Map<String, PropertyDefinition> props, Map<String, String> attribs) {
boolean ok = true;
Element code = tgt.getNamedChild("code");
if (code != null && tgtCS != null) {
String c = code.getValue();
ConceptDefinitionComponent cd = CodeSystemUtilities.getCode(tgtCS, c);
if (warningOrError(tgtCS.getContent() == CodeSystemContentMode.COMPLETE, errors, "2023-03-05", IssueType.REQUIRED, code.line(), code.col(), stack.push(code, -1, null, null).getLiteralPath(), cd != null, I18nConstants.CONCEPTMAP_GROUP_TARGET_CODE_INVALID, c, tgtCS.getVersionedUrl())) {
Element display = tgt.getNamedChild("display");
if (display != null) {
List<String> displays = CodeSystemUtilities.getDisplays(tgtCS, cd);
ok = rule(errors, "2023-03-05", IssueType.REQUIRED, code.line(), code.col(), stack.push(code, -1, null, null).getLiteralPath(), displays.contains(display.getValue()), I18nConstants.CONCEPTMAP_GROUP_TARGET_DISPLAY_INVALID, display.getValue(), displays) && ok;
}
} else {
ok = false;
}
}
if (VersionUtilities.isR5Plus(context.getVersion())) {
List<Element> properties = tgt.getChildrenByName("property");
int ci = 0;
for (Element property : properties) {
ok = validateGroupElementTargetProperty(errors, property, stack.push(property, ci, null, null), props) && ok;
ci++;
}
List<Element> attributes = tgt.getChildrenByName("dependsOn");
ci = 0;
for (Element attribute : attributes) {
ok = validateGroupElementTargetAttribute(errors, attribute, stack.push(attribute, ci, null, null), attribs) && ok;
ci++;
}
attributes = tgt.getChildrenByName("product");
ci = 0;
for (Element attribute : attributes) {
ok = validateGroupElementTargetAttribute(errors, attribute, stack.push(attribute, ci, null, null), attribs) && ok;
ci++;
}
}
return ok;
}
private boolean validateGroupElementTargetProperty(List<ValidationMessage> errors, Element property, NodeStack stack, Map<String, PropertyDefinition> props) {
boolean ok = true;
Element codeE = property.getNamedChild("code");
Element valueE = property.getNamedChild("value");
String code = codeE.getValue();
if (rule(errors, "2023-03-05", IssueType.REQUIRED, codeE.line(), codeE.col(), stack.push(codeE, -1, null, null).getLiteralPath(), props.containsKey(code), I18nConstants.CONCEPTMAP_GROUP_TARGET_PROPERTY_INVALID, code, props.keySet())) {
PropertyDefinition defn = props.get(code);
NodeStack stackV = stack.push(valueE, -1, null, null);
if (rule(errors, "2023-03-05", IssueType.REQUIRED, codeE.line(), codeE.col(), stackV.getLiteralPath(), valueE.fhirType().equals(defn.getType()), I18nConstants.CONCEPTMAP_GROUP_TARGET_PROPERTY_TYPE_MISMATCH, valueE.fhirType(), defn.getType())) {
if (valueE.fhirType().equals("code")) {
if (defn.getCs() != null) {
ok = rule(errors, "2023-03-05", IssueType.REQUIRED, codeE.line(), codeE.col(), stackV.getLiteralPath(),
CodeSystemUtilities.findCode(defn.getCs().getConcept(), valueE.getValue()) != null, I18nConstants.CONCEPTMAP_GROUP_TARGET_PROPERTY_CODE_INVALID, valueE.getValue(), defn.getCs().getVersionedUrl()) && ok;
} else {
ok = false;
}
}
} else {
ok = false;
}
} else {
ok = false;
}
return ok;
}
private boolean validateGroupElementTargetAttribute(List<ValidationMessage> errors, Element attribute, NodeStack stack, Map<String, String> attribs) {
boolean ok = true;
Element codeE = attribute.getNamedChild("attribute");
Element valueE = attribute.getNamedChild("value");
String code = codeE.getValue();
if (rule(errors, "2023-03-05", IssueType.REQUIRED, codeE.line(), codeE.col(), stack.push(codeE, -1, null, null).getLiteralPath(), attribs.containsKey(code), I18nConstants.CONCEPTMAP_GROUP_TARGET_PROPERTY_INVALID, code, attribs.keySet())) {
NodeStack stackV = stack.push(valueE, -1, null, null);
ok = rule(errors, "2023-03-05", IssueType.REQUIRED, codeE.line(), codeE.col(), stackV.getLiteralPath(), valueE.fhirType().equals(attribs.get(code)), I18nConstants.CONCEPTMAP_GROUP_TARGET_PROPERTY_TYPE_MISMATCH, valueE.fhirType(), attribs.get(code)) && ok;
} else {
ok = false;
}
return ok;
}
private boolean checkShareableConceptMap(List<ValidationMessage> errors, Element cs, NodeStack stack) {
if (parent.isForPublication()) {
if (isHL7(cs)) {
boolean ok = true;
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("url"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING_HL7, "url") && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("version"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING_HL7, "version") && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("title"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING_HL7, "title") && ok;
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("name"), I18nConstants.CONCEPTMAP_SHAREABLE_EXTRA_MISSING_HL7, "name");
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("status"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING_HL7, "status") && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("experimental"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING_HL7, "experimental") && ok;
ok = rule(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("description"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING_HL7, "description") && ok;
return ok;
} else {
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("url"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING, "url");
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("version"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING, "version");
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("title"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING, "title");
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("name"), I18nConstants.CONCEPTMAP_SHAREABLE_EXTRA_MISSING, "name");
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("status"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING, "status");
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("experimental"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING, "experimental");
warning(errors, NO_RULE_DATE, IssueType.REQUIRED, cs.line(), cs.col(), stack.getLiteralPath(), cs.hasChild("description"), I18nConstants.CONCEPTMAP_SHAREABLE_MISSING, "description");
}
}
return true;
}
}

View File

@ -4,13 +4,18 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
import org.hl7.fhir.r5.context.ContextUtilities;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.ConceptMap;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.r5.model.Enumerations.BindingStrength;
import org.hl7.fhir.r5.model.Property;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.StructureMap;
import org.hl7.fhir.r5.model.StructureMap.StructureMapGroupComponent;
@ -20,6 +25,12 @@ import org.hl7.fhir.r5.model.StructureMap.StructureMapInputMode;
import org.hl7.fhir.r5.model.StructureMap.StructureMapModelMode;
import org.hl7.fhir.r5.model.StructureMap.StructureMapStructureComponent;
import org.hl7.fhir.r5.model.TypeDetails;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.r5.terminologies.ConceptMapUtilities;
import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.r5.terminologies.ValueSetUtilities;
import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.r5.utils.XVerExtensionManager;
import org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities;
@ -668,6 +679,46 @@ public class StructureMapValidator extends BaseValidator {
ok = false;
}
break;
case "translate":
ok = rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), params.size() == 3, I18nConstants.SM_TARGET_TRANSFORM_MISSING_PARAMS, transform) && ok;
Element srcE = params.size() > 0 ? params.get(0).getNamedChild("value") : null;
Element mapE = params.size() > 1? params.get(1).getNamedChild("value") : null;
Element modeE = params.size() > 2 ? params.get(2).getNamedChild("value") : null;
VariableDefn sv = null;
// srcE - if it's an id, the variable must exist
if (rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), srcE != null, I18nConstants.SM_TARGET_TRANSFORM_TRANSLATE_NO_PARAM, "source")) {
if ("id".equals(srcE.fhirType())) {
sv = variables.getVariable(srcE.getValue(), true);
rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), sv != null, I18nConstants.SM_TARGET_TRANSFORM_TRANSLATE_UNKNOWN_SOURCE, srcE.getValue());
}
} else {
ok = false;
}
// mapE - it must resolve (may be reference to contained)
if (rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), mapE != null, I18nConstants.SM_TARGET_TRANSFORM_TRANSLATE_NO_PARAM, "map_uri")) {
String ref = mapE.getValue();
ConceptMap cm = null;
if (ref.startsWith("#")) {
cm = (ConceptMap) loadContainedResource(errors, stack.getLiteralPath(), src, ref.substring(1), ConceptMap.class);
ok = rule(errors, "2023-03-01", IssueType.NOTFOUND, target.line(), target.col(), stack.getLiteralPath(), srcE != null, I18nConstants.SM_TARGET_TRANSFORM_TRANSLATE_CM_NOT_FOUND, ref) && ok;
} else {
// todo: look in Bundle?
cm = this.context.fetchResource(ConceptMap.class, ref);
warning(errors, "2023-03-01", IssueType.NOTFOUND, target.line(), target.col(), stack.getLiteralPath(), srcE != null, I18nConstants.SM_TARGET_TRANSFORM_TRANSLATE_CM_NOT_FOUND, ref);
}
if (cm != null && (v != null && v.hasTypeInfo() || (sv != null && sv.hasTypeInfo()))) {
ok = checkConceptMap(errors, target.line(), target.col(), stack.getLiteralPath(), cm, sv == null ? null : sv.getEd(), el == null ? null : el.getEd()) && ok;
}
}
if (modeE != null) {
String t = modeE.getValue();
if (rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), Utilities.existsInList(t, "code", "system", "display", "Coding", "CodeableConcept"), I18nConstants.SM_TARGET_TRANSFORM_TRANSLATE_CM_BAD_MODE, t)) {
// cross check the type
} else {
ok = false;
}
}
break;
default:
rule(errors, "2023-03-01", IssueType.INVALID, target.line(), target.col(), stack.getLiteralPath(), false, I18nConstants.SM_TARGET_TRANSFORM_NOT_CHECKED, transform);
ok = false;
@ -688,6 +739,58 @@ public class StructureMapValidator extends BaseValidator {
}
}
return ok;
}
private boolean checkConceptMap(List<ValidationMessage> errors, int line, int col, String literalPath, ConceptMap cm, ElementDefinition srcED, ElementDefinition tgtED) {
boolean ok = true;
ValueSet srcVS = null;
if (srcED != null) {
if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, srcED.getBinding().hasValueSet() && srcED.getBinding().getStrength() == BindingStrength.REQUIRED, I18nConstants.SM_TARGET_TRANSLATE_BINDING_SOURCE)) {
srcVS = context.fetchResource(ValueSet.class, srcED.getBinding().getValueSet());
if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, srcVS != null, I18nConstants.SM_TARGET_TRANSLATE_BINDING_VS_SOURCE)) {
ValueSetExpansionOutcome vse = context.expandVS(srcVS, true, false);
if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, vse.isOk(), I18nConstants.SM_TARGET_TRANSLATE_BINDING_VSE_SOURCE, vse.getError())) {
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (ValueSetExpansionContainsComponent c : vse.getValueset().getExpansion().getContains()) {
if (ConceptMapUtilities.hasMappingForSource(cm, c.getSystem(), c.getVersion(), c.getCode())) {
b.append(c.getCode());
}
}
if (b.count() > 0) {
warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, srcED.getBinding().hasValueSet() && srcED.getBinding().getStrength() == BindingStrength.REQUIRED, I18nConstants.SM_TARGET_TRANSLATE_BINDING_SOURCE_UNMAPPED, b.toString());
}
}
}
}
}
if (srcED != null) {
if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, tgtED.getBinding().hasValueSet() && tgtED.getBinding().getStrength() == BindingStrength.REQUIRED, I18nConstants.SM_TARGET_TRANSLATE_BINDING_TARGET)) {
ValueSet vs = context.fetchResource(ValueSet.class, tgtED.getBinding().getValueSet());
if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, vs != null, I18nConstants.SM_TARGET_TRANSLATE_BINDING_VS_TARGET)) {
ValueSetExpansionOutcome vse = context.expandVS(vs, true, false);
if (warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, vse.isOk(), I18nConstants.SM_TARGET_TRANSLATE_BINDING_VSE_TARGET, vse.getError())) {
List<String> systems = new ArrayList<>();
if (srcVS != null) {
for (ConceptSetComponent inc : srcVS.getCompose().getInclude()) {
systems.add(inc.getSystem());
}
}
List<Coding> codes = ConceptMapUtilities.listTargets(cm, systems);
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (Coding code : codes) {
if (ValueSetUtilities.hasCodeInExpansion(vse.getValueset(), code)) {
b.append(code.getCode());
}
}
if (b.count() > 0) {
warning(errors, "2023-03-01", IssueType.INVALID, line, col, literalPath, srcED.getBinding().hasValueSet() && srcED.getBinding().getStrength() == BindingStrength.REQUIRED, I18nConstants.SM_TARGET_TRANSLATE_BINDING_TARGET_WRONG, b.toString());
}
}
}
}
}
return ok;
}
private String inferType(RuleInformation ruleInfo, VariableSet variables, Element rule, String transform, List<Element> params) {

View File

@ -127,4 +127,14 @@ public class FHIRPathExpressionFixer {
return expr;
}
public static String fixRegex(String regex) {
if (regex == null) {
return null;
}
if (regex.equals("-?(0|[1-9][0-9]{0,17})(\\.[0-9]{1,17})?([eE][+-]?[0-9]{1,9}})?")) {
return "-?(0|[1-9][0-9]{0,17})(\\.[0-9]{1,17})?([eE](0|[+\\-]?[1-9][0-9]{0,9}))?";
}
return regex;
}
}