Merge pull request #1271 from hapifhir/gg-202304-more-snapshot

Gg 202304 more snapshot
This commit is contained in:
Grahame Grieve 2023-05-24 08:44:27 +10:00 committed by GitHub
commit 8f3367772c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 198 additions and 49 deletions

View File

@ -2,11 +2,16 @@
* Snapshot Generation Changes: * Snapshot Generation Changes:
** Check for slicenames without any slicing ** Check for slicenames without any slicing
** Check that slice names are unique
** Check for additional slicing rules in a set of slices ** Check for additional slicing rules in a set of slices
** Check that the minimum cardinality of a set of slices is correct ** Check that the minimum cardinality of a set of slices is correct
* Clean up duplicate errors when dates/dateTimes/instants have invalid formats * Clean up duplicate errors when dates/dateTimes/instants have invalid formats
* Handle unknown code systems consistently when validating coded elements * Handle unknown code systems consistently when validating coded elements
* Handle sub-slicing case where slice matches both the slice definition and the sub-slice definition
## Other code changes ## Other code changes
* Add support for R4B to loader * Add support for R4B to loader
* Change type if cache-id parameter

View File

@ -174,17 +174,13 @@ public class ProfilePathProcessor {
// in the simple case, source is not sliced. // in the simple case, source is not sliced.
if (!currentBase.hasSlicing() || currentBasePath.equals(getSlicing().getPath())) if (!currentBase.hasSlicing() || currentBasePath.equals(getSlicing().getPath()))
{ {
ElementDefinition currentRes = processSimplePath(currentBase, currentBasePath, diffMatches, typeList, ElementDefinition currentRes = processSimplePath(currentBase, currentBasePath, diffMatches, typeList, cursors);
cursors
);
if (res == null) { if (res == null) {
res = currentRes; res = currentRes;
} }
} }
else { else {
processPathWithSlicedBase(currentBase, currentBasePath, diffMatches, typeList, processPathWithSlicedBase(currentBase, currentBasePath, diffMatches, typeList, cursors);
cursors
);
} }
} }

View File

