Merge pull request #1563 from hapifhir/2024-02-gg-misc-fixes-1

2024 02 gg misc fixes 1
This commit is contained in:
Grahame Grieve 2024-02-11 22:45:09 +11:00 committed by GitHub
commit 1df3b9651b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
30 changed files with 2078 additions and 56 deletions

2
.gitignore vendored
View File

@ -307,3 +307,5 @@ local.properties
/org.hl7.fhir.r4b.new
org.hl7.fhir.r5/var/lib/.fhir/packages/
org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/DebugUtilities.java

View File

@ -190,7 +190,10 @@ public class R5ExtensionsLoader {
context.cacheResourceFromPackage(vs, vs.getSourcePackage());
for (ConceptSetComponent inc : vs.getCompose().getInclude()) {
for (CanonicalType t : inc.getValueSet()) {
loadValueSet(t.asStringValue(), context, valueSets, codeSystems);
ValueSet vsi = context.fetchResource(ValueSet.class, t.getValue());
if (vsi == null) {
loadValueSet(t.asStringValue(), context, valueSets, codeSystems);
}
}
if (inc.hasSystem()) {
if (!inc.hasVersion()) {

View File

@ -16,6 +16,7 @@ import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent
import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules;
import org.hl7.fhir.r5.model.OperationOutcome.IssueType;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionSnapshotComponent;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.utilities.Utilities;
@ -621,7 +622,12 @@ public class ProfilePathProcessor {
if (firstTypeStructureDefinition.getSnapshot().getElement().isEmpty()) {
throw new FHIRException(profileUtilities.getContext().formatMessage(I18nConstants.SNAPSHOT_IS_EMPTY, firstTypeStructureDefinition.getVersionedUrl(), "Source for first element"));
} else {
src = firstTypeStructureDefinition.getSnapshot().getElement().get(0);
src = firstTypeStructureDefinition.getSnapshot().getElement().get(0).copy();
if (!src.getPath().contains(".") && firstTypeStructureDefinition.getKind() == StructureDefinitionKind.RESOURCE) {
// we can't migrate the constraints in this case, because the sense of %resource changes when the root resource
// is treated as an element. The validator will enforce the constraint
src.getConstraint().clear(); //
}
}
}
template = src.copy().setPath(currentBase.getPath());

View File

@ -4605,6 +4605,11 @@ public boolean hasTarget() {
}
@Override
public String toString() {
return key + ":" + expression + (severity == null ? "("+severity.asStringValue()+")" : "");
}
}
@Block()
@ -13093,6 +13098,10 @@ If a pattern[x] is declared on a repeating element, the pattern applies to all r
return t;
}
public boolean repeats() {
return !Utilities.existsInList(getMax(), "0", "1");
}
// end addition
}

View File

@ -1499,6 +1499,34 @@ public class StructureDefinition extends CanonicalResource {
}
//added from java-adornments.txt:
public ElementDefinition getElementByPath(String path) {
if (path == null) {
return null;
}
for (ElementDefinition ed : getElement()) {
if (path.equals(ed.getPath()) || (path+"[x]").equals(ed.getPath())) {
return ed;
}
}
return null;
}
public ElementDefinition getElementById(String id) {
if (id == null) {
return null;
}
for (ElementDefinition ed : getElement()) {
if (id.equals(ed.getId())) {
return ed;
}
}
return null;
}
//end addition
}
/**

View File

@ -11,6 +11,7 @@ import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.comparison.VersionComparisonAnnotation;
import org.hl7.fhir.r5.model.BooleanType;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode;
import org.hl7.fhir.r5.model.CodeSystem.CodeSystemFilterComponent;
@ -539,8 +540,15 @@ public class CodeSystemRenderer extends TerminologyRenderer {
if (first) first = false; else td.addText(", ");
if (pcv.hasValueCoding()) {
td.addText(pcv.getValueCoding().getCode());
} else if (pcv.hasValueStringType() && Utilities.isAbsoluteUrlLinkable(pcv.getValue().primitiveValue())) {
td.ah(pcv.getValue().primitiveValue()).tx(pcv.getValue().primitiveValue());
} else if (pcv.hasValueStringType() && Utilities.isAbsoluteUrl(pcv.getValue().primitiveValue())) {
CanonicalResource cr = (CanonicalResource) context.getContext().fetchResource(Resource.class, pcv.getValue().primitiveValue());
if (cr != null) {
td.ah(cr.getWebPath(), cr.getVersionedUrl()).tx(cr.present());
} else if (Utilities.isAbsoluteUrlLinkable(pcv.getValue().primitiveValue())) {
td.ah(pcv.getValue().primitiveValue()).tx(pcv.getValue().primitiveValue());
} else {
td.code(pcv.getValue().primitiveValue());
}
} else if ("parent".equals(pcv.getCode())) {
td.ah("#"+cs.getId()+"-"+Utilities.nmtokenize(pcv.getValue().primitiveValue())).addText(pcv.getValue().primitiveValue());
} else {

View File

@ -92,6 +92,12 @@ public abstract class ResourceRenderer extends DataRenderer {
XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
boolean hasExtensions;
hasExtensions = render(x, r);
String an = r.fhirType()+"_"+r.getId();
if (context.isAddName()) {
if (!hasAnchorName(x, an)) {
injectAnchorName(x, an);
}
}
inject(r, x, hasExtensions ? NarrativeStatus.EXTENSIONS : NarrativeStatus.GENERATED);
}
@ -99,12 +105,53 @@ public abstract class ResourceRenderer extends DataRenderer {
assert r.getContext() == context;
XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
boolean hasExtensions = render(x, r);
String an = r.fhirType()+"_"+r.getId();
if (context.isAddName()) {
if (!hasAnchorName(x, an)) {
injectAnchorName(x, an);
}
}
if (r.hasNarrative()) {
r.injectNarrative(x, hasExtensions ? NarrativeStatus.EXTENSIONS : NarrativeStatus.GENERATED);
}
return x;
}
public XhtmlNode checkNarrative(ResourceWrapper r) throws IOException, FHIRException, EOperationOutcome {
assert r.getContext() == context;
XhtmlNode x = r.getNarrative();
String an = r.fhirType()+"_"+r.getId();
if (context.isAddName()) {
if (!hasAnchorName(x, an)) {
injectAnchorName(x, an);
}
}
return x;
}
private void injectAnchorName(XhtmlNode x, String an) {
XhtmlNode ip = x;
while (ip.hasChildren() && "div".equals(ip.getChildNodes().get(0).getName())) {
ip = ip.getChildNodes().get(0);
}
ip.addTag(0, "a").setAttribute("name", an).tx(" ");
}
protected boolean hasAnchorName(XhtmlNode x, String an) {
if ("a".equals(x.getName()) && an.equals(x.getAttribute("name"))) {
return true;
}
if (x.hasChildren()) {
for (XhtmlNode c : x.getChildNodes()) {
if (hasAnchorName(c, an)) {
return true;
}
}
}
return false;
}
public abstract boolean render(XhtmlNode x, Resource r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome;
public boolean render(XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome {
@ -438,7 +485,11 @@ public abstract class ResourceRenderer extends DataRenderer {
String bundleUrl = null;
Element br = bundleElement.getNamedChild("resource", false);
if (br.getChildValue("id") != null) {
bundleUrl = "#" + br.fhirType() + "_" + br.getChildValue("id");
if ("Bundle".equals(br.fhirType())) {
bundleUrl = "#";
} else {
bundleUrl = "#" + br.fhirType() + "_" + br.getChildValue("id");
}
} else {
bundleUrl = "#" +fullUrlToAnchor(bundleElement.getChildValue("fullUrl"));
}

View File

@ -792,8 +792,9 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
UnusedTracker used = new UnusedTracker();
String ref = defPath == null ? null : defPath + anchorPrefix + element.getId();
String sName = tail(element.getPath());
if (element.hasSliceName())
if (element.hasSliceName()) {
sName = sName +":"+element.getSliceName();
}
used.used = true;
if (logicalModel) {
if (element.hasRepresentation(PropertyRepresentation.XMLATTR)) {
@ -1331,10 +1332,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
String ref2 = null;
String fixedUrl = null;
if (ed != null) {
String p = ed.getWebPath();
if (p != null) {
ref = p.startsWith("http:") || context.getRules() == GenerationRules.IG_PUBLISHER ? p : Utilities.pathURL(corePath, p);
}
String p = ed.getWebPath();
fixedUrl = getFixedUrl(ed);
if (fixedUrl != null) {// if its null, we guess that it's not a profiled extension?
if (fixedUrl.equals(url))
@ -3315,6 +3313,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
if (!tl.contains(tc)) {
aliases.add(name.replace("[x]", Utilities.capitalize(tc)));
aliases.add(name+":"+name.replace("[x]", Utilities.capitalize(tc)));
aliases.add(name.replace("[x]", Utilities.capitalize(tc))+":"+name.replace("[x]", Utilities.capitalize(tc)));
tl.add(tc);
}
}
@ -3334,7 +3333,6 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
list.addAll(generated);
}
ElementDefinition ed = stack.get(stack.size()-1);
// now we have all the possible names, but some of them might be inappropriate if we've
// already generated a type slicer. On the other hand, if we've already done that, we're
// going to steal any type specific ones off it.

View File

@ -82,6 +82,8 @@ public class ValueSetRenderer extends TerminologyRenderer {
private static final int MAX_DESIGNATIONS_IN_LINE = 5;
private static final int MAX_BATCH_VALIDATION_SIZE = 1000;
private List<ConceptMapRenderInstructions> renderingMaps = new ArrayList<ConceptMapRenderInstructions>();
public boolean render(XhtmlNode x, Resource dr) throws FHIRFormatError, DefinitionException, IOException {
@ -1431,12 +1433,23 @@ public class ValueSetRenderer extends TerminologyRenderer {
}
}
if (!context.isNoSlowLookup() && !serverList.isEmpty()) {
getContext().getWorker().validateCodeBatch(getContext().getTerminologyServiceOptions(), serverList, null);
for (CodingValidationRequest vr : serverList) {
ConceptDefinitionComponent v = vr.getResult().asConceptDefinition();
if (v != null) {
results.put(vr.getCoding().getCode(), v);
try {
// todo: split this into 10k batches
int i = 0;
while (serverList.size() > i) {
int len = Integer.min(serverList.size(), MAX_BATCH_VALIDATION_SIZE);
List<CodingValidationRequest> list = serverList.subList(i, i+len);
i += len;
getContext().getWorker().validateCodeBatch(getContext().getTerminologyServiceOptions(), list, null);
for (CodingValidationRequest vr : list) {
ConceptDefinitionComponent v = vr.getResult().asConceptDefinition();
if (v != null) {
results.put(vr.getCoding().getCode(), v);
}
}
}
} catch (Exception e1) {
return null;
}
}
return results;

View File

@ -215,6 +215,8 @@ public class RenderingContext {
private Map<KnownLinkType, String> links = new HashMap<>();
private Map<String, String> namedLinks = new HashMap<>();
private boolean addName = false;
/**
*
* @param context - access to all related resources that might be needed
@ -728,5 +730,14 @@ public class RenderingContext {
this.fixedFormat = fixedFormat;
}
public boolean isAddName() {
return addName;
}
public RenderingContext setAddName(boolean addName) {
this.addName = addName;
return this;
}
}

View File

@ -1,7 +1,11 @@
package org.hl7.fhir.r5.terminologies;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hl7.fhir.r5.model.CanonicalType;
import org.hl7.fhir.r5.model.CodeSystem;
@ -10,6 +14,8 @@ import org.hl7.fhir.r5.model.ConceptMap;
import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent;
import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent;
import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent;
import org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship;
import org.hl7.fhir.r5.terminologies.ConceptMapUtilities.ConceptMapElementSorter;
import org.hl7.fhir.r5.model.Identifier;
import org.hl7.fhir.r5.model.Meta;
import org.hl7.fhir.r5.model.UriType;
@ -17,6 +23,40 @@ import org.hl7.fhir.r5.model.ValueSet;
public class ConceptMapUtilities {
public static class TranslatedCode {
private String code;
private ConceptMapRelationship relationship;
public TranslatedCode(String code, ConceptMapRelationship relationship) {
super();
this.code = code;
this.relationship = relationship;
}
public String getCode() {
return code;
}
public ConceptMapRelationship getRelationship() {
return relationship;
}
}
public static class ConceptMapElementSorter implements Comparator<SourceElementComponent> {
@Override
public int compare(SourceElementComponent o1, SourceElementComponent o2) {
return o1.getCode().compareTo(o2.getCode());
}
}
public static class ConceptMapTargetElementSorter implements Comparator<TargetElementComponent> {
@Override
public int compare(TargetElementComponent o1, TargetElementComponent o2) {
return o1.getCode().compareTo(o2.getCode());
}
}
public static boolean hasOID(ConceptMap cm) {
return getOID(cm) != null;
}
@ -85,4 +125,167 @@ public class ConceptMapUtilities {
return cm;
}
public static ConceptMap invert(ConceptMap src, String id, String url, String name, boolean collate) {
ConceptMap dst = src.copy();
dst.setId(id);
dst.setUrl(url);
dst.setName(name);
dst.getGroup().clear();
dst.setSourceScope(src.getTargetScope());
dst.setTargetScope(src.getSourceScope());
for (ConceptMapGroupComponent gs : src.getGroup()) {
ConceptMapGroupComponent gd = dst.addGroup();
gd.setTargetElement(gs.getSourceElement());
gd.setSourceElement(gs.getTargetElement());
Map<String, SourceElementComponent> dstMap = new HashMap<>();
for (SourceElementComponent es : gs.getElement()) {
for (TargetElementComponent ts : es.getTarget()) {
SourceElementComponent ed = collate ? dstMap.get(ts.getCode()) : null;
if (ed == null) {
ed = gd.addElement();
ed.setCodeElement(ts.getCodeElement());
if (collate) {
dstMap.put(ed.getCode(), ed);
}
}
TargetElementComponent td = ed.addTarget();
td.setCode(es.getCode());
td.setComment(ts.getComment());
td.setRelationship(invertRelationship(ts.getRelationship()));
}
}
}
return dst;
}
private static ConceptMapRelationship invertRelationship(ConceptMapRelationship relationship) {
if (relationship == null) {
return null;
}
switch (relationship) {
case EQUIVALENT:
return ConceptMapRelationship.EQUIVALENT;
case NOTRELATEDTO:
return ConceptMapRelationship.NOTRELATEDTO;
case NULL:
return ConceptMapRelationship.NULL;
case RELATEDTO:
return ConceptMapRelationship.RELATEDTO;
case SOURCEISBROADERTHANTARGET:
return ConceptMapRelationship.SOURCEISNARROWERTHANTARGET;
case SOURCEISNARROWERTHANTARGET:
return ConceptMapRelationship.SOURCEISBROADERTHANTARGET;
default:
return null;
}
}
public static ConceptMap collapse(String id, String url, boolean cumulative, ConceptMap src, ConceptMap... sequence) {
ConceptMap res = src.copy();
res.setId(id);
res.setUrl(url);
for (ConceptMap cm : sequence) {
if (res.hasTargetScope() && src.hasTargetScope()) {
if (!cm.getSourceScope().equals(cm.getTargetScope())) {
throw new Error("Mismatch between seqeuntial concept maps: ");
} else {
res.setTargetScope(cm.getTargetScope());
}
} else {
res.setTargetScope(null);
}
}
for (ConceptMapGroupComponent gd : res.getGroup()) {
for (ConceptMap cm : sequence) {
for (ConceptMapGroupComponent gt : cm.getGroup()) {
if (gt.getSource().equals(gd.getTarget())) {
gd.setTarget(gt.getTarget());
List<SourceElementComponent> processed = new ArrayList<ConceptMap.SourceElementComponent>();
for (SourceElementComponent ed : gd.getElement()) {
List<TargetElementComponent> list = new ArrayList<>();
list.addAll(ed.getTarget());
ed.getTarget().clear();
for (TargetElementComponent ts : list) {
for (SourceElementComponent et : gt.getElement()) {
if (et.getCode().equals(ed.getCode())) {
processed.add(et);
for (TargetElementComponent tt : et.getTarget()) {
ed.addTarget().setCode(tt.getCode()).setRelationship(combineRelationships(ts.getRelationship(), tt.getRelationship()));
}
}
}
}
if (ed.getTarget().isEmpty()) {
if (cumulative) {
ed.getTarget().addAll(list);
} else {
ed.setNoMap(true);
}
}
}
if (cumulative) {
for (SourceElementComponent et : gt.getElement()) {
if (!processed.contains(et)) {
gd.addElement(et.copy());
}
}
}
}
Collections.sort(gt.getElement(), new ConceptMapElementSorter());
for (SourceElementComponent e: gt.getElement()) {
Collections.sort(e.getTarget(), new ConceptMapTargetElementSorter());
}
}
}
}
return res;
}
public static ConceptMapRelationship combineRelationships(ConceptMapRelationship rel1, ConceptMapRelationship rel2) {
switch (rel1) {
case EQUIVALENT:
return rel2;
case NOTRELATEDTO:
return ConceptMapRelationship.NOTRELATEDTO;
case NULL:
return null;
case RELATEDTO:
return rel2;
case SOURCEISBROADERTHANTARGET:
switch (rel2) {
case EQUIVALENT:
return ConceptMapRelationship.SOURCEISBROADERTHANTARGET;
case NOTRELATEDTO:
return ConceptMapRelationship.NOTRELATEDTO;
case NULL:
return null;
case RELATEDTO:
return ConceptMapRelationship.RELATEDTO;
case SOURCEISBROADERTHANTARGET:
return ConceptMapRelationship.SOURCEISBROADERTHANTARGET;
case SOURCEISNARROWERTHANTARGET:
return ConceptMapRelationship.RELATEDTO;
}
case SOURCEISNARROWERTHANTARGET:
switch (rel2) {
case EQUIVALENT:
return ConceptMapRelationship.SOURCEISNARROWERTHANTARGET;
case NOTRELATEDTO:
return ConceptMapRelationship.NOTRELATEDTO;
case NULL:
return null;
case RELATEDTO:
return ConceptMapRelationship.RELATEDTO;
case SOURCEISBROADERTHANTARGET:
return ConceptMapRelationship.RELATEDTO;
case SOURCEISNARROWERTHANTARGET:
return ConceptMapRelationship.SOURCEISNARROWERTHANTARGET;
}
}
return null;
}
}

View File

@ -31,6 +31,8 @@ public class XVerExtensionManager {
public static final String XVER_EXT_MARKER = "XVER_EXT_MARKER";
public static final String XVER_VER_MARKER = "XVER_VER_MARKER";
private Map<String, JsonObject> lists = new HashMap<>();
private IWorkerContext context;
@ -92,6 +94,7 @@ public class XVerExtensionManager {
StructureDefinition sd = new StructureDefinition();
sd.setUserData(XVER_EXT_MARKER, "true");
sd.setUserData(XVER_VER_MARKER, verSource);
if (context.getResourceNamesAsSet().contains(r)) {
sd.setWebPath(Utilities.pathURL(context.getSpecUrl(), r.toLowerCase()+"-definitions.html#"+e));
} else {

View File

@ -161,4 +161,14 @@ public class CommaSeparatedStringBuilder {
}
return res;
}
public static String joinWrapped(String sep, String leftWrap, String rightWrap, Collection<String> list) {
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(sep);
for (String s : list) {
if (s != null) {
b.append(leftWrap+s+rightWrap);
}
}
return b.toString();
}
}

View File

@ -729,5 +729,15 @@ public class VersionUtilities {
}
}
public static boolean includedInRange(String startVer, String stopVer, String ver) {
if (ver.equals(startVer)) {
return true;
}
if (ver.equals(stopVer)) {
return true;
}
return startVer.compareTo(ver) < 0 && stopVer.compareTo(ver) > 0;
}
}

View File

@ -551,6 +551,8 @@ public class I18nConstants {
public static final String TYPE_SPECIFIC_CHECKS_DT_URI_UUID = "Type_Specific_Checks_DT_URI_UUID";
public static final String TYPE_SPECIFIC_CHECKS_DT_URI_WS = "Type_Specific_Checks_DT_URI_WS";
public static final String TYPE_SPECIFIC_CHECKS_DT_URL_RESOLVE = "Type_Specific_Checks_DT_URL_Resolve";
public static final String TYPE_SPECIFIC_CHECKS_DT_XHTML_RESOLVE = "Type_Specific_Checks_DT_XHTML_Resolve";
public static final String TYPE_SPECIFIC_CHECKS_DT_XHTML_RESOLVE_IMG = "Type_Specific_Checks_DT_XHTML_Resolve_Img";
public static final String TYPE_SPECIFIC_CHECKS_DT_URL_EXAMPLE = "TYPE_SPECIFIC_CHECKS_DT_URL_EXAMPLE";
public static final String TYPE_SPECIFIC_CHECKS_DT_CANONICAL_TYPE = "TYPE_SPECIFIC_CHECKS_DT_CANONICAL_TYPE";
public static final String TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE = "TYPE_SPECIFIC_CHECKS_DT_CANONICAL_RESOLVE";
@ -1048,6 +1050,13 @@ public class I18nConstants {
public static final String SD_CONTEXT_SHOULD_NOT_BE_FHIRPATH = "SD_CONTEXT_SHOULD_NOT_BE_FHIRPATH";
public static final String TX_GENERAL_CC_ERROR_MESSAGE = "TX_GENERAL_CC_ERROR_MESSAGE";
public static final String FHIRPATH_UNKNOWN_EXTENSION = "FHIRPATH_UNKNOWN_EXTENSION";
public static final String TYPE_SPECIFIC_CHECKS_DT_XHTML_MULTIPLE_MATCHES = "TYPE_SPECIFIC_CHECKS_DT_XHTML_MULTIPLE_MATCHES";
public static final String CONTAINED_ORPHAN_DOM3 = "CONTAINED_ORPHAN_DOM3";
public static final String VALUESET_INCLUDE_CS_NOT_CS = "VALUESET_INCLUDE_CS_NOT_CS";
public static final String VALUESET_INCLUDE_CS_NOT_FOUND = "VALUESET_INCLUDE_CS_NOT_FOUND";
public static final String VALUESET_INCLUDE_CSVER_NOT_FOUND = "VALUESET_INCLUDE_CSVER_NOT_FOUND";
public static final String VALUESET_INCLUDE_CS_MULTI_FOUND = "VALUESET_INCLUDE_CS_MULTI_FOUND";
public static final String VALUESET_INCLUDE_CSVER_MULTI_FOUND = "VALUESET_INCLUDE_CSVER_MULTI_FOUND";
}

View File

@ -976,5 +976,27 @@ public class XhtmlNode extends XhtmlFluent implements IBaseXhtml {
}
return btn;
}
public XhtmlNode head() {
return addTag("head");
}
public XhtmlNode body() {
return addTag("body");
}
public XhtmlNode title(String title) {
return addTag("title").tx(title);
}
public XhtmlNode link(String rel, String href) {
return addTag("link").attribute("rel", rel).attribute("href", href);
}
public void wbr() {
addTag("wbr");
}
}

View File

@ -917,10 +917,10 @@ CONCEPTMAP_GROUP_TARGET_MISSING = No Target Code System, so the target codes can
CONCEPTMAP_GROUP_TARGET_UNKNOWN = The Target Code System {0} is not fully defined and populated, and no targetScope is specified, so the target code checking will not be performed
CONCEPTMAP_GROUP_SOURCE_CODE_INVALID = The source code ''{0}'' is not valid in the code system {1}
CONCEPTMAP_GROUP_SOURCE_CODE_INVALID_VS = The source code ''{0}'' is not valid in the value set {1}
CONCEPTMAP_GROUP_SOURCE_DISPLAY_INVALID = The source display ''{0}'' is not valid. Possible codes {1}
CONCEPTMAP_GROUP_SOURCE_DISPLAY_INVALID = The source display ''{0}'' for the code ''{2}'' is not valid. Possible displays: {1}
CONCEPTMAP_GROUP_TARGET_CODE_INVALID = The target code ''{0}'' is not valid in the code system {1}
CONCEPTMAP_GROUP_TARGET_CODE_INVALID_VS = The target code ''{0}'' is not valid in the value set {1}
CONCEPTMAP_GROUP_TARGET_DISPLAY_INVALID = The target display ''{0}'' is not valid. Possible displays {1}
CONCEPTMAP_GROUP_TARGET_DISPLAY_INVALID = The target display ''{0}'' for the code ''{2}'' is not valid. Possible displays: {1}
CONCEPTMAP_GROUP_TARGET_PROPERTY_INVALID = The property code ''{0}'' is not known
CONCEPTMAP_GROUP_TARGET_PROPERTY_TYPE_MISMATCH = The type of this property should be {1} not {0}
CONCEPTMAP_GROUP_TARGET_PROPERTY_TYPE_NO_SYSTEM = Since no system has been provided, a plain code cannot be used
@ -1059,7 +1059,7 @@ CONCEPTMAP_VS_TOO_MANY_CODES = The concept map has too many codes to validate ({
CONCEPTMAP_VS_CONCEPT_CODE_UNKNOWN_SYSTEM = The code ''{1}'' comes from the system {0} which could not be found, so it''s not known whether it''s valid in the value set ''{2}''
CONCEPTMAP_VS_INVALID_CONCEPT_CODE = The code ''{1}'' in the system {0} is not valid in the value set ''{2}''
CONCEPTMAP_VS_INVALID_CONCEPT_CODE_VER = The code ''{2}'' in the system {0} version {1} is not valid in the value set ''{3}''
VALUESET_INC_TOO_MANY_CODES = The value set include has too many codes to validate ({0})
VALUESET_INC_TOO_MANY_CODES = The value set include has too many codes to validate ({0}), so each individual code has not been checked
BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_NO_MATCH = The {1} resource did not match any of the allowed profiles (Type {2}: {3})
BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_MULTIPLE_MATCHES = The {1} resource matched more than one of the allowed profiles ({3})
BUNDLE_BUNDLE_ENTRY_MULTIPLE_PROFILES_NO_MATCH_REASON = The {1} resource did not math the profile {2} because: {3}
@ -1106,3 +1106,12 @@ TX_GENERAL_CC_ERROR_MESSAGE = No valid coding was found for the value set ''{0}'
Validation_VAL_Profile_Minimum_SLICE_one = Slice ''{3}'': a matching slice is required, but not found (from {1}). Note that other slices are allowed in addition to this required slice
Validation_VAL_Profile_Minimum_SLICE_other = Slice ''{3}'': minimum required = {0}, but only found {7} (from {1})
FHIRPATH_UNKNOWN_EXTENSION = Reference to an unknown extension - double check that the URL ''{0}'' is correct
Type_Specific_Checks_DT_XHTML_Resolve = Hyperlink ''{0}'' at ''{1}'' for ''{2}''' does not resolve
Type_Specific_Checks_DT_XHTML_Resolve_Img = Image source ''{0}'' at ''{1}'' does not resolve
TYPE_SPECIFIC_CHECKS_DT_XHTML_MULTIPLE_MATCHES = Hyperlink ''{0}'' at ''{1}'' for ''{2}''' resolves to multiple targets
CONTAINED_ORPHAN_DOM3 = The contained resource ''{0}'' is not referenced to from elsewhere in the containing resource nor does it refer to the containing resource (dom-3)
VALUESET_INCLUDE_CS_NOT_CS = The include system ''{0}'' is a reference to a contained resource, but the contained resource with that id is not a CodeSystem, it's a {1}
VALUESET_INCLUDE_CS_NOT_FOUND = No matching contained code system found for system ''{0}''
VALUESET_INCLUDE_CSVER_NOT_FOUND = No matching contained code system found for system ''{0}'' version ''{1}''
VALUESET_INCLUDE_CS_MULTI_FOUND = Multiple matching contained code systems found for system ''{0}''
VALUESET_INCLUDE_CSVER_MULTI_FOUND = Multiple matching contained code systems found for system ''{0}'' version ''{1}''

View File

@ -85,7 +85,7 @@ import org.hl7.fhir.validation.instance.utils.NodeStack;
public class BaseValidator implements IValidationContextResourceLoader {
public class BooleanHolder {
public static class BooleanHolder {
private boolean value = true;
public BooleanHolder() {

View File

@ -494,18 +494,18 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
Element e = new ObjectConverter(context).convert((Resource) item);
setParents(e);
self.validateResource(new ValidationContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage), null,
mode);
mode, false);
} catch (IOException e1) {
throw new FHIRException(e1);
}
} else if (item instanceof Element) {
Element e = (Element) item;
if (e.getSpecial() == SpecialElement.CONTAINED) {
self.validateResource(new ValidationContext(ctxt.getAppContext(), e, ctxt.getRootResource(), ctxt.getGroupingResource()), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage), null, mode);
self.validateResource(new ValidationContext(ctxt.getAppContext(), e, ctxt.getRootResource(), ctxt.getGroupingResource()), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage), null, mode, false);
} else if (e.getSpecial() != null) {
self.validateResource(new ValidationContext(ctxt.getAppContext(), e, e, ctxt.getRootResource()), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage), null, mode);
self.validateResource(new ValidationContext(ctxt.getAppContext(), e, e, ctxt.getRootResource()), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage), null, mode, false);
} else {
self.validateResource(new ValidationContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage), null, mode);
self.validateResource(new ValidationContext(ctxt.getAppContext(), e), valerrors, e, e, sd, IdStatus.OPTIONAL, new NodeStack(context, null, e, validationLanguage), null, mode, false);
}
} else
throw new NotImplementedException(context.formatMessage(I18nConstants.NOT_DONE_YET_VALIDATORHOSTSERVICESCONFORMSTOPROFILE_WHEN_ITEM_IS_NOT_AN_ELEMENT));
@ -1002,7 +1002,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
long t = System.nanoTime();
NodeStack stack = new NodeStack(context, path, element, validationLanguage);
if (profiles == null || profiles.isEmpty()) {
validateResource(new ValidationContext(appContext, element), errors, element, element, null, resourceIdRule, stack.resetIds(), null, new ValidationMode(ValidationReason.Validation, ProfileSource.BaseDefinition));
validateResource(new ValidationContext(appContext, element), errors, element, element, null, resourceIdRule, stack.resetIds(), null, new ValidationMode(ValidationReason.Validation, ProfileSource.BaseDefinition), false);
} else {
int i = 0;
while (i < profiles.size()) {
@ -1020,7 +1020,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
i++;
}
for (StructureDefinition defn : profiles) {
validateResource(new ValidationContext(appContext, element), errors, element, element, defn, resourceIdRule, stack.resetIds(), null, new ValidationMode(ValidationReason.Validation, ProfileSource.ConfigProfile));
validateResource(new ValidationContext(appContext, element), errors, element, element, defn, resourceIdRule, stack.resetIds(), null, new ValidationMode(ValidationReason.Validation, ProfileSource.ConfigProfile), false);
}
}
if (hintAboutNonMustSupport) {
@ -2319,10 +2319,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (!ok) {
if (definition.hasUserData(XVerExtensionManager.XVER_EXT_MARKER)) {
warning(errors, NO_RULE_DATE, IssueType.STRUCTURE, container.line(), container.col(), stack.getLiteralPath(), false,
modifier ? I18nConstants.EXTENSION_EXTM_CONTEXT_WRONG_XVER : I18nConstants.EXTENSION_EXTP_CONTEXT_WRONG_XVER, extUrl, contexts.toString(), plist.toString());
modifier ? I18nConstants.EXTENSION_EXTM_CONTEXT_WRONG_XVER : I18nConstants.EXTENSION_EXTP_CONTEXT_WRONG_XVER, extUrl, contexts.toString(), plist.toString(), definition.getUserString(XVerExtensionManager.XVER_VER_MARKER));
} else {
rule(errors, NO_RULE_DATE, IssueType.STRUCTURE, container.line(), container.col(), stack.getLiteralPath(), false,
modifier ? I18nConstants.EXTENSION_EXTM_CONTEXT_WRONG : I18nConstants.EXTENSION_EXTP_CONTEXT_WRONG, extUrl, contexts.toString(), plist.toString());
modifier ? I18nConstants.EXTENSION_EXTM_CONTEXT_WRONG : I18nConstants.EXTENSION_EXTP_CONTEXT_WRONG, extUrl, contexts.toString(), plist.toString(), definition.getUserString(XVerExtensionManager.XVER_VER_MARKER));
}
return false;
} else {
@ -2399,7 +2399,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else if (sd.getType().equals(resource.fhirType())) {
List<ValidationMessage> valerrors = new ArrayList<ValidationMessage>();
ValidationMode mode = new ValidationMode(ValidationReason.Expression, ProfileSource.FromExpression);
validateResource(new ValidationContext(appContext, resource), valerrors, resource, resource, sd, IdStatus.OPTIONAL, new NodeStack(context, null, resource, validationLanguage), null, mode);
validateResource(new ValidationContext(appContext, resource), valerrors, resource, resource, sd, IdStatus.OPTIONAL, new NodeStack(context, null, resource, validationLanguage), null, mode, false);
boolean ok = true;
List<ValidationMessage> record = new ArrayList<>();
for (ValidationMessage v : valerrors) {
@ -2951,6 +2951,12 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
ok = checkInnerNames(errors, e, path, xhtml.getChildNodes(), false) && ok;
ok = checkUrls(errors, e, path, xhtml.getChildNodes()) && ok;
ok = checkIdRefs(errors, e, path, xhtml, resource) && ok;
if (true) {
ok = checkReferences(valContext, errors, e, path, "div", xhtml, resource) && ok;
}
if (true) {
ok = checkImageSources(valContext, errors, e, path, "div", xhtml, resource) && ok;
}
}
}
@ -3066,6 +3072,9 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
boolean ok = true;
// now, do we check the URI target?
if (fetcher != null && !type.equals("uuid")) {
if (url.startsWith("#")) {
valContext.getInternalRefs().add(url.substring(1));
}
boolean found;
try {
found = isDefinitionURL(url) || (allowExamples && (url.contains("example.org") || url.contains("acme.com")) || url.contains("acme.org")) /* || (url.startsWith("http://hl7.org/fhir/tools")) */ ||
@ -3319,6 +3328,99 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return ok;
}
private boolean checkReferences(ValidationContext valContext, List<ValidationMessage> errors, Element e, String path, String xpath, XhtmlNode node, Element resource) {
boolean ok = true;
if (node.getNodeType() == NodeType.Element & "a".equals(node.getName()) && node.getAttribute("href") != null) {
String href = node.getAttribute("href");
if (!Utilities.noString(href) && href.startsWith("#") && !href.equals("#")) {
String ref = href.substring(1);
valContext.getInternalRefs().add(ref);
int count = countTargetMatches(resource, ref, true);
if (count == 0) {
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_XHTML_RESOLVE, href, xpath, node.allText());
} else if (count > 1) {
warning(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_XHTML_MULTIPLE_MATCHES, href, xpath, node.allText());
}
} else {
// we can't validate at this point. Come back and revisit this some time in the future
}
}
if (node.hasChildren()) {
for (XhtmlNode child : node.getChildNodes()) {
checkReferences(valContext, errors, e, path, xpath+"/"+child.getName(), child, resource);
}
}
return ok;
}
protected int countTargetMatches(Element element, String fragment, boolean checkBundle) {
int count = 0;
if (fragment.equals(element.getIdBase())) {
count++;
}
if (element.getXhtml() != null) {
count = count + countTargetMatches(element.getXhtml(), fragment);
}
if (element.hasChildren()) {
for (Element child : element.getChildren()) {
count = count + countTargetMatches(child, fragment, false);
}
}
if (count == 0 && checkBundle) {
Element e = element.getParentForValidator();
while (e != null) {
if (e.fhirType().equals("Bundle")) {
return countTargetMatches(e, fragment, false);
}
e = e.getParentForValidator();
}
}
return count;
}
private int countTargetMatches(XhtmlNode node, String fragment) {
int count = 0;
if (fragment.equals(node.getAttribute("id"))) {
count++;
}
if ("a".equals(node.getName()) && fragment.equals(node.getAttribute("name"))) {
count++;
}
if (node.hasChildren()) {
for (XhtmlNode child : node.getChildNodes()) {
count = count + countTargetMatches(child, fragment);
}
}
return count;
}
private boolean checkImageSources(ValidationContext valContext, List<ValidationMessage> errors, Element e, String path, String xpath, XhtmlNode node, Element resource) {
boolean ok = true;
if (node.getNodeType() == NodeType.Element & "img".equals(node.getName()) && node.getAttribute("src") != null) {
String src = node.getAttribute("src");
if (src.startsWith("#")) {
String ref = src.substring(1);
valContext.getInternalRefs().add(ref);
int count = countFragmentMatches(resource, ref);
if (count == 0) {
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_XHTML_RESOLVE_IMG, src, xpath);
} else if (count > 1) {
rule(errors, NO_RULE_DATE, IssueType.INVALID, e.line(), e.col(), path, false, I18nConstants.TYPE_SPECIFIC_CHECKS_DT_XHTML_MULTIPLE_MATCHES, src, xpath);
}
} else {
// we can't validate at this point. Come back and revisit this some time in the future
}
}
if (node.hasChildren()) {
for (XhtmlNode child : node.getChildNodes()) {
checkImageSources(valContext, errors, e, path, path+"/"+child.getName(), child, resource);
}
}
return ok;
}
private boolean checkIdRefs(List<ValidationMessage> errors, Element e, String path, XhtmlNode node, Element resource) {
boolean ok = true;
if (node.getNodeType() == NodeType.Element && node.getAttribute("idref") != null) {
@ -3764,6 +3866,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
ok = bh.ok() && ok;
String refType;
if (ref.startsWith("#")) {
valContext.getInternalRefs().add(ref.substring(1));
refType = "contained";
} else {
if (we == null) {
@ -3889,7 +3992,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
for (StructureDefinition pr : profiles) {
List<ValidationMessage> profileErrors = new ArrayList<ValidationMessage>();
validateResource(we.valContext(valContext, pr), profileErrors, we.getResource(), we.getFocus(), pr,
IdStatus.OPTIONAL, we.getStack().resetIds(), pct, vmode.withReason(ValidationReason.MatchingSlice));
IdStatus.OPTIONAL, we.getStack().resetIds(), pct, vmode.withReason(ValidationReason.MatchingSlice), true);
if (!hasErrors(profileErrors)) {
goodCount++;
goodProfiles.put(pr, profileErrors);
@ -5829,7 +5932,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(),
profile != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOPROFILE_EXPL, special == null ? "??" : special.toHuman(), resourceName, typeForResource.getProfile().get(0).asStringValue())) {
trackUsage(profile, valContext, element);
ok = validateResource(hc, errors, resource, element, profile, idstatus, stack, pct, mode) && ok;
ok = validateResource(hc, errors, resource, element, profile, idstatus, stack, pct, mode, false) && ok;
} else {
ok = false;
}
@ -5841,7 +5944,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
trackUsage(profile, valContext, element);
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, element.line(), element.col(), stack.getLiteralPath(),
profile != null, I18nConstants.BUNDLE_BUNDLE_ENTRY_NOPROFILE_TYPE, special == null ? "??" : special.toHuman(), resourceName)) {
ok = validateResource(hc, errors, resource, element, profile, idstatus, stack, pct, mode) && ok;
ok = validateResource(hc, errors, resource, element, profile, idstatus, stack, pct, mode, false) && ok;
} else {
ok = false;
}
@ -5862,7 +5965,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
trackUsage(profile, valContext, element);
List<ValidationMessage> perrors = new ArrayList<>();
errorsList.add(perrors);
if (validateResource(hc, perrors, resource, element, profile, idstatus, stack, pct, mode)) {
if (validateResource(hc, perrors, resource, element, profile, idstatus, stack, pct, mode, false)) {
bm.append(u.asStringValue());
matched++;
}
@ -6942,6 +7045,11 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (debug) {
System.out.println("inv "+inv.getKey()+" on "+path+" in "+resource.fhirType()+" {{ "+inv.getExpression()+" }}"+time());
}
// we don't allow dom-3 to execute - it takes too long (and is wrong).
// instead, we enforce it in code
if ("dom-3".equals(inv.getKey())) {
return true;
}
ExpressionNode n = (ExpressionNode) inv.getUserData("validator.expression.cache");
if (n == null) {
long t = System.nanoTime();
@ -7014,7 +7122,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
* The actual base entry point for internal use (re-entrant)
*/
private boolean validateResource(ValidationContext valContext, List<ValidationMessage> errors, Element resource,
Element element, StructureDefinition defn, IdStatus idstatus, NodeStack stack, PercentageTracker pct, ValidationMode mode) throws FHIRException {
Element element, StructureDefinition defn, IdStatus idstatus, NodeStack stack, PercentageTracker pct, ValidationMode mode, boolean forReference) throws FHIRException {
boolean ok = true;
// check here if we call validation policy here, and then change it to the new interface
@ -7070,6 +7178,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} else {
ok = false;
}
if (!forReference) {
// last step: check that all contained resources are referenced or reference #
ok = checkContainedReferences(valContext, errors, element, stack) && ok;
}
}
if (testMode && ok == hasErrors(errors)) {
throw new Error("ok is wrong. ok = "+ok+", errors = "+errorIds(stack.getLiteralPath(), ok, errors));
@ -7077,6 +7189,58 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
return ok;
}
private boolean checkContainedReferences(ValidationContext valContext, List<ValidationMessage> errors, Element element, NodeStack stack) {
boolean ok = true;
Set<String> baseRefs = (Set<String>) element.getUserData(ValidationContext.INTERNAL_REFERENCES_NAME);
List<Element> containedList = element.getChildrenByName("contained");
if (!containedList.isEmpty()) {
boolean allDone = true;
for (Element contained : containedList) {
allDone = allDone && contained.hasUserData(ValidationContext.INTERNAL_REFERENCES_NAME);
}
if (allDone) {
// We collected all the internal references in sets on the resource and the contained resources
int i = 0;
for (Element contained : containedList) {
ok = checkContainedReferences(errors, stack, ok, baseRefs, containedList, i, contained);
i++;
}
}
}
return ok;
}
private boolean checkContainedReferences(List<ValidationMessage> errors, NodeStack stack, boolean ok,
Set<String> baseRefs, List<Element> containedList, int i, Element contained) {
NodeStack n = stack.push(contained, i, null, null);
boolean found = isReferencedFromBase(contained, baseRefs, containedList, new ArrayList<>());
ok = rule(errors, NO_RULE_DATE, IssueType.INVALID, n, found, I18nConstants.CONTAINED_ORPHAN_DOM3, contained.getIdBase()) && ok;
return ok;
}
private boolean isReferencedFromBase(Element contained, Set<String> baseRefs, List<Element> containedList, List<Element> ignoreList) {
String id = contained.getIdBase();
if (baseRefs.contains(id)) {
return true;
}
Set<String> irefs = (Set<String>) contained.getUserData(ValidationContext.INTERNAL_REFERENCES_NAME);
if (irefs.contains("")) {
return true;
}
for (Element c : containedList) {
if (c != contained && !ignoreList.contains(c)) { // ignore list is to prevent getting into an unterminated loop
Set<String> refs = (Set<String>) c.getUserData(ValidationContext.INTERNAL_REFERENCES_NAME);
List<Element> ignoreList2 = new ArrayList<Element>();
ignoreList.addAll(ignoreList);
ignoreList.add(c);
if (refs != null && refs.contains(id) && isReferencedFromBase(c, baseRefs, containedList, ignoreList2)) {
return true;
}
}
}
return false;
}
private boolean checkResourceName(StructureDefinition defn, String resourceName, FhirFormat format) {
if (resourceName.equals(defn.getType())) {
return true;

View File

@ -15,6 +15,7 @@ import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
import org.hl7.fhir.r5.terminologies.utilities.CodingValidationRequest;
import org.hl7.fhir.r5.terminologies.utilities.TerminologyServiceErrorClass;
import org.hl7.fhir.r5.terminologies.utilities.ValidationResult;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage;
@ -277,7 +278,7 @@ public class ConceptMapValidator extends BaseValidator {
if (warningOrError(ctxt.source.cs.getContent() == CodeSystemContentMode.COMPLETE, errors, "2023-03-05", IssueType.REQUIRED, code.line(), code.col(), cstack.getLiteralPath(), cd != null, I18nConstants.CONCEPTMAP_GROUP_SOURCE_CODE_INVALID, c, ctxt.source.cs.getVersionedUrl())) {
Element display = src.getNamedChild("display", false);
if (display != null) {
warning(errors, "2023-03-05", IssueType.REQUIRED, code.line(), code.col(), cstack.getLiteralPath(), CodeSystemUtilities.checkDisplay(ctxt.source.cs, cd, display.getValue()), I18nConstants.CONCEPTMAP_GROUP_SOURCE_DISPLAY_INVALID, display.getValue(), CodeSystemUtilities.getDisplays(ctxt.source.cs, cd));
warning(errors, "2023-03-05", IssueType.REQUIRED, code.line(), code.col(), cstack.getLiteralPath(), CodeSystemUtilities.checkDisplay(ctxt.source.cs, cd, display.getValue()), I18nConstants.CONCEPTMAP_GROUP_SOURCE_DISPLAY_INVALID, display.getValue(), CommaSeparatedStringBuilder.joinWrapped(", ", "'", "'", CodeSystemUtilities.getDisplays(ctxt.source.cs, cd)), ctxt.source.cs.getVersionedUrl()+"#"+cd.getCode());
}
if (ctxt.hasSourceVS() && ctxt.source != null) {
ValidationResult vr = context.validateCode(options.withCheckValueSetOnly().withNoServer(), ctxt.source.url, ctxt.source.version, c, null, ctxt.sourceScope.vs);
@ -314,7 +315,7 @@ public class ConceptMapValidator extends BaseValidator {
if (warningOrError(ctxt.target.cs.getContent() == CodeSystemContentMode.COMPLETE, errors, "2023-03-05", IssueType.REQUIRED, code.line(), code.col(), cstack.getLiteralPath(), cd != null, I18nConstants.CONCEPTMAP_GROUP_TARGET_CODE_INVALID, c, ctxt.target.cs.getVersionedUrl())) {
Element display = tgt.getNamedChild("display", false);
if (display != null) {
warning(errors, "2023-03-05", IssueType.REQUIRED, code.line(), code.col(), cstack.getLiteralPath(), CodeSystemUtilities.checkDisplay(ctxt.target.cs, cd, display.getValue()), I18nConstants.CONCEPTMAP_GROUP_TARGET_DISPLAY_INVALID, display.getValue(), CodeSystemUtilities.getDisplays(ctxt.target.cs, cd));
warning(errors, "2023-03-05", IssueType.REQUIRED, code.line(), code.col(), cstack.getLiteralPath(), CodeSystemUtilities.checkDisplay(ctxt.target.cs, cd, display.getValue()), I18nConstants.CONCEPTMAP_GROUP_TARGET_DISPLAY_INVALID, display.getValue(), CommaSeparatedStringBuilder.joinWrapped(", ", "'", "'", CodeSystemUtilities.getDisplays(ctxt.target.cs, cd)), ctxt.target.cs.getVersionedUrl()+"#"+cd.getCode());
}
if (ctxt.hasTargetVS() && ctxt.target != null) {
ValidationResult vr = context.validateCode(options.withCheckValueSetOnly().withNoServer(), ctxt.target.url, ctxt.target.version, c, null, ctxt.targetScope.vs);

View File

@ -60,7 +60,7 @@ public class ObservationValidator extends BaseValidator {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bodyweight", "Body weight", "LOINC", codes, pct, mode) && ok;
} else if (hasLoincCode(code, codes, "39156-5", "39156-5", "59574-4", "89270-3")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bmi", "Body mass index", "LOINC", codes, pct, mode) && ok;
} else if (hasLoincCode(code, codes, "85354-9", "96607-7", "35094-2", "8459-0", "85354-9", "76534-7", "96607-7", "55284-4", "8480-6")) {
} else if (hasLoincCode(code, codes, "85354-9", "35094-2", "8459-0", "85354-9", "76534-7", "55284-4", "8480-6")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bp", "Blood pressure systolic and diastolic", "LOINC", codes, pct, mode) && ok;
} else if (hasSctCode(code, codes, "46680005")) {
@ -81,7 +81,7 @@ public class ObservationValidator extends BaseValidator {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bodyweight", "Body weight", "SNOMED CT", codes, pct, mode) && ok;
} else if (hasSctCode(code, codes, "60621009")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bmi", "Body mass index", "SNOMED CT", codes, pct, mode) && ok;
} else if (hasSctCode(code, codes, "75367002", "251076008", "6797001", "163033001", "123820005", "163035008", "723232008", "386534000", "386536003", "271649006", "271649006", "271650006", "407556006", "407554009", "716579001", "399304008")) {
} else if (hasSctCode(code, codes, "75367002", "251076008", "163033001", "163035008", "386534000", "386536003", "271649006", "271649006", "271650006", "407556006", "407554009", "716579001", "399304008")) {
ok = checkObservationAgainstProfile(valContext,errors, element, stack, "http://hl7.org/fhir/StructureDefinition/bp", "Blood pressure systolic and diastolic", "SNOMED CT", codes, pct, mode) && ok;
}
}

View File

@ -62,7 +62,7 @@ public class ValueSetValidator extends BaseValidator {
List<Element> composes = vs.getChildrenByName("compose");
int cc = 0;
for (Element compose : composes) {
ok = validateValueSetCompose(errors, compose, stack.push(compose, composes.size() > 1 ? cc : -1, null, null), vs.getNamedChildValue("url", false), "retired".equals(vs.getNamedChildValue("url", false))) & ok;
ok = validateValueSetCompose(errors, compose, stack.push(compose, composes.size() > 1 ? cc : -1, null, null), vs.getNamedChildValue("url", false), "retired".equals(vs.getNamedChildValue("url", false)), vs) & ok;
cc++;
}
}
@ -98,24 +98,24 @@ public class ValueSetValidator extends BaseValidator {
}
private boolean validateValueSetCompose(List<ValidationMessage> errors, Element compose, NodeStack stack, String vsid, boolean retired) {
private boolean validateValueSetCompose(List<ValidationMessage> errors, Element compose, NodeStack stack, String vsid, boolean retired, Element vsSrc) {
boolean ok = true;
List<Element> includes = compose.getChildrenByName("include");
int ci = 0;
for (Element include : includes) {
ok = validateValueSetInclude(errors, include, stack.push(include, ci, null, null), vsid, retired) && ok;
ok = validateValueSetInclude(errors, include, stack.push(include, ci, null, null), vsid, retired, vsSrc) && ok;
ci++;
}
List<Element> excludes = compose.getChildrenByName("exclude");
int ce = 0;
for (Element exclude : excludes) {
ok = validateValueSetInclude(errors, exclude, stack.push(exclude, ce, null, null), vsid, retired) && ok;
ok = validateValueSetInclude(errors, exclude, stack.push(exclude, ce, null, null), vsid, retired, vsSrc) && ok;
ce++;
}
return ok;
}
private boolean validateValueSetInclude(List<ValidationMessage> errors, Element include, NodeStack stack, String vsid, boolean retired) {
private boolean validateValueSetInclude(List<ValidationMessage> errors, Element include, NodeStack stack, String vsid, boolean retired, Element vsSrc) {
boolean ok = true;
String system = include.getChildValue("system");
String version = include.getChildValue("version");
@ -141,6 +141,25 @@ public class ValueSetValidator extends BaseValidator {
if (valuesets.size() > 1) {
warning(errors, NO_RULE_DATE, IssueType.INFORMATIONAL, stack.getLiteralPath(), false, I18nConstants.VALUESET_IMPORT_UNION_INTERSECTION);
}
if (system != null && system.startsWith("#")) {
List<Element> cs = new ArrayList<>();
for (Element contained : vsSrc.getChildrenByName("contained")) {
if (("#"+contained.getIdBase()).equals(system)) {
if (rule(errors, "2024-02-10", IssueType.INVALID, stack.getLiteralPath(), "CodeSystem".equals(contained.fhirType()), I18nConstants.VALUESET_INCLUDE_CS_NOT_CS, system, contained.fhirType())) {
if (version == null || version.equals(contained.getChildValue("version"))) {
cs.add(contained);
}
} else {
ok = false;
}
}
}
if (cs.isEmpty()) {
ok = rule(errors, "2024-02-10", IssueType.INVALID, stack.getLiteralPath(), false, version == null ? I18nConstants.VALUESET_INCLUDE_CS_NOT_FOUND : I18nConstants.VALUESET_INCLUDE_CSVER_NOT_FOUND, system, version) && ok;
} else {
ok = rule(errors, "2024-02-10", IssueType.INVALID, stack.getLiteralPath(), cs.size() == 1, version == null ? I18nConstants.VALUESET_INCLUDE_CS_MULTI_FOUND : I18nConstants.VALUESET_INCLUDE_CSVER_MULTI_FOUND, system, version) && ok;
}
}
List<Element> concepts = include.getChildrenByName("concept");
List<Element> filters = include.getChildrenByName("filter");

View File

@ -9,10 +9,16 @@ public class FHIRPathExpressionFixer {
// this is a hack work around for past publication of wrong FHIRPath expressions
boolean r5 = VersionUtilities.isR5Ver(version);
// if (r5) {
// return expr;
// }
boolean r4 = VersionUtilities.isR4Ver(version) || VersionUtilities.isR4BVer(version);
// see https://chat.fhir.org/#narrow/stream/196008-ig-publishing-requirements/topic/Operation.20Definition.20Parameters.20table
if (r5 && "opd-3".equals(key)) {
return "targetProfile.exists() implies (type = 'Reference' or type = 'canonical' or type.memberOf('http://hl7.org/fhir/ValueSet/all-resource-types'))";
}
if (r4 && "opd-3".equals(key)) {
return "targetProfile.exists() implies (type = 'Reference' or type = 'canonical' or type.memberOf('http://hl7.org/fhir/ValueSet/resource-types'))";
}
if ("probability is decimal implies (probability as decimal) <= 100".equals(expr)) {
return "(probability.exists() and (probability is decimal)) implies ((probability as decimal) <= 100)";
}

View File

@ -1,8 +1,10 @@
package org.hl7.fhir.validation.instance.utils;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.elementmodel.Manager.FhirFormat;
@ -11,6 +13,8 @@ import org.hl7.fhir.utilities.validation.ValidationMessage;
public class ValidationContext {
public static final String INTERNAL_REFERENCES_NAME = "internal.references";
private Object appContext;
// the version we are currently validating for right now
@ -27,6 +31,7 @@ public class ValidationContext {
private boolean checkSpecials = true;
private Map<String, List<ValidationMessage>> sliceRecords;
private Set<String> internalRefs;
public ValidationContext(Object appContext) {
this.appContext = appContext;
@ -36,12 +41,22 @@ public class ValidationContext {
this.appContext = appContext;
this.resource = element;
this.rootResource = element;
this.internalRefs = setupInternalRefs(element);
check();
// no groupingResource (Bundle or Parameters)
dump("creating");
}
private Set<String> setupInternalRefs(Element element) {
Set<String> res = (Set<String>) element.getUserData(INTERNAL_REFERENCES_NAME);
if (res == null) {
res = new HashSet<String>();
element.setUserData(INTERNAL_REFERENCES_NAME, res);
}
return res;
}
private void check() {
if (!rootResource.hasParentForValidator()) {
throw new Error("No parent on root resource");
@ -52,6 +67,7 @@ public class ValidationContext {
this.appContext = appContext;
this.resource = element;
this.rootResource = root;
this.internalRefs = setupInternalRefs(element);
check();
// no groupingResource (Bundle or Parameters)
dump("creating");
@ -62,6 +78,7 @@ public class ValidationContext {
this.resource = element;
this.rootResource = root;
this.groupingResource = groupingResource;
this.internalRefs = setupInternalRefs(element);
check();
dump("creating");
}
@ -137,6 +154,7 @@ public class ValidationContext {
res.profile = profile;
res.groupingResource = groupingResource;
res.version = version;
res.internalRefs = setupInternalRefs(element);
res.dump("forContained");
return res;
}
@ -148,6 +166,7 @@ public class ValidationContext {
res.profile = profile;
res.groupingResource = groupingResource;
res.version = version;
res.internalRefs = setupInternalRefs(element);
res.dump("forEntry");
return res;
}
@ -159,6 +178,7 @@ public class ValidationContext {
res.profile = profile;
res.version = version;
res.groupingResource = groupingResource;
res.internalRefs = internalRefs;
res.sliceRecords = sliceRecords != null ? sliceRecords : new HashMap<String, List<ValidationMessage>>();
res.dump("forProfile "+profile.getUrl());
return res;
@ -171,6 +191,7 @@ public class ValidationContext {
res.profile = profile;
res.groupingResource = groupingResource;
res.checkSpecials = false;
res.internalRefs = setupInternalRefs(resource);
res.dump("forLocalReference "+profile.getUrl());
res.version = version;
return res;
@ -191,6 +212,7 @@ public class ValidationContext {
res.groupingResource = null;
res.checkSpecials = false;
res.version = version;
res.internalRefs = setupInternalRefs(resource);
res.dump("forRemoteReference "+profile.getUrl());
return res;
}
@ -203,6 +225,7 @@ public class ValidationContext {
res.profile = profile;
res.checkSpecials = false;
res.version = version;
res.internalRefs = internalRefs;
res.sliceRecords = new HashMap<String, List<ValidationMessage>>();
res.dump("forSlicing");
return res;
@ -217,5 +240,9 @@ public class ValidationContext {
return this;
}
public Set<String> getInternalRefs() {
return internalRefs;
}
}

View File

@ -6562,7 +6562,6 @@ v: {
"error" : "The provided code 'http://loinc.org#5792-7' was not found in the value set 'http://hl7.org/fhir/ValueSet/birthDate'",
"class" : "UNKNOWN",
"server" : "http://tx-dev.fhir.org/r4",
"unknown-systems" : "",
"issues" : {
"resourceType" : "OperationOutcome",
"issue" : [{
@ -6586,3 +6585,68 @@ v: {
}
-------------------------------------------------------------------------------------
{"code" : {
"system" : "http://loinc.org",
"code" : "76534-7",
"display" : "Systolic blood pressure by Noninvasive"
}, "valueSet" :null, "langs":"", "useServer":"true", "useClient":"false", "guessSystem":"false", "activeOnly":"false", "membershipOnly":"false", "displayWarningMode":"false", "versionFlexible":"false", "profile": {
"resourceType" : "Parameters",
"parameter" : [{
"name" : "profile-url",
"valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891"
}]
}}####
v: {
"display" : "Systolic blood pressure by Noninvasive",
"code" : "76534-7",
"system" : "http://loinc.org",
"version" : "2.74",
"server" : "http://tx-dev.fhir.org/r4",
"unknown-systems" : "",
"issues" : {
"resourceType" : "OperationOutcome"
}
}
-------------------------------------------------------------------------------------
{"code" : {
"system" : "http://loinc.org",
"code" : "76534-7"
}, "url": "http://hl7.org/fhir/ValueSet/observation-vitalsignresult--0", "version": "4.0.1", "langs":"", "useServer":"true", "useClient":"false", "guessSystem":"false", "activeOnly":"false", "membershipOnly":"false", "displayWarningMode":"false", "versionFlexible":"false", "profile": {
"resourceType" : "Parameters",
"parameter" : [{
"name" : "profile-url",
"valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891"
}]
}}####
v: {
"display" : "Systolic blood pressure by Noninvasive",
"code" : "76534-7",
"severity" : "error",
"error" : "The provided code 'http://loinc.org#76534-7' was not found in the value set 'http://hl7.org/fhir/ValueSet/observation-vitalsignresult--0|4.0.1'",
"class" : "UNKNOWN",
"server" : "http://tx-dev.fhir.org/r4",
"unknown-systems" : "",
"issues" : {
"resourceType" : "OperationOutcome",
"issue" : [{
"extension" : [{
"url" : "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-server",
"valueUrl" : "http://tx-dev.fhir.org/r4"
}],
"severity" : "error",
"code" : "code-invalid",
"details" : {
"coding" : [{
"system" : "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code" : "not-in-vs"
}],
"text" : "The provided code 'http://loinc.org#76534-7' was not found in the value set 'http://hl7.org/fhir/ValueSet/observation-vitalsignresult--0|4.0.1'"
},
"location" : ["Coding.code"],
"expression" : ["Coding.code"]
}]
}
}
-------------------------------------------------------------------------------------

View File

@ -0,0 +1,42 @@
-------------------------------------------------------------------------------------
{"code" : {
"system" : "http://hl7.org/fhir/smart-app-launch/CodeSystem/user-access-category",
"code" : "laboratory",
"display" : "Laboratory"
}, "valueSet" :null, "langs":"", "useServer":"true", "useClient":"false", "guessSystem":"false", "activeOnly":"false", "membershipOnly":"false", "displayWarningMode":"false", "versionFlexible":"false", "profile": {
"resourceType" : "Parameters",
"parameter" : [{
"name" : "profile-url",
"valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891"
}]
}}####
v: {
"code" : "laboratory",
"severity" : "error",
"error" : "A definition for CodeSystem 'http://hl7.org/fhir/smart-app-launch/CodeSystem/user-access-category' could not be found, so the code cannot be validated",
"class" : "CODESYSTEM_UNSUPPORTED",
"server" : "http://tx-dev.fhir.org/r4",
"unknown-systems" : "http://hl7.org/fhir/smart-app-launch/CodeSystem/user-access-category",
"issues" : {
"resourceType" : "OperationOutcome",
"issue" : [{
"extension" : [{
"url" : "http://hl7.org/fhir/StructureDefinition/operationoutcome-issue-server",
"valueUrl" : "http://tx-dev.fhir.org/r4"
}],
"severity" : "error",
"code" : "not-found",
"details" : {
"coding" : [{
"system" : "http://hl7.org/fhir/tools/CodeSystem/tx-issue-type",
"code" : "not-found"
}],
"text" : "A definition for CodeSystem 'http://hl7.org/fhir/smart-app-launch/CodeSystem/user-access-category' could not be found, so the code cannot be validated"
},
"location" : ["Coding.system"],
"expression" : ["Coding.system"]
}]
}
}
-------------------------------------------------------------------------------------

View File

@ -8,6 +8,7 @@
"server" : "https://tx.ontoserver.csiro.au/fhir",
"filename" : "vs-2a3253d9-d12e-45f3-877e-0dc2654b3904.json"
},
"http://hl7.org/fhir/smart-app-launch/ValueSet/user-access-category" : null,
"http://fhir.ch/ig/ch-ig/ValueSet/OrganizationType" : null,
"http://loinc.org/vs/LL4048-6" : null,
"https://fhir.kbv.de/ValueSet/KBV_VS_SFHIR_ICD_SEITENLOKALISATION" : null

View File

@ -214,7 +214,6 @@ v: {
"code" : "text/plain",
"system" : "urn:ietf:bcp:13",
"server" : "http://tx-dev.fhir.org/r4",
"unknown-systems" : "",
"issues" : {
"resourceType" : "OperationOutcome"
}
@ -239,7 +238,6 @@ v: {
"error" : "The provided code 'http://snomed.info/sct#271649006 ('Systolic blood pressure')' was not found in the value set 'http://hl7.org/fhir/ValueSet/observation-vitalsignresult|5.0.0'",
"class" : "UNKNOWN",
"server" : "http://tx-dev.fhir.org/r4",
"unknown-systems" : "",
"issues" : {
"resourceType" : "OperationOutcome",
"issue" : [{
@ -277,7 +275,6 @@ v: {
"code" : "json",
"system" : "urn:ietf:bcp:13",
"server" : "http://tx-dev.fhir.org/r4",
"unknown-systems" : "",
"issues" : {
"resourceType" : "OperationOutcome"
}
@ -299,7 +296,6 @@ v: {
"code" : "001",
"system" : "http://unstats.un.org/unsd/methods/m49/m49.htm",
"server" : "http://tx-dev.fhir.org/r4",
"unknown-systems" : "",
"issues" : {
"resourceType" : "OperationOutcome",
"issue" : [{
@ -335,7 +331,6 @@ v: {
"code" : "nl-NL",
"system" : "urn:ietf:bcp:47",
"server" : "http://tx-dev.fhir.org/r4",
"unknown-systems" : "",
"issues" : {
"resourceType" : "OperationOutcome"
}
@ -356,7 +351,6 @@ v: {
"code" : "en-AU",
"system" : "urn:ietf:bcp:47",
"server" : "http://tx-dev.fhir.org/r4",
"unknown-systems" : "",
"issues" : {
"resourceType" : "OperationOutcome"
}
@ -377,7 +371,6 @@ v: {
"code" : "en",
"system" : "urn:ietf:bcp:47",
"server" : "http://tx-dev.fhir.org/r4",
"unknown-systems" : "",
"issues" : {
"resourceType" : "OperationOutcome"
}
@ -400,7 +393,6 @@ v: {
"code" : "001",
"system" : "http://unstats.un.org/unsd/methods/m49/m49.htm",
"server" : "http://tx-dev.fhir.org/r4",
"unknown-systems" : "",
"issues" : {
"resourceType" : "OperationOutcome",
"issue" : [{
@ -422,3 +414,24 @@ v: {
}
-------------------------------------------------------------------------------------
{"code" : {
"code" : "image/gif"
}, "url": "http://hl7.org/fhir/ValueSet/mimetypes", "version": "5.0.0", "langs":"", "useServer":"true", "useClient":"true", "guessSystem":"true", "activeOnly":"false", "membershipOnly":"false", "displayWarningMode":"false", "versionFlexible":"false", "profile": {
"resourceType" : "Parameters",
"parameter" : [{
"name" : "profile-url",
"valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891"
}]
}}####
v: {
"display" : "image/gif",
"code" : "image/gif",
"system" : "urn:ietf:bcp:13",
"server" : "http://tx-dev.fhir.org/r4",
"unknown-systems" : "",
"issues" : {
"resourceType" : "OperationOutcome"
}
}
-------------------------------------------------------------------------------------

View File

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