more XML checking for CDA + control over date rules

This commit is contained in:
Grahame Grieve 2023-10-12 20:15:16 +11:00
parent 04597eea27
commit 84e3bec25f
9 changed files with 3107 additions and 22 deletions

View File

@ -232,7 +232,7 @@ public class XmlParser extends ParserBase {
Element result = new Element(element.getLocalName(), new Property(context, sd.getSnapshot().getElement().get(0), sd)).setFormat(FhirFormat.XML);
result.setPath(element.getLocalName());
checkElement(errors, element, path, result.getProperty());
checkElement(errors, element, path, result.getProperty(), false);
result.markLocation(line(element, false), col(element, false));
result.setType(element.getLocalName());
parseChildren(errors, path, element, result);
@ -274,7 +274,7 @@ public class XmlParser extends ParserBase {
return true;
}
private void checkElement(List<ValidationMessage> errors, org.w3c.dom.Element element, String path, Property prop) throws FHIRFormatError {
private void checkElement(List<ValidationMessage> errors, org.w3c.dom.Element element, String path, Property prop, boolean xsiTypeChecked) throws FHIRFormatError {
if (policy == ValidationPolicy.EVERYTHING) {
if (empty(element) && FormatUtilities.FHIR_NS.equals(element.getNamespaceURI())) // this rule only applies to FHIR Content
logError(errors, ValidationMessage.NO_RULE_DATE, line(element, false), col(element, false), path, IssueType.INVALID, context.formatMessage(I18nConstants.ELEMENT_MUST_HAVE_SOME_CONTENT), IssueSeverity.ERROR);
@ -283,8 +283,20 @@ public class XmlParser extends ParserBase {
if (elementNs == null) {
elementNs = "noNamespace";
}
if (!elementNs.equals(ns))
if (!elementNs.equals(ns)) {
logError(errors, ValidationMessage.NO_RULE_DATE, line(element, false), col(element, false), path, IssueType.INVALID, context.formatMessage(I18nConstants.WRONG_NAMESPACE__EXPECTED_, ns), IssueSeverity.ERROR);
}
if (!xsiTypeChecked) {
String xsiType = element.getAttributeNS(FormatUtilities.NS_XSI, "type");
if (!Utilities.noString(xsiType)) {
String actualType = prop.getXmlTypeName();
if (!xsiType.equals(actualType)) {
logError(errors, "2023-10-12", line(element, false), col(element, false), path, IssueType.INVALID, context.formatMessage(I18nConstants.XSI_TYPE_WRONG, xsiType, actualType), IssueSeverity.ERROR);
} else {
logError(errors, "2023-10-12", line(element, false), col(element, false), path, IssueType.INVALID, context.formatMessage(I18nConstants.XSI_TYPE_UNNECESSARY), IssueSeverity.INFORMATION);
}
}
}
}
}
@ -293,7 +305,7 @@ public class XmlParser extends ParserBase {
Element result = new Element(base.getLocalName(), new Property(context, sd.getSnapshot().getElement().get(0), sd)).setFormat(FhirFormat.XML);
result.setPath(base.getLocalName());
String path = "/"+pathPrefix(base.getNamespaceURI())+base.getLocalName();
checkElement(errors, base, path, result.getProperty());
checkElement(errors, base, path, result.getProperty(), false);
result.setType(base.getLocalName());
parseChildren(errors, path, base, result);
result.numberChildren();
@ -434,7 +446,7 @@ public class XmlParser extends ParserBase {
} else {
n.setPath(element.getPath()+"."+property.getName());
}
checkElement(errors, (org.w3c.dom.Element) child, npath, n.getProperty());
boolean xsiTypeChecked = false;
boolean ok = true;
if (property.isChoice()) {
if (property.getDefinition().hasRepresentation(PropertyRepresentation.TYPEATTR)) {
@ -453,9 +465,11 @@ public class XmlParser extends ParserBase {
n.setType(xsiType);
n.setExplicitType(xsiType);
}
xsiTypeChecked = true;
} else
n.setType(n.getType());
}
checkElement(errors, (org.w3c.dom.Element) child, npath, n.getProperty(), xsiTypeChecked);
element.getChildren().add(n);
if (ok) {
if (property.isResource())
@ -486,7 +500,7 @@ public class XmlParser extends ParserBase {
Element n = new Element(name, property).markLocation(line(child, false), col(child, false)).setFormat(FhirFormat.XML);
cgn.getChildren().add(n);
n.setPath(element.getPath()+"."+property.getName());
checkElement(errors, (org.w3c.dom.Element) child, npath, n.getProperty());
checkElement(errors, (org.w3c.dom.Element) child, npath, n.getProperty(), false);
parseChildren(errors, npath, (org.w3c.dom.Element) child, n);
}
}

View File

@ -1007,7 +1007,8 @@ public class I18nConstants {
public static final String FHIRPATH_CHOICE_SPURIOUS_TYPE_SPECIFIER = "FHIRPATH_CHOICE_SPURIOUS_TYPE_SPECIFIER";
public static final String FHIRPATH_NOT_A_COLLECTION = "FHIRPATH_NOT_A_COLLECTION";
public static final String TERMINOLOGY_TX_UNKNOWN_OID = "TERMINOLOGY_TX_UNKNOWN_OID";
public static final String TERMINOLOGY_TX_NO_SYSTEM = "TERMINOLOGY_TX_NO_SYSTEM";
public static final String XSI_TYPE_WRONG = "XSI_TYPE_WRONG";
public static final String XSI_TYPE_UNNECESSARY = "XSI_TYPE_UNNECESSARY";
}

View File

@ -25,8 +25,8 @@ public class JsonObject extends JsonElement {
}
public JsonObject add(String name, JsonElement value) throws JsonException {
check(name != null, "Name is null");
check(value != null, "Value is null");
check(name != null, "Json Property Name is null");
check(value != null, "Json Property Value is null");
if (get(name) != null) {
check(false, "Name '"+name+"' already exists (value = "+get(name).toString()+")");
}

View File

@ -501,7 +501,6 @@ MEASURE_MR_GRP_MISSING_BY_CODE = The MeasureReport does not include a group for
MEASURE_MR_GRP_NO_USABLE_CODE = None of the codes provided are usable for comparison - need both system and code on at least one code
MEASURE_MR_GRP_NO_WRONG_CODE = The code provided ({0}) does not match the code specified in the measure report ({1})
DUPLICATE_ID = Duplicate id value ''{0}''
TERMINOLOGY_TX_SYSTEM_NO_CODE = A code with no system has no defined meaning. A system should be provided
MEASURE_MR_GRP_POP_NO_CODE = Group should have a code that matches the group population definition in the measure
MEASURE_MR_GRP_POP_UNK_CODE = The code for this group population has no match in the measure definition
MEASURE_MR_GRPST_POP_UNK_CODE = The code for this group stratifier has no match in the measure definition
@ -1064,5 +1063,6 @@ FHIRPATH_CHOICE_NO_TYPE_SPECIFIER = The expression ''{0}'' refers to an element
FHIRPATH_CHOICE_SPURIOUS_TYPE_SPECIFIER = The expression ''{0}'' refers to an element that is not a choice, but has an .ofType(). SQL view runners are likely to pre-determine an incorrect full element name
FHIRPATH_NOT_A_COLLECTION = Found a use of a collection operator on something that is not a collection at ''{0}'' - check that there's no mistakes in the expression syntax
TERMINOLOGY_TX_UNKNOWN_OID = The OID ''{0}'' is not known
TERMINOLOGY_TX_NO_SYSTEM = In the absence of a code system, the code cannot be validated
TERMINOLOGY_TX_SYSTEM_NO_CODE = A code with no system has no defined meaning, and it cannot be validated. A system should be provided
XSI_TYPE_WRONG = The xsi:type value ''{0}'' is wrong (should be ''{1}''). Note that xsi:type is unnecessary at this point
XSI_TYPE_UNNECESSARY = xsi:type is unnecessary at this point

View File

@ -1660,7 +1660,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
c.setSystem(url);
}
} else {
warning(errors, "2023-10-11", IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NO_SYSTEM);
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_NO_CODE);
}
c.setCode(element.getNamedChildValue("code"));
@ -1837,7 +1837,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
String code = element.getNamedChildValue("code");
String system = element.getNamedChildValue("system");
if (code != null && system == null) {
warning(errors, "2023-10-11", IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_NO_SYSTEM);
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, false, I18nConstants.TERMINOLOGY_TX_SYSTEM_NO_CODE);
}
String version = element.getNamedChildValue("version");
String display = element.getNamedChildValue("display");
@ -1849,7 +1849,6 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
boolean ok = true;
ok = rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, theSystem == null || isCodeSystemReferenceValid(theSystem), I18nConstants.TERMINOLOGY_TX_SYSTEM_RELATIVE) && ok;
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, Utilities.noString(theCode) || !Utilities.noString(theSystem), I18nConstants.TERMINOLOGY_TX_SYSTEM_NO_CODE);
if (theSystem != null && theCode != null && !noTerminologyChecks) {
ok = rule(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, !isValueSet(theSystem), I18nConstants.TERMINOLOGY_TX_SYSTEM_VALUESET2, theSystem) && ok;
@ -2625,12 +2624,16 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
boolean dok = ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path,
e.primitiveValue()
.matches("([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)(-(0[1-9]|1[0-2])(-(0[1-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?"), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_VALID, "'"+e.primitiveValue()+"' doesn't meet format requirements for dateTime") && ok;
dok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !hasTime(e.primitiveValue()) || hasTimeZone(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_TZ) && dok;
if (isCoreDefinition(profile) || (context.hasExtension(ToolingExtensions.EXT_DATE_RULES) && ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_DATE_RULES).contains("tz-for-time"))) {
dok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, !hasTime(e.primitiveValue()) || hasTimeZone(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_TZ) && dok;
}
dok = 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()) && dok;
if (dok) {
try {
DateTimeType dt = new DateTimeType(e.primitiveValue());
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue());
if (isCoreDefinition(profile) || (context.hasExtension(ToolingExtensions.EXT_DATE_RULES) && ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_DATE_RULES).contains("year-valid"))) {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue());
}
} catch (Exception ex) {
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_VALID, ex.getMessage());
dok = false;
@ -2655,7 +2658,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (dok) {
try {
DateType dt = new DateType(e.primitiveValue());
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue());
if (isCoreDefinition(profile) || (context.hasExtension(ToolingExtensions.EXT_DATE_RULES) && ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_DATE_RULES).contains("year-valid"))) {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue());
}
} catch (Exception ex) {
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATE_VALID, ex.getMessage());
dok = false;
@ -2750,7 +2755,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (dok) {
try {
InstantType dt = new InstantType(e.primitiveValue());
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue());
if (isCoreDefinition(profile) || (context.hasExtension(ToolingExtensions.EXT_DATE_RULES) && ToolingExtensions.readStringExtension(context, ToolingExtensions.EXT_DATE_RULES).contains("year-valid"))) {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, yearIsValid(e.primitiveValue()), I18nConstants.TYPE_SPECIFIC_CHECKS_DT_DATETIME_REASONABLE, e.primitiveValue());
}
} catch (Exception ex) {
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_INSTANT_VALID, ex.getMessage());
dok = false;
@ -2834,6 +2841,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return ok;
}
private boolean isCoreDefinition(StructureDefinition profile) {
return profile.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/") && profile.getKind() != StructureDefinitionKind.LOGICAL;
}
private String getRegexFromType(String fhirType) {
StructureDefinition sd = context.fetchTypeDefinition(fhirType);
if (sd != null) {
@ -6149,6 +6160,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
private boolean checkCDACoding(List<ValidationMessage> errors, String path, boolean isPQ, Element element, StructureDefinition profile, ElementDefinition checkDefn, NodeStack stack, StructureDefinition defn, boolean inCodeableConcept, boolean checkDisplay) {
boolean ok = true;
String system = null;
String code = element.getNamedChildValue(isPQ ? "unit" : "code");
String oid = element.getNamedChildValue("codeSystem");
if (oid != null) {
String url = context.urlForOid(true, oid);
@ -6159,9 +6171,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else {
system = url;
}
}
} else {
warning(errors, NO_RULE_DATE, IssueType.CODEINVALID, element.line(), element.col(), path, code == null, I18nConstants.TERMINOLOGY_TX_SYSTEM_NO_CODE);
}
String code = element.getNamedChildValue(isPQ ? "unit" : "code");
String version = element.getNamedChildValue("codeSystemVersion");
String display = element.getNamedChildValue("displayName");
return checkCodedElement(errors, path, element, profile, checkDefn, inCodeableConcept, checkDisplay, stack, code, system, version, display) && ok;

View File

@ -1407,6 +1407,45 @@ v: {
"code" : "CHE",
"system" : "urn:iso:std:iso:3166",
"version" : "2018",
"issues" : {
"resourceType" : "OperationOutcome"
}
}
-------------------------------------------------------------------------------------
{"code" : {
"code" : "en-US"
}, "url": "http://terminology.hl7.org/ValueSet/v3-HumanLanguage", "version": "2.0.0", "langs":"", "useServer":"true", "useClient":"true", "guessSystem":"true", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": {
"resourceType" : "Parameters",
"parameter" : [{
"name" : "profile-url",
"valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891"
}]
}}####
v: {
"severity" : "error",
"error" : "The CodeSystem http://terminology.hl7.org/CodeSystem/ietf3066 is unknown; Unable to check whether the code is in the value set http://terminology.hl7.org/ValueSet/v3-HumanLanguage|2.0.0 (from Tx-Server)",
"class" : "CODESYSTEM_UNSUPPORTED",
"unknown-systems" : "http://terminology.hl7.org/CodeSystem/ietf3066",
"issues" : {
"resourceType" : "OperationOutcome"
}
}
-------------------------------------------------------------------------------------
{"code" : {
"code" : "PRN"
}, "valueSet" :null, "langs":"en", "useServer":"true", "useClient":"false", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": {
"resourceType" : "Parameters",
"parameter" : [{
"name" : "profile-url",
"valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891"
}]
}}####
v: {
"severity" : "error",
"error" : "The CodeSystem is unknown (from Tx-Server)",
"class" : "UNKNOWN",
"unknown-systems" : "",
"issues" : {
"resourceType" : "OperationOutcome"

View File

@ -0,0 +1,23 @@
-------------------------------------------------------------------------------------
{"code" : {
"system" : "urn:oid:2.16.840.1.113883.5.10588",
"code" : "GIM",
"display" : "General internal medicine clinic"
}, "valueSet" :null, "langs":"en", "useServer":"true", "useClient":"false", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": {
"resourceType" : "Parameters",
"parameter" : [{
"name" : "profile-url",
"valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891"
}]
}}####
v: {
"severity" : "error",
"error" : "The CodeSystem urn:oid:2.16.840.1.113883.5.10588 is unknown (from Tx-Server)",
"class" : "CODESYSTEM_UNSUPPORTED",
"unknown-systems" : "urn:oid:2.16.840.1.113883.5.10588",
"issues" : {
"resourceType" : "OperationOutcome"
}
}
-------------------------------------------------------------------------------------