@ -135,6 +135,7 @@ public class ProfileUtilities extends TranslatingUtilities {
public class ElementDefinitionCounter { public class ElementDefinitionCounter {
int count = 0; int count = 0;
ElementDefinition focus; ElementDefinition focus;
Set<String> names = new HashSet<>();
public ElementDefinitionCounter(ElementDefinition ed) { public ElementDefinitionCounter(ElementDefinition ed) {
focus = ed; focus = ed;
@ -142,15 +143,16 @@ public class ProfileUtilities extends TranslatingUtilities {
public int update() { public int update() {
if (count > focus.getMin()) { if (count > focus.getMin()) {
int was = focus.getMin(); return count;
focus.setMin(count);
return was;
} }
return -1; return -1;
} }
public void count(ElementDefinition ed) { public boolean count(ElementDefinition ed, String name) {
count = count + ed.getMin(); count = count + ed.getMin();
boolean ok = !names.contains(name);
names.add(name);
return ok;
} }
public ElementDefinition getFocus() { public ElementDefinition getFocus() {
@ -725,13 +727,13 @@ public class ProfileUtilities extends TranslatingUtilities {
if (tn.contains("/")) { if (tn.contains("/")) {
tn = tn.substring(tn.lastIndexOf("/")+1); tn = tn.substring(tn.lastIndexOf("/")+1);
} }
System.out.println("Check slicing for "+derived.getVersionedUrl()); // System.out.println("Check slicing for "+derived.getVersionedUrl());
Map<String, ElementDefinitionCounter> slices = new HashMap<>(); Map<String, ElementDefinitionCounter> slices = new HashMap<>();
int i = 0; int i = 0;
for (ElementDefinition ed : derived.getSnapshot().getElement()) { for (ElementDefinition ed : derived.getSnapshot().getElement()) {
if (ed.hasSlicing()) { if (ed.hasSlicing()) {
slices.put(ed.getPath(), new ElementDefinitionCounter(ed)); slices.put(ed.getPath(), new ElementDefinitionCounter(ed));
System.out.println("Entering slicing for "+ed.getPath()+" ["+i+"]"); // System.out.println("Entering slicing for "+ed.getPath()+" ["+i+"]");
} else { } else {
Set<String> toRemove = new HashSet<>(); Set<String> toRemove = new HashSet<>();
for (String s : slices.keySet()) { for (String s : slices.keySet()) {
@ -740,13 +742,14 @@ public class ProfileUtilities extends TranslatingUtilities {
} }
} }
for (String s : toRemove) { for (String s : toRemove) {
int was = slices.get(s).update(); int count = slices.get(s).update();
if (was > -1) { if (count > -1) {
String msg = "The slice definition for "+slices.get(s).getFocus().getId()+" had a minimum of "+was+" but the slices added up to a minimum of "+slices.get(s).getFocus().getMin()+" so the value has been adjusted in the snapshot"; String msg = "The slice definition for "+slices.get(s).getFocus().getId()+" has a minimum of "+slices.get(s).getFocus().getMin()+" but the slices add up to a minimum of "+count;
System.out.println(msg); //+" so the value has been adjusted in the snapshot"; we don't adjust it because of downstream effects. But if it's for publication, they better get it right.
// System.out.println(msg);
messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), msg, forPublication ? ValidationMessage.IssueSeverity.ERROR : ValidationMessage.IssueSeverity.INFORMATION)); messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), msg, forPublication ? ValidationMessage.IssueSeverity.ERROR : ValidationMessage.IssueSeverity.INFORMATION));
} }
System.out.println("Exiting slicing for "+s+" at "+ed.getPath()+" ["+i+"]"); // System.out.println("Exiting slicing for "+s+" at "+ed.getPath()+" ["+i+"]");
slices.remove(s); slices.remove(s);
} }
} }
@ -758,7 +761,10 @@ public class ProfileUtilities extends TranslatingUtilities {
messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), msg, ValidationMessage.IssueSeverity.ERROR)); messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), msg, ValidationMessage.IssueSeverity.ERROR));
} }
if (ed.hasSliceName() && slices.containsKey(ed.getPath())) { if (ed.hasSliceName() && slices.containsKey(ed.getPath())) {
slices.get(ed.getPath()).count(ed); if (!slices.get(ed.getPath()).count(ed, ed.getSliceName())) {
String msg = "Duplicate slice name "+ed.getSliceName()+" on "+ed.getId()+" (["+i+"])";
messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), msg, ValidationMessage.IssueSeverity.ERROR));
}
} }
i++; i++;
} }
@ -2043,10 +2049,15 @@ public class ProfileUtilities extends TranslatingUtilities {
} }
// Before applying changes, apply them to what's in the profile // Before applying changes, apply them to what's in the profile
StructureDefinition profile = null; StructureDefinition profile = null;
if (base.hasSliceName()) if (base.hasSliceName()) {
profile = base.getType().size() == 1 && base.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, base.getTypeFirstRep().getProfile().get(0).getValue(), srcSD) : null; profile = base.getType().size() == 1 && base.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, base.getTypeFirstRep().getProfile().get(0).getValue(), srcSD) : null;
if (profile==null) }
if (profile==null) {
profile = source.getType().size() == 1 && source.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, source.getTypeFirstRep().getProfile().get(0).getValue(), derivedSrc) : null; profile = source.getType().size() == 1 && source.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, source.getTypeFirstRep().getProfile().get(0).getValue(), derivedSrc) : null;
if (profile != null && !"Extension".equals(profile.getType())) {
profile = null;
}
}
if (profile != null) { if (profile != null) {
ElementDefinition e = profile.getSnapshot().getElement().get(0); ElementDefinition e = profile.getSnapshot().getElement().get(0);
String webroot = profile.getUserString("webroot"); String webroot = profile.getUserString("webroot");
@ -2157,7 +2168,7 @@ public class ProfileUtilities extends TranslatingUtilities {
if (derived.hasMinElement()) { if (derived.hasMinElement()) {
if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) { if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) {
if (derived.getMin() < base.getMin() && !derived.hasSliceName()) // in a slice, minimum cardinality rules do not apply if (derived.getMin() < base.getMin() && !derived.hasSliceName()) // in a slice, minimum cardinality rules do not apply
messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived min ("+Integer.toString(derived.getMin())+") cannot be less than base min ("+Integer.toString(base.getMin())+")", ValidationMessage.IssueSeverity.ERROR)); messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived min ("+Integer.toString(derived.getMin())+") cannot be less than the base min ("+Integer.toString(base.getMin())+") in "+srcSD.getVersionedUrl(), ValidationMessage.IssueSeverity.ERROR));
base.setMinElement(derived.getMinElement().copy()); base.setMinElement(derived.getMinElement().copy());
} else if (trimDifferential) } else if (trimDifferential)
derived.setMinElement(null); derived.setMinElement(null);
@ -2168,7 +2179,7 @@ public class ProfileUtilities extends TranslatingUtilities {
if (derived.hasMaxElement()) { if (derived.hasMaxElement()) {
if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) { if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) {
if (isLargerMax(derived.getMax(), base.getMax())) if (isLargerMax(derived.getMax(), base.getMax()))
messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived max ("+derived.getMax()+") cannot be greater than base max ("+base.getMax()+")", ValidationMessage.IssueSeverity.ERROR)); messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived max ("+derived.getMax()+") cannot be greater than the base max ("+base.getMax()+")", ValidationMessage.IssueSeverity.ERROR));
base.setMaxElement(derived.getMaxElement().copy()); base.setMaxElement(derived.getMaxElement().copy());
} else if (trimDifferential) } else if (trimDifferential)
derived.setMaxElement(null); derived.setMaxElement(null);

