diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CanonicalResourceComparer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CanonicalResourceComparer.java
index 2bc03621c..7beb420dc 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CanonicalResourceComparer.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CanonicalResourceComparer.java
@@ -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) {
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CodeSystemComparer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CodeSystemComparer.java
index ad19c6dea..9689e3aa3 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CodeSystemComparer.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/CodeSystemComparer.java
@@ -33,8 +33,8 @@ public class CodeSystemComparer extends CanonicalResourceComparer {
public class CodeSystemComparison extends CanonicalResourceComparison {
- private StructuralMatch properties;
- private StructuralMatch filters;
+ private StructuralMatch properties = new StructuralMatch();
+ private StructuralMatch filters = new StructuralMatch();
private StructuralMatch combined;
private Map propMap = new HashMap<>(); // right to left; left retains it's name
public CodeSystemComparison(CodeSystem left, CodeSystem right) {
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonSession.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonSession.java
index 5d85b5298..2c7b9f253 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonSession.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ComparisonSession.java
@@ -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);
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ProfileComparer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ProfileComparer.java
index 8d5127216..ca6d9ebdf 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ProfileComparer.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ProfileComparer.java
@@ -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 lc = left.children();
List 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 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;
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ValueSetComparer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ValueSetComparer.java
index 7b6914218..5394f14fe 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ValueSetComparer.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/ValueSetComparer.java
@@ -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 sm = new StructuralMatch(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 sm = new StructuralMatch(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 combined, ConceptSetComponent union, ConceptSetComponent intersection, ValueSetComparison res) {
+ private boolean compareDefinitions(String path, ConceptSetComponent left, ConceptSetComponent right, StructuralMatch 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 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(l, vmI(IssueSeverity.INFORMATION, "Removed ValueSet", "ValueSet.compose.include.valueSet")));
+ combined.getChildren().add(new StructuralMatch(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 sm = new StructuralMatch(l, r, vmI(IssueSeverity.INFORMATION, "Values are different", "ValueSet.compose.include.valueSet"));
+ StructuralMatch sm = new StructuralMatch(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(vmI(IssueSeverity.INFORMATION, "Add ValueSet", "ValueSet.compose.include.valueSet"), r));
+ combined.getChildren().add(new StructuralMatch(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(l, vmI(IssueSeverity.INFORMATION, "Removed this Concept", "ValueSet.compose.include.concept")));
+ combined.getChildren().add(new StructuralMatch(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 sm = new StructuralMatch(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 sm = new StructuralMatch(l, r, vmI(IssueSeverity.INFORMATION, "Concepts are different", "ValueSet.compose.include.concept"));
+ StructuralMatch sm = new StructuralMatch(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(vmI(IssueSeverity.INFORMATION, "Added this Concept", "ValueSet.compose.include.concept"), r));
+ combined.getChildren().add(new StructuralMatch(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(l, vmI(IssueSeverity.INFORMATION, "Removed this item", "ValueSet.compose.include.filter")));
+ combined.getChildren().add(new StructuralMatch(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 sm = new StructuralMatch(l, r, vmI(IssueSeverity.INFORMATION, "Codes are different", "ValueSet.compose.include.filter"));
+ StructuralMatch sm = new StructuralMatch(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(vmI(IssueSeverity.INFORMATION, "Added this item", "ValueSet.compose.include.filter"), r));
+ combined.getChildren().add(new StructuralMatch(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 sm, ConceptReferenceComponent cu, ConceptReferenceComponent ci) {
+ private boolean compareConcepts(String path, ConceptReferenceComponent l, ConceptReferenceComponent r, StructuralMatch sm, ConceptReferenceComponent cu, ConceptReferenceComponent ci, ValueSetComparison res) {
boolean def = false;
sm.getChildren().add(new StructuralMatch(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(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(null, r.getDisplayElement(), vmI(IssueSeverity.INFORMATION, "Display added", "ValueSet.compose.include.concept")));
if (ci != null) {
ci.setDisplay(r.getDisplay());
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/VersionComparisonAnnotation.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/VersionComparisonAnnotation.java
index 31760be76..3df357510 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/VersionComparisonAnnotation.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/VersionComparisonAnnotation.java
@@ -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 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("--");
+ }
+ }
+
+
}
\ No newline at end of file
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java
index 7d5305b4a..16c232e39 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java
@@ -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) {
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java
index 7bad517a2..1fddc8b06 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java
@@ -1,28 +1,35 @@
package org.hl7.fhir.r5.renderers;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
+import org.hl7.fhir.r5.comparison.VersionComparisonAnnotation;
import org.hl7.fhir.r5.conformance.profile.BindingResolution;
-import org.hl7.fhir.r5.conformance.profile.ProfileKnowledgeProvider;
import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.ElementChoiceGroup;
import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.ExtensionContext;
-import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult;
import org.hl7.fhir.r5.formats.IParser;
+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.model.ActorDefinition;
import org.hl7.fhir.r5.model.Base;
+import org.hl7.fhir.r5.model.BooleanType;
+import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CanonicalType;
+import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.CodeType;
import org.hl7.fhir.r5.model.CodeableConcept;
import org.hl7.fhir.r5.model.Coding;
@@ -43,6 +50,7 @@ import org.hl7.fhir.r5.model.ElementDefinition.PropertyRepresentation;
import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules;
import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.r5.model.Enumeration;
+import org.hl7.fhir.r5.model.Enumerations.BindingStrength;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.IdType;
import org.hl7.fhir.r5.model.IntegerType;
@@ -55,10 +63,9 @@ import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionMappingComponent;
import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
import org.hl7.fhir.r5.model.UriType;
-import org.hl7.fhir.r5.model.UsageContext;
+import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper;
-import org.hl7.fhir.r5.renderers.CodeResolver.CodeResolution;
-import org.hl7.fhir.r5.renderers.ObligationsRenderer.ObligationDetail;
+import org.hl7.fhir.r5.renderers.StructureDefinitionRenderer.InternalMarkdownProcessor;
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.KnownLinkType;
@@ -78,10 +85,9 @@ import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableGenerationMode;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
-import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Title;
import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
-import org.hl7.fhir.utilities.xhtml.XhtmlNodeList;
+import org.hl7.fhir.utilities.xhtml.XhtmlParser;
public class StructureDefinitionRenderer extends ResourceRenderer {
@@ -148,25 +154,189 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
//
// }
+ public class InternalMarkdownProcessor implements IMarkdownProcessor {
+ @Override
+ public String processMarkdown(String location, PrimitiveType md) throws FHIRException {
+ return context.getMarkdown().process(md.primitiveValue(), location);
+ }
+
+ @Override
+ public String processMarkdown(String location, String text) throws FHIRException {
+ return context.getMarkdown().process(text, location);
+ }
+ }
+
+ private enum ListItemStatus { New, Unchanged, Removed};
+
+ private abstract class ItemWithStatus {
+ ListItemStatus status = ListItemStatus.New; // new, unchanged, removed
+
+ protected abstract void renderDetails(XhtmlNode f);
+ protected abstract boolean matches(ItemWithStatus other);
+
+ public void render(XhtmlNode x) {
+ XhtmlNode f = x;
+ if (status == ListItemStatus.Unchanged) {
+ f = unchanged(f);
+ } else if (status == ListItemStatus.Removed) {
+ f = removed(f);
+ }
+ renderDetails(f);
+ }
+ }
+
+ protected class StatusList extends ArrayList implements List {
+
+ public boolean merge(T item) {
+ if (item == null) {
+ return false;
+ }
+ boolean found = false;
+ for (T t : this) {
+ if (t.matches(item)) {
+ found = true;
+ t.status = ListItemStatus.Unchanged;
+ }
+ }
+ if (!found) {
+ item.status = ListItemStatus.Removed;
+ return add(item);
+ } else {
+ return false;
+ }
+ }
+
+ public boolean add(T item) {
+ if (item != null) {
+ return super.add(item);
+ } else {
+ return false;
+ }
+ }
+ }
+
+ private class ResolvedCanonical extends ItemWithStatus {
+ String url; // what we used to resolve
+ CanonicalResource cr; // what we resolved
+
+ public ResolvedCanonical(String url, CanonicalResource cr) {
+ this.url = url;
+ this.cr = cr;
+ }
+ public void renderDetails(XhtmlNode f) {
+ if (cr != null && cr.hasWebPath()) {
+ f.ah(cr.getWebPath()).tx(cr.present());
+ } else {
+ f.code().tx(url);
+ }
+ }
+ protected boolean matches(ItemWithStatus other) {
+ return ((ResolvedCanonical) other).url.equals(url);
+ }
+ }
+
+ private class InvariantWithStatus extends ItemWithStatus {
+ ElementDefinitionConstraintComponent value;
+ protected InvariantWithStatus(ElementDefinitionConstraintComponent value) {
+ this.value = value;
+ }
+
+ protected boolean matches(ItemWithStatus other) {
+ return ((InvariantWithStatus) other).value.equalsDeep(value);
+ }
+
+ public void renderDetails(XhtmlNode f) {
+ f.b().attribute("title", "Formal Invariant Identifier").tx(value.getKey());
+ f.tx(": ");
+ f.tx(value.getHuman());
+ f.tx(" (");
+ if (status == ListItemStatus.New) {
+ f.code().tx(value.getExpression());
+ } else {
+ f.tx(value.getExpression());
+ }
+ f.tx(")");
+ }
+ }
+
+ private class DiscriminatorWithStatus extends ItemWithStatus {
+ ElementDefinitionSlicingDiscriminatorComponent value;
+ protected DiscriminatorWithStatus(ElementDefinitionSlicingDiscriminatorComponent value) {
+ this.value = value;
+ }
+
+ protected boolean matches(ItemWithStatus other) {
+ return ((DiscriminatorWithStatus) other).value.equalsDeep(value);
+ }
+
+ public void renderDetails(XhtmlNode f) {
+ f.tx(value.getType().toCode());
+ f.tx(" @ ");
+ f.tx(value.getPath());
+ }
+ }
+
+ private class ValueWithStatus extends ItemWithStatus {
+ PrimitiveType value;
+ protected ValueWithStatus(PrimitiveType value) {
+ this.value = value;
+ }
+
+ protected boolean matches(ItemWithStatus other) {
+ return ((ValueWithStatus) other).value.equalsDeep(value);
+ }
+
+ public void renderDetails(XhtmlNode f) {
+ if (value.hasUserData("render.link")) {
+ f = f.ah(value.getUserString("render.link"));
+ }
+ f.tx(value.asStringValue());
+ }
+ }
+
private List keyRows = new ArrayList<>();
+ private Map> sdMapCache = new HashMap<>();
+ private IMarkdownProcessor hostMd;
public StructureDefinitionRenderer(RenderingContext context) {
super(context);
+ hostMd = new InternalMarkdownProcessor();
}
public StructureDefinitionRenderer(RenderingContext context, ResourceContext rcontext) {
super(context, rcontext);
}
+
+ public Map> getSdMapCache() {
+ return sdMapCache;
+ }
+
+ public void setSdMapCache(Map> sdMapCache) {
+ this.sdMapCache = sdMapCache;
+ }
+
+ public IMarkdownProcessor getHostMd() {
+ return hostMd;
+ }
+
+ public void setHostMd(IMarkdownProcessor hostMd) {
+ this.hostMd = hostMd;
+ }
+
public boolean render(XhtmlNode x, Resource dr) throws FHIRFormatError, DefinitionException, IOException {
return render(x, (StructureDefinition) dr);
}
public boolean render(XhtmlNode x, StructureDefinition sd) throws FHIRFormatError, DefinitionException, IOException {
- x.getChildNodes().add(generateTable(context.getDefinitionsTarget(), sd, true, context.getDestDir(), false, sd.getId(), false,
+ if (context.getStructureMode() == StructureDefinitionRendererMode.DATA_DICT) {
+ renderDict(sd, sd.getDifferential().getElement(), x.table("dict"), false, GEN_MODE_DIFF, "");
+ } else {
+ x.getChildNodes().add(generateTable(context.getDefinitionsTarget(), sd, true, context.getDestDir(), false, sd.getId(), false,
context.getLink(KnownLinkType.SPEC), "", sd.getKind() == StructureDefinitionKind.LOGICAL, false, null, false, context, ""));
+ }
return true;
}
@@ -200,9 +370,19 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
// private static final boolean TABLE_FORMAT_FOR_FIXED_VALUES = false;
public static final String CONSTRAINT_CHAR = "C";
public static final String CONSTRAINT_STYLE = "padding-left: 3px; padding-right: 3px; border: 1px maroon solid; font-weight: bold; color: #301212; background-color: #fdf4f4;";
+ public static final int GEN_MODE_SNAP = 1;
+ public static final int GEN_MODE_DIFF = 2;
+ public static final int GEN_MODE_MS = 3;
+ public static final int GEN_MODE_KEY = 4;
+ public static final String RIM_MAPPING = "http://hl7.org/v3";
+ public static final String v2_MAPPING = "http://hl7.org/v2";
+ public static final String LOINC_MAPPING = "http://loinc.org";
+ public static final String SNOMED_MAPPING = "http://snomed.info";
+
private final boolean ADD_REFERENCE_TO_TABLE = true;
private boolean useTableForFixedValues = true;
+ private String corePath;
public static class UnusedTracker {
private boolean used;
@@ -2227,6 +2407,8 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
+
+
protected boolean isPrimitive(String value) {
StructureDefinition sd = context.getWorker().fetchTypeDefinition(value);
if (sd == null) // might be running before all SDs are available
@@ -2727,7 +2909,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
return ed.getPath().substring(ed.getPath().indexOf(".")+1);
}
- public XhtmlNode formatTypeSpecifiers(IWorkerContext context, ElementDefinition d) {
+ public XhtmlNode formatTypeSpecifiers(ElementDefinition d) {
XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
boolean first = true;
for (Extension e : d.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_SPEC)) {
@@ -2737,7 +2919,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
x.tx("If ");
x.code().tx(cond);
x.tx(" then the type is ");
- StructureDefinition sd = context.fetchTypeDefinition(type);
+ StructureDefinition sd = context.getContext().fetchTypeDefinition(type);
if (sd == null) {
x.code().tx(type);
} else {
@@ -2890,6 +3072,1316 @@ public class StructureDefinitionRenderer extends ResourceRenderer {
return null;
}
+ public void renderDict(StructureDefinition sd, List elements, XhtmlNode t, boolean incProfiledOut, int mode, String anchorPrefix) throws FHIRException, IOException {
+ int i = 0;
+ Map allAnchors = new HashMap<>();
+ List excluded = new ArrayList<>();
+ List stack = new ArrayList<>(); // keeps track of parents, for anchor generation
+
+ for (ElementDefinition ec : elements) {
+ addToStack(stack, ec);
+ generateAnchors(stack, allAnchors);
+ checkInScope(stack, excluded);
+ }
+ for (ElementDefinition ec : elements) {
+ if ((incProfiledOut || !"0".equals(ec.getMax())) && !excluded.contains(ec)) {
+ ElementDefinition compareElement = null;
+ if (mode==GEN_MODE_DIFF)
+ compareElement = getBaseElement(ec, sd.getBaseDefinition());
+ else if (mode==GEN_MODE_KEY)
+ compareElement = getRootElement(ec);
+ List anchors = makeAnchors(ec, anchorPrefix);
+ String title = ec.getId();
+ XhtmlNode tr = t.tr();
+ XhtmlNode sp = tr.td("structure").colspan(2).spanClss("self-link-parent");
+ for (String s : anchors) {
+ sp.an(s).tx(" ");
+ }
+ sp.span("color: grey", null).tx(Integer.toString(i++));
+ sp.b().tx(". "+title);
+ link(sp, ec.getId(), anchorPrefix);
+ if (isProfiledExtension(ec)) {
+ StructureDefinition extDefn = context.getContext().fetchResource(StructureDefinition.class, ec.getType().get(0).getProfile().get(0).getValue());
+ if (extDefn == null) {
+ generateElementInner(t, sd, ec, 1, null, compareElement, null);
+ } else {
+ ElementDefinition valueDefn = getExtensionValueDefinition(extDefn);
+ ElementDefinition compareValueDefn = null;
+ try {
+ StructureDefinition compareExtDefn = context.getContext().fetchResource(StructureDefinition.class, compareElement.getType().get(0).getProfile().get(0).getValue());
+ compareValueDefn = getExtensionValueDefinition(extDefn);
+ } catch (Exception except) {}
+ generateElementInner(t, sd, ec, valueDefn == null || valueDefn.prohibited() ? 2 : 3, valueDefn, compareElement, compareValueDefn);
+ // generateElementInner(b, extDefn, extDefn.getSnapshot().getElement().get(0), valueDefn == null ? 2 : 3, valueDefn);
+ }
+ } else {
+ generateElementInner(t, sd, ec, mode, null, compareElement, null);
+ if (ec.hasSlicing()) {
+ generateSlicing(t, sd, ec, ec.getSlicing(), compareElement, mode);
+ }
+ }
+ }
+ t.tx("\r\n");
+ i++;
+ }
+ }
+
+ public ElementDefinition getElementById(String url, String id) {
+ Map sdCache = sdMapCache.get(url);
+
+ if (sdCache == null) {
+ StructureDefinition sd = (StructureDefinition) context.getContext().fetchResource(StructureDefinition.class, url);
+ if (sd == null) {
+ if (url.equals("http://hl7.org/fhir/StructureDefinition/Base")) {
+ sd = (StructureDefinition) context.getContext().fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/Element");
+ }
+ if (sd == null) {
+ throw new FHIRException("Unable to retrieve StructureDefinition with URL " + url);
+ }
+ }
+ sdCache = new HashMap();
+ sdMapCache.put(url, sdCache);
+ String webroot = sd.getUserString("webroot");
+ for (ElementDefinition e : sd.getSnapshot().getElement()) {
+ context.getProfileUtilities().updateURLs(sd.getUrl(), webroot, e);
+ sdCache.put(e.getId(), e);
+ }
+ }
+ return sdCache.get(id);
+ }
+
+
+ // Returns the ElementDefinition for the 'parent' of the current element
+ private ElementDefinition getBaseElement(ElementDefinition e, String url) {
+ if (e.hasUserData(ProfileUtilities.UD_DERIVATION_POINTER)) {
+ return getElementById(url, e.getUserString(ProfileUtilities.UD_DERIVATION_POINTER));
+ }
+ return null;
+ }
+
+ // Returns the ElementDefinition for the 'root' ancestor of the current element
+ private ElementDefinition getRootElement(ElementDefinition e) {
+ if (!e.hasBase())
+ return null;
+ String basePath = e.getBase().getPath();
+ String url = "http://hl7.org/fhir/StructureDefinition/" + (basePath.contains(".") ? basePath.substring(0, basePath.indexOf(".")) : basePath);
+ try {
+ return getElementById(url, basePath);
+ } catch (FHIRException except) {
+ // Likely a logical model, so this is ok
+ return null;
+ }
+ }
+ private void checkInScope(List stack, List excluded) {
+ if (stack.size() > 2) {
+ ElementDefinition parent = stack.get(stack.size()-2);
+ ElementDefinition focus = stack.get(stack.size()-1);
+
+ if (excluded.contains(parent) || "0".equals(parent.getMax())) {
+ excluded.add(focus);
+ }
+ }
+ }
+
+ private void generateAnchors(List stack, Map allAnchors) {
+ List list = new ArrayList<>();
+ list.add(stack.get(0).getId()); // initialise
+ for (int i = 1; i < stack.size(); i++) {
+ ElementDefinition ed = stack.get(i);
+ List aliases = new ArrayList<>();
+ String name = tail(ed.getPath());
+ if (name.endsWith("[x]")) {
+ aliases.add(name);
+ Set tl = new HashSet(); // guard against duplicate type names - can happn in some versions
+ for (TypeRefComponent tr : ed.getType()) {
+ String tc = tr.getWorkingCode();
+ if (!tl.contains(tc)) {
+ aliases.add(name.replace("[x]", Utilities.capitalize(tc)));
+ aliases.add(name+":"+name.replace("[x]", Utilities.capitalize(tc)));
+ tl.add(tc);
+ }
+ }
+ } else if (ed.hasSliceName()) {
+ aliases.add(name+":"+ed.getSliceName());
+ // names.add(name); no good generating this?
+ } else {
+ aliases.add(name);
+ }
+ List generated = new ArrayList<>();
+ for (String l : list) {
+ for (String a : aliases) {
+ generated.add(l+"."+a);
+ }
+ }
+ list.clear();
+ list.addAll(generated);
+ }
+ ElementDefinition ed = stack.get(stack.size()-1);
+
+ // now we have all the possible names, but some of them might be inappropriate if we've
+ // already generated a type slicer. On the other hand, if we've already done that, we're
+ // going to steal any type specific ones off it.
+ List removed = new ArrayList<>();
+ for (String s : list) {
+ if (!allAnchors.containsKey(s)) {
+ allAnchors.put(s, ed);
+ } else if (s.endsWith("[x]")) {
+ // that belongs on the earlier element
+ removed.add(s);
+ } else {
+ // we delete it from the other
+ @SuppressWarnings("unchecked")
+ List other = (List) allAnchors.get(s).getUserData("dict.generator.anchors");
+ other.remove(s);
+ allAnchors.put(s, ed);
+ }
+ }
+ list.removeAll(removed);
+ ed.setUserData("dict.generator.anchors", list);
+ }
+
+ private void addToStack(List stack, ElementDefinition ec) {
+ while (!stack.isEmpty() && !isParent(stack.get(stack.size()-1), ec)) {
+ stack.remove(stack.size()-1);
+ }
+ stack.add(ec);
+ }
+
+ private boolean isParent(ElementDefinition ed, ElementDefinition ec) {
+ return ec.getPath().startsWith(ed.getPath()+".");
+ }
+
+ private List makeAnchors(ElementDefinition ed, String anchorPrefix) {
+ List list = (List) ed.getUserData("dict.generator.anchors");
+ List res = new ArrayList<>();
+ res.add(anchorPrefix + ed.getId());
+ for (String s : list) {
+ if (!s.equals(ed.getId())) {
+ res.add(anchorPrefix + s);
+ }
+ }
+ return res;
+ }
+
+
+
+ private void link(XhtmlNode x, String id, String anchorPrefix) {
+ var ah = x.ah("#" + anchorPrefix + id);
+ ah.attribute("title", "link to here");
+ ah.attribute("class", "self-link");
+ var svg = ah.svg();
+ svg.attribute("viewBox", "0 0 1792 1792");
+ svg.attribute("width", "16");
+ svg.attribute("height", "16");
+ svg.attribute("class", "self-link");
+ svg.path("M1520 1216q0-40-28-68l-208-208q-28-28-68-28-42 0-72 32 3 3 19 18.5t21.5 21.5 15 19 13 25.5 3.5 27.5q0 40-28 68t-68 28q-15 0-27.5-3.5t-25.5-13-19-15-21.5-21.5-18.5-19q-33 31-33 73 0 40 28 68l206 207q27 27 68 27 40 0 68-26l147-146q28-28 28-67zm-703-705q0-40-28-68l-206-207q-28-28-68-28-39 0-68 27l-147 146q-28 28-28 67 0 40 28 68l208 208q27 27 68 27 42 0 72-31-3-3-19-18.5t-21.5-21.5-15-19-13-25.5-3.5-27.5q0-40 28-68t68-28q15 0 27.5 3.5t25.5 13 19 15 21.5 21.5 18.5 19q33-31 33-73zm895 705q0 120-85 203l-147 146q-83 83-203 83-121 0-204-85l-206-207q-83-83-83-203 0-123 88-209l-88-88q-86 88-208 88-120 0-204-84l-208-208q-84-84-84-204t85-203l147-146q83-83 203-83 121 0 204 85l206 207q83 83 83 203 0 123-88 209l88 88q86-88 208-88 120 0 204 84l208 208q84 84 84 204z");
+ }
+
+ private boolean isProfiledExtension(ElementDefinition ec) {
+ return ec.getType().size() == 1 && "Extension".equals(ec.getType().get(0).getWorkingCode()) && ec.getType().get(0).hasProfile();
+ }
+
+ private ElementDefinition getExtensionValueDefinition(StructureDefinition extDefn) {
+ for (ElementDefinition ed : extDefn.getSnapshot().getElement()) {
+ if (ed.getPath().startsWith("Extension.value"))
+ return ed;
+ }
+ return null;
+ }
+
+ public XhtmlNode compareMarkdown(String location, PrimitiveType md, PrimitiveType compare, int mode) throws FHIRException, IOException {
+ if (compare == null || mode == GEN_MODE_DIFF) {
+ if (md.hasValue()) {
+ String xhtml = hostMd.processMarkdown(location, md);
+ XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
+ VersionComparisonAnnotation.renderDiv(md, x).add(new XhtmlParser().parseFragment(xhtml));
+ return x;
+ } else {
+ return null;
+ }
+ } else if (areEqual(compare, md)) {
+ if (md.hasValue()) {
+ String xhtml = ""+hostMd.processMarkdown(location, md)+"
";
+ XhtmlNode div = new XhtmlParser().parseFragment(xhtml);
+ for (XhtmlNode n : div.getChildNodes()) {
+ if (n.getNodeType() == NodeType.Element) {
+ n.style(unchangedStyle());
+ }
+ }
+ return div;
+ } else {
+ return null;
+ }
+ } else {
+ XhtmlNode ndiv = new XhtmlNode(NodeType.Element, "div");
+ if (md.hasValue()) {
+ String xhtml = ""+hostMd.processMarkdown(location, md)+"
";
+ XhtmlNode div = new XhtmlParser().parseFragment(xhtml);
+ ndiv.copyAllContent(div);
+ }
+ if (compare.hasValue()) {
+ String xhtml = ""+hostMd.processMarkdown(location, compare)+"
";
+ XhtmlNode div = new XhtmlParser().parseFragment(xhtml);
+ for (XhtmlNode n : div.getChildNodes()) {
+ if (n.getNodeType() == NodeType.Element) {
+ n.style(removedStyle());
+ }
+ }
+ ndiv.br();
+ ndiv.copyAllContent(div);
+ }
+ return ndiv;
+ }
+ }
+
+ private boolean areEqual(PrimitiveType compare, PrimitiveType md) {
+ if (compare == null && md == null) {
+ return true;
+ } else if (compare != null && md != null) {
+ String one = compare.getValueAsString();
+ String two = md.getValueAsString();
+ if (one == null && two == null) {
+ return true;
+ } else if (one != null && one.equals(two)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public XhtmlNode compareString(String newStr, Base source, String nLink, String name, Base parent, String oldStr, String oLink, int mode) {
+ XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
+ if (mode != GEN_MODE_KEY) {
+ if (newStr != null) {
+ VersionComparisonAnnotation.render(source, x).ah(nLink).tx(newStr);
+ } else if (VersionComparisonAnnotation.hasDeleted(parent, name)) {
+ PrimitiveType p = (PrimitiveType) VersionComparisonAnnotation.getDeletedItem(parent, name);
+ VersionComparisonAnnotation.render(p, x).tx(p.primitiveValue());
+ } else {
+ return null;
+ }
+ } else if (oldStr==null || oldStr.isEmpty()) {
+ if (newStr==null || newStr.isEmpty()) {
+ return null;
+ } else {
+ VersionComparisonAnnotation.render(source, x).ah(nLink).tx(newStr);
+ }
+ } else if (oldStr!=null && !oldStr.isEmpty() && (newStr==null || newStr.isEmpty())) {
+ if (mode == GEN_MODE_DIFF) {
+ return null;
+ } else {
+ removed(x).ah(oLink).tx(oldStr);
+ }
+ } else if (oldStr.equals(newStr)) {
+ if (mode==GEN_MODE_DIFF) {
+ return null;
+ } else {
+ unchanged(x).ah(nLink).tx(newStr);
+ }
+ } else if (newStr.startsWith(oldStr)) {
+ unchanged(x).ah(oLink).tx(oldStr);
+ VersionComparisonAnnotation.render(source, x).ah(nLink).tx(newStr.substring(oldStr.length()));
+ } else {
+ // TODO: improve comparision in this fall-through case, by looking for matches in sub-paragraphs?
+ VersionComparisonAnnotation.render(source, x).ah(nLink).tx(newStr);
+ removed(x).ah(oLink).tx(oldStr);
+ }
+ return x;
+ }
+
+ public boolean compareString(XhtmlNode x, String newStr, Base source, String nLink, String name, Base parent, String oldStr, String oLink, int mode) {
+ XhtmlNode x1 = compareString(newStr, source, nLink, name, parent, oldStr, oLink, mode);
+ if (x1 == null) {
+ return false;
+ } else {
+ x.getChildNodes().addAll(x1.getChildNodes());
+ return true;
+ }
+ }
+
+ public XhtmlNode unchanged(XhtmlNode x) {
+ return x.span(unchangedStyle());
+ }
+
+ private String unchangedStyle() {
+ return "color:DarkGray";
+ }
+
+ public XhtmlNode removed(XhtmlNode x) {
+ return x.span(removedStyle());
+ }
+
+ private String removedStyle() {
+ return "color:DarkGray;text-decoration:line-through";
+ }
+
+ private void generateElementInner(XhtmlNode tbl, StructureDefinition sd, ElementDefinition d, int mode, ElementDefinition value, ElementDefinition compare, ElementDefinition compareValue) throws FHIRException, IOException {
+ boolean root = !d.getPath().contains(".");
+ boolean slicedExtension = d.hasSliceName() && (d.getPath().endsWith(".extension") || d.getPath().endsWith(".modifierExtension"));
+// int slicedExtensionMode = (mode == GEN_MODE_KEY) && slicedExtension ? GEN_MODE_SNAP : mode; // see ProfileUtilities.checkExtensionDoco / Task 3970
+ if (d.hasSliceName()) {
+ tableRow(tbl, "SliceName", "profiling.html#slicing").tx(d.getSliceName());
+ }
+ tableRow(tbl, "Definition", null, compareMarkdown(sd.getName(), d.getDefinitionElement(), (compare==null) || slicedExtension ? null : compare.getDefinitionElement(), mode));
+ tableRow(tbl, "Short", null, compareString(d.hasShort() ? d.getShort() : null, d.getShortElement(), null, "short", d, compare!= null && compare.hasShortElement() ? compare.getShort() : null, null, mode));
+ tableRow(tbl, "Note", null, businessIdWarning(sd.getName(), tail(d.getPath())));
+ tableRow(tbl, "Control", "conformance-rules.html#conformance", describeCardinality(d, compare, mode));
+ tableRow(tbl, "Binding", "terminologies.html", describeBinding(sd, d, d.getPath(), compare, mode));
+ if (d.hasContentReference()) {
+ tableRow(tbl, "Type", null, "See " + d.getContentReference().substring(1));
+ } else {
+ tableRow(tbl, "Type", "datatypes.html", describeTypes(d.getType(), false, compare, mode, value, compareValue, sd));
+ }
+ if (d.hasExtension(ToolingExtensions.EXT_DEF_TYPE)) {
+ tableRow(tbl, "Default Type", "datatypes.html", ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_DEF_TYPE));
+ }
+ if (d.hasExtension(ToolingExtensions.EXT_TYPE_SPEC)) {
+ tableRow(tbl, Utilities.pluralize("Type Specifier", d.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_SPEC).size()), "datatypes.html", formatTypeSpecifiers(d));
+ }
+ if (d.getPath().endsWith("[x]") && !d.prohibited()) {
+ tableRow(tbl, "[x] Note", null).ahWithText("See ", spec("formats.html#choice"), null, "Choice of Data Types", " for further information about how to use [x]");
+ }
+ tableRow(tbl, "Is Modifier", "conformance-rules.html#ismodifier", displayBoolean(d.getIsModifier(), d.getIsModifierElement(), "isModifier", d, null, mode));
+ if (d.getMustHaveValue()) {
+ tableRow(tbl, "Primitive Value", "elementdefinition.html#primitives", "This primitive type must have a value (the value must be present, and cannot be replaced by an extension)");
+ } else if (d.hasValueAlternatives()) {
+ tableRow(tbl, "Primitive Value", "elementdefinition.html#primitives", renderCanonicalList(d.getValueAlternatives()));
+ } else if (hasPrimitiveTypes(d)) {
+ tableRow(tbl, "Primitive Value", "elementdefinition.html#primitives", "This primitive element may be present, or absent, or replaced by an extension");
+ }
+ if (ToolingExtensions.hasAllowedUnits(d)) {
+ tableRow(tbl, "Allowed Units", "http://hl7.org/fhir/extensions/StructureDefinition-elementdefinition-allowedUnits.html", describeAllowedUnits(d));
+ }
+ tableRow(tbl, "Must Support", "conformance-rules.html#mustSupport", displayBoolean(d.getMustSupport(), d.getMustSupportElement(), "mustSupport", d, compare==null ? null : compare.getMustSupportElement(), mode));
+ if (d.getMustSupport()) {
+ if (hasMustSupportTypes(d.getType())) {
+ tableRow(tbl, "Must Support Types", "datatypes.html", describeTypes(d.getType(), true, compare, mode, null, null, sd));
+ } else if (hasChoices(d.getType())) {
+ tableRow(tbl, "Must Support Types", "datatypes.html", "No must-support rules about the choice of types/profiles");
+ }
+ }
+ if (root && sd.getKind() == StructureDefinitionKind.LOGICAL) {
+ tableRow(tbl, "Logical Model", null, ToolingExtensions.readBoolExtension(sd, ToolingExtensions.EXT_LOGICAL_TARGET) ? "This logical model can be the target of a reference" : "This logical model cannot be the target of a reference");
+ }
+
+ if (root && sd.hasExtension(ToolingExtensions.EXT_SD_IMPOSE_PROFILE)) {
+ tableRow(tbl, "Impose Profile", "http://hl7.org/fhir/extensions/StructureDefinition-structuredefinition-imposeProfile.html",
+ renderCanonicalListExt(sd.getExtensionsByUrl(ToolingExtensions.EXT_SD_IMPOSE_PROFILE)));
+ }
+ if (root && sd.hasExtension(ToolingExtensions.EXT_SD_COMPLIES_WITH_PROFILE)) {
+ tableRow(tbl, "Complies with Profile", "http://hl7.org/fhir/extensions/StructureDefinition-structuredefinition-compliesWithProfile.html",
+ renderCanonicalListExt(sd.getExtensionsByUrl(ToolingExtensions.EXT_SD_COMPLIES_WITH_PROFILE)));
+ }
+ tableRow(tbl, "Obligations", null, describeObligations(d, root, sd));
+
+ if (d.hasExtension(ToolingExtensions.EXT_EXTENSION_STYLE)) {
+ String es = d.getExtensionString(ToolingExtensions.EXT_EXTENSION_STYLE);
+ if ("named-elements".equals(es)) {
+ if (context.hasLink(KnownLinkType.JSON_NAMES)) {
+ tableRow(tbl, "Extension Style", context.getLink(KnownLinkType.JSON_NAMES), "This element can be extended by named JSON elements");
+ } else {
+ tableRow(tbl, "Extension Style", ToolingExtensions.WEB_EXTENSION_STYLE, "This element can be extended by named JSON elements");
+ }
+ }
+ }
+
+ if (!d.getPath().contains(".") && ToolingExtensions.hasExtension(sd, ToolingExtensions.EXT_BINDING_STYLE)) {
+ tableRow(tbl, "Binding Style", ToolingExtensions.WEB_BINDING_STYLE,
+ "This type can be bound to a value set using the " + ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_BINDING_STYLE)+" binding style");
+ }
+
+ if (d.hasExtension(ToolingExtensions.EXT_DATE_FORMAT)) {
+ tableRow(tbl, "Date Format", null, ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_DATE_FORMAT));
+ }
+ String ide = ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_ID_EXPECTATION);
+ if (ide != null) {
+ if (ide.equals("optional")) {
+ tableRow(tbl, "ID Expectation", null, "Id may or not be present (this is the default for elements but not resources)");
+ } else if (ide.equals("required")) {
+ tableRow(tbl, "ID Expectation", null, "Id is required to be present (this is the default for resources but not elements)");
+ } else if (ide.equals("required")) {
+ tableRow(tbl, "ID Expectation", null, "An ID is not allowed in this context");
+ }
+ }
+ // tooling extensions for formats
+ if (ToolingExtensions.hasExtensions(d, ToolingExtensions.EXT_JSON_EMPTY, ToolingExtensions.EXT_JSON_PROP_KEY, ToolingExtensions.EXT_JSON_NULLABLE,
+ ToolingExtensions.EXT_JSON_NAME, ToolingExtensions.EXT_JSON_PRIMITIVE_CHOICE)) {
+ tableRow(tbl, "JSON Format", null, describeJson(d));
+ }
+ if (d.hasExtension(ToolingExtensions.EXT_XML_NAMESPACE) || sd.hasExtension(ToolingExtensions.EXT_XML_NAMESPACE) || d.hasExtension(ToolingExtensions.EXT_XML_NAME) || (root && sd.hasExtension(ToolingExtensions.EXT_XML_NO_ORDER)) ||
+ d.hasRepresentation()) {
+ tableRow(tbl, "XML Format", null, describeXml(sd, d, root));
+ }
+
+ if (d.hasExtension(ToolingExtensions.EXT_IMPLIED_PREFIX)) {
+ tableRow(tbl, "String Format", null).codeWithText("When this element is read ", ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_IMPLIED_PREFIX), "is prefixed to the value before validation");
+ }
+
+ if (d.hasExtension(ToolingExtensions.EXT_STANDARDS_STATUS)) {
+ StandardsStatus ss = StandardsStatus.fromCode(d.getExtensionString(ToolingExtensions.EXT_STANDARDS_STATUS));
+ // gc.addStyledText("Standards Status = "+ss.toDisplay(), ss.getAbbrev(), "black", ss.getColor(), baseSpecUrl()+, true);
+ StructureDefinition sdb = context.getContext().fetchResource(StructureDefinition.class, sd.getBaseDefinition());
+ if (sdb != null) {
+ StandardsStatus base = determineStandardsStatus(sdb, (ElementDefinition) d.getUserData("derived.pointer"));
+ if (base != null) {
+ tableRow(tbl, "Standards Status", "versions.html#std-process", ss.toDisplay()+" (from "+base.toDisplay()+")");
+ } else {
+ tableRow(tbl, "Standards Status", "versions.html#std-process", ss.toDisplay());
+ }
+ } else {
+ tableRow(tbl, "Standards Status", "versions.html#std-process", ss.toDisplay());
+ }
+ }
+ if (mode != GEN_MODE_DIFF && d.hasIsSummary()) {
+ tableRow(tbl, "Summary", "search.html#summary", Boolean.toString(d.getIsSummary()));
+ }
+ tableRow(tbl, "Requirements", null, compareMarkdown(sd.getName(), d.getRequirementsElement(), (compare==null) || slicedExtension ? null : compare.getRequirementsElement(), mode));
+ tableRow(tbl, "Alternate Names", null, compareSimpleTypeLists(d.getAlias(), ((compare==null) || slicedExtension ? null : compare.getAlias()), mode));
+ tableRow(tbl, "Comments", null, compareMarkdown(sd.getName(), d.getCommentElement(), (compare==null) || slicedExtension ? null : compare.getCommentElement(), mode));
+ tableRow(tbl, "Max Length", null, compareString(d.hasMaxLength() ? toStr(d.getMaxLength()) : null, d.getMaxLengthElement(), null, "maxLength", d, compare!= null && compare.hasMaxLengthElement() ? toStr(compare.getMaxLength()) : null, null, mode));
+ tableRow(tbl, "Default Value", null, encodeValue(d.getDefaultValue(), "defaultValue", d, compare==null ? null : compare.getDefaultValue(), mode));
+ tableRow(tbl, "Meaning if Missing", null, d.getMeaningWhenMissing());
+ tableRow(tbl, "Fixed Value", null, encodeValue(d.getFixed(), "fixed", d, compare==null ? null : compare.getFixed(), mode));
+ tableRow(tbl, "Pattern Value", null, encodeValue(d.getPattern(), "pattern", d, compare==null ? null : compare.getPattern(), mode));
+ tableRow(tbl, "Example", null, encodeValues(d.getExample()));
+ tableRow(tbl, "Invariants", null, invariants(d.getConstraint(), compare==null ? null : compare.getConstraint(), mode));
+ tableRow(tbl, "LOINC Code", null, getMapping(sd, d, LOINC_MAPPING, compare, mode));
+ tableRow(tbl, "SNOMED-CT Code", null, getMapping(sd, d, SNOMED_MAPPING, compare, mode));
+ }
+
+ private String spec(String name) {
+ return Utilities.pathURL(VersionUtilities.getSpecUrl(context.getWorker().getVersion()) , name);
+ }
+
+ private XhtmlNode describeXml(StructureDefinition profile, ElementDefinition d, boolean root) {
+ XhtmlNode ret = new XhtmlNode(NodeType.Element, "div");
+ for (PropertyRepresentation pr : PropertyRepresentation.values()) {
+ if (d.hasRepresentation(pr)) {
+ switch (pr) {
+ case CDATEXT:
+ ret.tx("This property is represented as CDA Text in the XML.");
+ break;
+ case TYPEATTR:
+ ret.codeWithText("The type of this property is determined using the ", "xsi:type", "attribute.");
+ break;
+ case XHTML:
+ ret.tx("This property is represented as XHTML Text in the XML.");
+ break;
+ case XMLATTR:
+ ret.tx("In the XML format, this property is represented as an attribute.");
+ break;
+ case XMLTEXT:
+ ret.tx("In the XML format, this property is represented as unadorned text.");
+ break;
+ default:
+ }
+ }
+ }
+ String name = ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_XML_NAMESPACE);
+ if (name == null && root) {
+ name = ToolingExtensions.readStringExtension(profile, ToolingExtensions.EXT_XML_NAMESPACE);
+ }
+ if (name != null) {
+ ret.codeWithText("In the XML format, this property has the namespace ", name, ".");
+ }
+ name = ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_XML_NAME);
+ if (name != null) {
+ ret.codeWithText("In the XML format, this property has the actual name", name, ".");
+ }
+ boolean no = root && ToolingExtensions.readBoolExtension(profile, ToolingExtensions.EXT_XML_NO_ORDER);
+ if (no) {
+ ret.tx("The children of this property can appear in any order in the XML.");
+ }
+ return ret;
+ }
+
+ private XhtmlNode describeJson(ElementDefinition d) {
+ XhtmlNode ret = new XhtmlNode(NodeType.Element, "div");
+ var ul = ret.ul();
+ boolean list = ToolingExtensions.countExtensions(d, ToolingExtensions.EXT_JSON_EMPTY, ToolingExtensions.EXT_JSON_PROP_KEY, ToolingExtensions.EXT_JSON_NULLABLE, ToolingExtensions.EXT_JSON_NAME) > 1;
+
+ String code = ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_JSON_EMPTY);
+ if (code != null) {
+ switch (code) {
+ case "present":
+ ul.li().tx("The JSON Array for this property is present even when there are no items in the instance (e.g. as an empty array)");
+ break;
+ case "absent":
+ ul.li().tx("The JSON Array for this property is not present when there are no items in the instance (e.g. never as an empty array)");
+ break;
+ case "either":
+ ul.li().tx("The JSON Array for this property may be present even when there are no items in the instance (e.g. may be present as an empty array)");
+ break;
+ }
+ }
+ String jn = ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_JSON_NAME);
+ if (jn != null) {
+ if (d.getPath().contains(".")) {
+ ul.li().codeWithText("This property appears in JSON with the property name ", jn, null);
+ } else {
+ ul.li().codeWithText("This type can appear in JSON with the property name ", jn, " (in elements using named extensions)");
+ }
+ }
+ code = ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_JSON_PROP_KEY);
+ if (code != null) {
+ ul.li().codeWithText("This repeating object is represented as a single JSON object with named properties. The name of the property (key) is the value of the ", code, " child");
+ }
+ if (ToolingExtensions.readBoolExtension(d, ToolingExtensions.EXT_JSON_NULLABLE)) {
+ ul.li().tx("This object can be represented as null in the JSON structure (which counts as 'present' for cardinality purposes)");
+ }
+ if (ToolingExtensions.readBoolExtension(d, ToolingExtensions.EXT_JSON_PRIMITIVE_CHOICE)) {
+ ul.li().tx("The type of this element is inferred from the JSON type in the instance");
+ }
+
+ switch (ul.getChildNodes().size()) {
+ case 0: return null;
+ case 1: return ul.getChildNodes().get(0);
+ default: return ret;
+ }
+ }
+
+ private XhtmlNode describeObligations(ElementDefinition d, boolean root, StructureDefinition sdx) throws IOException {
+ XhtmlNode ret = new XhtmlNode(NodeType.Element, "div");
+ ObligationsRenderer obr = new ObligationsRenderer(corePath, sdx, d.getPath(), context, hostMd, this);
+ obr.seeObligations(d.getExtensionsByUrl(ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS));
+ obr.seeRootObligations(d.getId(), sdx.getExtensionsByUrl(ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS));
+ if (obr.hasObligations() || (root && (sdx.hasExtension(ToolingExtensions.EXT_OBLIGATION_PROFILE_FLAG) || sdx.hasExtension(ToolingExtensions.EXT_OBLIGATION_INHERITS)))) {
+ XhtmlNode ul = ret.ul();
+ if (root) {
+ if (sdx.hasExtension(ToolingExtensions.EXT_OBLIGATION_PROFILE_FLAG)) {
+ ul.li().tx("This is an obligation profile that only contains obligations and additional bindings");
+ }
+ for (Extension ext : sdx.getExtensionsByUrl(ToolingExtensions.EXT_OBLIGATION_INHERITS)) {
+ String iu = ext.getValue().primitiveValue();
+ XhtmlNode bb = ul.li();
+ bb.tx("This profile picks up obligations and additional bindings from ");
+ StructureDefinition sd = context.getContext().fetchResource(StructureDefinition.class, iu);
+ if (sd == null) {
+ bb.code().tx(iu);
+ } else if (sd.hasWebPath()) {
+ bb.ah(sd.getWebPath()).tx(sd.present());
+ } else {
+ bb.ah(iu).tx(sd.present());
+ }
+ }
+ if (ul.isEmpty()) {
+ ret.remove(ul);
+ }
+ }
+ if (obr.hasObligations()) {
+ XhtmlNode tbl = ret.table("grid");
+ obr.renderTable(tbl.getChildNodes(), true);
+ if (tbl.isEmpty()) {
+ ret.remove(tbl);
+ }
+ }
+ return ret.hasChildren() ? ret : null;
+ } else {
+ return null;
+ }
+ }
+
+ private XhtmlNode describeAllowedUnits(ElementDefinition d) {
+ XhtmlNode ret = new XhtmlNode(NodeType.Element, "div");
+ DataType au = ToolingExtensions.getAllowedUnits(d);
+ if (au instanceof CanonicalType) {
+ String url = ((CanonicalType) au).asStringValue();
+ ValueSet vs = context.getContext().fetchResource(ValueSet.class, url);
+ ret.tx("Value set ");
+ genCT(ret, url, vs);
+ return ret;
+ } else if (au instanceof CodeableConcept) {
+ CodeableConcept cc = (CodeableConcept) au;
+ if (cc.getCoding().size() != 1) {
+ ret.tx("One of:");
+ }
+ ret.tx(summarise(cc));
+ return ret;
+ }
+ return null;
+ }
+
+ private void genCT(XhtmlNode x, String url, CanonicalResource cr) {
+ if (cr == null) {
+ x.code().tx(url);
+ } else if (!cr.hasWebPath()) {
+ x.ah(url).tx(cr.present());
+ } else {
+ x.ah(cr.getWebPath()).tx(cr.present());
+ }
+ }
+
+ private boolean hasPrimitiveTypes(ElementDefinition d) {
+ for (TypeRefComponent tr : d.getType()) {
+ if (isPrimitive(tr.getCode())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ private XhtmlNode renderCanonicalListExt(List list) {
+ List clist = new ArrayList<>();
+ for (Extension ext : list) {
+ if (ext.hasValueCanonicalType()) {
+ clist.add(ext.getValueCanonicalType());
+ }
+ }
+ return renderCanonicalList(clist);
+ }
+
+ private XhtmlNode renderCanonicalList(List list) {
+ XhtmlNode ret = new XhtmlNode(NodeType.Element, "div");
+ ret.tx("This primitive type may be present, or absent, or replaced by one of the following extensions: ");
+ var ul = ret.ul();
+ for (CanonicalType ct : list) {
+ CanonicalResource cr = (CanonicalResource) context.getContext().fetchResource(Resource.class, ct.getValue());
+ genCT(ul.li(), ct.getValue(), cr);
+ }
+ return ret;
+ }
+
+ private StandardsStatus determineStandardsStatus(StructureDefinition sd, ElementDefinition ed) {
+ if (ed != null && ed.hasExtension(ToolingExtensions.EXT_STANDARDS_STATUS)) {
+ return StandardsStatus.fromCode(ed.getExtensionString(ToolingExtensions.EXT_STANDARDS_STATUS));
+ }
+ while (sd != null) {
+ if (sd.hasExtension(ToolingExtensions.EXT_STANDARDS_STATUS)) {
+ return ToolingExtensions.getStandardsStatus(sd);
+ }
+ sd = context.getContext().fetchResource(StructureDefinition.class, sd.getBaseDefinition());
+ }
+ return null;
+ }
+
+ private boolean hasChoices(List types) {
+ for (TypeRefComponent type : types) {
+ if (type.getProfile().size() > 1 || type.getTargetProfile().size() > 1) {
+ return true;
+ }
+ }
+ return types.size() > 1;
+ }
+
+ private String sliceOrderString(ElementDefinitionSlicingComponent slicing) {
+ if (slicing.getOrdered())
+ return "ordered";
+ else
+ return "unordered";
+ }
+
+ private void generateSlicing(XhtmlNode tbl, StructureDefinition profile, ElementDefinition ed, ElementDefinitionSlicingComponent slicing, ElementDefinition compare, int mode) throws IOException {
+ XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
+
+ x.codeWithText("This element introduces a set of slices on ", ed.getPath(), ". The slices are ");
+ String newOrdered = sliceOrderString(slicing);
+ String oldOrdered = (compare==null || !compare.hasSlicing()) ? null : sliceOrderString(compare.getSlicing());
+ compareString(x, newOrdered, slicing.getOrderedElement(), null, null, null, oldOrdered, null, mode);
+ x.tx(" and ");
+ compareString(x, slicing.hasRules() ? slicing.getRules().getDisplay() : null, slicing.getRulesElement(), null, "rules", slicing, compare!=null && compare.hasSlicing() && compare.getSlicing().hasRules() ? compare.getSlicing().getRules().getDisplay() : null, null, mode);
+
+ if (slicing.hasDiscriminator()) {
+ x.tx(", and can be differentiated using the following discriminators: ");
+ StatusList list = new StatusList<>();
+ for (ElementDefinitionSlicingDiscriminatorComponent d : slicing.getDiscriminator()) {
+ list.add(new DiscriminatorWithStatus(d));
+ }
+ if (compare != null) {
+ for (ElementDefinitionSlicingDiscriminatorComponent d : slicing.getDiscriminator()) {
+ list.merge(new DiscriminatorWithStatus(d));
+ }
+ }
+ x.tx(", and can be differentiated using the following discriminators: ");
+ var ul = x.ul();
+ for (DiscriminatorWithStatus rc : list) {
+ rc.render(x.li());
+ }
+ } else {
+ x.tx(", and defines no discriminators to differentiate the slices");
+ }
+ tableRow(tbl, "Slicing", "profiling.html#slicing", x);
+ }
+
+ private XhtmlNode tableRow(XhtmlNode x, String name, String defRef) throws IOException {
+ var tr = x.tr();
+ addFirstCell(name, defRef, tr);
+ return tr.td();
+ }
+
+
+ private void tableRow(XhtmlNode x, String name, String defRef, XhtmlNode possibleTd) throws IOException {
+ if (possibleTd != null && !possibleTd.isEmpty()) {
+ var tr = x.tr();
+ addFirstCell(name, defRef, tr);
+ tr.td().copyAllContent(possibleTd);
+ }
+ }
+
+ private void tableRow(XhtmlNode x, String name, String defRef, String text) throws IOException {
+ if (!Utilities.noString(text)) {
+ var tr = x.tr();
+ addFirstCell(name, defRef, tr);
+ tr.td().tx(text);
+ }
+ }
+
+ private void addFirstCell(String name, String defRef, XhtmlNode tr) {
+ var td = tr.td();
+ if (name.length() <= 16) {
+ td.style("white-space: nowrap");
+ }
+ if (defRef == null) {
+ td.tx(name);
+ } else if (Utilities.isAbsoluteUrl(defRef)) {
+ td.ah(defRef).tx(name);
+ } else {
+ td.ah(corePath+defRef).tx(name);
+ }
+ }
+
+ private String head(String path) {
+ if (path.contains("."))
+ return path.substring(0, path.indexOf("."));
+ else
+ return path;
+ }
+ private String nottail(String path) {
+ if (path.contains("."))
+ return path.substring(0, path.lastIndexOf("."));
+ else
+ return path;
+ }
+
+ private XhtmlNode businessIdWarning(String resource, String name) {
+ if (name.equals("identifier")) {
+ XhtmlNode ret = new XhtmlNode(NodeType.Element, "div");
+ ret.tx("This is a business identifier, not a resource identifier (see ");
+ ret.ah(corePath + "resource.html#identifiers").tx("discussion");
+ ret.tx(")");
+ return ret;
+ }
+ if (name.equals("version")) {// && !resource.equals("Device"))
+ XhtmlNode ret = new XhtmlNode(NodeType.Element, "div");
+ ret.tx("This is a business versionId, not a resource version id (see ");
+ ret.ah(corePath + "resource.html#versions").tx("discussion");
+ ret.tx(")");
+ return ret;
+ }
+ return null;
+ }
+
+ private XhtmlNode describeCardinality(ElementDefinition d, ElementDefinition compare, int mode) {
+ XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
+ if (compare==null || mode==GEN_MODE_DIFF) {
+ if (!d.hasMax() && !d.hasMin())
+ return null;
+ else if (d.getMax() == null) {
+ VersionComparisonAnnotation.render(d.getMinElement(), x).tx(toStr(d.getMin()));
+ x.tx("..?");
+ } else {
+ VersionComparisonAnnotation.render(d.getMinElement(), x).tx(toStr(d.getMin()));
+ x.tx( "..");
+ VersionComparisonAnnotation.render(d.getMaxElement(), x).tx( d.getMax());
+ }
+ } else {
+ if (!(mode==GEN_MODE_DIFF && (d.getMin()==compare.getMin() || d.getMin()==0))) {
+ compareString(x, toStr(d.getMin()), d.getMinElement(), null, "min", d, toStr(compare.getMin()), null, mode);
+ }
+ x.tx("..");
+ if (!(mode==GEN_MODE_DIFF && (d.getMax().equals(compare.getMax()) || "1".equals(d.getMax())))) {
+ compareString(x, d.getMax(), d.getMaxElement(), null, "max", d, compare.getMax(), null, mode);
+ }
+ }
+ XhtmlNode t = compareSimpleTypeLists(d.getCondition(), compare == null ? null : compare.getCondition(), mode);
+ if (t != null) {
+ x.br();
+ x.tx("This element is affected by the following invariants: ");
+ x.copyAllContent(t);
+ }
+ return x;
+ }
+
+ private boolean hasMustSupportTypes(List types) {
+ for (TypeRefComponent tr : types) {
+ if (isMustSupport(tr)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private XhtmlNode describeTypes(List types, boolean mustSupportOnly, ElementDefinition compare, int mode, ElementDefinition value, ElementDefinition compareValue, StructureDefinition sd) throws FHIRException {
+ if (types.isEmpty())
+ return null;
+
+ List compareTypes = compare==null ? new ArrayList<>() : compare.getType();
+ XhtmlNode ret = new XhtmlNode(NodeType.Element, "div");
+ if ((!mustSupportOnly && types.size() == 1 && compareTypes.size() <=1) || (mustSupportOnly && mustSupportCount(types) == 1)) {
+ if (!mustSupportOnly || isMustSupport(types.get(0))) {
+ describeType(ret, types.get(0), mustSupportOnly, compareTypes.size()==0 ? null : compareTypes.get(0), mode, sd);
+ }
+ } else {
+ boolean first = true;
+ ret.tx("Choice of: ");
+ Map map = new HashMap();
+ for (TypeRefComponent t : compareTypes) {
+ map.put(t.getCode(), t);
+ }
+ for (TypeRefComponent t : types) {
+ TypeRefComponent compareType = map.get(t.getCode());
+ if (compareType!=null)
+ map.remove(t.getCode());
+ if (!mustSupportOnly || isMustSupport(t)) {
+ if (first) {
+ first = false;
+ } else {
+ ret.tx(", ");
+ }
+ describeType(ret, t, mustSupportOnly, compareType, mode, sd);
+ }
+ }
+ for (TypeRefComponent t : map.values()) {
+ ret.tx(", ");
+ describeType(removed(ret), t, mustSupportOnly, null, mode, sd);
+ }
+ }
+ if (value != null) {
+ XhtmlNode xt = processSecondary(mode, value, compareValue, mode, sd);
+ if (xt != null) {
+ ret.copyAllContent(xt);
+ }
+ }
+ return ret;
+ }
+
+ private XhtmlNode processSecondary(int mode, ElementDefinition value, ElementDefinition compareValue, int compMode, StructureDefinition sd) throws FHIRException {
+ switch (mode) {
+ case 1:
+ return null;
+ case 2:
+ XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
+ x.tx(" (Complex Extension)");
+ return x;
+ case 3:
+ x = new XhtmlNode(NodeType.Element, "div");
+ x.tx(" (Extension Type: ");
+ x.copyAllContent(describeTypes(value.getType(), false, compareValue, compMode, null, null, sd));
+ x.tx(")");
+ return x;
+ default:
+ return null;
+ }
+ }
+
+
+ private int mustSupportCount(List types) {
+ int c = 0;
+ for (TypeRefComponent tr : types) {
+ if (isMustSupport(tr)) {
+ c++;
+ }
+ }
+ return c;
+ }
+
+
+ private void describeType(XhtmlNode x, TypeRefComponent t, boolean mustSupportOnly, TypeRefComponent compare, int mode, StructureDefinition sd) throws FHIRException {
+ if (t.getWorkingCode() == null) {
+ return;
+ }
+ if (t.getWorkingCode().startsWith("=")) {
+ return;
+ }
+
+ boolean ts = false;
+ if (t.getWorkingCode().startsWith("xs:")) {
+ ts = compareString(x, t.getWorkingCode(), t.getCodeElement(), null, "code", t, compare==null ? null : compare.getWorkingCode(), null, mode);
+ } else {
+ ts = compareString(x, t.getWorkingCode(), t.getCodeElement(), getTypeLink(t, sd), "code", t, compare==null ? null : compare.getWorkingCode(), compare==null ? null : getTypeLink(compare, sd), mode);
+ }
+
+ if ((!mustSupportOnly && (t.hasProfile() || (compare!=null && compare.hasProfile()))) || isMustSupport(t.getProfile())) {
+ StatusList profiles = analyseProfiles(t.getProfile(), compare == null ? null : compare.getProfile(), mustSupportOnly, mode);
+ if (profiles.size() > 0) {
+ if (!ts) {
+ getTypeLink(unchanged(x), t, sd);
+ ts = true;
+ }
+ x.tx("(");
+ boolean first = true;
+ for (ResolvedCanonical rc : profiles) {
+ if (first) first = false; else x.tx(", ");
+ rc.render(x);
+ }
+ x.tx(")");
+ }
+ }
+
+ if ((!mustSupportOnly && (t.hasTargetProfile() || (compare!=null && compare.hasTargetProfile()))) || isMustSupport(t.getTargetProfile())) {
+ List profiles = analyseProfiles(t.getTargetProfile(), compare == null ? null : compare.getTargetProfile(), mustSupportOnly, mode);
+ if (profiles.size() > 0) {
+ if (!ts) {
+ getTypeLink(unchanged(x), t, sd);
+ }
+ x.tx("("); // todo: double use of "(" is problematic
+ boolean first = true;
+ for (ResolvedCanonical rc : profiles) {
+ if (first) first = false; else x.tx(", ");
+ rc.render(x);
+ }
+ x.tx(")");
+ }
+
+ if (!t.getAggregation().isEmpty() || (compare!=null && !compare.getAggregation().isEmpty())) {
+
+ for (Enumeration a :t.getAggregation()) {
+ a.setUserData("render.link", corePath + "codesystem-resource-aggregation-mode.html#content");
+ }
+ if (compare!=null) {
+ for (Enumeration a : compare.getAggregation()) {
+ a.setUserData("render.link", corePath + "codesystem-resource-aggregation-mode.html#content");
+ }
+ }
+ var xt = compareSimpleTypeLists(t.getAggregation(), compare == null ? null : compare.getAggregation(), mode);
+ if (xt != null) {
+ x.copyAllContent(xt);
+ }
+ }
+ }
+ }
+
+ private StatusList analyseProfiles(List newProfiles, List oldProfiles, boolean mustSupportOnly, int mode) {
+ StatusList profiles = new StatusList();
+ for (CanonicalType pt : newProfiles) {
+ ResolvedCanonical rc = fetchProfile(pt, mustSupportOnly);
+ profiles.add(rc);
+ }
+ if (oldProfiles!=null && mode != GEN_MODE_DIFF) {
+ for (CanonicalType pt : oldProfiles) {
+ profiles.merge(fetchProfile(pt, mustSupportOnly));
+ }
+ }
+ return profiles;
+ }
+
+ private ResolvedCanonical fetchProfile(CanonicalType pt, boolean mustSupportOnly) {
+ if (!pt.hasValue()) {
+ return null;
+ }
+ if (!mustSupportOnly || isMustSupport(pt)) {
+ StructureDefinition p = context.getContext().fetchResource(StructureDefinition.class, pt.getValue());
+ return new ResolvedCanonical(pt.getValue(), p);
+ } else {
+ return null;
+ }
+ }
+//
+// private String getTypeProfile(CanonicalType pt, boolean mustSupportOnly) {
+// StringBuilder b = new StringBuilder();
+// if (!mustSupportOnly || isMustSupport(pt)) {
+// StructureDefinition p = context.getContext().fetchResource(StructureDefinition.class, pt.getValue());
+// if (p == null)
+// b.append(pt.getValue());
+// else {
+// String pth = p.getWebPath();
+// b.append("");
+// b.append(p.getName());
+// b.append("");
+// }
+// }
+// return b.toString();
+// }
+
+ private void getTypeLink(XhtmlNode x, TypeRefComponent t, StructureDefinition sd) {
+ String s = context.getPkp().getLinkFor(sd.getWebPath(), t.getWorkingCode());
+ if (s != null) {
+ x.ah(s).tx(t.getWorkingCode());
+ } else {
+ x.code().tx(t.getWorkingCode());
+ }
+ }
+
+
+ private String getTypeLink(TypeRefComponent t, StructureDefinition sd) {
+ String s = context.getPkp().getLinkFor(sd.getWebPath(), t.getWorkingCode());
+ return s;
+ }
+
+ private XhtmlNode displayBoolean(boolean value, BooleanType source, String name, Base parent, BooleanType compare, int mode) {
+ String newValue = value ? "true" : source.hasValue() ? "false" : null;
+ String oldValue = compare==null || compare.getValue()==null ? null : (compare.getValue()!=true ? null : "true");
+ return compareString(newValue, source, null, name, parent, oldValue, null, mode);
+ }
+
+
+ private XhtmlNode invariants(List originalList, List compareList, int mode) {
+ StatusList list = new StatusList<>();
+ for (ElementDefinitionConstraintComponent v : originalList) {
+ if (!v.isEmpty()) {
+ list.add(new InvariantWithStatus(v));
+ }
+ }
+ if (compareList != null && mode != GEN_MODE_DIFF) {
+ for (ElementDefinitionConstraintComponent v : compareList) {
+ list.merge(new InvariantWithStatus(v));
+ }
+ }
+ if (list.size() == 0) {
+ return null;
+ }
+ XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
+ boolean first = true;
+ for (InvariantWithStatus t : list) {
+ if (first) first = false; else x.br();
+ t.render(x);
+ }
+ return x;
+ }
+
+ private XhtmlNode describeBinding(StructureDefinition sd, ElementDefinition d, String path, ElementDefinition compare, int mode) throws FHIRException, IOException {
+ if (!d.hasBinding())
+ return null;
+ else {
+ ElementDefinitionBindingComponent binding = d.getBinding();
+ ElementDefinitionBindingComponent compBinding = compare == null ? null : compare.getBinding();
+ XhtmlNode bindingDesc = null;
+ if (binding.hasDescription()) {
+ StringType newBinding = PublicationHacker.fixBindingDescriptions(context.getContext(), binding.getDescriptionElement());
+ if (mode == GEN_MODE_SNAP || mode == GEN_MODE_MS) {
+ bindingDesc = new XhtmlNode(NodeType.Element, "div");
+ bindingDesc.add(new XhtmlParser().parseFragment(hostMd.processMarkdown("Binding.description", newBinding)));
+ } else {
+ StringType oldBinding = compBinding != null && compBinding.hasDescription() ? PublicationHacker.fixBindingDescriptions(context.getContext(), compBinding.getDescriptionElement()) : null;
+ bindingDesc = compareMarkdown("Binding.description", newBinding, oldBinding, mode);
+ }
+ }
+ if (!binding.hasValueSet())
+ return bindingDesc;
+
+ XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
+ var nsp = x.span();
+ renderBinding(nsp, binding, path, sd);
+ if (compBinding!=null ) {
+ var osp = x.span();
+ renderBinding(osp, compBinding, path, sd);
+ if (osp.allText().equals(nsp.allText())) {
+ nsp.style(unchangedStyle());
+ x.remove(osp);
+ } else {
+ osp.style(removedStyle());
+ }
+ }
+ if (bindingDesc != null) {
+ if (isSimpleContent(bindingDesc)) {
+ x.tx(": ");
+ x.copyAllContent(bindingDesc.getChildNodes().get(0));
+ } else {
+ x.br();
+ x.copyAllContent(bindingDesc);
+ }
+ }
+
+ AdditionalBindingsRenderer abr = new AdditionalBindingsRenderer(context.getPkp(), corePath, sd, d.getPath(), context, hostMd, this);
+
+ if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) {
+ abr.seeMaxBinding(ToolingExtensions.getExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), compBinding==null ? null : ToolingExtensions.getExtension(compBinding, ToolingExtensions.EXT_MAX_VALUESET), mode!=GEN_MODE_SNAP && mode!=GEN_MODE_MS);
+ }
+ if (binding.hasExtension(ToolingExtensions.EXT_MIN_VALUESET)) {
+ abr.seeMinBinding(ToolingExtensions.getExtension(binding, ToolingExtensions.EXT_MIN_VALUESET), compBinding==null ? null : ToolingExtensions.getExtension(compBinding, ToolingExtensions.EXT_MIN_VALUESET), mode!=GEN_MODE_SNAP && mode!=GEN_MODE_MS);
+ }
+ if (binding.hasExtension(ToolingExtensions.EXT_BINDING_ADDITIONAL)) {
+ abr.seeAdditionalBindings(binding.getExtensionsByUrl(ToolingExtensions.EXT_BINDING_ADDITIONAL), compBinding==null ? null : compBinding.getExtensionsByUrl(ToolingExtensions.EXT_BINDING_ADDITIONAL), mode!=GEN_MODE_SNAP && mode!=GEN_MODE_MS);
+ }
+
+ if (abr.hasBindings()) {
+ var tbl = x.table("grid");
+ abr.render(tbl.getChildNodes(), true);
+ }
+ return x;
+ }
+ }
+
+
+ private boolean isSimpleContent(XhtmlNode bindingDesc) {
+ return bindingDesc.getChildNodes().size() == 1 && bindingDesc.getChildNodes().get(0).isPara();
+ }
+
+ private void renderBinding(XhtmlNode span, ElementDefinitionBindingComponent binding, String path, StructureDefinition sd) {
+ BindingResolution br = context.getPkp().resolveBinding(sd, binding, path);
+ span.tx(conf(binding));
+ if (br.url == null) {
+ span.code().tx(br.display);
+ } else {
+ span.ah(br.url).tx(br.display);
+ }
+ span.tx(confTail(binding));
+
+ }
+
+ private String stripPara(String s) {
+ if (s.startsWith("")) {
+ s = s.substring(3);
+ }
+ if (s.trim().endsWith("
")) {
+ s = s.substring(0, s.lastIndexOf("
")-1) + s.substring(s.lastIndexOf("") +4);
+ }
+ return s;
+ }
+
+ private String confTail(ElementDefinitionBindingComponent def) {
+ if (def.getStrength() == BindingStrength.EXTENSIBLE)
+ return "; other codes may be used where these codes are not suitable";
+ else
+ return "";
+ }
+
+ private String conf(ElementDefinitionBindingComponent def) {
+ if (def.getStrength() == null) {
+ return "For codes, see ";
+ }
+ switch (def.getStrength()) {
+ case EXAMPLE:
+ return "For example codes, see ";
+ case PREFERRED:
+ return "The codes SHOULD be taken from ";
+ case EXTENSIBLE:
+ return "The codes SHALL be taken from ";
+ case REQUIRED:
+ return "The codes SHALL be taken from ";
+ default:
+ return "?sd-conf?";
+ }
+ }
+
+ private String encodeValues(List examples) throws FHIRException, IOException {
+ StringBuilder b = new StringBuilder();
+ boolean first = false;
+ for (ElementDefinitionExampleComponent ex : examples) {
+ if (first)
+ first = false;
+ else
+ b.append("
");
+ b.append("" + Utilities.escapeXml(ex.getLabel()) + ":" + encodeValue(ex.getValue()) + "\r\n");
+ }
+ return b.toString();
+
+ }
+
+ private XhtmlNode encodeValue(DataType value, String name, Base parent, DataType compare, int mode) throws FHIRException, IOException {
+ String oldValue = encodeValue(compare);
+ String newValue = encodeValue(value);
+ return compareString(newValue, value, null, name, parent, oldValue, null, mode);
+ }
+
+ private String encodeValue(DataType value) throws FHIRException, IOException {
+ if (value == null || value.isEmpty())
+ return null;
+ if (value instanceof PrimitiveType)
+ return Utilities.escapeXml(((PrimitiveType) value).asStringValue());
+
+ ByteArrayOutputStream bs = new ByteArrayOutputStream();
+ XmlParser parser = new XmlParser();
+ parser.setOutputStyle(OutputStyle.PRETTY);
+ parser.compose(bs, null, value);
+ String[] lines = bs.toString().split("\\r?\\n");
+ StringBuilder b = new StringBuilder();
+ for (String s : lines) {
+ if (!Utilities.noString(s) && !s.startsWith("")) { // eliminate the xml header
+ b.append(Utilities.escapeXml(s).replace(" ", " ") + "
");
+ }
+ }
+ return b.toString();
+
+ }
+
+ private XhtmlNode getMapping(StructureDefinition profile, ElementDefinition d, String uri, ElementDefinition compare, int mode) {
+ String id = null;
+ for (StructureDefinitionMappingComponent m : profile.getMapping()) {
+ if (m.hasUri() && m.getUri().equals(uri))
+ id = m.getIdentity();
+ }
+ if (id == null)
+ return null;
+ String newMap = null;
+ for (ElementDefinitionMappingComponent m : d.getMapping()) {
+ if (m.getIdentity().equals(id)) {
+ newMap = m.getMap();
+ break;
+ }
+ }
+ if (compare==null)
+ return new XhtmlNode(NodeType.Element, "div").tx(newMap);
+ String oldMap = null;
+ for (ElementDefinitionMappingComponent m : compare.getMapping()) {
+ if (m.getIdentity().equals(id)) {
+ oldMap = m.getMap();
+ break;
+ }
+ }
+
+ return compareString(Utilities.escapeXml(newMap), null, null, "mapping", d, Utilities.escapeXml(oldMap), null, mode);
+ }
+
+ private XhtmlNode compareSimpleTypeLists(List extends PrimitiveType> original, List extends PrimitiveType> compare, int mode) {
+ return compareSimpleTypeLists(original, compare, mode, ", ");
+ }
+
+
+ private XhtmlNode compareSimpleTypeLists(List extends PrimitiveType> originalList, List extends PrimitiveType> compareList, int mode, String separator) {
+ StatusList list = new StatusList<>();
+ for (PrimitiveType v : originalList) {
+ if (!v.isEmpty()) {
+ list.add(new ValueWithStatus(v));
+ }
+ }
+ if (compareList != null && mode != GEN_MODE_DIFF) {
+ for (PrimitiveType v : compareList) {
+ list.merge(new ValueWithStatus(v));
+ }
+ }
+ if (list.size() == 0) {
+ return null;
+ }
+ XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
+ boolean first = true;
+ for (ValueWithStatus t : list) {
+ if (first) first = false; else x.tx(separator);
+ t.render(x);
+ }
+ return x;
+ }
+
+
+ private String summarise(CodeableConcept cc) throws FHIRException {
+ if (cc.getCoding().size() == 1 && cc.getText() == null) {
+ return summarise(cc.getCoding().get(0));
+ } else if (cc.hasText()) {
+ return "\"" + cc.getText() + "\"";
+ } else if (cc.getCoding().size() > 0) {
+ CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
+ for (Coding c : cc.getCoding()) {
+ b.append(summarise(c));
+ }
+ return b.toString();
+ } else {
+ throw new FHIRException("Error describing concept - not done yet (no codings, no text)");
+ }
+ }
+
+ private String summarise(Coding coding) throws FHIRException {
+ if ("http://snomed.info/sct".equals(coding.getSystem()))
+ return "" + translate("sd.summary", "SNOMED CT code") + " " + coding.getCode() + (!coding.hasDisplay() ? "" : "(\"" + gt(coding.getDisplayElement()) + "\")");
+ if ("http://loinc.org".equals(coding.getSystem()))
+ return "" + translate("sd.summary", "LOINC code") + " " + coding.getCode() + (!coding.hasDisplay() ? "" : "(\"" + gt(coding.getDisplayElement()) + "\")");
+ if ("http://unitsofmeasure.org/".equals(coding.getSystem()))
+ return " (" + translate("sd.summary", "UCUM") + ": " + coding.getCode() + ")";
+ CodeSystem cs = context.getContext().fetchCodeSystem(coding.getSystem());
+ if (cs == null)
+ return "" + coding.getCode() + "" + (!coding.hasDisplay() ? "" : "(\"" + gt(coding.getDisplayElement()) + "\")");
+ else
+ return "" + coding.getCode() + "" + (!coding.hasDisplay() ? "" : "(\"" + gt(coding.getDisplayElement()) + "\")");
+ }
}
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java
index 68a4d8f1c..651817927 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java
@@ -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 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 langs, boolean doDesignations,
+ List maps, Map designations, Map 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 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 designations, XhtmlNode tr) {
for (String url : designations.keySet()) {
String d = null;
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java
index 37cc20ff0..932bc44ea 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java
@@ -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
diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/DefinitionNavigator.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/DefinitionNavigator.java
index 1d549286a..8e0b39ed9 100644
--- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/DefinitionNavigator.java
+++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/utils/DefinitionNavigator.java
@@ -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();
master.slices.add(dn);
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java
index c3e93c736..3782539c5 100644
--- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java
@@ -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";
}
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlFluent.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlFluent.java
index 3ae249226..b2e349b1b 100644
--- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlFluent.java
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlFluent.java
@@ -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) {
diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java
index 6731a0812..e13cdabec 100644
--- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java
+++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java
@@ -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 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;
+ }
+
}
\ No newline at end of file
diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties
index 5674e2b17..618c39368 100644
--- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties
+++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties
@@ -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
+
\ No newline at end of file
diff --git a/org.hl7.fhir.validation/pom.xml b/org.hl7.fhir.validation/pom.xml
index f1d784b1a..bbd8dbb35 100644
--- a/org.hl7.fhir.validation/pom.xml
+++ b/org.hl7.fhir.validation/pom.xml
@@ -194,7 +194,12 @@
true
test
-
+
+ com.atlassian.commonmark
+ commonmark-ext-gfm-tables
+ true
+ test
+
org.slf4j
diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/CodeSystemValidator.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/CodeSystemValidator.java
index 0d60ae90e..aa1b0f706 100644
--- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/CodeSystemValidator.java
+++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/instance/type/CodeSystemValidator.java
@@ -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 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 errors, Element cs, NodeStack stack, String url, String content, String caseSensitive, String hierarchyMeaning, boolean isSupplement) {
+ private void metaChecks(List 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 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);
+ }
+ }
}
diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java
index 2ce208dfe..e7432fabf 100644
--- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java
+++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java
@@ -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 examples = new HashMap();
- manifest = (JsonObject) new com.google.gson.JsonParser().parse(contents);
- for (Entry 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 names = new ArrayList(examples.size());
@@ -97,6 +108,8 @@ public class ComparisonTests {
private static final String HEADER = "";
private static final String BREAK = "
";
private static final String FOOTER = "";
+ 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 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) + ".");
}
}
\ No newline at end of file
diff --git a/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.r5/v3-ObservationInterpretation.cache b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.r5/v3-ObservationInterpretation.cache
new file mode 100644
index 000000000..0e8f15dcd
--- /dev/null
+++ b/org.hl7.fhir.validation/src/test/resources/txCache/org.hl7.fhir.r5/v3-ObservationInterpretation.cache
@@ -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"
+}
+-------------------------------------------------------------------------------------