diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/CanonicalResourceUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/CanonicalResourceUtilities.java index 6bfc7593f..bb0beeb49 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/CanonicalResourceUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/CanonicalResourceUtilities.java @@ -82,7 +82,7 @@ public class CanonicalResourceUtilities { if (wgext == null) { wgext = res.getOwnerDocument().createElementNS(Constants.NS_FHIR_ROOT, "extension"); wgext.setAttribute("url", ToolingExtensions.EXT_WORKGROUP); - org.w3c.dom.Element after = XMLUtil.getFirstChild(res, "modifierExtension", "url", "identifier", "version", "status"); + org.w3c.dom.Element after = XMLUtil.getFirstChild(res, "modifierExtension", "url", "identifier", "version", "status", "name", "title"); if (after == null) { after = XMLUtil.getLastChild(res, "id", "meta", "text", "implicitRules", "language", "text", "contained"); if (after != null) { @@ -90,6 +90,7 @@ public class CanonicalResourceUtilities { } } res.insertBefore(wgext, after); + res.insertBefore(res.getOwnerDocument().createTextNode("/n "), after); } XMLUtil.clearChildren(wgext); org.w3c.dom.Element valueCode = res.getOwnerDocument().createElementNS(Constants.NS_FHIR_ROOT, "valueCode"); @@ -99,7 +100,7 @@ public class CanonicalResourceUtilities { org.w3c.dom.Element pub = XMLUtil.getNamedChild(res, "publisher"); if (pub == null) { pub = res.getOwnerDocument().createElementNS(Constants.NS_FHIR_ROOT, "publisher"); - org.w3c.dom.Element after = XMLUtil.getFirstChild(res, "contact", "description", "useContext", "jurisdiction", "purpose", "copyright"); + org.w3c.dom.Element after = XMLUtil.getFirstChild(res, "contact", "relatedArtifact", "description", "useContext", "jurisdiction", "purpose", "copyright"); res.insertBefore(pub, after); } pub.setAttribute("value", "HL7 International / "+wg.getName()); diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java index 76bd0a27e..225d386bc 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java @@ -1034,6 +1034,7 @@ public class I18nConstants { public static final String BUNDLE_BUNDLE_ENTRY_FOUND_MULTIPLE_FRAGMENT = "BUNDLE_BUNDLE_ENTRY_FOUND_MULTIPLE_FRAGMENT"; public static final String XHTML_IDREF_NOT_FOUND = "XHTML_IDREF_NOT_FOUND"; public static final String XHTML_IDREF_NOT_MULTIPLE_MATCHES = "XHTML_IDREF_NOT_MULTIPLE_MATCHES"; + public static final String SD_CONTEXT_SHOULD_NOT_BE_FHIRPATH = "SD_CONTEXT_SHOULD_NOT_BE_FHIRPATH"; } diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index 7cec067e0..3adacca6f 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -933,6 +933,7 @@ NO_VALID_DISPLAY_FOUND_other = No valid Display Names found for {1}#{2} in the l SD_NO_CONTEXT_WHEN_NOT_EXTENSION = The type is {0} so an extension context should not be specified SD_NO_CONTEXT_INV_WHEN_NOT_EXTENSION = The type is {0} so an extension context invariants should not be specified SD_CONTEXT_SHOULD_NOT_BE_ELEMENT = Review the extension type for {1}: extensions should not have a context of {0} unless it''s really intended that they can be used anywhere +SD_CONTEXT_SHOULD_NOT_BE_FHIRPATH = Review the extension type for {1}: the context of {0} appears to be a simple element, so the context type should be 'element' not 'fhirpath' ED_PATH_WRONG_TYPE_MATCH = The path must be ''{0}'' not ''{1}'' when the type list is not constrained ATTEMPT_TO_CHANGE_SLICING = The element at {0} defines the slicing {1} but then an element in the slicing {2} tries to redefine the slicing to {3} REPEAT_SLICING_IGNORED = The element at {0} defines the slicing but then an element in the slicing {2} repeats it, which is ignored diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java index a951a9c9b..7dcd5ed99 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/StructureDefinitionValidator.java @@ -70,17 +70,17 @@ public class StructureDefinitionValidator extends BaseValidator { this.fpe = fpe; this.wantCheckSnapshotUnchanged = wantCheckSnapshotUnchanged; } - + public boolean validateStructureDefinition(List errors, Element src, NodeStack stack) { boolean ok = true; StructureDefinition sd = null; String typeName = null; try { String url = src.getNamedChildValue("url", false); - + sd = loadAsSD(src); ok = checkExtensionContext(errors, src, stack) && ok; - + List snapshot = sd.getSnapshot().getElement(); sd.setSnapshot(null); typeName = sd.getTypeName(); @@ -139,7 +139,7 @@ public class StructureDefinitionValidator extends BaseValidator { for (Element snapshotE : snapshots) { ok = validateElementList(errors, snapshotE, stack.push(snapshotE, -1, null, null), true, true, sd, typeName, logical, constraint, src.getNamedChildValue("type", false), src.getNamedChildValue("url", false), src.getNamedChildValue("type", false), base) && ok; } - + // obligation profile support if (src.hasExtension(ToolingExtensions.EXT_OBLIGATION_PROFILE_FLAG)) { Element ext = src.getExtension(ToolingExtensions.EXT_OBLIGATION_PROFILE_FLAG); @@ -165,14 +165,14 @@ public class StructureDefinitionValidator extends BaseValidator { } c++; } - + List contextInvariants = src.getChildren("contextInvariant"); c = 0; for (Element contextInvariant : contextInvariants) { ok = validateContextInvariant(errors, contextInvariant, src, stack.push(contextInvariant, c, null, null)) && ok; c++; } - + // if this is defining an extension, make sure that the extension fixed value matches the URL String type = src.getNamedChildValue("type", false); if ("Extension".equals(type)) { @@ -192,7 +192,7 @@ public class StructureDefinitionValidator extends BaseValidator { } return ok; } - + private String getFixedValue(Element src) { Element diff = src.getNamedChild("differential", false); @@ -270,7 +270,7 @@ public class StructureDefinitionValidator extends BaseValidator { // this is ok (it must have this), and there's nothing to check } else if (child.getName().equals("binding")) { if (rule(errors, "2023-05-27", IssueType.INVALID, stack.getLiteralPath(), bd.hasBinding(), - I18nConstants.SD_OBGLIGATION_PROFILE_ILLEGAL_BINDING, id)) { + I18nConstants.SD_OBGLIGATION_PROFILE_ILLEGAL_BINDING, id)) { ok = validateObligationProfileElementBinding(errors, child, stack, id, bd) && ok; } else { ok = false; @@ -289,7 +289,7 @@ public class StructureDefinitionValidator extends BaseValidator { return false; } } - + private boolean validateObligationProfileElementBinding(List errors, Element element, NodeStack nstack, String id, ElementDefinition bd) { // rules can only have additional bindings boolean ok = true; @@ -357,7 +357,9 @@ public class StructureDefinitionValidator extends BaseValidator { } if ("element".equals(ct) && "Element".equals(cv)) { warning(errors, "2023-04-23", IssueType.BUSINESSRULE, n.getLiteralPath(), false, I18nConstants.SD_CONTEXT_SHOULD_NOT_BE_ELEMENT, cv, src.getNamedChildValue("id", false)); - } + } else if ("fhirpath".equals(ct)) { + warning(errors, "2023-12-05", IssueType.BUSINESSRULE, n.getLiteralPath(), !isElement(cv), I18nConstants.SD_CONTEXT_SHOULD_NOT_BE_FHIRPATH, cv, src.getNamedChildValue("id", false)); + } } else { ok = rule(errors, "2023-04-23", IssueType.INVALID, n.getLiteralPath(), false, I18nConstants.SD_NO_CONTEXT_WHEN_NOT_EXTENSION, type) && ok; } @@ -366,7 +368,7 @@ public class StructureDefinitionValidator extends BaseValidator { for (Element ci : cilist) { NodeStack n = stack.push(ci, i, null, null); if ("Extension".equals(type)) { - + } else { ok = rule(errors, "2023-04-23", IssueType.INVALID, n.getLiteralPath(), false, I18nConstants.SD_NO_CONTEXT_INV_WHEN_NOT_EXTENSION, type) && ok; } @@ -374,6 +376,16 @@ public class StructureDefinitionValidator extends BaseValidator { return ok; } + private boolean isElement(String cv) { + String tn = cv.contains(".") ? cv.substring(0, cv.indexOf(".")) : cv; + StructureDefinition sd = context.fetchTypeDefinition(tn); + if (sd != null) { + return sd.getSnapshot().getElementByPath(cv) != null; + } else { + return false; + } + } + private boolean validateElementList(List errors, Element elementList, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint, String rootPath, String profileUrl, String profileType, StructureDefinition base) { Map invariantMap = new HashMap<>(); boolean ok = true; @@ -395,7 +407,7 @@ public class StructureDefinitionValidator extends BaseValidator { ok = rule(errors, "2023-01-17", IssueType.INVALID, stack.getLiteralPath(), path.contains(".") || !element.hasChild("slicing", false), I18nConstants.SD_NO_SLICING_ON_ROOT, path) && ok; } ok = rule(errors, "2023-05-22", IssueType.NOTFOUND, stack.getLiteralPath(), snapshot || !constraint || !element.hasChild("meaningWhenMissing", false) || meaningWhenMissingAllowed(element), I18nConstants.SD_ELEMENT_NOT_IN_CONSTRAINT, "meaningWhenMissing", path) && ok; - + List types = element.getChildrenByName("type"); Set typeCodes = new HashSet<>(); Set characteristics = new HashSet<>(); @@ -403,7 +415,7 @@ public class StructureDefinitionValidator extends BaseValidator { typeCodes.add(path); // root is type addCharacteristics(characteristics, path); } - + for (Element type : types) { if (hasMustSupportExtension(type)) { typeMustSupport = true; @@ -457,8 +469,8 @@ public class StructureDefinitionValidator extends BaseValidator { ok = validateBinding(errors, binding, stack.push(binding, -1, null, null), typeCodes, snapshot, path) && ok; } else { // this is a good idea but there's plenty of cases where the rule isn't met; maybe one day it's worth investing the time to exclude these cases and bring this rule back -// String bt = boundType(typeCodes); -// hint(errors, UNKNOWN_DATE_TIME, IssueType.BUSINESSRULE, stack.getLiteralPath(), !snapshot || bt == null, I18nConstants.SD_ED_SHOULD_BIND, element.getNamedChildValue("path", false), bt); + // String bt = boundType(typeCodes); + // hint(errors, UNKNOWN_DATE_TIME, IssueType.BUSINESSRULE, stack.getLiteralPath(), !snapshot || bt == null, I18nConstants.SD_ED_SHOULD_BIND, element.getNamedChildValue("path", false), bt); } if (!typeCodes.isEmpty()) { if (element.hasChild("maxLength", false)) { @@ -485,7 +497,7 @@ public class StructureDefinitionValidator extends BaseValidator { if (rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), !typeCodes.isEmpty() || element.hasChild("contentReference", false), I18nConstants.SD_NO_TYPES_OR_CONTENTREF, element.getIdBase())) { // if we see fixed[x] or pattern[x] applied to a repeating element, we'll give the user a hint boolean repeating = !Utilities.existsInList(element.getChildValue("max"), "0", "1"); - + Element v = element.getNamedChild("defaultValue", false); if (v != null) { ok = rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.push(v, -1, null, null).getLiteralPath(), typeCodes.contains(v.fhirType()), I18nConstants.SD_VALUE_TYPE_IILEGAL, element.getIdBase(), "defaultValue", v.fhirType(), typeCodes) && ok; @@ -521,7 +533,7 @@ public class StructureDefinitionValidator extends BaseValidator { } return ok; } - + private boolean validateElementDefinitionInvariant(List errors, Element invariant, NodeStack stack, Map invariantMap, List elements, Element element, String path, String rootPath, String profileUrl, String profileType, boolean snapshot, StructureDefinition base) { boolean ok = true; @@ -582,7 +594,7 @@ public class StructureDefinitionValidator extends BaseValidator { } else { ok = rule(errors, "2023-07-27", IssueType.INVALID, stack, source == null || source.equals(profileUrl), I18nConstants.ED_INVARIANT_DIFF_NO_SOURCE, key, source) && rule(errors, "2023-07-27", IssueType.INVALID, stack, !haseHasInvariant(base, key), I18nConstants.ED_INVARIANT_KEY_ALREADY_USED, key, base.getVersionedUrl()); - + } } } @@ -662,16 +674,16 @@ public class StructureDefinitionValidator extends BaseValidator { } private Element getParent(List elements, Element te) { - int i = elements.indexOf(te) - 1; - String path = te.getNamedChildValue("path", false); - while (i >= 0) { - String p = elements.get(i).getNamedChildValue("path", false); - if (path.startsWith(p+".")) { - return elements.get(i); - } - i--; - } - return null; + int i = elements.indexOf(te) - 1; + String path = te.getNamedChildValue("path", false); + while (i >= 0) { + String p = elements.get(i).getNamedChildValue("path", false); + if (path.startsWith(p+".")) { + return elements.get(i); + } + i--; + } + return null; } private List getTypesForElement(List elements, Element element, String profileType) { @@ -803,9 +815,9 @@ public class StructureDefinitionValidator extends BaseValidator { case "Element" :return addCharacteristicsForType(set); case "Base" :return addCharacteristicsForType(set); default: -// if (!context.getResourceNames().contains(tc)) { -// System.out.println("Unhandled data type in addCharacteristics: "+tc); -// } + // if (!context.getResourceNames().contains(tc)) { + // System.out.println("Unhandled data type in addCharacteristics: "+tc); + // } return addCharacteristicsForType(set); } } @@ -865,15 +877,15 @@ public class StructureDefinitionValidator extends BaseValidator { Set bindables = getListofBindableTypes(typeCodes); hint(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), bindables.size() <= 1, I18nConstants.SD_ED_BIND_MULTIPLE_TYPES, path, typeCodes.toString()); } - + if (binding.hasChild("valueSet", false)) { Element valueSet = binding.getNamedChild("valueSet", false); String ref = valueSet.hasPrimitiveValue() ? valueSet.primitiveValue() : valueSet.getNamedChildValue("reference", false); if (warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), !snapshot || ref != null, I18nConstants.SD_ED_SHOULD_BIND_WITH_VS, path)) { Resource vs = context.fetchResource(Resource.class, ref); - + // just because we can't resolve it directly doesn't mean that terminology server can't. Check with it - + if (warning(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs != null || serverSupportsValueSet(ref), I18nConstants.SD_ED_BIND_UNKNOWN_VS, path, ref)) { if (vs != null) { ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs instanceof ValueSet, I18nConstants.SD_ED_BIND_NOT_VS, path, ref, vs.fhirType()) && ok; @@ -914,7 +926,7 @@ public class StructureDefinitionValidator extends BaseValidator { for (Element profile : profiles) { ok = validateProfileTypeOrTarget(errors, profile, code, stack.push(profile, -1, null, null), path) && ok; } - + } else { for (Element profile : profiles) { ok = validateTypeProfile(errors, profile, code, stack.push(profile, -1, null, null), path, sd) && ok; @@ -1060,7 +1072,7 @@ public class StructureDefinitionValidator extends BaseValidator { private boolean isReferenceableTarget(StructureDefinition t) { for (Extension ext : t.getExtensionsByUrl(ExtensionConstants.EXT_SDTYPE_CHARACTERISTICS)) { if (ext.hasValue()) { - String c = ext.getValue().primitiveValue(); + String c = ext.getValue().primitiveValue(); if ("can-be-target".equals(c)) { return true; } @@ -1085,7 +1097,7 @@ public class StructureDefinitionValidator extends BaseValidator { sd = null; } } - + return false; }