View File

@ -73,6 +73,7 @@ import org.hl7.fhir.r5.model.DomainResource;
import org.hl7.fhir.r5.model.ElementDefinition; import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent; import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
import org.hl7.fhir.r5.model.IdType;
import org.hl7.fhir.r5.model.ImplementationGuide; import org.hl7.fhir.r5.model.ImplementationGuide;
import org.hl7.fhir.r5.model.Library; import org.hl7.fhir.r5.model.Library;
import org.hl7.fhir.r5.model.Measure; import org.hl7.fhir.r5.model.Measure;
@ -868,7 +869,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
// if that failed, we try to expand on the server // if that failed, we try to expand on the server
if (addDependentResources(p, vs)) { if (addDependentResources(p, vs)) {
p.addParameter().setName("cache-id").setValue(new StringType(tcc.getCacheId())); p.addParameter().setName("cache-id").setValue(new IdType(tcc.getCacheId()));
} }
if (noTerminologyServer) { if (noTerminologyServer) {
@ -1147,7 +1148,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
boolean cached = addDependentResources(p, vs); boolean cached = addDependentResources(p, vs);
if (cached) { if (cached) {
p.addParameter().setName("cache-id").setValue(new StringType(tcc.getCacheId())); p.addParameter().setName("cache-id").setValue(new IdType(tcc.getCacheId()));
} }
return p; return p;
} }
@ -1276,7 +1277,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
addDependentResources(pin, vs); addDependentResources(pin, vs);
} }
if (cache) { if (cache) {
pin.addParameter().setName("cache-id").setValue(new StringType(tcc.getCacheId())); pin.addParameter().setName("cache-id").setValue(new IdType(tcc.getCacheId()));
} }
for (ParametersParameterComponent pp : pin.getParameter()) { for (ParametersParameterComponent pp : pin.getParameter()) {
if (pp.getName().equals("profile")) { if (pp.getName().equals("profile")) {
@ -1917,6 +1918,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
private Set<String> notCanonical = new HashSet<String>(); private Set<String> notCanonical = new HashSet<String>();
protected IWorkerContextManager.IPackageLoadingTracker packageTracker; protected IWorkerContextManager.IPackageLoadingTracker packageTracker;
private boolean forPublication;
@Override @Override
public Resource fetchResourceById(String type, String uri) { public Resource fetchResourceById(String type, String uri) {
@ -2467,4 +2469,13 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
// TODO Auto-generated method stub // TODO Auto-generated method stub
return new PEBuilder(this, elementProps, fixedProps); return new PEBuilder(this, elementProps, fixedProps);
} }
public boolean isForPublication() {
return forPublication;
}
public void setForPublication(boolean value) {
forPublication = value;
}
} }

View File

@ -274,6 +274,7 @@ public class ContextUtilities implements ProfileKnowledgeProvider {
ProfileUtilities pu = new ProfileUtilities(context, msgs, this); ProfileUtilities pu = new ProfileUtilities(context, msgs, this);
pu.setAutoFixSliceNames(true); pu.setAutoFixSliceNames(true);
pu.setThrowException(false); pu.setThrowException(false);
pu.setForPublication(context.isForPublication());
if (xverManager == null) { if (xverManager == null) {
xverManager = new XVerExtensionManager(context); xverManager = new XVerExtensionManager(context);
} }
@ -282,8 +283,9 @@ public class ContextUtilities implements ProfileKnowledgeProvider {
pu.sortDifferential(sd, p, p.getUrl(), errors, true); pu.sortDifferential(sd, p, p.getUrl(), errors, true);
} }
pu.setDebug(false); pu.setDebug(false);
for (String err : errors) for (String err : errors) {
msgs.add(new ValidationMessage(Source.ProfileValidator, IssueType.EXCEPTION, p.getWebPath(), "Error sorting Differential: "+err, ValidationMessage.IssueSeverity.ERROR)); msgs.add(new ValidationMessage(Source.ProfileValidator, IssueType.EXCEPTION, p.getWebPath(), "Error sorting Differential: "+err, ValidationMessage.IssueSeverity.ERROR));
}
pu.generateSnapshot(sd, p, p.getUrl(), sd.getUserString("webroot"), p.getName()); pu.generateSnapshot(sd, p, p.getUrl(), sd.getUserString("webroot"), p.getName());
for (ValidationMessage msg : msgs) { for (ValidationMessage msg : msgs) {
if ((!ignoreProfileErrors && msg.getLevel() == ValidationMessage.IssueSeverity.ERROR) || msg.getLevel() == ValidationMessage.IssueSeverity.FATAL) if ((!ignoreProfileErrors && msg.getLevel() == ValidationMessage.IssueSeverity.ERROR) || msg.getLevel() == ValidationMessage.IssueSeverity.FATAL)

View File

@ -941,4 +941,6 @@ public interface IWorkerContext {
public PEBuilder getProfiledElementBuilder(PEElementPropertiesPolicy elementProps, boolean fixedProps); public PEBuilder getProfiledElementBuilder(PEElementPropertiesPolicy elementProps, boolean fixedProps);
public boolean isForPublication();
public void setForPublication(boolean value);
} }

View File

@ -544,7 +544,7 @@ public class Coding extends DataType implements IBaseCoding, ICompositeType, ICo
base = base+"|"+getVersion(); base = base+"|"+getVersion();
base = base + "#"+getCode(); base = base + "#"+getCode();
if (hasDisplay()) if (hasDisplay())
base = base+": "+getDisplay(); base = base+": '"+getDisplay()+"'";
return base; return base;
} }

View File

@ -127,7 +127,7 @@ public class ResourceUtilities {
public static String getLink(Bundle feed, String rel) { public static String getLink(Bundle feed, String rel) {
for (BundleLinkComponent link : feed.getLink()) { for (BundleLinkComponent link : feed.getLink()) {
if (link.getRelation().equals(rel)) if (link.getRelation().toCode().equals(rel))
return link.getUrl(); return link.getUrl();
} }
return null; return null;

View File

@ -1951,6 +1951,30 @@ public class Utilities {
return p; return p;
} }
public static String stripAllPara(String p) {
if (noString(p)) {
return "";
}
p = p.trim();
if (p.startsWith("<p>")) {
p = p.substring(3);
}
if (p.endsWith("</p>")) {
p = p.substring(0, p.length()-4);
}
p = p.replace("</p>", " ");
p = p.replace("<p>", "");
while (p.contains("<p ")) {
int start = p.indexOf("<p ");
int end = start;
while (end < p.length() && p.charAt(end) != '>') {
end++;
}
p = p.substring(start, end);
}
return p;
}
//public static boolean !isWhitespace(String s) { //public static boolean !isWhitespace(String s) {

View File

@ -869,6 +869,7 @@ public class I18nConstants {
public static final String ED_PATH_WRONG_TYPE_MATCH = "ED_PATH_WRONG_TYPE_MATCH"; public static final String ED_PATH_WRONG_TYPE_MATCH = "ED_PATH_WRONG_TYPE_MATCH";
public static final String ATTEMPT_TO_CHANGE_SLICING = "ATTEMPT_TO_CHANGE_SLICING"; public static final String ATTEMPT_TO_CHANGE_SLICING = "ATTEMPT_TO_CHANGE_SLICING";
public static final String REPEAT_SLICING_IGNORED = "REPEAT_SLICING_IGNORED"; public static final String REPEAT_SLICING_IGNORED = "REPEAT_SLICING_IGNORED";
public static final String SD_ELEMENT_NOT_IN_CONSTRAINT = "SD_ELEMENT_NOT_IN_CONSTRAINT";
} }

View File

@ -923,4 +923,4 @@ SD_CONTEXT_SHOULD_NOT_BE_ELEMENT = Review the extension type: extensions should
ED_PATH_WRONG_TYPE_MATCH = The path must be ''{0}'' not ''{1}'' when the type list is not constrained 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} 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 REPEAT_SLICING_IGNORED = The element at {0} defines the slicing but then an element in the slicing {2} repeats it, which is ignored
SD_ELEMENT_NOT_IN_CONSTRAINT = The element definition for {1} has a property {0} which is not allowed in a profile

View File

@ -5968,8 +5968,23 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
} }
} }
if (match) { if (match) {
boolean update = true;
boolean isOk = ei.definition == null || ei.definition == slicer || (ei.definition.getPath().endsWith("[x]") && ed.getPath().startsWith(ei.definition.getPath().replace("[x]", ""))); boolean isOk = ei.definition == null || ei.definition == slicer || (ei.definition.getPath().endsWith("[x]") && ed.getPath().startsWith(ei.definition.getPath().replace("[x]", "")));
if (!isOk) {
// is this a subslice? then we put it in as a replacement
String existingName = ei.definition == null || !ei.definition.hasSliceName() ? null : ei.definition.getSliceName();
String matchingName = ed.hasSliceName() ? ed.getSliceName() : null;
if (existingName != null && matchingName != null) {
if (matchingName.startsWith(existingName+"/")) {
isOk = true;
} else if (existingName.startsWith(matchingName+"/")) {
update = false;
isOk = true;
}
}
}
if (rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), isOk, I18nConstants.VALIDATION_VAL_PROFILE_MATCHMULTIPLE, profile.getVersionedUrl(), (ei.definition == null || !ei.definition.hasSliceName() ? "" : ei.definition.getSliceName()), (ed.hasSliceName() ? ed.getSliceName() : ""))) { if (rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), isOk, I18nConstants.VALIDATION_VAL_PROFILE_MATCHMULTIPLE, profile.getVersionedUrl(), (ei.definition == null || !ei.definition.hasSliceName() ? "" : ei.definition.getSliceName()), (ed.hasSliceName() ? ed.getSliceName() : ""))) {
if (update) {
ei.definition = ed; ei.definition = ed;
if (ei.slice == null) { if (ei.slice == null) {
ei.index = i; ei.index = i;
@ -5978,6 +5993,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
ei.sliceindex = i - (sliceOffset + 1); ei.sliceindex = i - (sliceOffset + 1);
} }
} }
}
} else if (childUnsupportedSlicing) { } else if (childUnsupportedSlicing) {
problematicPaths.add(ed.getPath()); problematicPaths.add(ed.getPath());
} }

