Fix for sparse differentials

This commit is contained in:
Grahame Grieve 2019-07-29 14:10:38 +10:00
parent 94feb31bef
commit bc776064f5
17 changed files with 4862 additions and 1591 deletions

View File

@ -166,7 +166,6 @@ public class ProfileUtilities extends TranslatingUtilities {
} }
} }
private static int nextSliceId = 0;
private static final int MAX_RECURSION_LIMIT = 10; private static final int MAX_RECURSION_LIMIT = 10;
public class ExtensionContext { public class ExtensionContext {
@ -225,7 +224,8 @@ public class ProfileUtilities extends TranslatingUtilities {
public static final String IS_DERIVED = "derived.fact"; public static final String IS_DERIVED = "derived.fact";
public static final String UD_ERROR_STATUS = "error-status"; public static final String UD_ERROR_STATUS = "error-status";
private static final String GENERATED_IN_SNAPSHOT = "profileutilities.snapshot.processed"; private static final String GENERATED_IN_SNAPSHOT = "profileutilities.snapshot.processed";
private static final boolean DEBUG = false;
private boolean debug;
// note that ProfileUtilities are used re-entrantly internally, so nothing with process state can be here // note that ProfileUtilities are used re-entrantly internally, so nothing with process state can be here
private final IWorkerContext context; private final IWorkerContext context;
@ -449,13 +449,12 @@ public class ProfileUtilities extends TranslatingUtilities {
StructureDefinitionDifferentialComponent diff = derived.getDifferential().copy(); // we make a copy here because we're sometimes going to hack the differential while processing it. StructureDefinitionDifferentialComponent diff = derived.getDifferential().copy(); // we make a copy here because we're sometimes going to hack the differential while processing it.
processPaths("", derived.getSnapshot(), base.getSnapshot(), diff, baseCursor, diffCursor, base.getSnapshot().getElement().size()-1, processPaths("", derived.getSnapshot(), base.getSnapshot(), diff, baseCursor, diffCursor, base.getSnapshot().getElement().size()-1,
derived.getDifferential().hasElement() ? derived.getDifferential().getElement().size()-1 : -1, url, webUrl, derived.getId(), null, null, false, base.getUrl(), null, false, new ArrayList<ElementRedirection>(), base); derived.getDifferential().hasElement() ? derived.getDifferential().getElement().size()-1 : -1, url, webUrl, derived.present(), null, null, false, base.getUrl(), null, false, new ArrayList<ElementRedirection>(), base);
if (!derived.getSnapshot().getElementFirstRep().getType().isEmpty()) if (!derived.getSnapshot().getElementFirstRep().getType().isEmpty())
throw new Error("type on first snapshot element for "+derived.getSnapshot().getElementFirstRep().getPath()+" in "+derived.getUrl()+" from "+base.getUrl()); throw new Error("type on first snapshot element for "+derived.getSnapshot().getElementFirstRep().getPath()+" in "+derived.getUrl()+" from "+base.getUrl());
updateMaps(base, derived); updateMaps(base, derived);
setIds(derived, false);
if (DEBUG) { if (debug) {
System.out.println("Differential: "); System.out.println("Differential: ");
for (ElementDefinition ed : derived.getDifferential().getElement()) for (ElementDefinition ed : derived.getDifferential().getElement())
System.out.println(" "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" id = "+ed.getId()+" "+constraintSummary(ed)); System.out.println(" "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" id = "+ed.getId()+" "+constraintSummary(ed));
@ -463,12 +462,13 @@ public class ProfileUtilities extends TranslatingUtilities {
for (ElementDefinition ed : derived.getSnapshot().getElement()) for (ElementDefinition ed : derived.getSnapshot().getElement())
System.out.println(" "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" id = "+ed.getId()+" "+constraintSummary(ed)); System.out.println(" "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+" id = "+ed.getId()+" "+constraintSummary(ed));
} }
setIds(derived, false);
//Check that all differential elements have a corresponding snapshot element //Check that all differential elements have a corresponding snapshot element
for (ElementDefinition e : diff.getElement()) { for (ElementDefinition e : diff.getElement()) {
if (!e.hasUserData(GENERATED_IN_SNAPSHOT)) { if (!e.hasUserData(GENERATED_IN_SNAPSHOT)) {
System.out.println("Error in snapshot generation: Differential for "+derived.getUrl()+" with id: " + e.getId()+" has an element that is not marked with a snapshot match"); System.out.println("Error in snapshot generation: Differential for "+derived.getUrl()+" with " + (e.hasId() ? "id: "+e.getId() : "path: "+e.getPath())+" has an element that is not marked with a snapshot match");
if (exception) if (exception)
throw new DefinitionException("Snapshot for "+derived.getUrl()+" does not contain an element that matches an existing differential element that has id: " + e.getId()); throw new DefinitionException("Snapshot for "+derived.getUrl()+" does not contain an element that matches an existing differential element that has "+(e.hasId() ? "id: "+e.getId() : "path: "+e.getPath()));
else else
messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url, "Snapshot for "+derived.getUrl()+" does not contain an element that matches an existing differential element that has id: " + e.getId(), ValidationMessage.IssueSeverity.ERROR)); messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url, "Snapshot for "+derived.getUrl()+" does not contain an element that matches an existing differential element that has id: " + e.getId(), ValidationMessage.IssueSeverity.ERROR));
} }
@ -572,7 +572,7 @@ public class ProfileUtilities extends TranslatingUtilities {
*/ */
private ElementDefinition processPaths(String indent, StructureDefinitionSnapshotComponent result, StructureDefinitionSnapshotComponent base, StructureDefinitionDifferentialComponent differential, int baseCursor, int diffCursor, int baseLimit, private ElementDefinition processPaths(String indent, StructureDefinitionSnapshotComponent result, StructureDefinitionSnapshotComponent base, StructureDefinitionDifferentialComponent differential, int baseCursor, int diffCursor, int baseLimit,
int diffLimit, String url, String webUrl, String profileName, String contextPathSrc, String contextPathDst, boolean trimDifferential, String contextName, String resultPathBase, boolean slicingDone, List<ElementRedirection> redirector, StructureDefinition srcSD) throws DefinitionException, FHIRException { int diffLimit, String url, String webUrl, String profileName, String contextPathSrc, String contextPathDst, boolean trimDifferential, String contextName, String resultPathBase, boolean slicingDone, List<ElementRedirection> redirector, StructureDefinition srcSD) throws DefinitionException, FHIRException {
if (DEBUG) if (debug)
System.out.println(indent+"PP @ "+resultPathBase+" / "+contextPathSrc+" : base = "+baseCursor+" to "+baseLimit+", diff = "+diffCursor+" to "+diffLimit+" (slicing = "+slicingDone+", redirector = "+(redirector == null ? "null" : redirector.toString())+")"); System.out.println(indent+"PP @ "+resultPathBase+" / "+contextPathSrc+" : base = "+baseCursor+" to "+baseLimit+", diff = "+diffCursor+" to "+diffLimit+" (slicing = "+slicingDone+", redirector = "+(redirector == null ? "null" : redirector.toString())+")");
ElementDefinition res = null; ElementDefinition res = null;
List<TypeSlice> typeList = new ArrayList<>(); List<TypeSlice> typeList = new ArrayList<>();
@ -581,7 +581,7 @@ public class ProfileUtilities extends TranslatingUtilities {
// get the current focus of the base, and decide what to do // get the current focus of the base, and decide what to do
ElementDefinition currentBase = base.getElement().get(baseCursor); ElementDefinition currentBase = base.getElement().get(baseCursor);
String cpath = fixedPathSource(contextPathSrc, currentBase.getPath(), redirector); String cpath = fixedPathSource(contextPathSrc, currentBase.getPath(), redirector);
if (DEBUG) if (debug)
System.out.println(indent+" - "+cpath+": base = "+baseCursor+" to "+baseLimit+", diff = "+diffCursor+" to "+diffLimit+" (slicingDone = "+slicingDone+") (diffpath= "+(differential.getElement().size() > diffCursor ? differential.getElement().get(diffCursor).getPath() : "n/a")+")"); System.out.println(indent+" - "+cpath+": base = "+baseCursor+" to "+baseLimit+", diff = "+diffCursor+" to "+diffLimit+" (slicingDone = "+slicingDone+") (diffpath= "+(differential.getElement().size() > diffCursor ? differential.getElement().get(diffCursor).getPath() : "n/a")+")");
List<ElementDefinition> diffMatches = getDiffMatches(differential, cpath, diffCursor, diffLimit, profileName); // get a list of matching elements in scope List<ElementDefinition> diffMatches = getDiffMatches(differential, cpath, diffCursor, diffLimit, profileName); // get a list of matching elements in scope
@ -600,15 +600,23 @@ public class ProfileUtilities extends TranslatingUtilities {
result.getElement().add(outcome); result.getElement().add(outcome);
if (hasInnerDiffMatches(differential, cpath, diffCursor, diffLimit, base.getElement())) { if (hasInnerDiffMatches(differential, cpath, diffCursor, diffLimit, base.getElement())) {
// well, the profile walks into this, so we need to as well // well, the profile walks into this, so we need to as well
// did we implicitly step into a new type?
if (baseHasChildren(base, currentBase)) { // not a new type here
processPaths(indent+" ", result, base, differential, baseCursor+1, diffCursor, baseLimit, diffLimit, url, webUrl, profileName, contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, false, redirector, srcSD);
baseCursor = indexOfFirstNonChild(base, currentBase, baseCursor+1, baseLimit);
} else {
if (outcome.getType().size() == 0) {
throw new DefinitionException(diffMatches.get(0).getPath()+" has no children ("+differential.getElement().get(diffCursor).getPath()+") and no types in profile "+profileName);
}
if (outcome.getType().size() > 1) { if (outcome.getType().size() > 1) {
for (TypeRefComponent t : outcome.getType()) { for (TypeRefComponent t : outcome.getType()) {
if (!t.getCode().equals("Reference")) if (!t.getCode().equals("Reference"))
throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName); throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName);
} }
} }
StructureDefinition dt = outcome.getType().isEmpty() ? null : getProfileForDataType(outcome.getType().get(0)); StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
if (dt == null) if (dt == null)
throw new DefinitionException(cpath+" has children for type "+typeCode(outcome.getType())+" in profile "+profileName+", but can't find type"); throw new DefinitionException("Unknown type "+outcome.getType().get(0)+" at "+diffMatches.get(0).getPath());
contextName = dt.getUrl(); contextName = dt.getUrl();
int start = diffCursor; int start = diffCursor;
while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), cpath+".")) while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), cpath+"."))
@ -616,6 +624,7 @@ public class ProfileUtilities extends TranslatingUtilities {
processPaths(indent+" ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1, processPaths(indent+" ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1,
diffCursor-1, url, webUrl, profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false, redirector, srcSD); diffCursor-1, url, webUrl, profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false, redirector, srcSD);
} }
}
baseCursor++; baseCursor++;
} else if (diffMatches.size() == 1 && (slicingDone || (!isImplicitSlicing(diffMatches.get(0), cpath) && !(diffMatches.get(0).hasSlicing() || (isExtension(diffMatches.get(0)) && diffMatches.get(0).hasSliceName()))))) {// one matching element in the differential } else if (diffMatches.size() == 1 && (slicingDone || (!isImplicitSlicing(diffMatches.get(0), cpath) && !(diffMatches.get(0).hasSlicing() || (isExtension(diffMatches.get(0)) && diffMatches.get(0).hasSliceName()))))) {// one matching element in the differential
ElementDefinition template = null; ElementDefinition template = null;
@ -775,10 +784,15 @@ public class ProfileUtilities extends TranslatingUtilities {
if (diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getType() != DiscriminatorType.TYPE) if (diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getType() != DiscriminatorType.TYPE)
throw new FHIRException("Error at path "+contextPathSrc+": Type slicing with slicing.discriminator.type != 'type'"); throw new FHIRException("Error at path "+contextPathSrc+": Type slicing with slicing.discriminator.type != 'type'");
} }
// fix the slice names too while we're at it... // check the slice names too while we're at it...
for (TypeSlice ts : typeList) for (TypeSlice ts : typeList)
if (ts.type != null) if (ts.type != null) {
ts.defn.setSliceName(rootName(cpath)+Utilities.capitalize(ts.type)); String tn = rootName(cpath)+Utilities.capitalize(ts.type);
if (!ts.defn.hasSliceName())
ts.defn.setSliceName(tn);
else if (!ts.defn.getSliceName().equals(tn))
throw new FHIRException("Error at path "+(!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath)+": Slice name must be '"+tn+"' but is '"+ts.defn.getSliceName()+"'");
}
// ok passed the checks. // ok passed the checks.
// copy the root diff, and then process any children it has // copy the root diff, and then process any children it has
@ -1072,6 +1086,29 @@ public class ProfileUtilities extends TranslatingUtilities {
} }
private boolean baseHasChildren(StructureDefinitionSnapshotComponent base, ElementDefinition ed) {
int index = base.getElement().indexOf(ed);
if (index == -1 || index >= base.getElement().size()-1)
return false;
String p = base.getElement().get(index+1).getPath();
return isChildOf(p, ed.getPath());
}
private boolean isChildOf(String sub, String focus) {
if (focus.endsWith("[x]")) {
focus = focus.substring(0, focus.length()-3);
return sub.startsWith(focus);
} else
return sub.startsWith(focus+".");
}
private int indexOfFirstNonChild(StructureDefinitionSnapshotComponent base, ElementDefinition currentBase, int i, int baseLimit) {
return baseLimit+1;
}
private String rootName(String cpath) { private String rootName(String cpath) {
String t = tail(cpath); String t = tail(cpath);
return t.replace("[x]", ""); return t.replace("[x]", "");
@ -1500,8 +1537,6 @@ public class ProfileUtilities extends TranslatingUtilities {
private ElementDefinitionSlicingComponent makeExtensionSlicing() { private ElementDefinitionSlicingComponent makeExtensionSlicing() {
ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent(); ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent();
nextSliceId++;
slice.setId(Integer.toString(nextSliceId));
slice.addDiscriminator().setPath("url").setType(DiscriminatorType.VALUE); slice.addDiscriminator().setPath("url").setType(DiscriminatorType.VALUE);
slice.setOrdered(false); slice.setOrdered(false);
slice.setRules(SlicingRules.OPEN); slice.setRules(SlicingRules.OPEN);
@ -1515,13 +1550,16 @@ public class ProfileUtilities extends TranslatingUtilities {
private boolean hasInnerDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, List<ElementDefinition> base) throws DefinitionException { private boolean hasInnerDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, List<ElementDefinition> base) throws DefinitionException {
for (int i = start; i <= end; i++) { for (int i = start; i <= end; i++) {
String statedPath = context.getElement().get(i).getPath(); String statedPath = context.getElement().get(i).getPath();
if (statedPath.startsWith(path+".") && !statedPath.substring(path.length()+1).contains(".")) { if (statedPath.startsWith(path+".")) {
boolean found = false; boolean found = false;
for (ElementDefinition ed : base) { // GG - I don't know what this code is doing....
String ep = ed.getPath(); // for (ElementDefinition ed : base) {
if (ep.equals(statedPath) || (ep.endsWith("[x]") && statedPath.length() > ep.length() - 2 && statedPath.substring(0, ep.length()-3).equals(ep.substring(0, ep.length()-3)) && !statedPath.substring(ep.length()).contains("."))) // String ep = ed.getPath();
found = true; // if (ep.equals(statedPath) || (ep.endsWith("[x]") && statedPath.length() > ep.length() - 2 && statedPath.substring(0, ep.length()-3).equals(ep.substring(0, ep.length()-3)) && !statedPath.substring(ep.length()).contains("."))) {
} // found = true;
// break;
// }
// }
if (!found) if (!found)
return true; return true;
} }
@ -4380,6 +4418,16 @@ public class ProfileUtilities extends TranslatingUtilities {
} }
public boolean isDebug() {
return debug;
}
public void setDebug(boolean debug) {
this.debug = debug;
}
} }

View File

@ -23,6 +23,7 @@ import org.hl7.fhir.r5.conformance.ProfileUtilities;
import org.hl7.fhir.r5.conformance.ProfileUtilities.ProfileKnowledgeProvider; import org.hl7.fhir.r5.conformance.ProfileUtilities.ProfileKnowledgeProvider;
import org.hl7.fhir.r5.context.SimpleWorkerContext; import org.hl7.fhir.r5.context.SimpleWorkerContext;
import org.hl7.fhir.r5.formats.IParser.OutputStyle; import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.formats.JsonParser;
import org.hl7.fhir.r5.formats.XmlParser; import org.hl7.fhir.r5.formats.XmlParser;
import org.hl7.fhir.r5.model.Base; import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.Coding; import org.hl7.fhir.r5.model.Coding;
@ -161,12 +162,16 @@ public class SnapShotGenerationTests {
this.output = output; this.output = output;
} }
public void load() throws FHIRFormatError, FileNotFoundException, IOException { public void load() throws FHIRFormatError, FileNotFoundException, IOException {
source = (StructureDefinition) new XmlParser().parse(new FileInputStream(Utilities.path(TestingUtilities.resourceNameToFile("snapshot-generation", id+"-input.xml")))); if (new File(TestingUtilities.resourceNameToFile("snapshot-generation", id+"-input.json")).exists())
expected = (StructureDefinition) new XmlParser().parse(new FileInputStream(Utilities.path(TestingUtilities.resourceNameToFile("snapshot-generation", id+"-expected.xml")))); source = (StructureDefinition) new JsonParser().parse(new FileInputStream(TestingUtilities.resourceNameToFile("snapshot-generation", id+"-input.json")));
else
source = (StructureDefinition) new XmlParser().parse(new FileInputStream(TestingUtilities.resourceNameToFile("snapshot-generation", id+"-input.xml")));
if (!fail)
expected = (StructureDefinition) new XmlParser().parse(new FileInputStream(TestingUtilities.resourceNameToFile("snapshot-generation", id+"-expected.xml")));
if (!Utilities.noString(include)) if (!Utilities.noString(include))
included = (StructureDefinition) new XmlParser().parse(new FileInputStream(Utilities.path(TestingUtilities.resourceNameToFile("snapshot-generation", include+".xml")))); included = (StructureDefinition) new XmlParser().parse(new FileInputStream(TestingUtilities.resourceNameToFile("snapshot-generation", include+".xml")));
if (!Utilities.noString(register)) { if (!Utilities.noString(register)) {
included = (StructureDefinition) new XmlParser().parse(new FileInputStream(Utilities.path(TestingUtilities.resourceNameToFile("snapshot-generation", register+".xml")))); included = (StructureDefinition) new XmlParser().parse(new FileInputStream(TestingUtilities.resourceNameToFile("snapshot-generation", register+".xml")));
} }
} }
} }
@ -338,7 +343,7 @@ public class SnapShotGenerationTests {
if (url == null) if (url == null)
return null; return null;
for (TestDetails t : tests) { for (TestDetails t : tests) {
if (url.equals(t.expected.getUrl())) if (t.expected != null && url.equals(t.expected.getUrl()))
return t.expected; return t.expected;
if (t.included != null && url.equals(t.included.getUrl())) if (t.included != null && url.equals(t.included.getUrl()))
return t.expected; return t.expected;
@ -417,7 +422,7 @@ public class SnapShotGenerationTests {
pu.sortDifferential(base, test.getOutput(), test.getOutput().getUrl(), errors); pu.sortDifferential(base, test.getOutput(), test.getOutput().getUrl(), errors);
if (!errors.isEmpty()) if (!errors.isEmpty())
throw new FHIRException(errors.get(0)); throw new FHIRException(errors.get(0));
new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(TestingUtilities.resourceNameToFile("snapshot-generation", test.getId()+"-output.xml")), test.getOutput()); new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(TestingUtilities.resourceNameToFile("snapshot-generation", test.getId()+"-actual.xml")), test.getOutput());
Assert.assertTrue("Output does not match expected", test.expected.equalsDeep(test.output)); Assert.assertTrue("Output does not match expected", test.expected.equalsDeep(test.output));
} }
@ -435,6 +440,8 @@ public class SnapShotGenerationTests {
StructureDefinition output = test.getSource().copy(); StructureDefinition output = test.getSource().copy();
ProfileUtilities pu = new ProfileUtilities(TestingUtilities.context(), messages , new TestPKP()); ProfileUtilities pu = new ProfileUtilities(TestingUtilities.context(), messages , new TestPKP());
pu.setNewSlicingProcessing(true); pu.setNewSlicingProcessing(true);
pu.setThrowException(true);
pu.setDebug(true);
pu.setIds(test.getSource(), false); pu.setIds(test.getSource(), false);
if (test.isSort()) { if (test.isSort()) {
List<String> errors = new ArrayList<String>(); List<String> errors = new ArrayList<String>();
@ -443,12 +450,17 @@ public class SnapShotGenerationTests {
if (errors.size() > 0) if (errors.size() > 0)
throw new FHIRException("Sort failed: "+errors.toString()); throw new FHIRException("Sort failed: "+errors.toString());
} }
try {
pu.generateSnapshot(base, output, test.getSource().getUrl(), "http://test.org/profile", test.getSource().getName()); pu.generateSnapshot(base, output, test.getSource().getUrl(), "http://test.org/profile", test.getSource().getName());
} catch (Throwable e) {
System.out.println("\r\nException: "+e.getMessage());
throw e;
}
if (output.getDifferential().hasElement()) if (output.getDifferential().hasElement())
new NarrativeGenerator("", "http://hl7.org/fhir", TestingUtilities.context()).setPkp(new TestPKP()).generate(output, null); new NarrativeGenerator("", "http://hl7.org/fhir", TestingUtilities.context()).setPkp(new TestPKP()).generate(output, null);
test.output = output; test.output = output;
TestingUtilities.context().cacheResource(output); TestingUtilities.context().cacheResource(output);
new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(TestingUtilities.resourceNameToFile("snapshot-generation", test.getId()+"-output.xml")), output); new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(TestingUtilities.resourceNameToFile("snapshot-generation", 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

@ -0,0 +1,2 @@
del *.bak
del *-actual.*

View File

@ -142,9 +142,10 @@
<test gen="true" id="t22"> <test gen="true" id="t22">
<rule text="element count increased by 76" fhirpath="fixture('t22-output').snapshot.element.count().trace('t22o') = fixture('patient').snapshot.element.count().trace('t22patient') + 76"/> <rule text="element count increased by 76" fhirpath="fixture('t22-output').snapshot.element.count().trace('t22o') = fixture('patient').snapshot.element.count().trace('t22patient') + 76"/>
</test> </test>
<test gen="true" id="t23"> <test gen="true" sort="true" id="t23">
<rule text="element count increased by ??" fhirpath="fixture('t23-output').snapshot.element.count().trace('t23o') = fixture('patient').snapshot.element.count().trace('t23patient') + 11"/> <rule text="element count increased by ??" fhirpath="fixture('t23-output').snapshot.element.count().trace('t23o') = fixture('patient').snapshot.element.count().trace('t23patient') + 11"/>
</test> </test>
<test gen="true" id="t23a" fail="true"/>
<test gen="true" id="t24b" register="t24a"> <test gen="true" id="t24b" register="t24a">
<rule text="Element count of profile a is increased by 22 from base Patient" fhirpath="fixture('t24b-output').snapshot.element.count().trace('t24ao') = fixture('patient').snapshot.element.count().trace('t24Patient') + 22"/> <rule text="Element count of profile a is increased by 22 from base Patient" fhirpath="fixture('t24b-output').snapshot.element.count().trace('t24ao') = fixture('patient').snapshot.element.count().trace('t24Patient') + 22"/>
<rule text="Element count of profile b is identical to profile a" fhirpath="fixture('t24b-output').snapshot.element.count().trace('t24bo') = fixture('t24b-include').snapshot.element.count().trace('t24ao')"/> <rule text="Element count of profile b is identical to profile a" fhirpath="fixture('t24b-output').snapshot.element.count().trace('t24bo') = fixture('t24b-include').snapshot.element.count().trace('t24ao')"/>
@ -219,4 +220,6 @@
<rule text="The element definition for value[x] quantity slice should have sliceName = 'Quantity'" fhirpath="fixture('t44-output').snapshot.element.where(id = 'Observation.value[x]:valueQuantity').check(exists(), 'no slice').sliceName = 'valueQuantity'"/> <rule text="The element definition for value[x] quantity slice should have sliceName = 'Quantity'" fhirpath="fixture('t44-output').snapshot.element.where(id = 'Observation.value[x]:valueQuantity').check(exists(), 'no slice').sliceName = 'valueQuantity'"/>
<rule text="The element definition for value[x].value quantity slice should have sliceName = 'Quantity'" fhirpath="fixture('t44-output').snapshot.element.where(id = 'Observation.value[x]:valueQuantity.value').check(exists(), 'no slice').min = 1"/> <rule text="The element definition for value[x].value quantity slice should have sliceName = 'Quantity'" fhirpath="fixture('t44-output').snapshot.element.where(id = 'Observation.value[x]:valueQuantity.value').check(exists(), 'no slice').min = 1"/>
</test> </test>
<test gen="true" id="samply1">
</test>
</snapshot-generation-tests> </snapshot-generation-tests>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,277 @@
{
"resourceType": "StructureDefinition",
"url": "https://fhir.bbmri.de/StructureDefinition/BbmriBiobank",
"name": "BbmriBiobank",
"status": "draft",
"fhirVersion": "4.0.0",
"kind": "resource",
"abstract": false,
"type": "Organization",
"baseDefinition": "http://hl7.org/fhir/StructureDefinition/Organization",
"derivation": "constraint",
"differential": {
"element": [
{
"id": "Organization.extension",
"path": "Organization.extension",
"slicing": {
"discriminator": [
{
"type": "value",
"path": "url"
}
],
"rules": "open"
}
},
{
"id": "Organization.extension:description",
"path": "Organization.extension",
"sliceName": "description",
"min": 1,
"max": "1",
"type": [
{
"code": "Extension",
"profile": [
"https://fhir.bbmri.de/StructureDefinition/BbmriOrganizationDescription"
]
}
]
},
{
"id": "Organization.extension:juridicalPerson",
"path": "Organization.extension",
"sliceName": "juridicalPerson",
"min": 1,
"max": "1",
"type": [
{
"code": "Extension",
"profile": [
"https://fhir.bbmri.de/StructureDefinition/BbmriJuridicalPerson"
]
}
]
},
{
"id": "Organization.extension:qualityStandard",
"path": "Organization.extension",
"sliceName": "qualityStandard",
"min": 0,
"max": "*",
"type": [
{
"code": "Extension",
"profile": [
"https://fhir.bbmri.de/StructureDefinition/BbmriQualityStandards"
]
}
]
},
{
"id": "Organization.identifier",
"path": "Organization.identifier",
"slicing": {
"discriminator": [
{
"type": "value",
"path": "system"
}
],
"rules": "open"
},
"min": 1
},
{
"id": "Organization.identifier:Bbmri-EricId",
"path": "Organization.identifier",
"sliceName": "Bbmri-EricId",
"min": 1,
"max": "1"
},
{
"id": "Organization.identifier:Bbmri-EricId.system",
"path": "Organization.identifier.system",
"fixedUri": "http://www.bbmri-eric.eu/"
},
{
"id": "Organization.identifier:Bbmri-EricId.value",
"path": "Organization.identifier.value",
"constraint": [
{
"key": "bbmri-id-1",
"requirements": "Make sure valid BBMRI IDs are provided",
"severity": "warning",
"human": "<CC>_<local id>: SHALL contain valid ISO-3166-1 alpha 2 country code followed by _ followed by biobank ID of national registry (if present).",
"expression": "matches('^([a-z]){2}_([a-zA-Z\\\\d])+$')"
}
]
},
{
"id": "Organization.name",
"path": "Organization.name",
"min": 1
},
{
"id": "Organization.alias",
"path": "Organization.alias",
"max": "1"
},
{
"id": "Organization.telecom",
"path": "Organization.telecom",
"slicing": {
"discriminator": [
{
"type": "value",
"path": "system"
}
],
"rules": "open"
}
},
{
"id": "Organization.telecom:url",
"path": "Organization.telecom",
"sliceName": "url"
},
{
"id": "Organization.telecom:url.system",
"path": "Organization.telecom.system",
"min": 1,
"fixedCode": "url"
},
{
"id": "Organization.telecom:url.value",
"path": "Organization.telecom.value",
"min": 1
},
{
"id": "Organization.address",
"path": "Organization.address",
"min": 1
},
{
"id": "Organization.address.country",
"path": "Organization.address.country",
"min": 1
},
{
"id": "Organization.contact",
"path": "Organization.contact",
"slicing": {
"discriminator": [
{
"type": "value",
"path": "purpose"
}
],
"rules": "open"
},
"min": 2
},
{
"id": "Organization.contact:head",
"path": "Organization.contact",
"sliceName": "head",
"min": 1,
"max": "1"
},
{
"id": "Organization.contact:head.purpose.coding.system",
"path": "Organization.contact.purpose.coding.system",
"fixedUri": "http://terminology.hl7.org/CodeSystem/contactentity-type"
},
{
"id": "Organization.contact:head.purpose.coding.code",
"path": "Organization.contact.purpose.coding.code",
"fixedCode": "ADMIN"
},
{
"id": "Organization.contact:head.name.family",
"path": "Organization.contact.name.family",
"min": 1
},
{
"id": "Organization.contact:head.name.given",
"path": "Organization.contact.name.given",
"min": 1,
"max": "1"
},
{
"id": "Organization.contact:contact",
"path": "Organization.contact",
"sliceName": "contact",
"min": 1,
"max": "1"
},
{
"id": "Organization.contact:contact.purpose.coding.system",
"path": "Organization.contact.purpose.coding.system",
"fixedUri": "https://fhir.bbmri.de/CodeSystem/BbmriContactTypes"
},
{
"id": "Organization.contact:contact.purpose.coding.code",
"path": "Organization.contact.purpose.coding.code",
"fixedCode": "RESEARCH"
},
{
"id": "Organization.contact:contact.telecom",
"path": "Organization.contact.telecom",
"slicing": {
"discriminator": [
{
"type": "value",
"path": "system"
}
],
"rules": "open"
},
"min": 1
},
{
"id": "Organization.contact:contact.telecom:phone",
"path": "Organization.contact.telecom",
"sliceName": "phone"
},
{
"id": "Organization.contact:contact.telecom:phone.system",
"path": "Organization.contact.telecom.system",
"fixedCode": "phone"
},
{
"id": "Organization.contact:contact.telecom:email",
"path": "Organization.contact.telecom",
"sliceName": "email",
"min": 1,
"max": "1"
},
{
"id": "Organization.contact:contact.telecom:email.system",
"path": "Organization.contact.telecom.system",
"fixedCode": "email"
},
{
"id": "Organization.contact:contact.telecom:email.value",
"path": "Organization.contact.telecom.value",
"min": 1
},
{
"id": "Organization.contact:contact.address",
"path": "Organization.contact.address",
"min": 1
},
{
"id": "Organization.contact:contact.address.line",
"path": "Organization.contact.address.line",
"max": "1"
},
{
"id": "Organization.contact:contact.address.country",
"path": "Organization.contact.address.country",
"min": 1
}
]
}
}

View File

@ -304,7 +304,7 @@
</element> </element>
<element id="Patient.extension"> <element id="Patient.extension">
<path value="Patient.extension"/> <path value="Patient.extension"/>
<slicing id="1"> <slicing>
<discriminator> <discriminator>
<type value="value"/> <type value="value"/>
<path value="url"/> <path value="url"/>

View File

@ -304,7 +304,7 @@
</element> </element>
<element id="Patient.extension"> <element id="Patient.extension">
<path value="Patient.extension"/> <path value="Patient.extension"/>
<slicing id="2"> <slicing>
<discriminator> <discriminator>
<type value="value"/> <type value="value"/>
<path value="url"/> <path value="url"/>

View File

@ -909,7 +909,7 @@
</element> </element>
<element id="Patient.address.extension:Geolocation.extension:latitude.valueDecimal.extension"> <element id="Patient.address.extension:Geolocation.extension:latitude.valueDecimal.extension">
<path value="Patient.address.extension.extension.valueDecimal.extension"/> <path value="Patient.address.extension.extension.valueDecimal.extension"/>
<slicing id="3"> <slicing>
<discriminator> <discriminator>
<type value="value"/> <type value="value"/>
<path value="url"/> <path value="url"/>
@ -1229,7 +1229,7 @@
</element> </element>
<element id="Patient.address.extension:Geolocation.extension:longitude.valueDecimal.extension"> <element id="Patient.address.extension:Geolocation.extension:longitude.valueDecimal.extension">
<path value="Patient.address.extension.extension.valueDecimal.extension"/> <path value="Patient.address.extension.extension.valueDecimal.extension"/>
<slicing id="4"> <slicing>
<discriminator> <discriminator>
<type value="value"/> <type value="value"/>
<path value="url"/> <path value="url"/>

View File

@ -325,7 +325,7 @@
</element> </element>
<element id="Patient.modifierExtension"> <element id="Patient.modifierExtension">
<path value="Patient.modifierExtension"/> <path value="Patient.modifierExtension"/>
<slicing id="5"> <slicing>
<discriminator> <discriminator>
<type value="value"/> <type value="value"/>
<path value="url"/> <path value="url"/>

View File

@ -325,7 +325,7 @@
</element> </element>
<element id="Patient.modifierExtension"> <element id="Patient.modifierExtension">
<path value="Patient.modifierExtension"/> <path value="Patient.modifierExtension"/>
<slicing id="6"> <slicing>
<discriminator> <discriminator>
<type value="value"/> <type value="value"/>
<path value="url"/> <path value="url"/>

View File

@ -78,16 +78,13 @@
<img src="tbl_blank.png" alt="." style="background-color: inherit" class="hierarchy"/> <img src="tbl_blank.png" alt="." style="background-color: inherit" class="hierarchy"/>
<img src="tbl_vjoin_slice.png" alt="." style="background-color: inherit" class="hierarchy"/> <img src="tbl_vjoin_slice.png" alt="." style="background-color: inherit" class="hierarchy"/>
<img src="icon_element.gif" alt="." style="background-color: #F7F7F7; background-color: inherit" title="Element" class="hierarchy"/> <img src="icon_element.gif" alt="." style="background-color: #F7F7F7; background-color: inherit" title="Element" class="hierarchy"/>
<span title="null">gender</span> <span title="null">telecom</span>
<a name="Patient.contact.gender"> </a> <a name="Patient.contact.telecom"> </a>
</td> </td>
<td style="vertical-align: top; text-align : left; background-color: #F7F7F7; border: 0px #F0F0F0 solid; padding:0px 4px 0px 4px" class="hierarchy"/> <td style="vertical-align: top; text-align : left; background-color: #F7F7F7; border: 0px #F0F0F0 solid; padding:0px 4px 0px 4px" class="hierarchy"/>
<td style="vertical-align: top; text-align : left; background-color: #F7F7F7; border: 0px #F0F0F0 solid; padding:0px 4px 0px 4px" class="hierarchy">1..</td>
<td style="vertical-align: top; text-align : left; background-color: #F7F7F7; border: 0px #F0F0F0 solid; padding:0px 4px 0px 4px" class="hierarchy"/> <td style="vertical-align: top; text-align : left; background-color: #F7F7F7; border: 0px #F0F0F0 solid; padding:0px 4px 0px 4px" class="hierarchy"/>
<td style="vertical-align: top; text-align : left; background-color: #F7F7F7; border: 0px #F0F0F0 solid; padding:0px 4px 0px 4px" class="hierarchy"/> <td style="vertical-align: top; text-align : left; background-color: #F7F7F7; border: 0px #F0F0F0 solid; padding:0px 4px 0px 4px" class="hierarchy"/>
<td style="vertical-align: top; text-align : left; background-color: #F7F7F7; border: 0px #F0F0F0 solid; padding:0px 4px 0px 4px" class="hierarchy">
<span style="font-weight:bold">Fixed Value: </span>
<span style="color: darkgreen">male</span>
</td>
</tr> </tr>
<tr style="border: 0px #F0F0F0 solid; padding:0px; vertical-align: top; background-color: white;"> <tr style="border: 0px #F0F0F0 solid; padding:0px; vertical-align: top; background-color: white;">
@ -96,13 +93,16 @@
<img src="tbl_blank.png" alt="." style="background-color: inherit" class="hierarchy"/> <img src="tbl_blank.png" alt="." style="background-color: inherit" class="hierarchy"/>
<img src="tbl_vjoin_end_slice.png" alt="." style="background-color: inherit" class="hierarchy"/> <img src="tbl_vjoin_end_slice.png" alt="." style="background-color: inherit" class="hierarchy"/>
<img src="icon_element.gif" alt="." style="background-color: white; background-color: inherit" title="Element" class="hierarchy"/> <img src="icon_element.gif" alt="." style="background-color: white; background-color: inherit" title="Element" class="hierarchy"/>
<span title="null">telecom</span> <span title="null">gender</span>
<a name="Patient.contact.telecom"> </a> <a name="Patient.contact.gender"> </a>
</td> </td>
<td style="vertical-align: top; text-align : left; background-color: white; border: 0px #F0F0F0 solid; padding:0px 4px 0px 4px" class="hierarchy"/> <td style="vertical-align: top; text-align : left; background-color: white; border: 0px #F0F0F0 solid; padding:0px 4px 0px 4px" class="hierarchy"/>
<td style="vertical-align: top; text-align : left; background-color: white; border: 0px #F0F0F0 solid; padding:0px 4px 0px 4px" class="hierarchy">1..</td>
<td style="vertical-align: top; text-align : left; background-color: white; border: 0px #F0F0F0 solid; padding:0px 4px 0px 4px" class="hierarchy"/> <td style="vertical-align: top; text-align : left; background-color: white; border: 0px #F0F0F0 solid; padding:0px 4px 0px 4px" class="hierarchy"/>
<td style="vertical-align: top; text-align : left; background-color: white; border: 0px #F0F0F0 solid; padding:0px 4px 0px 4px" class="hierarchy"/> <td style="vertical-align: top; text-align : left; background-color: white; border: 0px #F0F0F0 solid; padding:0px 4px 0px 4px" class="hierarchy"/>
<td style="vertical-align: top; text-align : left; background-color: white; border: 0px #F0F0F0 solid; padding:0px 4px 0px 4px" class="hierarchy">
<span style="font-weight:bold">Fixed Value: </span>
<span style="color: darkgreen">male</span>
</td>
</tr> </tr>
<tr> <tr>
@ -1406,6 +1406,7 @@
<type> <type>
<code value="code"/> <code value="code"/>
</type> </type>
<fixedCode value="male"/>
<isModifier value="false"/> <isModifier value="false"/>
<isSummary value="false"/> <isSummary value="false"/>
<binding> <binding>
@ -1931,13 +1932,13 @@
<path value="Patient.contact"/> <path value="Patient.contact"/>
<sliceName value="males"/> <sliceName value="males"/>
</element> </element>
<element id="Patient.contact:males.gender">
<path value="Patient.contact.gender"/>
<fixedCode value="male"/>
</element>
<element id="Patient.contact:males.telecom"> <element id="Patient.contact:males.telecom">
<path value="Patient.contact.telecom"/> <path value="Patient.contact.telecom"/>
<min value="1"/> <min value="1"/>
</element> </element>
<element id="Patient.contact:males.gender">
<path value="Patient.contact.gender"/>
<fixedCode value="male"/>
</element>
</differential> </differential>
</StructureDefinition> </StructureDefinition>

View File

@ -802,7 +802,7 @@
</element> </element>
<element id="OperationOutcome.issue.details.text.extension"> <element id="OperationOutcome.issue.details.text.extension">
<path value="OperationOutcome.issue.details.text.extension"/> <path value="OperationOutcome.issue.details.text.extension"/>
<slicing id="7"> <slicing>
<discriminator> <discriminator>
<type value="value"/> <type value="value"/>
<path value="url"/> <path value="url"/>
@ -1005,7 +1005,7 @@
</element> </element>
<element id="OperationOutcome.issue.details.text.extension:translation.value[x]:string.extension"> <element id="OperationOutcome.issue.details.text.extension:translation.value[x]:string.extension">
<path value="OperationOutcome.issue.details.text.extension.value[x].extension"/> <path value="OperationOutcome.issue.details.text.extension.value[x].extension"/>
<slicing id="8"> <slicing>
<discriminator> <discriminator>
<type value="value"/> <type value="value"/>
<path value="url"/> <path value="url"/>

View File

@ -1077,16 +1077,16 @@
<code value="Coding"/> <code value="Coding"/>
</type> </type>
</element> </element>
<element id="Parameters.parameter.part:foo.value[x]:valueCoding"> <element id="Parameters.parameter.part:foo.valueCoding:valueCoding">
<path value="Parameters.parameter.part.value[x]"/> <path value="Parameters.parameter.part.valueCoding"/>
<sliceName value="valueCoding"/> <sliceName value="valueCoding"/>
<min value="1"/> <min value="1"/>
<type> <type>
<code value="Coding"/> <code value="Coding"/>
</type> </type>
</element> </element>
<element id="Parameters.parameter.part:foo.value[x]:Coding.code"> <element id="Parameters.parameter.part:foo.valueCoding:valueCoding.code">
<path value="Parameters.parameter.part.value[x].code"/> <path value="Parameters.parameter.part.valueCoding.code"/>
<min value="1"/> <min value="1"/>
</element> </element>
</differential> </differential>

View File

@ -805,7 +805,7 @@
</element> </element>
<element id="Parameters.parameter:string.value[x].extension"> <element id="Parameters.parameter:string.value[x].extension">
<path value="Parameters.parameter.value[x].extension"/> <path value="Parameters.parameter.value[x].extension"/>
<slicing id="9"> <slicing>
<discriminator> <discriminator>
<type value="value"/> <type value="value"/>
<path value="url"/> <path value="url"/>

View File

@ -144,6 +144,9 @@ public class Utilities {
if (isBlank(string)) { if (isBlank(string)) {
return false; return false;
} }
if (string.startsWith("-"))
string = string.substring(1);
boolean havePeriod = false; boolean havePeriod = false;
for (char next : string.toCharArray()) { for (char next : string.toCharArray()) {
if (next == '.') { if (next == '.') {