Merge pull request #1392 from hapifhir/2023-08-gg-rendering-overhaul2

2023 08 gg rendering overhaul2
This commit is contained in:
Grahame Grieve 2023-08-16 06:09:54 +10:00 committed by GitHub
commit 0087a77ec7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1937 additions and 135 deletions

View File

@ -232,6 +232,10 @@ public abstract class CanonicalResourceComparer extends ResourceComparer {
}
return b.toString();
}
public boolean noUpdates() {
return !(changedMetadata.noteable() || changedDefinitions.noteable() || !changedContent.noteable() || !changedContentInterpretation.noteable());
}
}
public CanonicalResourceComparer(ComparisonSession session) {

View File

@ -33,8 +33,8 @@ public class CodeSystemComparer extends CanonicalResourceComparer {
public class CodeSystemComparison extends CanonicalResourceComparison<CodeSystem> {
private StructuralMatch<PropertyComponent> properties;
private StructuralMatch<CodeSystemFilterComponent> filters;
private StructuralMatch<PropertyComponent> properties = new StructuralMatch<PropertyComponent>();
private StructuralMatch<CodeSystemFilterComponent> filters = new StructuralMatch<CodeSystemFilterComponent>();
private StructuralMatch<ConceptDefinitionComponent> combined;
private Map<String, String> propMap = new HashMap<>(); // right to left; left retains it's name
public CodeSystemComparison(CodeSystem left, CodeSystem right) {

View File

@ -115,6 +115,7 @@ public class ComparisonSession {
return csc;
}
} else if (left != null) {
VersionComparisonAnnotation.markDeleted(null, forVersion, left.fhirType(), left); // todo: waht?
String key = key(left.getUrl(), left.getVersion(), left.getUrl(), left.getVersion());
if (compares.containsKey(key)) {
return compares.get(key);
@ -123,6 +124,7 @@ public class ComparisonSession {
compares.put(key, csc);
return csc;
} else {
VersionComparisonAnnotation.markAdded(right, forVersion);
String key = key(right.getUrl(), right.getVersion(), right.getUrl(), right.getVersion());
if (compares.containsKey(key)) {
return compares.get(key);

View File

@ -173,7 +173,7 @@ public class ProfileComparer extends CanonicalResourceComparer implements Profil
res.combined = sm;
ln = new DefinitionNavigator(session.getContextLeft(), left, true);
rn = new DefinitionNavigator(session.getContextRight(), right, true);
ch = compareDiff(ln.path(), null, ln, rn) || ch;
ch = compareDiff(ln.path(), null, ln, rn, res) || ch;
// we don't preserve the differences - we only want the annotations
}
res.updateDefinitionsState(ch);
@ -389,7 +389,7 @@ public class ProfileComparer extends CanonicalResourceComparer implements Profil
}
private boolean compareDiff(String path, String sliceName, DefinitionNavigator left, DefinitionNavigator right) throws DefinitionException, FHIRFormatError, IOException {
private boolean compareDiff(String path, String sliceName, DefinitionNavigator left, DefinitionNavigator right, ProfileComparison res) throws DefinitionException, FHIRFormatError, IOException {
assert(path != null);
assert(left != null);
assert(right != null);
@ -414,7 +414,7 @@ public class ProfileComparer extends CanonicalResourceComparer implements Profil
def = comparePrimitivesWithTracking("max", left.current().getMaxElement(), right.current().getMaxElement(), null, IssueSeverity.INFORMATION, null, right.current(), session.getForVersion()) || def;
// add the children
def = compareDiffChildren(path, left, right) || def;
def = compareDiffChildren(path, left, right, right.current(), res) || def;
//
// // now process the slices
// if (left.current().hasSlicing() || right.current().hasSlicing()) {
@ -478,31 +478,26 @@ public class ProfileComparer extends CanonicalResourceComparer implements Profil
}
private boolean compareDiffChildren(String path, DefinitionNavigator left, DefinitionNavigator right) throws DefinitionException, IOException, FHIRFormatError {
private boolean compareDiffChildren(String path, DefinitionNavigator left, DefinitionNavigator right, Base parent, ProfileComparison res) throws DefinitionException, IOException, FHIRFormatError {
boolean def = false;
List<DefinitionNavigator> lc = left.children();
List<DefinitionNavigator> rc = right.children();
// it's possible that one of these profiles walks into a data type and the other doesn't
// if it does, we have to load the children for that data into the profile that doesn't
// walk into it
if (lc.isEmpty() && !rc.isEmpty() && right.current().getType().size() == 1 && left.hasTypeChildren(right.current().getType().get(0), left.getStructure()))
lc = left.childrenFromType(right.current().getType().get(0), right.getStructure());
if (rc.isEmpty() && !lc.isEmpty() && left.current().getType().size() == 1 && right.hasTypeChildren(left.current().getType().get(0), right.getStructure()))
rc = right.childrenFromType(left.current().getType().get(0), left.getStructure());
List<DefinitionNavigator> matchR = new ArrayList<>();
for (DefinitionNavigator l : lc) {
DefinitionNavigator r = findInList(rc, l);
if (r == null) {
// todo
VersionComparisonAnnotation.markDeleted(parent, session.getForVersion(), "element", l.current());
res.updateContentState(true);
} else {
def = compareDiff(l.path(), null, l, r) || def;
def = compareDiff(l.path(), null, l, r, res) || def;
}
}
for (DefinitionNavigator r : rc) {
if (!matchR.contains(r)) {
// todo
VersionComparisonAnnotation.markAdded(r.current(), session.getForVersion());
res.updateContentState(true);
}
}
return def;

View File

@ -142,7 +142,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
def = compareCompose(left.getCompose(), right.getCompose(), res, res.getUnion().getCompose(), res.getIntersection().getCompose()) || def;
res.updateDefinitionsState(def);
compareExpansions(left, right, res);
// compareExpansions(left, right, res);
VersionComparisonAnnotation.annotate(right, session.getForVersion(), res);
return res;
}
@ -166,7 +166,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
intersection.getInclude().add(csI);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
res.getIncludes().getChildren().add(sm);
def = compareDefinitions(l, r, sm, csM, csI, res) || def;
def = compareDefinitions("ValueSet.compose.exclude["+right.getInclude().indexOf(r)+"]", l, r, sm, csM, csI, res) || def;
}
}
for (ConceptSetComponent r : right.getInclude()) {
@ -194,7 +194,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
intersection.getExclude().add(csI);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
res.getExcludes().getChildren().add(sm);
def = compareDefinitions(l, r, sm, csM, csI, res) || def;
def = compareDefinitions("ValueSet.compose.exclude["+right.getExclude().indexOf(r)+"]", l, r, sm, csM, csI, res) || def;
}
}
for (ConceptSetComponent r : right.getExclude()) {
@ -241,7 +241,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
}
private boolean compareDefinitions(ConceptSetComponent left, ConceptSetComponent right, StructuralMatch<Element> combined, ConceptSetComponent union, ConceptSetComponent intersection, ValueSetComparison res) {
private boolean compareDefinitions(String path, ConceptSetComponent left, ConceptSetComponent right, StructuralMatch<Element> combined, ConceptSetComponent union, ConceptSetComponent intersection, ValueSetComparison res) {
boolean def = false;
// system must match, but the rest might not. we're going to do the full comparison whatever, so the outcome looks consistent to the user
List<CanonicalType> matchVSR = new ArrayList<>();
@ -250,7 +250,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
if (r == null) {
union.getValueSet().add(l);
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed ValueSet", "ValueSet.compose.include.valueSet")));
combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.ERROR, "Removed ValueSet", "ValueSet.compose.include.valueSet")));
if (session.isAnnotate()) {
VersionComparisonAnnotation.markDeleted(right, session.getForVersion(), "valueset", l);
}
@ -266,7 +266,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
union.getValueSet().add(l);
union.getValueSet().add(r);
res.updateContentState(true);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, vmI(IssueSeverity.INFORMATION, "Values are different", "ValueSet.compose.include.valueSet"));
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, vmI(IssueSeverity.WARNING, "Values are different", "ValueSet.compose.include.valueSet"));
combined.getChildren().add(sm);
if (session.isAnnotate()) {
VersionComparisonAnnotation.markChanged(r, session.getForVersion());
@ -279,7 +279,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
if (!matchVSR.contains(r)) {
union.getValueSet().add(r);
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Add ValueSet", "ValueSet.compose.include.valueSet"), r));
combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.ERROR, "Add ValueSet", "ValueSet.compose.include.valueSet"), r));
VersionComparisonAnnotation.markAdded(r, session.getForVersion());
}
}
@ -290,7 +290,8 @@ public class ValueSetComparer extends CanonicalResourceComparer {
if (r == null) {
union.getConcept().add(l);
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed this Concept", "ValueSet.compose.include.concept")));
combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.ERROR, "Removed this Concept", "ValueSet.compose.include.concept")));
res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path, "Code "+l.getCode()+" removed", IssueSeverity.ERROR));
VersionComparisonAnnotation.markDeleted(right, session.getForVersion(), "concept", l);
} else {
matchCR.add(r);
@ -301,15 +302,15 @@ public class ValueSetComparer extends CanonicalResourceComparer {
intersection.getConcept().add(ci);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
combined.getChildren().add(sm);
def = compareConcepts(l, r, sm, cu, ci) || def;
def = compareConcepts(path+".concept["+right.getConcept().indexOf(r)+"]", l, r, sm, cu, ci, res) || def;
} else {
// not that it's possible to get here?
union.getConcept().add(l);
union.getConcept().add(r);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, vmI(IssueSeverity.INFORMATION, "Concepts are different", "ValueSet.compose.include.concept"));
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, vmI(IssueSeverity.WARNING, "Concepts are different", "ValueSet.compose.include.concept"));
combined.getChildren().add(sm);
res.updateContentState(true);
compareConcepts(l, r, sm, null, null);
compareConcepts(path+".concept["+right.getConcept().indexOf(r)+"]", l, r, sm, null, null, res);
VersionComparisonAnnotation.markChanged(r, session.getForVersion());
}
}
@ -318,7 +319,8 @@ public class ValueSetComparer extends CanonicalResourceComparer {
if (!matchCR.contains(r)) {
union.getConcept().add(r);
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this Concept", "ValueSet.compose.include.concept"), r));
combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.ERROR, "Added this Concept", "ValueSet.compose.include.concept"), r));
res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path, "Code "+r.getCode()+" added", IssueSeverity.ERROR));
VersionComparisonAnnotation.markAdded(r, session.getForVersion());
}
}
@ -329,7 +331,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
if (r == null) {
union.getFilter().add(l);
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed this item", "ValueSet.compose.include.filter")));
combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.ERROR, "Removed this item", "ValueSet.compose.include.filter")));
VersionComparisonAnnotation.markDeleted(right, session.getForVersion(), "filter", l);
} else {
matchFR.add(r);
@ -347,7 +349,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
} else {
union.getFilter().add(l);
union.getFilter().add(r);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, vmI(IssueSeverity.INFORMATION, "Codes are different", "ValueSet.compose.include.filter"));
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, vmI(IssueSeverity.WARNING, "Codes are different", "ValueSet.compose.include.filter"));
res.updateContentState(true);
combined.getChildren().add(sm);
compareFilters(l, r, sm, null, null);
@ -358,14 +360,14 @@ public class ValueSetComparer extends CanonicalResourceComparer {
if (!matchFR.contains(r)) {
union.getFilter().add(r);
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this item", "ValueSet.compose.include.filter"), r));
combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.ERROR, "Added this item", "ValueSet.compose.include.filter"), r));
VersionComparisonAnnotation.markAdded(r, session.getForVersion());
}
}
return def;
}
private boolean compareConcepts(ConceptReferenceComponent l, ConceptReferenceComponent r, StructuralMatch<Element> sm, ConceptReferenceComponent cu, ConceptReferenceComponent ci) {
private boolean compareConcepts(String path, ConceptReferenceComponent l, ConceptReferenceComponent r, StructuralMatch<Element> sm, ConceptReferenceComponent cu, ConceptReferenceComponent ci, ValueSetComparison res) {
boolean def = false;
sm.getChildren().add(new StructuralMatch<Element>(l.getCodeElement(), r.getCodeElement(), l.getCode().equals(r.getCode()) ? null : vmI(IssueSeverity.INFORMATION, "Codes do not match", "ValueSet.compose.include.concept")));
if (ci != null) {
@ -379,7 +381,13 @@ public class ValueSetComparer extends CanonicalResourceComparer {
cu.setDisplay(r.getDisplay());
}
def = !l.getDisplay().equals(r.getDisplay());
if (def) {
res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path, "Code "+l.getCode()+" display changed from '"+l.getDisplay()+"' to '"+r.getDisplay()+"'", IssueSeverity.WARNING));
VersionComparisonAnnotation.markChanged(r.getDisplayElement(), session.getForVersion());
}
} else if (l.hasDisplay()) {
VersionComparisonAnnotation.markDeleted(r, session.getForVersion(), "display", l.getDisplayElement());
res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path, "Code "+l.getCode()+" display '"+l.getDisplay()+"' removed", IssueSeverity.WARNING));
sm.getChildren().add(new StructuralMatch<Element>(l.getDisplayElement(), null, vmI(IssueSeverity.INFORMATION, "Display Removed", "ValueSet.compose.include.concept")));
if (ci != null) {
ci.setDisplay(l.getDisplay());
@ -387,6 +395,8 @@ public class ValueSetComparer extends CanonicalResourceComparer {
}
def = true;
} else if (r.hasDisplay()) {
VersionComparisonAnnotation.markAdded(r.getDisplayElement(), session.getForVersion());
res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path, "Code "+l.getCode()+" display '"+r.getDisplay()+"' added", IssueSeverity.WARNING));
sm.getChildren().add(new StructuralMatch<Element>(null, r.getDisplayElement(), vmI(IssueSeverity.INFORMATION, "Display added", "ValueSet.compose.include.concept")));
if (ci != null) {
ci.setDisplay(r.getDisplay());

View File

@ -9,13 +9,17 @@ import org.hl7.fhir.r5.comparison.CanonicalResourceComparer.CanonicalResourceCom
import org.hl7.fhir.r5.comparison.CanonicalResourceComparer.ChangeAnalysisState;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent;
import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
public class VersionComparisonAnnotation {
public enum AnotationType {
NoChange, Added, Changed, Deleted;
NoChange, Added, Changed, ChildrenDeleted, Deleted;
}
public static final String USER_DATA_NAME = "version-annotation";
@ -37,7 +41,7 @@ public class VersionComparisonAnnotation {
public static void annotate(Base base, String version, CanonicalResourceComparison<? extends CanonicalResource> comp) {
if (version != null) {
VersionComparisonAnnotation vca = new VersionComparisonAnnotation(comp.noChange() ? AnotationType.NoChange : AnotationType.Added, version);
VersionComparisonAnnotation vca = new VersionComparisonAnnotation(comp.noUpdates() ? AnotationType.NoChange : AnotationType.Changed, version);
vca.comp = comp;
base.setUserData(USER_DATA_NAME, vca);
}
@ -57,13 +61,13 @@ public class VersionComparisonAnnotation {
}
public static void markDeleted(Base parent, String version, String name, Base other) {
if (version != null) {
if (version != null && other != null) {
VersionComparisonAnnotation vca = null;
if (parent.hasUserData(USER_DATA_NAME)) {
vca = (VersionComparisonAnnotation) parent.getUserData(USER_DATA_NAME);
assert vca.type != AnotationType.Added;
} else {
vca = new VersionComparisonAnnotation(AnotationType.Changed, version);
vca = new VersionComparisonAnnotation(AnotationType.ChildrenDeleted, version);
parent.setUserData(USER_DATA_NAME, vca);
}
if (vca.deletedChildren == null) {
@ -103,13 +107,13 @@ public class VersionComparisonAnnotation {
return spanOuter;
case Changed:
spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null);
spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version);
spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been changed since "+version);
spanInner.img("icon-change-edit.png", "icon");
spanInner.tx(" Changed:");
return spanOuter;
case Deleted:
spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null);
spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version);
spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been removed since "+version);
spanInner.img("icon-change-remove.png", "icon");
spanInner.tx(" Removed:");
return spanOuter.strikethrough();
@ -137,13 +141,13 @@ public class VersionComparisonAnnotation {
return divOuter;
case Changed:
divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px");
spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version);
spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been changed since "+version);
spanInner.img("icon-change-edit.png", "icon");
spanInner.tx(" Changed:");
return divOuter;
case Deleted:
divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px");
spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version);
spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been removed since "+version);
spanInner.img("icon-change-remove.png", "icon");
spanInner.tx(" Removed:");
return divOuter.strikethrough();
@ -151,6 +155,51 @@ public class VersionComparisonAnnotation {
return x;
}
}
public static XhtmlNode renderRow(Base b, XhtmlNode tbl, XhtmlNode tr) {
if (b.hasUserData(USER_DATA_NAME)) {
VersionComparisonAnnotation self = (VersionComparisonAnnotation) b.getUserData(USER_DATA_NAME);
return self.renderRow(tbl, tr);
} else {
return tr.td();
}
}
private XhtmlNode renderRow(XhtmlNode tbl, XhtmlNode tr) {
switch (type) {
case Added:
if (tbl.isClass("grid")) {
tr.style("border: solid 1px #dddddd; margin: 2px; padding: 2px");
}
XhtmlNode td = tr.td();
XhtmlNode span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This row of content has been added since "+version);
span.img("icon-change-add.png", "icon");
span.tx(" Added:");
XhtmlNode x = new XhtmlNode(NodeType.Element, "holder");
x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This row of content has been added since "+version).tx(" ");
tr.styleCells(x);
return td;
case Changed:
td = tr.td();
span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This row of content has been changed since "+version);
span.img("icon-change-edit.png", "icon");
span.tx(" Changed:");
return td;
case Deleted:
tr.style("text-decoration: line-through");
td = tr.td();
span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been removed since "+version);
span.img("icon-change-remove.png", "icon");
span.tx(" Removed:");
x = new XhtmlNode(NodeType.Element, "holder");
x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px; text-decoration: none", "This row of content has been added since "+version).tx(" ");
tr.styleCells(x);
return td;
default:
return tr.td();
}
}
public static boolean hasDeleted(Base base, String... names) {
boolean result = false;
@ -178,6 +227,18 @@ public class VersionComparisonAnnotation {
return result;
}
public static Base getDeletedItem(Base base, String name) {
List<Base> result = new ArrayList<>();
if (base.hasUserData(USER_DATA_NAME)) {
VersionComparisonAnnotation self = (VersionComparisonAnnotation) base.getUserData(USER_DATA_NAME);
if (self.deletedChildren != null && self.deletedChildren.containsKey(name)) {
result.addAll(self.deletedChildren.get(name));
}
}
return result.isEmpty() ? null : result.get(0);
}
public static CanonicalResourceComparison<? extends CanonicalResource> artifactComparison(Base base) {
if (base.hasUserData(USER_DATA_NAME)) {
VersionComparisonAnnotation self = (VersionComparisonAnnotation) base.getUserData(USER_DATA_NAME);
@ -186,5 +247,34 @@ public class VersionComparisonAnnotation {
return null;
}
}
public static void renderSummary(Base base, XhtmlNode x, String version) {
if (base.hasUserData(USER_DATA_NAME)) {
VersionComparisonAnnotation self = (VersionComparisonAnnotation) base.getUserData(USER_DATA_NAME);
switch (self.type) {
case Added:
XhtmlNode spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version);
spanInner.img("icon-change-add.png", "icon");
spanInner.tx(" Added");
return;
case Changed:
spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version);
spanInner.img("icon-change-edit.png", "icon");
spanInner.tx(" Changed");
return;
case Deleted:
spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version);
spanInner.img("icon-change-remove.png", "icon");
spanInner.tx(" Removed");
return;
default:
x.span("color: #eeeeee").tx("n/c");
return;
}
} else {
x.span("color: #eeeeee").tx("--");
}
}
}

View File

@ -398,7 +398,7 @@ public class CodeSystemRenderer extends TerminologyRenderer {
tr.setAttribute("style", "background-color: #ffeeee");
}
XhtmlNode td = tr.td();
XhtmlNode td = VersionComparisonAnnotation.renderRow(c, t, tr);
if (hasHierarchy) {
td.addText(Integer.toString(level+1));
td = tr.td();
@ -407,9 +407,9 @@ public class CodeSystemRenderer extends TerminologyRenderer {
}
String link = isSupplement ? getLinkForCode(cs.getSupplements(), null, c.getCode()) : null;
if (link != null) {
td.ah(link).attribute("style", "white-space:nowrap").addText(c.getCode());
td.ah(link).style( "white-space:nowrap").addText(c.getCode());
} else {
VersionComparisonAnnotation.render(c, td.attribute("style", "white-space:nowrap")).addText(c.getCode());
td.style("white-space:nowrap").addText(c.getCode());
}
XhtmlNode a;
if (c.hasCodeElement()) {
@ -579,7 +579,7 @@ public class CodeSystemRenderer extends TerminologyRenderer {
td = tr.td();
String s = Utilities.padLeft("", '\u00A0', (level+1)*2);
td.addText(s);
td.attribute("style", "white-space:nowrap");
td.style("white-space:nowrap");
a = td.ah("#"+cs.getId()+"-" + Utilities.nmtokenize(cc.getCode()));
a.addText(cc.getCode());
if (hasDisplay) {

View File

@ -30,6 +30,7 @@ import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.ExtensionHelper;
import org.hl7.fhir.r5.model.PrimitiveType;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.r5.model.UriType;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.model.ValueSet.ConceptPropertyComponent;
@ -57,6 +58,7 @@ import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.ibm.icu.impl.locale.StringTokenIterator;
public class ValueSetRenderer extends TerminologyRenderer {
@ -1175,50 +1177,10 @@ public class ValueSetRenderer extends TerminologyRenderer {
hasExtensions = true;
addMapHeaders(addTableHeaderRowStandard(t, false, true, hasDefinition, hasComments, false, false, null, langs, designations, doDesignations), maps);
for (ConceptReferenceComponent c : inc.getConcept()) {
XhtmlNode tr = t.tr();
XhtmlNode td = tr.td();
ConceptDefinitionComponent cc = definitions == null ? null : definitions.get(c.getCode());
addCodeToTable(false, inc.getSystem(), c.getCode(), c.hasDisplay()? c.getDisplay() : cc != null ? cc.getDisplay() : "", td);
td = tr.td();
if (!Utilities.noString(c.getDisplay()))
td.addText(c.getDisplay());
else if (cc != null && !Utilities.noString(cc.getDisplay()))
td.addText(cc.getDisplay());
if (hasDefinition) {
td = tr.td();
if (ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_DEFINITION)) {
smartAddText(td, ToolingExtensions.readStringExtension(c, ToolingExtensions.EXT_DEFINITION));
} else if (cc != null && !Utilities.noString(cc.getDefinition())) {
smartAddText(td, cc.getDefinition());
}
}
if (hasComments) {
td = tr.td();
if (ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_VS_COMMENT)) {
smartAddText(td, "Note: "+ToolingExtensions.readStringExtension(c, ToolingExtensions.EXT_VS_COMMENT));
}
}
if (doDesignations) {
addDesignationsToRow(c, designations, tr);
addLangaugesToRow(c, langs, tr);
}
for (UsedConceptMap m : maps) {
td = tr.td();
List<TargetElementComponentWrapper> mappings = findMappingsForCode(c.getCode(), m.getMap());
boolean first = true;
for (TargetElementComponentWrapper mapping : mappings) {
if (!first)
td.br();
first = false;
XhtmlNode span = td.span(null, mapping.comp.getRelationship().toString());
span.addText(getCharForRelationship(mapping.comp));
addRefToCode(td, mapping.group.getTarget(), m.getLink(), mapping.comp.getCode());
if (!Utilities.noString(mapping.comp.getComment()))
td.i().tx("("+mapping.comp.getComment()+")");
}
}
renderConcept(inc, langs, doDesignations, maps, designations, definitions, t, hasComments, hasDefinition, c);
}
for (Base b : VersionComparisonAnnotation.getDeleted(inc, "concept" )) {
renderConcept(inc, langs, doDesignations, maps, designations, definitions, t, hasComments, hasDefinition, (ConceptReferenceComponent) b);
}
}
if (inc.getFilter().size() > 0) {
@ -1306,6 +1268,58 @@ public class ValueSetRenderer extends TerminologyRenderer {
return hasExtensions;
}
private void renderConcept(ConceptSetComponent inc, List<String> langs, boolean doDesignations,
List<UsedConceptMap> maps, Map<String, String> designations, Map<String, ConceptDefinitionComponent> definitions,
XhtmlNode t, boolean hasComments, boolean hasDefinition, ConceptReferenceComponent c) {
XhtmlNode tr = t.tr();
XhtmlNode td = VersionComparisonAnnotation.renderRow(c, t, tr);
ConceptDefinitionComponent cc = definitions == null ? null : definitions.get(c.getCode());
addCodeToTable(false, inc.getSystem(), c.getCode(), c.hasDisplay()? c.getDisplay() : cc != null ? cc.getDisplay() : "", td);
td = tr.td();
if (!Utilities.noString(c.getDisplay()))
VersionComparisonAnnotation.render(c.getDisplayElement(), td).addText(c.getDisplay());
else if (VersionComparisonAnnotation.hasDeleted(c, "display")) {
StringType d = (StringType) VersionComparisonAnnotation.getDeletedItem(c, "display");
VersionComparisonAnnotation.render(d, td).addText(d.primitiveValue());
} else if (cc != null && !Utilities.noString(cc.getDisplay()))
td.style("color: #cccccc").addText(cc.getDisplay());
if (hasDefinition) {
td = tr.td();
if (ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_DEFINITION)) {
smartAddText(td, ToolingExtensions.readStringExtension(c, ToolingExtensions.EXT_DEFINITION));
} else if (cc != null && !Utilities.noString(cc.getDefinition())) {
smartAddText(td, cc.getDefinition());
}
}
if (hasComments) {
td = tr.td();
if (ExtensionHelper.hasExtension(c, ToolingExtensions.EXT_VS_COMMENT)) {
smartAddText(td, "Note: "+ToolingExtensions.readStringExtension(c, ToolingExtensions.EXT_VS_COMMENT));
}
}
if (doDesignations) {
addDesignationsToRow(c, designations, tr);
addLangaugesToRow(c, langs, tr);
}
for (UsedConceptMap m : maps) {
td = tr.td();
List<TargetElementComponentWrapper> mappings = findMappingsForCode(c.getCode(), m.getMap());
boolean first = true;
for (TargetElementComponentWrapper mapping : mappings) {
if (!first)
td.br();
first = false;
XhtmlNode span = td.span(null, mapping.comp.getRelationship().toString());
span.addText(getCharForRelationship(mapping.comp));
addRefToCode(td, mapping.group.getTarget(), m.getLink(), mapping.comp.getCode());
if (!Utilities.noString(mapping.comp.getComment()))
td.i().tx("("+mapping.comp.getComment()+")");
}
}
}
public void addDesignationsToRow(ConceptReferenceComponent c, Map<String, String> designations, XhtmlNode tr) {
for (String url : designations.keySet()) {
String d = null;

View File

@ -79,6 +79,7 @@ public class RenderingContext {
SUMMARY, // 5 cells: tree/name | flags | cardinality | type | details
BINDINGS, // tree/name + column for each kind of binding found, cells are lists of bindings
OBLIGATIONS, // tree/name + column for each actor that has obligations
DATA_DICT, // detailed element view
}
public enum ExampleScenarioRendererMode {
@ -132,6 +133,7 @@ public class RenderingContext {
LINKS
}
public enum KnownLinkType {
SELF, // absolute link to where the content is to be found (only used in a few circumstances when making external references to tools)
SPEC, // version specific link to core specification

View File

@ -138,8 +138,8 @@ public class DefinitionNavigator {
if (nameMap.containsKey(path)) {
DefinitionNavigator master = nameMap.get(path);
ElementDefinition cm = master.current();
if (!cm.hasSlicing())
throw new DefinitionException("Found slices with no slicing details at "+dn.current().getPath());
// if (!cm.hasSlicing())
// throw new DefinitionException("Found slices with no slicing details at "+dn.current().getPath());
if (master.slices == null)
master.slices = new ArrayList<DefinitionNavigator>();
master.slices.add(dn);

View File

@ -966,6 +966,11 @@ public class I18nConstants {
public static final String MSG_DEPENDS_ON_EXTENSION = "MSG_DEPENDS_ON_EXTENSION";
public static final String MSG_DEPENDS_ON_PROFILE = "MSG_DEPENDS_ON_PROFILE";
public static final String VALIDATION_VAL_STATUS_INCONSISTENT = "VALIDATION_VAL_STATUS_INCONSISTENT";
public static final String CODESYSTEM_CS_COUNT_COMPLETE_WRONG = "CODESYSTEM_CS_COUNT_COMPLETE_WRONG";
public static final String CODESYSTEM_CS_COUNT_FRAGMENT_WRONG = "CODESYSTEM_CS_COUNT_FRAGMENT_WRONG";
public static final String CODESYSTEM_CS_COUNT_NOTPRESENT_ZERO = "CODESYSTEM_CS_COUNT_NOTPRESENT_ZERO";
public static final String CODESYSTEM_CS_COUNT_SUPPLEMENT_WRONG = "CODESYSTEM_CS_COUNT_SUPPLEMENT_WRONG";
public static final String CODESYSTEM_CS_COUNT_NO_CONTENT_ALLOWED = "CODESYSTEM_CS_COUNT_NO_CONTENT_ALLOWED";
}

View File

@ -54,7 +54,8 @@ public abstract class XhtmlFluent {
}
public XhtmlNode td() {
return addTag("td");
XhtmlNode x = addTag("td");
return x;
}
public XhtmlNode td(String clss) {

View File

@ -363,6 +363,10 @@ public class XhtmlNode extends XhtmlFluent implements IBaseXhtml {
return hasAttributes() && getAttributes().containsKey(name);
}
public boolean hasAttribute(String name, String value) {
return hasAttributes() && getAttributes().containsKey(name) && value.equals(getAttributes().get(name));
}
public String getAttribute(String name) {
return hasAttributes() ? getAttributes().get(name) : null;
}
@ -569,18 +573,25 @@ public class XhtmlNode extends XhtmlFluent implements IBaseXhtml {
throw new UnsupportedOperationException();
}
/**
* NOT SUPPORTED - Throws {@link UnsupportedOperationException}
*/
private Map<String, Object> userData;
public Object getUserData(String theName) {
throw new UnsupportedOperationException();
if (hasUserData(theName)) {
return userData.get(theName);
} else {
return null;
}
}
public boolean hasUserData(String theName) {
return userData != null && userData.containsKey(theName);
}
/**
* NOT SUPPORTED - Throws {@link UnsupportedOperationException}
*/
public void setUserData(String theName, Object theValue) {
throw new UnsupportedOperationException();
if (userData == null) {
userData = new HashMap<>();
}
userData.put(theName, theValue);
}
@ -892,4 +903,23 @@ public class XhtmlNode extends XhtmlFluent implements IBaseXhtml {
}
}
public boolean isClass(String name) {
return hasAttribute("class", name);
}
public void styleCells(XhtmlNode x) {
setUserData("cells", x);
}
public XhtmlNode td() {
XhtmlNode x = addTag("td");
XhtmlNode t = (XhtmlNode) getUserData("cells");
if (t != null) {
x.copyAllContent(t);
}
return x;
}
}

View File

@ -1024,5 +1024,11 @@ MSG_DEPENDS_ON_EXPERIMENTAL = The {0} {1} is an experimental resource
MSG_DEPENDS_ON_DRAFT = The {0} {1} is a draft resource
MSG_DEPENDS_ON_EXTENSION = extension
MSG_DEPENDS_ON_PROFILE = profile
VALIDATION_VAL_STATUS_INCONSISTENT = The resource status ''{0}'' amd the standards status ''{1}'' are not consistent
VALIDATION_VAL_STATUS_INCONSISTENT = The resource status ''{0}'' and the standards status ''{1}'' are not consistent
CODESYSTEM_CS_COUNT_COMPLETE_WRONG = The code system is complete, but the number of concepts ({0}) does not match the stated total number ({1})
CODESYSTEM_CS_COUNT_FRAGMENT_WRONG = The code system is a fragment/example, but the number of concepts ({0}) exceeds or matches the stated total number ({1})
CODESYSTEM_CS_COUNT_NOTPRESENT_ZERO = The code system has no content, but the exceeds the stated total number is 0 concepts - check that this isn't a complete code system that has no concepts, or update/remove the stated count
CODESYSTEM_CS_COUNT_SUPPLEMENT_WRONG = The code system supplement states the total number of concepts as {1}, but this is different to the underlying code system that states a value of {0}
CODESYSTEM_CS_COUNT_NO_CONTENT_ALLOWED = The code system says it has no content present, but concepts are found

View File

@ -194,7 +194,12 @@
<optional>true</optional>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.atlassian.commonmark</groupId>
<artifactId>commonmark-ext-gfm-tables</artifactId>
<optional>true</optional>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>

View File

@ -6,6 +6,7 @@ import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.elementmodel.Element;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.utils.XVerExtensionManager;
import org.hl7.fhir.utilities.Utilities;
@ -32,8 +33,9 @@ public class CodeSystemValidator extends BaseValidator {
String caseSensitive = cs.getNamedChildValue("caseSensitive");
String hierarchyMeaning = cs.getNamedChildValue("hierarchyMeaning");
String supp = cs.getNamedChildValue("supplements");
metaChecks(errors, cs, stack, url, content, caseSensitive, hierarchyMeaning, !Utilities.noString(supp));
int count = countConcepts(cs);
metaChecks(errors, cs, stack, url, content, caseSensitive, hierarchyMeaning, !Utilities.noString(supp), count, supp);
String vsu = cs.getNamedChildValue("valueSet");
if (!Utilities.noString(vsu)) {
@ -51,7 +53,6 @@ public class CodeSystemValidator extends BaseValidator {
ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), !vs.getCompose().getInclude().get(0).hasValueSet()
&& !vs.getCompose().getInclude().get(0).hasConcept() && !vs.getCompose().getInclude().get(0).hasFilter(), I18nConstants.CODESYSTEM_CS_VS_INCLUDEDETAILS, url, vsu) && ok;
if (vs.hasExpansion()) {
int count = countConcepts(cs);
ok = rule(errors, NO_RULE_DATE, IssueType.BUSINESSRULE, stack.getLiteralPath(), vs.getExpansion().getContains().size() == count, I18nConstants.CODESYSTEM_CS_VS_EXP_MISMATCH, url, vsu, count, vs.getExpansion().getContains().size()) && ok;
}
} else {
@ -71,7 +72,11 @@ public class CodeSystemValidator extends BaseValidator {
List<Element> concepts = cs.getChildrenByName("concept");
int ce = 0;
for (Element concept : concepts) {
ok = validateSupplementConcept(errors, concept, stack.push(concept, ce, null, null), supp, options) && ok;
NodeStack nstack = stack.push(concept, ce, null, null);
if (ce == 0) {
rule(errors, "2023-08-15", IssueType.INVALID, nstack, !"not-present".equals(content), I18nConstants.CODESYSTEM_CS_COUNT_NO_CONTENT_ALLOWED);
}
ok = validateSupplementConcept(errors, concept, nstack, supp, options) && ok;
ce++;
}
} else {
@ -121,7 +126,7 @@ public class CodeSystemValidator extends BaseValidator {
return true;
}
private void metaChecks(List<ValidationMessage> errors, Element cs, NodeStack stack, String url, String content, String caseSensitive, String hierarchyMeaning, boolean isSupplement) {
private void metaChecks(List<ValidationMessage> errors, Element cs, NodeStack stack, String url, String content, String caseSensitive, String hierarchyMeaning, boolean isSupplement, int count, String supp) {
if (isSupplement) {
if (!"supplement".equals(content)) {
NodeStack s = stack.push(cs.getNamedChild("content"), -1, null, null);
@ -178,6 +183,40 @@ public class CodeSystemValidator extends BaseValidator {
}
}
}
if (cs.hasChild("count")) {
int statedCount = Utilities.parseInt(cs.getNamedChildValue("count"), -1);
if (statedCount > -1 && content != null) { // error elsewhere
var nstack = stack.push(cs.getNamedChild("count"), -1, null, null);
switch (content) {
case "complete":
rule(errors, "2023-08-15", IssueType.INVALID, nstack, count == statedCount, I18nConstants.CODESYSTEM_CS_COUNT_COMPLETE_WRONG, count, statedCount);
break;
case "example":
case "fragment":
warning(errors, "2023-08-15", IssueType.INVALID, nstack, count < statedCount, I18nConstants.CODESYSTEM_CS_COUNT_FRAGMENT_WRONG, count, statedCount);
break;
case "not-present":
hint(errors, "2023-08-15", IssueType.INVALID, stack.push(cs.getNamedChild("concept"), -1, null, null), statedCount > 0, I18nConstants.CODESYSTEM_CS_COUNT_NOTPRESENT_ZERO, statedCount);
break;
case "supplement":
CodeSystem css = context.fetchCodeSystem(supp);
if (css != null) {
rule(errors, "2023-08-15", IssueType.INVALID, nstack, count == css.getCount(), I18nConstants.CODESYSTEM_CS_COUNT_SUPPLEMENT_WRONG, css.getCount(), statedCount);
}
break;
default:
// do nothing
}
}
}
if ("not-present".equals(content)) {
List<Element> concepts = cs.getChildrenByName("concept");
if (concepts.size() > 0) {
rule(errors, "2023-08-15", IssueType.INVALID, stack.push(concepts.get(0), 0, null, null), false, I18nConstants.CODESYSTEM_CS_COUNT_NO_CONTENT_ALLOWED);
}
}
}

View File

@ -10,7 +10,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Stream;
import org.apache.commons.io.IOUtils;
@ -45,10 +45,22 @@ import org.hl7.fhir.r5.model.Constants;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.renderers.CodeSystemRenderer;
import org.hl7.fhir.r5.renderers.StructureDefinitionRenderer;
import org.hl7.fhir.r5.renderers.ValueSetRenderer;
import org.hl7.fhir.r5.renderers.utils.RenderingContext;
import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules;
import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode;
import org.hl7.fhir.r5.renderers.utils.RenderingContext.StructureDefinitionRendererMode;
import org.hl7.fhir.r5.test.utils.CompareUtilities;
import org.hl7.fhir.r5.test.utils.TestingUtilities;
import org.hl7.fhir.utilities.MarkDownProcessor;
import org.hl7.fhir.utilities.MarkDownProcessor.Dialect;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.json.model.*;
import org.hl7.fhir.utilities.json.parser.*;
import org.hl7.fhir.utilities.npm.CommonPackages;
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
import org.hl7.fhir.utilities.npm.NpmPackage;
@ -56,14 +68,13 @@ import org.hl7.fhir.utilities.settings.FhirSettings;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import java.nio.charset.StandardCharsets;
import com.google.common.base.Charsets;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
public class ComparisonTests {
@ -73,9 +84,9 @@ public class ComparisonTests {
String contents = TestingUtilities.loadTestResource("comparison", "manifest.json");
Map<String, JsonObject> examples = new HashMap<String, JsonObject>();
manifest = (JsonObject) new com.google.gson.JsonParser().parse(contents);
for (Entry<String, JsonElement> e : manifest.getAsJsonObject("test-cases").entrySet()) {
examples.put(e.getKey(), e.getValue().getAsJsonObject());
manifest = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(contents);
for (JsonProperty e : manifest.getJsonObject("test-cases").getProperties()) {
examples.put(e.getName(), e.getValue().asJsonObject());
}
List<String> names = new ArrayList<String>(examples.size());
@ -97,6 +108,8 @@ public class ComparisonTests {
private static final String HEADER = "<html><link href=\"http://hl7.org/fhir/fhir.css\" rel=\"stylesheet\"/><body>";
private static final String BREAK = "<hr/>";
private static final String FOOTER = "</body></html>";
private String prefix;
private String suffix;
@ParameterizedTest(name = "{index}: id {0}")
@MethodSource("data")
@ -104,7 +117,7 @@ public class ComparisonTests {
TestingUtilities.injectCorePackageLoader();
this.content = content;
if (content.has("use-test") && !content.get("use-test").getAsBoolean())
if (content.has("use-test") && !content.asBoolean("use-test"))
return;
if (context == null) {
@ -132,9 +145,17 @@ public class ComparisonTests {
System.out.println("---- " + name + " ----------------------------------------------------------------");
CanonicalResource left = load("left");
CanonicalResource right = load("right");
prefix = loadResource("html-prefix.html");
suffix = loadResource("html-suffix.html");
ComparisonSession session = new ComparisonSession(context, context, "Comparison Tests", null, null);
if (content.has("version")) {
session.setForVersion(content.getJsonObject("version").asString("stated"));
session.setAnnotate(true);
}
RenderingContext lrc = new RenderingContext(context, new MarkDownProcessor(Dialect.COMMON_MARK), null, "http://hl7.org/fhir", "", "en", ResourceRendererMode.TECHNICAL, GenerationRules.IG_PUBLISHER);
lrc.setDestDir(Utilities.path("[tmp]", "comparison"));
if (left instanceof CodeSystem && right instanceof CodeSystem) {
CodeSystemComparer cs = new CodeSystemComparer(session);
CodeSystemComparison csc = cs.compare((CodeSystem) left, (CodeSystem) right);
@ -147,6 +168,8 @@ public class ComparisonTests {
String xml2 = new XhtmlComposer(true).compose(cs.renderConcepts(csc, "", ""));
TextFile.stringToFile(HEADER + hd("Messages") + xmle + BREAK + hd("Metadata") + xml1 + BREAK + hd("Concepts") + xml2 + FOOTER, Utilities.path("[tmp]", "comparison", name + ".html"));
checkOutcomes(csc.getMessages(), content);
new CodeSystemRenderer(lrc).render(right);
checkOutput(content.getJsonObject("version").asString("filename"), right);
} else if (left instanceof ValueSet && right instanceof ValueSet) {
ValueSetComparer cs = new ValueSetComparer(session);
ValueSetComparison csc = cs.compare((ValueSet) left, (ValueSet) right);
@ -159,6 +182,8 @@ public class ComparisonTests {
String xml3 = new XhtmlComposer(true).compose(cs.renderExpansion(csc, "", ""));
TextFile.stringToFile(HEADER + hd("Messages") + xmle + BREAK + hd("Metadata") + xml1 + BREAK + hd("Definition") + xml2 + BREAK + hd("Expansion") + xml3 + FOOTER, Utilities.path("[tmp]", "comparison", name + ".html"));
checkOutcomes(csc.getMessages(), content);
new ValueSetRenderer(lrc).render(right);
checkOutput(content.getJsonObject("version").asString("filename"), right);
} else if (left instanceof StructureDefinition && right instanceof StructureDefinition) {
ProfileUtilities utils = new ProfileUtilities(context, null, null);
genSnapshot(utils, (StructureDefinition) left);
@ -174,6 +199,14 @@ public class ComparisonTests {
// String xml3 = new XhtmlComposer(true).compose(cs.renderExpansion(csc, "", ""));
TextFile.stringToFile(HEADER + hd("Messages") + xmle + BREAK + hd("Metadata") + xml1 + BREAK + hd("Structure") + xml2 + FOOTER, Utilities.path("[tmp]", "comparison", name + ".html"));
checkOutcomes(csc.getMessages(), content);
lrc.setStructureMode(StructureDefinitionRendererMode.DATA_DICT);
new StructureDefinitionRenderer(lrc).render(right);
checkOutput(content.getJsonObject("version").asString("filename-dd"), right);
lrc.setStructureMode(StructureDefinitionRendererMode.SUMMARY);
new StructureDefinitionRenderer(lrc).render(right);
checkOutput(content.getJsonObject("version").asString("filename-tree"), right);
} else if (left instanceof CapabilityStatement && right instanceof CapabilityStatement) {
CapabilityStatementComparer pc = new CapabilityStatementComparer(session);
CapabilityStatementComparison csc = pc.compare((CapabilityStatement) left, (CapabilityStatement) right);
@ -191,6 +224,18 @@ public class ComparisonTests {
}
}
private void checkOutput(String name, CanonicalResource right) throws Exception {
String output = prefix+ new XhtmlComposer(false, true).compose(right.getText().getDiv()) + suffix;
String an = Utilities.path("[tmp]", "comparison", name);
TextFile.stringToFile(output, an);
String expected = loadResource(name);
String en = Utilities.path("[tmp]", "comparison", Utilities.changeFileExt(name, ".expected.html"));
TextFile.stringToFile(expected, en);
String msg = CompareUtilities.checkXMLIsSame(en, an);
Assertions.assertTrue(msg == null, "Output does not match expected: "+msg);
}
private void genSnapshot(ProfileUtilities utils, StructureDefinition sd) {
StructureDefinition base = context.fetchTypeDefinition(sd.getType());
utils.generateSnapshot(base, sd, sd.getUrl(), "http://hl7.org/fhir/r4", sd.present());
@ -201,13 +246,18 @@ public class ComparisonTests {
}
private CanonicalResource load(String name) throws IOException {
JsonObject details = content.getAsJsonObject(name);
String src = TestingUtilities.loadTestResource("comparison", details.get("source").getAsString());
return (CanonicalResource) loadResource(details.get("source").getAsString(), src, details.get("version").getAsString());
JsonObject details = content.getJsonObject(name);
String src = TestingUtilities.loadTestResource("comparison", details.asString("source"));
return (CanonicalResource) loadResource(details.asString("source"), src, details.asString("version"));
}
private String loadResource(String name) throws IOException {
String src = TestingUtilities.loadTestResource("comparison", name);
return src;
}
public Resource loadResource(String filename, String contents, String ver) throws IOException, FHIRFormatError, FileNotFoundException, FHIRException, DefinitionException {
try (InputStream inputStream = IOUtils.toInputStream(contents, Charsets.UTF_8)) {
try (InputStream inputStream = IOUtils.toInputStream(contents, StandardCharsets.UTF_8)) {
if (filename.contains(".json")) {
if (Constants.VERSION.equals(ver) || "5.0".equals(ver))
return new JsonParser().parse(inputStream);
@ -239,7 +289,7 @@ public class ComparisonTests {
}
private void checkOutcomes(List<ValidationMessage> errors, JsonObject focus) {
JsonObject output = focus.getAsJsonObject("output");
JsonObject output = focus.getJsonObject("output");
int ec = 0;
int wc = 0;
int hc = 0;
@ -265,11 +315,11 @@ public class ComparisonTests {
}
}
}
Assertions.assertEquals(output.get("errorCount").getAsInt(), ec, "Expected " + Integer.toString(output.get("errorCount").getAsInt()) + " errors, but found " + Integer.toString(ec) + ".");
Assertions.assertEquals(output.asInteger("errorCount"), ec, "Expected " + Integer.toString(output.asInteger("errorCount")) + " errors, but found " + Integer.toString(ec) + ".");
if (output.has("warningCount"))
Assertions.assertEquals(output.get("warningCount").getAsInt(), wc, "Expected " + Integer.toString(output.get("warningCount").getAsInt()) + " warnings, but found " + Integer.toString(wc) + ".");
Assertions.assertEquals(output.asInteger("warningCount"), wc, "Expected " + Integer.toString(output.asInteger("warningCount")) + " warnings, but found " + Integer.toString(wc) + ".");
if (output.has("infoCount"))
Assertions.assertEquals(output.get("infoCount").getAsInt(), hc, "Expected " + Integer.toString(output.get("infoCount").getAsInt()) + " hints, but found " + Integer.toString(hc) + ".");
Assertions.assertEquals(output.asInteger("infoCount"), hc, "Expected " + Integer.toString(output.asInteger("infoCount")) + " hints, but found " + Integer.toString(hc) + ".");
}
}

View File

@ -0,0 +1,57 @@
-------------------------------------------------------------------------------------
{"hierarchical" : false, "valueSet" :{
"resourceType" : "ValueSet",
"compose" : {
"inactive" : true,
"include" : [{
"system" : "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation",
"concept" : [{
"code" : "LL"
},
{
"code" : "HH"
},
{
"code" : "L",
"display" : "Extra Low"
},
{
"code" : "H"
}]
}]
}
}}####
e: {
"error" : "Cannot invoke \"org.hl7.fhir.r5.terminologies.client.ITerminologyClient.expandValueset(org.hl7.fhir.r5.model.ValueSet, org.hl7.fhir.r5.model.Parameters, java.util.Map)\" because the return value of \"org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.getClient()\" is null"
}
-------------------------------------------------------------------------------------
{"hierarchical" : false, "valueSet" :{
"resourceType" : "ValueSet",
"compose" : {
"inactive" : true,
"include" : [{
"system" : "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation",
"concept" : [{
"code" : "LL"
},
{
"code" : "HH"
},
{
"code" : "L",
"display" : "Extra Low"
},
{
"code" : "H",
"display" : "higher"
},
{
"code" : "P"
}]
}]
}
}}####
e: {
"error" : "Cannot invoke \"org.hl7.fhir.r5.terminologies.client.ITerminologyClient.expandValueset(org.hl7.fhir.r5.model.ValueSet, org.hl7.fhir.r5.model.Parameters, java.util.Map)\" because the return value of \"org.hl7.fhir.r5.terminologies.client.TerminologyClientContext.getClient()\" is null"
}
-------------------------------------------------------------------------------------