View File

@ -127,14 +127,16 @@ public class StructureDefinitionValidator extends BaseValidator {
rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.ERROR_GENERATING_SNAPSHOT, e.getMessage()); rule(errors, NO_RULE_DATE, IssueType.EXCEPTION, stack.getLiteralPath(), false, I18nConstants.ERROR_GENERATING_SNAPSHOT, e.getMessage());
ok = false; ok = false;
} }
List<Element> differentials = src.getChildrenByName("differential"); List<Element> differentials = src.getChildrenByName("differential");
List<Element> snapshots = src.getChildrenByName("snapshot"); List<Element> snapshots = src.getChildrenByName("snapshot");
boolean logical = "logical".equals(src.getNamedChildValue("kind")); boolean logical = "logical".equals(src.getNamedChildValue("kind"));
boolean constraint = "constraint".equals(src.getNamedChildValue("derivation"));
for (Element differential : differentials) { for (Element differential : differentials) {
ok = validateElementList(errors, differential, stack.push(differential, -1, null, null), false, snapshots.size() > 0, sd, typeName, logical) && ok; ok = validateElementList(errors, differential, stack.push(differential, -1, null, null), false, snapshots.size() > 0, sd, typeName, logical, constraint) && ok;
} }
for (Element snapshot : snapshots) { for (Element snapshot : snapshots) {
ok = validateElementList(errors, snapshot, stack.push(snapshot, -1, null, null), true, true, sd, typeName, logical) && ok; ok = validateElementList(errors, snapshot, stack.push(snapshot, -1, null, null), true, true, sd, typeName, logical, constraint) && ok;
} }
return ok; return ok;
} }
@ -174,18 +176,18 @@ public class StructureDefinitionValidator extends BaseValidator {
} }
} }
private boolean validateElementList(List<ValidationMessage> errors, Element elementList, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical) { private boolean validateElementList(List<ValidationMessage> errors, Element elementList, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint) {
boolean ok = true; boolean ok = true;
List<Element> elements = elementList.getChildrenByName("element"); List<Element> elements = elementList.getChildrenByName("element");
int cc = 0; int cc = 0;
for (Element element : elements) { for (Element element : elements) {
ok = validateElementDefinition(errors, element, stack.push(element, cc, null, null), snapshot, hasSnapshot, sd, typeName, logical) && ok; ok = validateElementDefinition(errors, element, stack.push(element, cc, null, null), snapshot, hasSnapshot, sd, typeName, logical, constraint) && ok;
cc++; cc++;
} }
return ok; return ok;
} }
private boolean validateElementDefinition(List<ValidationMessage> errors, Element element, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical) { private boolean validateElementDefinition(List<ValidationMessage> errors, Element element, NodeStack stack, boolean snapshot, boolean hasSnapshot, StructureDefinition sd, String typeName, boolean logical, boolean constraint) {
boolean ok = true; boolean ok = true;
boolean typeMustSupport = false; boolean typeMustSupport = false;
String path = element.getNamedChildValue("path"); String path = element.getNamedChildValue("path");
@ -194,6 +196,8 @@ public class StructureDefinitionValidator extends BaseValidator {
rule(errors, "2023-01-17", IssueType.INVALID, stack.getLiteralPath(), path.contains(".") || !element.hasChild("slicing"), I18nConstants.SD_NO_SLICING_ON_ROOT, path); rule(errors, "2023-01-17", IssueType.INVALID, stack.getLiteralPath(), path.contains(".") || !element.hasChild("slicing"), I18nConstants.SD_NO_SLICING_ON_ROOT, path);
} }
rule(errors, "2023-05-22", IssueType.NOTFOUND, stack.getLiteralPath(), snapshot || !constraint || !element.hasChild("meaningWhenMissing") || meaningWhenMissingAllowed(element), I18nConstants.SD_ELEMENT_NOT_IN_CONSTRAINT, "meaningWhenMissing", path);
List<Element> types = element.getChildrenByName("type"); List<Element> types = element.getChildrenByName("type");
Set<String> typeCodes = new HashSet<>(); Set<String> typeCodes = new HashSet<>();
Set<String> characteristics = new HashSet<>(); Set<String> characteristics = new HashSet<>();
@ -312,6 +316,12 @@ public class StructureDefinitionValidator extends BaseValidator {
return ok; return ok;
} }
private boolean meaningWhenMissingAllowed(Element element) {
// allowed to use meaningWhenMissing on the root of an element to say what it means when the extension
// is not present.
return "Extension".equals(element.getPath()) || (element.getPath().endsWith(".extension"));
}
private boolean addCharacteristics(Set<String> set, String tc) { private boolean addCharacteristics(Set<String> set, String tc) {
switch (tc) { switch (tc) {
case "boolean" : return addCharacteristicsForType(set); case "boolean" : return addCharacteristicsForType(set);

View File

@ -180,7 +180,7 @@ public class SnapShotGenerationXTests {
else else
source = (StructureDefinition) XVersionLoader.loadXml(version, TestingUtilities.loadTestResourceStream("rX", "snapshot-generation", id + "-input.xml")); source = (StructureDefinition) XVersionLoader.loadXml(version, TestingUtilities.loadTestResourceStream("rX", "snapshot-generation", id + "-input.xml"));
if (!fail) if (!fail)
expected = (StructureDefinition) XVersionLoader.loadXml(version, TestingUtilities.loadTestResourceStream("rX", "snapshot-generation", id + "-expected.xml")); expected = (StructureDefinition) XVersionLoader.loadXml(version, TestingUtilities.loadTestResourceStream("rX", "snapshot-generation", id + "-output.xml"));
if (!Utilities.noString(include)) if (!Utilities.noString(include))
included = (StructureDefinition) XVersionLoader.loadXml(version, TestingUtilities.loadTestResourceStream("rX", "snapshot-generation", include + ".xml")); included = (StructureDefinition) XVersionLoader.loadXml(version, TestingUtilities.loadTestResourceStream("rX", "snapshot-generation", include + ".xml"));
if (!Utilities.noString(register)) { if (!Utilities.noString(register)) {
@ -471,8 +471,7 @@ public class SnapShotGenerationXTests {
pu.sortDifferential(base, test.getOutput(), test.getOutput().getUrl(), errors, false); pu.sortDifferential(base, test.getOutput(), test.getOutput().getUrl(), errors, false);
if (!errors.isEmpty()) if (!errors.isEmpty())
throw new FHIRException(errors.get(0)); throw new FHIRException(errors.get(0));
IOUtils.copy(TestingUtilities.loadTestResourceStream("rX", "snapshot-generation", test.getId() + "-expected.xml"), new FileOutputStream(UtilitiesXTests.tempFile("snapshot", test.getId() + "-expected.xml"))); new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(UtilitiesXTests.tempFile("snapshot", test.getId() + "-output.xml")), test.getOutput());
new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(UtilitiesXTests.tempFile("snapshot", test.getId() + "-actual.xml")), test.getOutput());
Assertions.assertTrue(test.expected.equalsDeep(test.output), "Output does not match expected"); Assertions.assertTrue(test.expected.equalsDeep(test.output), "Output does not match expected");
} }
@ -542,11 +541,10 @@ public class SnapShotGenerationXTests {
if (!fail) { if (!fail) {
test.output = output; test.output = output;
UtilitiesXTests.context(version).cacheResource(output); UtilitiesXTests.context(version).cacheResource(output);
File dst = new File(UtilitiesXTests.tempFile("snapshot", test.getId() + "-expected.xml")); File dst = new File(UtilitiesXTests.tempFile("snapshot", test.getId() + "-output.xml"));
if (dst.exists()) if (dst.exists())
dst.delete(); dst.delete();
IOUtils.copy(TestingUtilities.loadTestResourceStream("rX", "snapshot-generation", test.getId() + "-expected.xml"), new FileOutputStream(dst)); new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(UtilitiesXTests.tempFile("snapshot", test.getId() + "-output.xml")), output);
new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(UtilitiesXTests.tempFile("snapshot", test.getId() + "-actual.xml")), output);
StructureDefinition t1 = test.expected.copy(); StructureDefinition t1 = test.expected.copy();
t1.setText(null); t1.setText(null);
StructureDefinition t2 = test.output.copy(); StructureDefinition t2 = test.output.copy();

View File

@ -107,7 +107,7 @@ public class ValidationTests implements IEvaluationContext, IValidatorResourceFe
} }
public final static boolean PRINT_OUTPUT_TO_CONSOLE = true; public final static boolean PRINT_OUTPUT_TO_CONSOLE = true;
private static final boolean BUILD_NEW = false; private static final boolean BUILD_NEW = true;
private static final boolean CLONE = true; private static final boolean CLONE = true;
@Parameters(name = "{index}: id {0}") @Parameters(name = "{index}: id {0}")

View File

@ -3443,3 +3443,22 @@ v: {
"class" : "SERVER_ERROR" "class" : "SERVER_ERROR"
} }
------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------
{"code" : {
"system" : "http://loinc.org",
"version" : "2.71",
"code" : "29463-7",
"display" : "Body weight"
}, "valueSet" :null, "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": {
"resourceType" : "Parameters",
"parameter" : [{
"name" : "profile-url",
"valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891"
}]
}}####
v: {
"display" : "Body weight",
"code" : "29463-7",
"system" : "http://loinc.org",
"version" : "2.74"
}
-------------------------------------------------------------------------------------

View File

@ -2150,3 +2150,41 @@ v: {
"version" : "http://snomed.info/sct/900000000000207008/version/20230131" "version" : "http://snomed.info/sct/900000000000207008/version/20230131"
} }
------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------
{"code" : {
"system" : "http://snomed.info/sct",
"version" : "http://snomed.info/sct/900000000000207008/version/20210731",
"code" : "27113001",
"display" : "Body weight (observable entity)"
}, "valueSet" :null, "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": {
"resourceType" : "Parameters",
"parameter" : [{
"name" : "profile-url",
"valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891"
}]
}}####
v: {
"display" : "Body weight",
"code" : "27113001",
"system" : "http://snomed.info/sct",
"version" : "http://snomed.info/sct/900000000000207008/version/20210731"
}
-------------------------------------------------------------------------------------
{"code" : {
"system" : "http://snomed.info/sct",
"version" : "http://snomed.info/sct/900000000000207008/version/20210731",
"code" : "38266002",
"display" : "Entire body as a whole (body structure)"
}, "valueSet" :null, "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": {
"resourceType" : "Parameters",
"parameter" : [{
"name" : "profile-url",
"valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891"
}]
}}####
v: {
"display" : "Entire body as a whole",
"code" : "38266002",
"system" : "http://snomed.info/sct",
"version" : "http://snomed.info/sct/900000000000207008/version/20210731"
}
-------------------------------------------------------------------------------------

View File

@ -420,3 +420,19 @@ v: {
"version" : "2.0.1" "version" : "2.0.1"
} }
------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------
{"code" : {
"system" : "http://unitsofmeasure.org",
"code" : "KG"
}, "valueSet" :null, "langs":"[en]", "useServer":"true", "useClient":"true", "guessSystem":"false", "valueSetMode":"ALL_CHECKS", "displayWarningMode":"false", "versionFlexible":"false", "profile": {
"resourceType" : "Parameters",
"parameter" : [{
"name" : "profile-url",
"valueString" : "http://hl7.org/fhir/ExpansionProfile/dc8fd4bc-091a-424a-8a3b-6198ef146891"
}]
}}####
v: {
"severity" : "error",
"error" : "Error processing Unit: 'KG': The unit \"KG\" is unknown at character 1; Unknown Code 'KG' in the system 'http://unitsofmeasure.org'; The provided code http://unitsofmeasure.org#KG is not in the value set 'http://hl7.org/fhir/ValueSet/@all' (from Tx-Server)",
"class" : "UNKNOWN"
}
-------------------------------------------------------------------------------------