From f30fda23824ac5584562cdc5cedc6791a7ae93d1 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 5 Dec 2023 16:56:21 +1100 Subject: [PATCH 01/13] more fixes for R6 build --- .../org/hl7/fhir/r5/utils/CanonicalResourceUtilities.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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()); From e697ec1c4c666f2bebd7491fd6432af80bb64622 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Tue, 5 Dec 2023 16:56:44 +1100 Subject: [PATCH 02/13] check extensions with fhirpath context --- .../fhir/utilities/i18n/I18nConstants.java | 1 + .../src/main/resources/Messages.properties | 1 + .../type/StructureDefinitionValidator.java | 86 +++++++++++-------- 3 files changed, 51 insertions(+), 37 deletions(-) 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; } From 161e92d439d5731ab81b7cd83dbca0d9330b6604 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 6 Dec 2023 17:57:35 +1100 Subject: [PATCH 03/13] other code --- .../java/org/hl7/fhir/r4/ips/IPSRenderer.java | 100 ++++++++++++++++++ .../r5/renderers/utils/RenderingContext.java | 3 +- .../r5/utils/CanonicalResourceUtilities.java | 64 +++++++++-- 3 files changed, 158 insertions(+), 9 deletions(-) create mode 100644 org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/ips/IPSRenderer.java diff --git a/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/ips/IPSRenderer.java b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/ips/IPSRenderer.java new file mode 100644 index 000000000..868f7145a --- /dev/null +++ b/org.hl7.fhir.r4/src/main/java/org/hl7/fhir/r4/ips/IPSRenderer.java @@ -0,0 +1,100 @@ +package org.hl7.fhir.r4.ips; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.hl7.fhir.r4.ips.IPSRenderer.InternalTemplateEngine; +import org.hl7.fhir.r4.model.Bundle; +import org.hl7.fhir.r4.model.Composition; +import org.hl7.fhir.r4.model.Composition.SectionComponent; +import org.hl7.fhir.r4.model.DomainResource; +import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.r4.terminologies.TerminologyClient; +import org.hl7.fhir.utilities.xhtml.NodeType; +import org.hl7.fhir.utilities.xhtml.XhtmlComposer; +import org.hl7.fhir.utilities.xhtml.XhtmlNode; + +public class IPSRenderer { + + public class InternalTemplateEngine implements ITemplateImplementer { + + @Override + public String buildPage(Map headers, String content) { + // TODO Auto-generated method stub + return null; + } + + } + + private interface ITemplateImplementer { + public String buildPage(Map headers, String content); + } + private TerminologyClient tx; + private String folder; // for images etc + private Map binaries; // from the pubpack + private ITemplateImplementer templater; + private Map headers; + + public IPSRenderer(TerminologyClient tx, String folder, Map binaries, ITemplateImplementer templater) { + super(); + this.tx = tx; + this.folder = folder; + this.binaries = binaries; + this.templater = templater; + } + + public IPSRenderer(TerminologyClient tx, String folder, Map binaries) { + super(); + this.tx = tx; + this.folder = folder; + this.binaries = binaries; + this.templater = new InternalTemplateEngine(); + } + + public String render(Bundle document) throws IOException { + headers = new HashMap<>(); + XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); + generate(x, document); + return templater.buildPage(headers, new XhtmlComposer(false, true).compose(x)); + } + + private void generate(XhtmlNode x, Bundle document) { + Composition cmp = (Composition) document.getEntryFirstRep().getResource(); + int sectionDepth = findSectionDepth(cmp.getSection()); + XhtmlNode table = x.table("grid"); + + // row 1: header + XhtmlNode tr = table.tr(); + XhtmlNode td = tr.td().colspan(1+sectionDepth); + td.b().tx("Provided"); + td = tr.td().colspan(1+sectionDepth); + td.b().tx("Generated"); + + // row 2: Subject + DomainResource subject = findResource(document, cmp.getSubject()); + tr = table.tr(); + td = tr.td().colspan(1+sectionDepth); + // genNarrative("subject", subject, td); + td = tr.td().colspan(1+sectionDepth); + td.b().tx("Generated"); + + + + + } + + private DomainResource findResource(Bundle document, Reference subject) { + // TODO Auto-generated method stub + return null; + } + + private int findSectionDepth(List list) { + int i = 1; + for (SectionComponent sect : list) { + i = Integer.max(i, 1+findSectionDepth(sect.getSection())); + } + return i; + } +} diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java index c652ba53e..39f8062e2 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java @@ -701,8 +701,9 @@ public class RenderingContext { public GenerationRules getRules() { return rules; } - public void setRules(GenerationRules rules) { + public RenderingContext setRules(GenerationRules rules) { this.rules = rules; + return this; } public StandardsStatus getDefaultStandardsStatus() { return defaultStandardsStatus; 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 bb0beeb49..91d924f7b 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 @@ -63,6 +63,12 @@ public class CanonicalResourceUtilities { } } + /** + * for use in the core build where the context is not fully populated. Only known safe for R6 resources + * + * @param res + * @param code + */ public static void setHl7WG(org.w3c.dom.Element res, String code) { String rt = res.getNodeName(); if (VersionUtilities.getExtendedCanonicalResourceNames("5.0.0").contains(rt)) { @@ -82,15 +88,12 @@ 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", "name", "title"); - if (after == null) { - after = XMLUtil.getLastChild(res, "id", "meta", "text", "implicitRules", "language", "text", "contained"); - if (after != null) { - after = XMLUtil.getNextSibling(after); - } + org.w3c.dom.Element after = XMLUtil.getLastChild(res, "id", "meta", "text", "implicitRules", "language", "text", "contained"); + if (after != null) { + after = XMLUtil.getNextSibling(after); } res.insertBefore(wgext, after); - res.insertBefore(res.getOwnerDocument().createTextNode("/n "), after); + res.insertBefore(res.getOwnerDocument().createTextNode("\n "), after); } XMLUtil.clearChildren(wgext); org.w3c.dom.Element valueCode = res.getOwnerDocument().createElementNS(Constants.NS_FHIR_ROOT, "valueCode"); @@ -100,10 +103,55 @@ 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", "relatedArtifact", "description", "useContext", "jurisdiction", "purpose", "copyright"); + org.w3c.dom.Element after = XMLUtil.getLastChild(res, "id", "meta", "text", "implicitRules", "language", "text", "contained", "extension", "modifierExtension", + "url", "identifier", "version", "versionAlgorithmString", "versionAlgorithmCoding", "name", "title", "status", "experimental", "date", ("EvidenceReport".equals(rt) ? "subject" : "xx")); + if (after != null) { + after = XMLUtil.getNextSibling(after); + } res.insertBefore(pub, after); + res.insertBefore(res.getOwnerDocument().createTextNode("\n "), after); } pub.setAttribute("value", "HL7 International / "+wg.getName()); + + org.w3c.dom.Element contact = XMLUtil.getNamedChild(res, "contact"); + if (contact == null) { + contact = res.getOwnerDocument().createElementNS(Constants.NS_FHIR_ROOT, "contact"); + res.insertBefore(contact, XMLUtil.getNextSibling(pub)); + res.insertBefore(res.getOwnerDocument().createTextNode("\n "), contact.getNextSibling()); + } + + org.w3c.dom.Element telecom = XMLUtil.getNamedChild(contact, "telecom"); + if (telecom == null) { + contact.appendChild(res.getOwnerDocument().createTextNode("\n ")); + telecom = res.getOwnerDocument().createElementNS(Constants.NS_FHIR_ROOT, "telecom"); + contact.appendChild(telecom); + contact.appendChild(res.getOwnerDocument().createTextNode("\n ")); + } + + org.w3c.dom.Element system = XMLUtil.getNamedChild(telecom, "system"); + if (system == null) { + system = res.getOwnerDocument().createElementNS(Constants.NS_FHIR_ROOT, "system"); + org.w3c.dom.Element after = XMLUtil.getLastChild(telecom, "id", "extension"); + if (after != null) { + after = XMLUtil.getNextSibling(after); + } + telecom.insertBefore(system, after); + telecom.insertBefore(res.getOwnerDocument().createTextNode("\n "), after); + } + system.setAttribute("value", "url"); + + + org.w3c.dom.Element value = XMLUtil.getNamedChild(telecom, "value"); + if (value == null) { + value = res.getOwnerDocument().createElementNS(Constants.NS_FHIR_ROOT, "value"); + org.w3c.dom.Element after = XMLUtil.getLastChild(telecom, "id", "extension", "system"); + if (after != null) { + after = XMLUtil.getNextSibling(after); + } + telecom.insertBefore(system, after); + telecom.insertBefore(res.getOwnerDocument().createTextNode("\n "), after); + } + value.setAttribute("value", wg.getLink()); } } } From ad9119ff3cc0074a9e9925c16ef45d0abf866bd2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Dec 2023 13:28:34 -0500 Subject: [PATCH 04/13] Bump ch.qos.logback:logback-classic from 1.2.3 to 1.2.13 (#1511) Bumps [ch.qos.logback:logback-classic](https://github.com/qos-ch/logback) from 1.2.3 to 1.2.13. - [Commits](https://github.com/qos-ch/logback/compare/v_1.2.3...v_1.2.13) --- updated-dependencies: - dependency-name: ch.qos.logback:logback-classic dependency-type: direct:development ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 29260eccb..0564cbf16 100644 --- a/pom.xml +++ b/pom.xml @@ -86,7 +86,7 @@ ch.qos.logback logback-classic - 1.2.3 + 1.2.13 test From e5688eb8ec6bd74dd07ef180455674e0c4fadae8 Mon Sep 17 00:00:00 2001 From: dotasek Date: Wed, 6 Dec 2023 17:03:12 -0500 Subject: [PATCH 05/13] Bump logback in dependencyManagement (#1514) --- pom.xml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 0564cbf16..7128c8986 100644 --- a/pom.xml +++ b/pom.xml @@ -86,7 +86,6 @@ ch.qos.logback logback-classic - 1.2.13 test @@ -297,6 +296,18 @@ sqlite-jdbc 3.43.0.0 + + + ch.qos.logback + logback-classic + 1.2.13 + + + + ch.qos.logback + logback-core + 1.2.13 + From 81fef9801362afd745f62b10e64a128f58607edc Mon Sep 17 00:00:00 2001 From: dotasek Date: Thu, 7 Dec 2023 11:01:23 -0500 Subject: [PATCH 06/13] Bump jackson version (#1515) * Bump jackson version * Fix license check --- .github/workflows/license-check/license-special-cases.txt | 4 +++- pom.xml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/license-check/license-special-cases.txt b/.github/workflows/license-check/license-special-cases.txt index 5f246fa46..310354d72 100644 --- a/.github/workflows/license-check/license-special-cases.txt +++ b/.github/workflows/license-check/license-special-cases.txt @@ -20,4 +20,6 @@ # Appears to be Apache 2.0: https://github.com/NCIP/lexevs/blob/master/lgSharedLibraries/apache/commons/jakarta-regexp-1.4.license.txt (Unknown license) jakarta-regexp (jakarta-regexp:jakarta-regexp:1.4 - no url defined) # License string includes nested brackets, causing parser breakage, but is a valid BSD license. -(BSD 3-Clause "New" or "Revised" License (BSD-3-Clause)) abego TreeLayout Core (org.abego.treelayout:org.abego.treelayout.core:1.0.3 - http://treelayout.sourceforge.net) \ No newline at end of file +(BSD 3-Clause "New" or "Revised" License (BSD-3-Clause)) abego TreeLayout Core (org.abego.treelayout:org.abego.treelayout.core:1.0.3 - http://treelayout.sourceforge.net) +# License name includes brackets (javax.xml.bind) + (The Apache Software License, Version 2.0) Jackson module: Old JAXB Annotations (javax.xml.bind) (com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.16.0 - https://github.com/FasterXML/jackson-modules-base) \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7128c8986..6b754ee08 100644 --- a/pom.xml +++ b/pom.xml @@ -21,7 +21,7 @@ 32.0.1-jre 6.4.1 1.4.19-SNAPSHOT - 2.15.2 + 2.16.0 5.9.2 1.8.2 3.0.0-M5 From f88f4e492e2e5bd7bef0e5ba6938a81c01f9c5b5 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 11 Dec 2023 14:08:28 +1100 Subject: [PATCH 07/13] fix bug loading packages with no specified version that don't exist --- .../org/hl7/fhir/utilities/npm/BasePackageCacheManager.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/BasePackageCacheManager.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/BasePackageCacheManager.java index 659e478db..7ac1ca647 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/BasePackageCacheManager.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/npm/BasePackageCacheManager.java @@ -75,6 +75,9 @@ public abstract class BasePackageCacheManager implements IPackageCacheManager { } if (version.endsWith(".x")) { version = packageClient.getLatestVersion(id, version); + if (version == null) { + return null; + } } InputStream stream = packageClient.fetch(id, version); From 2cfc7f3df79038ed8e1b0d75aa79f4f6ba98dc41 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 11 Dec 2023 14:08:48 +1100 Subject: [PATCH 08/13] Fix R5 con-3 FHIRPath expression --- .../4.0.1/all-systems.cache | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/all-systems.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/all-systems.cache index 8ecdd3e2b..19f6a44ef 100644 --- a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/all-systems.cache +++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.validation/4.0.1/all-systems.cache @@ -2183,6 +2183,44 @@ v: { "severity" : "error", "error" : "The provided code 'http://snomed.info/sct#16076005' was not found in the value set 'http://terminology.hl7.org/ValueSet/snomed-intl-ips|20211027' (from Tx-Server)", "class" : "UNKNOWN", + "issues" : { + "resourceType" : "OperationOutcome" +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "code" : "english" +}, "url": "http://hl7.org/fhir/ValueSet/all-languages", "version": "4.0.1", "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" : "Error from http://tx-dev.fhir.org/r4: Access violation", + "class" : "SERVER_ERROR", + "issues" : { + "resourceType" : "OperationOutcome" +} + +} +------------------------------------------------------------------------------------- +{"code" : { + "code" : "en" +}, "url": "http://hl7.org/fhir/ValueSet/all-languages", "version": "4.0.1", "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: { + "display" : "English", + "code" : "en", + "system" : "urn:ietf:bcp:47", "unknown-systems" : "", "issues" : { "resourceType" : "OperationOutcome" From e73404fe8a3bec2c6b6532948ff725349f70a0d7 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 11 Dec 2023 14:09:57 +1100 Subject: [PATCH 09/13] fix to CDA xsi:type validation per SD decision + apply regex pattern to literal format if defined + improvements to vital signs related messages --- .../instance/InstanceValidator.java | 106 +++++++++++++++--- 1 file changed, 91 insertions(+), 15 deletions(-) diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java index 0b911d9c6..fb58c02ad 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java @@ -103,6 +103,7 @@ import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.r5.model.CodeType; import org.hl7.fhir.r5.model.CodeableConcept; import org.hl7.fhir.r5.model.Coding; +import org.hl7.fhir.r5.model.Constants; import org.hl7.fhir.r5.model.ContactPoint; import org.hl7.fhir.r5.model.DataType; import org.hl7.fhir.r5.model.DateTimeType; @@ -2932,9 +2933,18 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat String regext = FHIRPathExpressionFixer.fixRegex(getRegexFromType(e.fhirType())); if (regext != null) { try { - boolean matches = e.primitiveValue().matches(regext); + String pt = e.primitiveValue(); + String ptFmt = null; + if (e.getProperty().getDefinition().hasExtension(ToolingExtensions.EXT_DATE_FORMAT)) { + ptFmt = convertForDateFormatToExternal(ToolingExtensions.readStringExtension(e.getProperty().getDefinition(), ToolingExtensions.EXT_DATE_FORMAT), pt); + } + boolean matches = pt.matches(regext) || (ptFmt != null && ptFmt.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; + if (ptFmt == null) { + ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, matches, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX_TYPE, pt, e.fhirType(), regext) && ok; + } else { + ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, matches, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX_TYPE_ALT, pt, ptFmt, 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; @@ -2946,6 +2956,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return ok; } + private String convertForDateFormatToExternal(String fmt, String av) throws FHIRException { + if ("v3".equals(fmt) || "YYYYMMDDHHMMSS.UUUU[+|-ZZzz]".equals(fmt)) { + DateTimeType d = new DateTimeType(av); + return d.getAsV3(); + } else + throw new FHIRException(context.formatMessage(I18nConstants.UNKNOWN_DATE_FORMAT_, fmt)); + } + private boolean isCoreDefinition(StructureDefinition profile) { return profile.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/") && profile.getKind() != StructureDefinitionKind.LOGICAL; } @@ -4289,7 +4307,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (sd != null && (sd.getTypeTail().equals(type) || sd.getUrl().equals(type)) && sd.hasSnapshot()) { return sd; } - if (sd != null && sd.getAbstract()) { + if (sd != null && (sd.getAbstract() || sd.getUrl().startsWith(Constants.NS_CDA_ROOT))) { StructureDefinition sdt = context.fetchTypeDefinition(type); StructureDefinition tt = sdt; while (tt != null) { @@ -4687,10 +4705,26 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat timeTracker.sd(t); if (sd != null && (sd.getType().equals(type) || sd.getUrl().equals(type)) && sd.hasSnapshot()) return sd.getSnapshot().getElement().get(0); + if (sd != null) { + StructureDefinition sdt = context.fetchTypeDefinition(type); + if (inheritsFrom(sdt, sd) && sdt.hasSnapshot()) { + return sdt.getSnapshot().getElement().get(0); + } + } } return null; } + private boolean inheritsFrom(StructureDefinition sdt, StructureDefinition sd) { + while (sdt != null) { + if (sdt == sd) { + return true; + } + sdt = context.fetchResource(StructureDefinition.class, sdt.getBaseDefinition()); + } + return false; + } + public void setAnyExtensionsAllowed(boolean anyExtensionsAllowed) { this.anyExtensionsAllowed = anyExtensionsAllowed; } @@ -6024,8 +6058,12 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat checkMustSupport(profile, ei); long s = System.currentTimeMillis(); - if (checkDefn.getType().size() == 1 && !"*".equals(checkDefn.getType().get(0).getWorkingCode()) && !"Element".equals(checkDefn.getType().get(0).getWorkingCode()) - && !"BackboneElement".equals(checkDefn.getType().get(0).getWorkingCode())) { + boolean hasType = checkDefn.getType().size() > 0; + boolean isAbstract = hasType && Utilities.existsInList(checkDefn.getType().get(0).getWorkingCode(), "Element", "BackboneElement"); + boolean isChoice = checkDefn.getType().size() > 1 || (hasType && "*".equals(checkDefn.getType().get(0).getWorkingCode())); + boolean isCDAChoice = profile.getUrl().startsWith(Constants.NS_CDA_ROOT) && ei.getElement().getExplicitType() != null; + + if (hasType && !isChoice && !isAbstract && !isCDAChoice) { type = checkDefn.getType().get(0).getWorkingCode(); typeName = type; if (Utilities.isAbsoluteUrl(type)) { @@ -6075,12 +6113,15 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat profiles.add(p.getValue()); } } - } else if (checkDefn.getType().size() > 1) { + } else if (checkDefn.getType().size() > 1 || isCDAChoice) { String prefix = tail(checkDefn.getPath()); - assert typesAreAllReference(checkDefn.getType()) || checkDefn.hasRepresentation(PropertyRepresentation.TYPEATTR) || prefix.endsWith("[x]") || isResourceAndTypes(checkDefn) : "Multiple Types allowed, but name is wrong @ "+checkDefn.getPath()+": "+checkDefn.typeSummaryVB(); + assert typesAreAllReference(checkDefn.getType()) || isCDAChoice|| checkDefn.hasRepresentation(PropertyRepresentation.TYPEATTR) || prefix.endsWith("[x]") || isResourceAndTypes(checkDefn) : "Multiple Types allowed, but name is wrong @ "+checkDefn.getPath()+": "+checkDefn.typeSummaryVB(); - if (checkDefn.hasRepresentation(PropertyRepresentation.TYPEATTR)) { + if (isCDAChoice) { + type = ei.getElement().getExplicitType(); + typeName = type; + } else if (checkDefn.hasRepresentation(PropertyRepresentation.TYPEATTR)) { type = ei.getElement().getType(); typeName = type; } else if (ei.getElement().isResource()) { @@ -6476,7 +6517,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat hintPlural(errors, NO_RULE_DATE, IssueType.NOTSUPPORTED, element.line(), element.col(), stack.getLiteralPath(), count >= ed.getMin(), count, I18nConstants.VALIDATION_VAL_PROFILE_NOCHECKMIN, profile.getVersionedUrl(), ed.getPath(), ed.getId(), ed.getSliceName(),ed.getLabel(), stack.getLiteralPath(), Integer.toString(ed.getMin())); else { if (count < ed.getMin()) { - ok = rulePlural(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), false, count, I18nConstants.VALIDATION_VAL_PROFILE_MINIMUM, profile.getVersionedUrl(), ed.getPath(), ed.getId(), ed.getSliceName(),ed.getLabel(), stack.getLiteralPath(), Integer.toString(ed.getMin())) && ok; + if (isObservationMagicValue(profile, ed)) { + ok = rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_MINIMUM_MAGIC, ed.getSliceName(), getFixedLOINCCode(ed, profile), profile.getVersionedUrl()) && ok; + } else { + ok = rulePlural(errors, NO_RULE_DATE, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), false, count, I18nConstants.VALIDATION_VAL_PROFILE_MINIMUM, profile.getVersionedUrl(), ed.getPath(), ed.getId(), ed.getSliceName(),ed.getLabel(), stack.getLiteralPath(), Integer.toString(ed.getMin())) && ok; + } } } } @@ -6492,6 +6537,28 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat return ok; } + private String getFixedLOINCCode(ElementDefinition ed, StructureDefinition profile) { + if (ed.hasFixedCoding() && "http://loinc.org".equals(ed.getFixedCoding().getSystem())) { + return ed.getFixedCoding().getCode(); + } + SourcedChildDefinitions children = profileUtilities.getChildMap(profile, ed); + if (children != null) { + for (ElementDefinition t : children.getList()) { + if (t.getPath().endsWith(".code") && t.hasFixed()) { + return t.getFixed().primitiveValue(); + } + } + } + return "??"; + } + + private boolean isObservationMagicValue(StructureDefinition profile, ElementDefinition ed) { + if (profile.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/") && ed.hasSliceName() && ed.getPath().equals("Observation.code.coding")) { + return true; + } + return false; + } + public List assignChildren(ValidationContext valContext, List errors, StructureDefinition profile, Element resource, NodeStack stack, SourcedChildDefinitions childDefinitions, List children, BooleanHolder bh) throws DefinitionException { // 2. assign children to a definition @@ -6545,12 +6612,14 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (!unsupportedSlicing) { if (ei.additionalSlice && ei.definition != null) { if (ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPEN) || - ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPENATEND) && true /* TODO: replace "true" with condition to check that this element is at "end" */) { - slicingHint(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, ei.line(), ei.col(), ei.getPath(), false, isProfile(slicer) || isCritical(ei.sliceInfo), - context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_, - profile == null ? "" : "defined in the profile " + profile.getVersionedUrl()), - context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_, profile == null ? "" : context.formatMessage(I18nConstants.DEFINED_IN_THE_PROFILE) + " "+profile.getVersionedUrl()) + errorSummaryForSlicingAsHtml(ei.sliceInfo), - errorSummaryForSlicingAsText(ei.sliceInfo)); + ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.OPENATEND) && true /* TODO: replace "true" with condition to check that this element is at "end" */) { + if (!ignoreSlicingHint(ei.definition, profile)) { + slicingHint(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, ei.line(), ei.col(), ei.getPath(), false, isProfile(slicer) || isCritical(ei.sliceInfo), + context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_, + profile == null ? "" : "defined in the profile " + profile.getVersionedUrl()), + context.formatMessage(I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_, profile == null ? "" : context.formatMessage(I18nConstants.DEFINED_IN_THE_PROFILE) + " "+profile.getVersionedUrl()) + errorSummaryForSlicingAsHtml(ei.sliceInfo), + errorSummaryForSlicingAsText(ei.sliceInfo)); + } } else if (ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.CLOSED)) { bh.see(rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_NOTSLICE, (profile == null ? "" : " defined in the profile " + profile.getVersionedUrl()), errorSummaryForSlicing(ei.sliceInfo))); } @@ -6593,6 +6662,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat } + private boolean ignoreSlicingHint(ElementDefinition definition, StructureDefinition profile) { + if (profile.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/") && "Observation.code.coding".equals(definition.getPath())) { + return true; + } + return false; + } + public List listChildren(Element element, NodeStack stack) { // 1. List the children, and remember their exact path (convenience) List children = new ArrayList(); From 4b1488438e2922fec9cf0149a68d032ad72f6c42 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 11 Dec 2023 14:10:26 +1100 Subject: [PATCH 10/13] missed files --- .../hl7/fhir/r5/elementmodel/Property.java | 44 ++++++++++++++++--- .../hl7/fhir/r5/elementmodel/XmlParser.java | 42 ++++++++++++++---- .../fhir/utilities/i18n/I18nConstants.java | 2 + .../src/main/resources/Messages.properties | 4 +- .../utils/FHIRPathExpressionFixer.java | 4 ++ 5 files changed, 79 insertions(+), 17 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Property.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Property.java index f38b8a6ba..720606484 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Property.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Property.java @@ -41,6 +41,7 @@ import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.SourcedChildDefiniti import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.fhirpath.TypeDetails; import org.hl7.fhir.r5.formats.FormatUtilities; +import org.hl7.fhir.r5.model.Constants; import org.hl7.fhir.r5.model.ElementDefinition; import org.hl7.fhir.r5.model.ElementDefinition.PropertyRepresentation; import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent; @@ -345,12 +346,13 @@ public class Property { } ElementDefinition ed = definition; StructureDefinition sd = structure; + boolean isCDA = isCDAElement(structure); SourcedChildDefinitions children = profileUtilities.getChildMap(sd, ed); String url = null; if (children.getList().isEmpty() || isElementWithOnlyExtension(ed, children.getList())) { // ok, find the right definitions String t = null; - if (ed.getType().size() == 1) + if (ed.getType().size() == 1 && (statedType == null || !isCDA)) t = ed.getType().get(0).getWorkingCode(); else if (ed.getType().size() == 0) throw new Error("types == 0, and no children found on "+getDefinition().getPath()); @@ -363,9 +365,9 @@ public class Property { break; } } - if (!all) { + if (!all || (isCDA && statedType != null)) { // ok, it's polymorphic - if (ed.hasRepresentation(PropertyRepresentation.TYPEATTR)) { + if (ed.hasRepresentation(PropertyRepresentation.TYPEATTR) || isCDA) { t = statedType; if (t == null && ToolingExtensions.hasExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype")) t = ToolingExtensions.readStringExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype"); @@ -379,13 +381,21 @@ public class Property { url = tr.getWorkingCode(); ok = true; } + if (!ok) { + sdt = findAncestor(t, sdt); + if (sdt != null) { + url = sdt.getUrl(); + ok = true; + } + } } - if (ok) + if (ok) { break; + } + } + if (!ok) { + throw new DefinitionException("Type '"+t+"' is not an acceptable type for '"+elementName+"' on property "+definition.getPath()); } - if (!ok) - throw new DefinitionException("Type '"+t+"' is not an acceptable type for '"+elementName+"' on property "+definition.getPath()); - } else { t = elementName.substring(tail(ed.getPath()).length() - 3); if (isPrimitive(lowFirst(t))) @@ -421,6 +431,26 @@ public class Property { return properties; } + private StructureDefinition findAncestor(String type, StructureDefinition sdt) { + if (sdt != null) { + StructureDefinition sd = context.fetchTypeDefinition(type); + StructureDefinition t = sd; + while (t != null) { + if (t == sdt) { + return sd; + } + t = context.fetchResource(StructureDefinition.class, t.getBaseDefinition()); + } + } + return null; + } + + + private boolean isCDAElement(StructureDefinition sd) { + return sd.getUrl().startsWith(Constants.NS_CDA_ROOT); + } + + protected List getChildProperties(TypeDetails type) throws DefinitionException { ElementDefinition ed = definition; StructureDefinition sd = structure; diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java index 8b64538c1..1313420b0 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/XmlParser.java @@ -60,6 +60,7 @@ import org.hl7.fhir.r5.elementmodel.Element.SpecialElement; import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat; import org.hl7.fhir.r5.formats.FormatUtilities; import org.hl7.fhir.r5.formats.IParser.OutputStyle; +import org.hl7.fhir.r5.model.Constants; import org.hl7.fhir.r5.model.DateTimeType; import org.hl7.fhir.r5.model.ElementDefinition; import org.hl7.fhir.r5.model.ElementDefinition.PropertyRepresentation; @@ -232,7 +233,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(), false); + checkElement(errors, element, result, path, result.getProperty(), false); result.markLocation(line(element, false), col(element, false)); result.setType(element.getLocalName()); parseChildren(errors, path, element, result); @@ -274,7 +275,7 @@ public class XmlParser extends ParserBase { return true; } - private void checkElement(List errors, org.w3c.dom.Element element, String path, Property prop, boolean xsiTypeChecked) throws FHIRFormatError { + private void checkElement(List errors, org.w3c.dom.Element element, Element e, 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); @@ -290,22 +291,40 @@ public class XmlParser extends ParserBase { 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 { + if (xsiType.equals(actualType)) { logError(errors, "2023-10-12", line(element, false), col(element, false), path, IssueType.INVALID, context.formatMessage(I18nConstants.XSI_TYPE_UNNECESSARY), IssueSeverity.INFORMATION); - } + } else { + StructureDefinition sd = findLegalConstraint(xsiType, actualType); + if (sd != null) { + e.setType(sd.getType()); + e.setExplicitType(xsiType); + } else { + logError(errors, "2023-10-12", line(element, false), col(element, false), path, IssueType.INVALID, context.formatMessage(I18nConstants.XSI_TYPE_WRONG, xsiType, actualType), IssueSeverity.ERROR); + } + } } } } } + private StructureDefinition findLegalConstraint(String xsiType, String actualType) { + StructureDefinition sdA = context.fetchTypeDefinition(actualType); + StructureDefinition sd = context.fetchTypeDefinition(xsiType); + while (sd != null) { + if (sd == sdA) { + return sd; + } + sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); + } + return null; + } + public Element parse(List errors, org.w3c.dom.Element base, String type) throws Exception { StructureDefinition sd = getDefinition(errors, 0, 0, FormatUtilities.FHIR_NS, type); Element result = new Element(base.getLocalName(), new Property(context, sd.getSnapshot().getElement().get(0), sd)).setFormat(FhirFormat.XML).setNativeObject(base); result.setPath(base.getLocalName()); String path = "/"+pathPrefix(base.getNamespaceURI())+base.getLocalName(); - checkElement(errors, base, path, result.getProperty(), false); + checkElement(errors, base, result, path, result.getProperty(), false); result.setType(base.getLocalName()); parseChildren(errors, path, base, result); result.numberChildren(); @@ -469,7 +488,7 @@ public class XmlParser extends ParserBase { } else n.setType(n.getType()); } - checkElement(errors, (org.w3c.dom.Element) child, npath, n.getProperty(), xsiTypeChecked); + checkElement(errors, (org.w3c.dom.Element) child, n, npath, n.getProperty(), xsiTypeChecked); element.getChildren().add(n); if (ok) { if (property.isResource()) @@ -500,7 +519,7 @@ public class XmlParser extends ParserBase { Element n = new Element(name, property).markLocation(line(child, false), col(child, false)).setFormat(FhirFormat.XML).setNativeObject(child); cgn.getChildren().add(n); n.setPath(element.getPath()+"."+property.getName()); - checkElement(errors, (org.w3c.dom.Element) child, npath, n.getProperty(), false); + checkElement(errors, (org.w3c.dom.Element) child, n, npath, n.getProperty(), false); parseChildren(errors, npath, (org.w3c.dom.Element) child, n); } } @@ -749,6 +768,11 @@ public class XmlParser extends ParserBase { if (hasTypeAttr(c)) return true; } + // xsi_type is always allowed on CDA elements. right now, I'm not sure where to indicate this in the model, + // so it's just hardcoded here + if (e.getType() != null && e.getType().startsWith(Constants.NS_CDA_ROOT)) { + return true; + } return false; } 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 225d386bc..56b3cb217 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 @@ -534,6 +534,7 @@ public class I18nConstants { 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_TYPE_ALT = "TYPE_SPECIFIC_CHECKS_DT_PRIMITIVE_REGEX_TYPE_ALT"; 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"; @@ -626,6 +627,7 @@ public class I18nConstants { public static final String VALIDATION_VAL_PROFILE_MATCHMULTIPLE = "Validation_VAL_Profile_MatchMultiple"; public static final String VALIDATION_VAL_PROFILE_MAXIMUM = "Validation_VAL_Profile_Maximum"; public static final String VALIDATION_VAL_PROFILE_MINIMUM = "Validation_VAL_Profile_Minimum"; + public static final String VALIDATION_VAL_PROFILE_MINIMUM_MAGIC = "VALIDATION_VAL_PROFILE_MINIMUM_MAGIC"; public static final String VALIDATION_VAL_PROFILE_MULTIPLEMATCHES = "Validation_VAL_Profile_MultipleMatches"; public static final String VALIDATION_VAL_PROFILE_NOCHECKMAX = "Validation_VAL_Profile_NoCheckMax"; public static final String VALIDATION_VAL_PROFILE_NOCHECKMIN = "Validation_VAL_Profile_NoCheckMin"; diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index 3adacca6f..6563057ac 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -222,6 +222,7 @@ 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_REGEX_TYPE_ALT = Neither the element value ''{0}'' or the formatted value ''{1}'' meet {2} regex ''{3}'' 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) @@ -243,6 +244,7 @@ Validation_VAL_Profile_Maximum_one = {3}: max allowed = {7}, but found {0} (from Validation_VAL_Profile_Maximum_other = {3}: max allowed = {7}, but found {0} (from {1}) Validation_VAL_Profile_Minimum_one = {3}: minimum required = {7}, but only found {0} (from {1}) Validation_VAL_Profile_Minimum_other = {3}: minimum required = {7}, but only found {0} (from {1}) +VALIDATION_VAL_PROFILE_MINIMUM_MAGIC = {0}: magic LOINC code {1} required, but not found (from {2}). Note that other Observation codes are allowed in addition to this required magic code Validation_VAL_Profile_NoCheckMax_one = {3}: Found {0} match, but unable to check max allowed ({2}) due to lack of slicing validation (from {1}) Validation_VAL_Profile_NoCheckMax_other = {3}: Found {0} matches, but unable to check max allowed ({2}) due to lack of slicing validation (from {1}) Validation_VAL_Profile_NoCheckMin_one = {3}: Found {0} match, but unable to check minimum required ({2}) due to lack of slicing validation (from {1}) @@ -1077,7 +1079,7 @@ CDA_UNKNOWN_TEMPLATE = The CDA Template {0} is not known CDA_UNKNOWN_TEMPLATE_EXT = The CDA Template {0} / {1} is not known UNABLE_TO_DETERMINE_TYPE_CONTEXT_INV = The types could not be determined from the extension context, so the invariant can't be validated (types = {0}) ED_CONTEXT_INVARIANT_EXPRESSION_ERROR = Error in constraint ''{0}'': {1} -VALIDATION_VAL_PROFILE_SIGNPOST_OBS = Validate Observation against {1} profile because the {2} code {3} was found +VALIDATION_VAL_PROFILE_SIGNPOST_OBS = Validate Observation against the {1} profile ({0}) which is required by the FHIR specification because the {2} code {3} was found FHIRPATH_INVALID_TYPE = The type {0} is not valid FHIRPATH_AS_COLLECTION = Attempt to use ''as()'' on more than one item (''{0}'', ''{1}'') FHIRPATH_ARITHMETIC_QTY = Error in date arithmetic: attempt to add a definite quantity duration time unit {0} diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/FHIRPathExpressionFixer.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/FHIRPathExpressionFixer.java index 3e4398661..40d531d8f 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/FHIRPathExpressionFixer.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/utils/FHIRPathExpressionFixer.java @@ -53,6 +53,10 @@ public class FHIRPathExpressionFixer { if (expr.equals("name.matches('[A-Z]([A-Za-z0-9_]){0,254}')")) { return ("name.exists() implies name.matches('[A-Z]([A-Za-z0-9_]){0,254}')"); } + // con-3 in R4 + if (expr.equals("clinicalStatus.exists() or verificationStatus.coding.where(system='http://terminology.hl7.org/CodeSystem/condition-ver-status' and code = 'entered-in-error').exists() or category.select($this='problem-list-item').empty()")) { + return "clinicalStatus.exists() or verificationStatus.coding.where(system='http://terminology.hl7.org/CodeSystem/condition-ver-status' and code = 'entered-in-error').exists() or category.coding.exists(system='http://terminology.hl7.org/CodeSystem/condition-category' and code ='problem-list-item').empty()"; + } // R5 ballot if (expr.equals("url.matches('([^|#])*')")) { From dfeb8a37ca8684cffbd1dff0f96ea857de355f45 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 11 Dec 2023 14:13:30 +1100 Subject: [PATCH 11/13] Release notes --- RELEASE_NOTES.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index fc20e8d31..d0475783d 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -3,6 +3,11 @@ * Fix bug where validator doesn't actually validate web sourced input * Fix narrative link validation and add id/idref validation * Remove fhir-test-cases from Validator CLI JAR (#1497) (reduce size) +* Fix to CDA xsi:type validation per SD decision +* Apply regex pattern to literal format if defined +* Improvements to vital signs related messages +* Fix R4 con-3 FHIRPath expression +* Fix bug loading packages with partially specified version that doesn't exist ## Other code changes @@ -20,3 +25,4 @@ * Give kinder error message for missing param * Fix commonmark group and bump version (#1500) * Remove dep used for local testing +* Bump jackson & logback versions From 9fae5714129be8fe6e215a742ca556c2c59ff37e Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 11 Dec 2023 15:37:14 +1100 Subject: [PATCH 12/13] fix NPE in CDA check --- .../src/main/java/org/hl7/fhir/r5/elementmodel/Property.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Property.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Property.java index 720606484..128c7ce45 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Property.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/elementmodel/Property.java @@ -447,7 +447,7 @@ public class Property { private boolean isCDAElement(StructureDefinition sd) { - return sd.getUrl().startsWith(Constants.NS_CDA_ROOT); + return sd.hasUrl() && sd.getUrl().startsWith(Constants.NS_CDA_ROOT); } From de7dc3023ddc3eb5d35d3d8ea13ce861b079aece Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Mon, 11 Dec 2023 16:56:27 +1100 Subject: [PATCH 13/13] fix validation issues on HL7 publisher --- .../org/hl7/fhir/utilities/HL7WorkGroups.java | 19 ++++++++++++++++--- .../fhir/utilities/i18n/I18nConstants.java | 1 + .../src/main/resources/Messages.properties | 1 + .../instance/InstanceValidator.java | 8 +++++++- 4 files changed, 25 insertions(+), 4 deletions(-) diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/HL7WorkGroups.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/HL7WorkGroups.java index e172ea7e3..a47dd003c 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/HL7WorkGroups.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/HL7WorkGroups.java @@ -5,13 +5,15 @@ public class HL7WorkGroups { public static class HL7WorkGroup { private String link; private String name; + private String name2; private String code; - protected HL7WorkGroup(String code, String name, String link) { + protected HL7WorkGroup(String code, String name, String name2, String link) { super(); this.code = code; this.name = name; + this.name2 = name2; this.link = link; } public String getLink() { @@ -20,6 +22,9 @@ public class HL7WorkGroups { public String getName() { return name; } + public String getName2() { + return name2; + } public String getCode() { return code; } @@ -27,9 +32,10 @@ public class HL7WorkGroups { public static HL7WorkGroup find(String wg) { String name = nameForWG(wg); + String name2 = name2ForWG(wg); String url = urlForWG(wg); if (name != null) { - return new HL7WorkGroup(wg, name, url); + return new HL7WorkGroup(wg, name, name2, url); } else { return null; } @@ -125,7 +131,7 @@ public class HL7WorkGroups { case "sd": return "Structured Documents"; case "sec": return "Security"; case "soa": return "Services Oriented Architecture"; - case "ti": return "Vocabulary"; + case "ti": return "Terminology Infrastructure"; case "tsmg": return "Terminology Services Management Group (TSMG)"; case "us": return "US Realm Steering Committee"; case "v2": return "V2 Management Group"; @@ -134,5 +140,12 @@ public class HL7WorkGroups { return null; } + private static String name2ForWG(String wg) { + switch (wg) { + case "ti": return "Vocabulary"; + case "vocab": return "Vocabulary"; + } + return null; + } } 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 56b3cb217..d3c455f0d 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 @@ -999,6 +999,7 @@ public class I18nConstants { public static final String VALIDATION_HL7_WG_NEEDED = "VALIDATION_HL7_WG_NEEDED"; public static final String VALIDATION_HL7_WG_UNKNOWN = "VALIDATION_HL7_WG_UNKNOWN"; public static final String VALIDATION_HL7_PUBLISHER_MISMATCH = "VALIDATION_HL7_PUBLISHER_MISMATCH"; + public static final String VALIDATION_HL7_PUBLISHER_MISMATCH2 = "VALIDATION_HL7_PUBLISHER_MISMATCH2"; public static final String VALIDATION_HL7_WG_URL = "VALIDATION_HL7_WG_URL"; public static final String VALIDATION_HL7_PUBLISHER_MISSING = "VALIDATION_HL7_PUBLISHER_MISSING"; public static final String TYPE_SPECIFIC_CHECKS_DT_QTY_UCUM_ANNOTATIONS = "TYPE_SPECIFIC_CHECKS_DT_QTY_UCUM_ANNOTATIONS"; diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index 6563057ac..d4dd6e758 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -1060,6 +1060,7 @@ BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_NO_MATCH_REASON = The {1} resource did no VALIDATION_HL7_WG_NEEDED = When HL7 is publishing a resource, the owning committee must be stated using the {0} extension VALIDATION_HL7_WG_UNKNOWN = The nominated WG ''{0}'' is unknown VALIDATION_HL7_PUBLISHER_MISMATCH = The nominated WG ''{0}'' means that the publisher should be ''{1}'' but ''{2}'' was found +VALIDATION_HL7_PUBLISHER_MISMATCH2 = The nominated WG ''{0}'' means that the publisher should be either ''{1}''or ''{2}'' but ''{3}'' was found VALIDATION_HL7_WG_URL = The nominated WG ''{0}'' means that the contact url should be ''{1}'' but it was not found VALIDATION_HL7_PUBLISHER_MISSING = When HL7 is publishing a resource, the publisher must be provided, and for WG ''{0}'' it should be ''{1}'' TYPE_SPECIFIC_CHECKS_DT_QTY_UCUM_ANNOTATIONS_NO_UNIT = UCUM Codes that contain human readable annotations like {0} can be misleading (e.g. they are ignored when comparing units). Best Practice is not to depend on annotations in the UCUM code, so this usage should be checked, and the Quantity.unit SHOULD contain the annotation diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java index fb58c02ad..4a2aaed1a 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/InstanceValidator.java @@ -5578,7 +5578,13 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat if (rule(errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), wgd != null, I18nConstants.VALIDATION_HL7_WG_UNKNOWN, wg)) { String rpub = "HL7 International / "+wgd.getName(); if (warning(errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), pub != null, I18nConstants.VALIDATION_HL7_PUBLISHER_MISSING, wg, rpub)) { - warningOrError(pub.contains("/"), errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), rpub.equals(pub), I18nConstants.VALIDATION_HL7_PUBLISHER_MISMATCH, wg, rpub, pub); + boolean ok = rpub.equals(pub); + if (!ok && wgd.getName2() != null) { + ok = ("HL7 International / "+wgd.getName2()).equals(pub); + warningOrError(pub.contains("/"), errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), ok, I18nConstants.VALIDATION_HL7_PUBLISHER_MISMATCH2, wg, rpub, "HL7 International / "+wgd.getName2(), pub); + } else { + warningOrError(pub.contains("/"), errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), ok, I18nConstants.VALIDATION_HL7_PUBLISHER_MISMATCH, wg, rpub, pub); + } } warning(errors, "2023-09-15", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), Utilities.startsWithInList( wgd.getLink(), urls), I18nConstants.VALIDATION_HL7_WG_URL, wg, wgd.getLink());