Start working on showing changes when rendering

This commit is contained in:
Grahame Grieve 2023-08-11 12:26:21 +10:00
parent b7fd419509
commit 991a2defee
10 changed files with 435 additions and 57 deletions

View File

@ -9,6 +9,7 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.comparison.CanonicalResourceComparer.ChangeAnalysisState;
import org.hl7.fhir.r5.comparison.ResourceComparer.MessageCounts; import org.hl7.fhir.r5.comparison.ResourceComparer.MessageCounts;
import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CanonicalType; import org.hl7.fhir.r5.model.CanonicalType;
@ -31,11 +32,26 @@ import org.hl7.fhir.utilities.xhtml.XhtmlNode;
public abstract class CanonicalResourceComparer extends ResourceComparer { public abstract class CanonicalResourceComparer extends ResourceComparer {
public enum ChangeAnalysisState {
Unknown, NotChanged, Changed, CannotEvaluate;
boolean noteable() {
return this == Changed || this == CannotEvaluate;
}
}
public abstract class CanonicalResourceComparison<T extends CanonicalResource> extends ResourceComparison { public abstract class CanonicalResourceComparison<T extends CanonicalResource> extends ResourceComparison {
protected T left; protected T left;
protected T right; protected T right;
protected T union; protected T union;
protected T intersection; protected T intersection;
private ChangeAnalysisState changedMetadata = ChangeAnalysisState.Unknown;
private ChangeAnalysisState changedDefinitions = ChangeAnalysisState.Unknown;
private ChangeAnalysisState changedContent = ChangeAnalysisState.Unknown;
private ChangeAnalysisState changedContentInterpretation = ChangeAnalysisState.Unknown;
protected Map<String, StructuralMatch<String>> metadata = new HashMap<>(); protected Map<String, StructuralMatch<String>> metadata = new HashMap<>();
public CanonicalResourceComparison(T left, T right) { public CanonicalResourceComparison(T left, T right) {
@ -80,6 +96,59 @@ public abstract class CanonicalResourceComparer extends ResourceComparer {
this.intersection = intersection; this.intersection = intersection;
} }
private ChangeAnalysisState updateState(ChangeAnalysisState newState, ChangeAnalysisState oldState) {
switch (newState) {
case CannotEvaluate:
return ChangeAnalysisState.CannotEvaluate;
case Changed:
if (oldState != ChangeAnalysisState.CannotEvaluate) {
return ChangeAnalysisState.Changed;
}
break;
case NotChanged:
if (oldState == ChangeAnalysisState.Unknown) {
return ChangeAnalysisState.NotChanged;
}
break;
case Unknown:
default:
break;
}
return oldState;
}
public void updatedMetadataState(ChangeAnalysisState state) {
changedMetadata = updateState(state, changedMetadata);
}
public void updateDefinitionsState(ChangeAnalysisState state) {
changedDefinitions = updateState(state, changedDefinitions);
}
public void updateContentState(ChangeAnalysisState state) {
changedContent = updateState(state, changedContent);
}
public void updateContentInterpretationState(ChangeAnalysisState state) {
changedContentInterpretation = updateState(state, changedContentInterpretation);
}
public void updatedMetadataState(boolean state) {
changedMetadata = updateState(state ? ChangeAnalysisState.Changed : ChangeAnalysisState.NotChanged, changedMetadata);
}
public void updateDefinitionsState(boolean state) {
changedDefinitions = updateState(state ? ChangeAnalysisState.Changed : ChangeAnalysisState.NotChanged, changedDefinitions);
}
public void updateContentState(boolean state) {
changedContent = updateState(state ? ChangeAnalysisState.Changed : ChangeAnalysisState.NotChanged, changedContent);
}
public void updateContentInterpretationState(boolean state) {
changedContentInterpretation = updateState(state ? ChangeAnalysisState.Changed : ChangeAnalysisState.NotChanged, changedContentInterpretation);
}
@Override @Override
protected String toTable() { protected String toTable() {
String s = ""; String s = "";
@ -96,34 +165,75 @@ public abstract class CanonicalResourceComparer extends ResourceComparer {
sm.countMessages(cnts); sm.countMessages(cnts);
} }
} }
protected String changeSummary() {
if (!(changedMetadata.noteable() || changedDefinitions.noteable() || changedContent.noteable() || changedContentInterpretation.noteable())) {
return null;
};
CommaSeparatedStringBuilder bc = new CommaSeparatedStringBuilder();
if (changedMetadata == ChangeAnalysisState.CannotEvaluate) {
bc.append("Metadata");
}
if (changedDefinitions == ChangeAnalysisState.CannotEvaluate) {
bc.append("Definitions");
}
if (changedContent == ChangeAnalysisState.CannotEvaluate) {
bc.append("Content");
}
if (changedContentInterpretation == ChangeAnalysisState.CannotEvaluate) {
bc.append("Interpretation");
}
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
if (changedMetadata == ChangeAnalysisState.Changed) {
b.append("Metadata");
}
if (changedDefinitions == ChangeAnalysisState.Changed) {
b.append("Definitions");
}
if (changedContent == ChangeAnalysisState.Changed) {
b.append("Content");
}
if (changedContentInterpretation == ChangeAnalysisState.Changed) {
b.append("Interpretation");
}
return (bc.length() == 0 ? "" : "Error Checking: "+bc.toString()+"; ")+ "Changed: "+b.toString();
}
} }
public CanonicalResourceComparer(ComparisonSession session) { public CanonicalResourceComparer(ComparisonSession session) {
super(session); super(session);
} }
protected void compareMetadata(CanonicalResource left, CanonicalResource right, Map<String, StructuralMatch<String>> comp, CanonicalResourceComparison<? extends CanonicalResource> res) { protected boolean compareMetadata(CanonicalResource left, CanonicalResource right, Map<String, StructuralMatch<String>> comp, CanonicalResourceComparison<? extends CanonicalResource> res) {
comparePrimitives("url", left.getUrlElement(), right.getUrlElement(), comp, IssueSeverity.ERROR, res); var changed = false;
comparePrimitives("version", left.getVersionElement(), right.getVersionElement(), comp, IssueSeverity.ERROR, res); changed = comparePrimitives("url", left.getUrlElement(), right.getUrlElement(), comp, IssueSeverity.ERROR, res) || changed;
comparePrimitives("name", left.getNameElement(), right.getNameElement(), comp, IssueSeverity.INFORMATION, res); if (session.getForVersion() == null) {
comparePrimitives("title", left.getTitleElement(), right.getTitleElement(), comp, IssueSeverity.INFORMATION, res); changed = comparePrimitives("version", left.getVersionElement(), right.getVersionElement(), comp, IssueSeverity.ERROR, res) || changed;
comparePrimitives("status", left.getStatusElement(), right.getStatusElement(), comp, IssueSeverity.INFORMATION, res); }
comparePrimitives("experimental", left.getExperimentalElement(), right.getExperimentalElement(), comp, IssueSeverity.WARNING, res); changed = comparePrimitives("name", left.getNameElement(), right.getNameElement(), comp, IssueSeverity.INFORMATION, res) || changed;
comparePrimitives("date", left.getDateElement(), right.getDateElement(), comp, IssueSeverity.INFORMATION, res); changed = comparePrimitives("title", left.getTitleElement(), right.getTitleElement(), comp, IssueSeverity.INFORMATION, res) || changed;
comparePrimitives("publisher", left.getPublisherElement(), right.getPublisherElement(), comp, IssueSeverity.INFORMATION, res); changed = comparePrimitives("status", left.getStatusElement(), right.getStatusElement(), comp, IssueSeverity.INFORMATION, res) || changed;
comparePrimitives("description", left.getDescriptionElement(), right.getDescriptionElement(), comp, IssueSeverity.NULL, res); changed = comparePrimitives("experimental", left.getExperimentalElement(), right.getExperimentalElement(), comp, IssueSeverity.WARNING, res) || changed;
comparePrimitives("purpose", left.getPurposeElement(), right.getPurposeElement(), comp, IssueSeverity.NULL, res); if (session.getForVersion() == null) {
comparePrimitives("copyright", left.getCopyrightElement(), right.getCopyrightElement(), comp, IssueSeverity.INFORMATION, res); changed = comparePrimitives("date", left.getDateElement(), right.getDateElement(), comp, IssueSeverity.INFORMATION, res) || changed;
compareCodeableConceptList("jurisdiction", left.getJurisdiction(), right.getJurisdiction(), comp, IssueSeverity.INFORMATION, res, res.getUnion().getJurisdiction(), res.getIntersection().getJurisdiction()); }
changed = comparePrimitives("publisher", left.getPublisherElement(), right.getPublisherElement(), comp, IssueSeverity.INFORMATION, res) || changed;
changed = comparePrimitives("description", left.getDescriptionElement(), right.getDescriptionElement(), comp, IssueSeverity.NULL, res) || changed;
changed = comparePrimitives("purpose", left.getPurposeElement(), right.getPurposeElement(), comp, IssueSeverity.NULL, res) || changed;
changed = comparePrimitives("copyright", left.getCopyrightElement(), right.getCopyrightElement(), comp, IssueSeverity.INFORMATION, res) || changed;
changed = compareCodeableConceptList("jurisdiction", left.getJurisdiction(), right.getJurisdiction(), comp, IssueSeverity.INFORMATION, res, res.getUnion().getJurisdiction(), res.getIntersection().getJurisdiction()) || changed;
return changed;
} }
protected void compareCodeableConceptList(String name, List<CodeableConcept> left, List<CodeableConcept> right, Map<String, StructuralMatch<String>> comp, IssueSeverity level, CanonicalResourceComparison<? extends CanonicalResource> res, List<CodeableConcept> union, List<CodeableConcept> intersection ) { protected boolean compareCodeableConceptList(String name, List<CodeableConcept> left, List<CodeableConcept> right, Map<String, StructuralMatch<String>> comp, IssueSeverity level, CanonicalResourceComparison<? extends CanonicalResource> res, List<CodeableConcept> union, List<CodeableConcept> intersection ) {
boolean result = false;
List<CodeableConcept> matchR = new ArrayList<>(); List<CodeableConcept> matchR = new ArrayList<>();
StructuralMatch<String> combined = new StructuralMatch<String>(); StructuralMatch<String> combined = new StructuralMatch<String>();
for (CodeableConcept l : left) { for (CodeableConcept l : left) {
CodeableConcept r = findCodeableConceptInList(right, l); CodeableConcept r = findCodeableConceptInList(right, l);
if (r == null) { if (r == null) {
union.add(l); union.add(l);
result = true;
combined.getChildren().add(new StructuralMatch<String>(gen(l), vm(IssueSeverity.INFORMATION, "Removed the item '"+gen(l)+"'", fhirType()+"."+name, res.getMessages()))); combined.getChildren().add(new StructuralMatch<String>(gen(l), vm(IssueSeverity.INFORMATION, "Removed the item '"+gen(l)+"'", fhirType()+"."+name, res.getMessages())));
} else { } else {
matchR.add(r); matchR.add(r);
@ -131,15 +241,20 @@ public abstract class CanonicalResourceComparer extends ResourceComparer {
intersection.add(r); intersection.add(r);
StructuralMatch<String> sm = new StructuralMatch<String>(gen(l), gen(r)); StructuralMatch<String> sm = new StructuralMatch<String>(gen(l), gen(r));
combined.getChildren().add(sm); combined.getChildren().add(sm);
if (sm.isDifferent()) {
result = true;
}
} }
} }
for (CodeableConcept r : right) { for (CodeableConcept r : right) {
if (!matchR.contains(r)) { if (!matchR.contains(r)) {
union.add(r); union.add(r);
result = true;
combined.getChildren().add(new StructuralMatch<String>(vm(IssueSeverity.INFORMATION, "Added the item '"+gen(r)+"'", fhirType()+"."+name, res.getMessages()), gen(r))); combined.getChildren().add(new StructuralMatch<String>(vm(IssueSeverity.INFORMATION, "Added the item '"+gen(r)+"'", fhirType()+"."+name, res.getMessages()), gen(r)));
} }
} }
comp.put(name, combined); comp.put(name, combined);
return result;
} }
@ -233,7 +348,7 @@ public abstract class CanonicalResourceComparer extends ResourceComparer {
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
protected void comparePrimitives(String name, PrimitiveType l, PrimitiveType r, Map<String, StructuralMatch<String>> comp, IssueSeverity level, CanonicalResourceComparison<? extends CanonicalResource> res) { protected boolean comparePrimitives(String name, PrimitiveType l, PrimitiveType r, Map<String, StructuralMatch<String>> comp, IssueSeverity level, CanonicalResourceComparison<? extends CanonicalResource> res) {
StructuralMatch<String> match = null; StructuralMatch<String> match = null;
if (l.isEmpty() && r.isEmpty()) { if (l.isEmpty() && r.isEmpty()) {
match = new StructuralMatch<>(null, null, null); match = new StructuralMatch<>(null, null, null);
@ -255,7 +370,8 @@ public abstract class CanonicalResourceComparer extends ResourceComparer {
res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, fhirType()+"."+name, "Values for "+name+" differ: '"+l.primitiveValue()+"' vs '"+r.primitiveValue()+"'", level)); res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, fhirType()+"."+name, "Values for "+name+" differ: '"+l.primitiveValue()+"' vs '"+r.primitiveValue()+"'", level));
} }
} }
comp.put(name, match); comp.put(name, match);
return match.isDifferent();
} }
protected abstract String fhirType(); protected abstract String fhirType();

View File

@ -33,7 +33,6 @@ public class CodeSystemComparer extends CanonicalResourceComparer {
private StructuralMatch<ConceptDefinitionComponent> combined; private StructuralMatch<ConceptDefinitionComponent> combined;
private Map<String, String> propMap = new HashMap<>(); // right to left; left retains it's name private Map<String, String> propMap = new HashMap<>(); // right to left; left retains it's name
public CodeSystemComparison(CodeSystem left, CodeSystem right) { public CodeSystemComparison(CodeSystem left, CodeSystem right) {
super(left, right); super(left, right);
combined = new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(); // base combined = new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(); // base
@ -54,9 +53,15 @@ public class CodeSystemComparer extends CanonicalResourceComparer {
@Override @Override
protected String summary() { protected String summary() {
return "CodeSystem: "+left.present()+" vs "+right.present(); String res = "CodeSystem: "+left.present()+" vs "+right.present();
String ch = changeSummary();
if (ch != null) {
res = res + ". "+ch;
}
return res;
} }
@Override @Override
protected String fhirType() { protected String fhirType() {
return "CodeSystem"; return "CodeSystem";
@ -114,14 +119,18 @@ public class CodeSystemComparer extends CanonicalResourceComparer {
cs1.setDate(new Date()); cs1.setDate(new Date());
cs1.getProperty().addAll(cs.getProperty()); cs1.getProperty().addAll(cs.getProperty());
compareMetadata(left, right, res.getMetadata(), res); boolean ch = compareMetadata(left, right, res.getMetadata(), res);
comparePrimitives("caseSensitive", left.getCaseSensitiveElement(), right.getCaseSensitiveElement(), res.getMetadata(), IssueSeverity.ERROR, res); ch = comparePrimitives("versionNeeded", left.getVersionNeededElement(), right.getVersionNeededElement(), res.getMetadata(), IssueSeverity.INFORMATION, res) || ch;
comparePrimitives("hierarchyMeaning", left.getHierarchyMeaningElement(), right.getHierarchyMeaningElement(), res.getMetadata(), IssueSeverity.ERROR, res); ch = comparePrimitives("compositional", left.getCompositionalElement(), right.getCompositionalElement(), res.getMetadata(), IssueSeverity.WARNING, res) || ch;
comparePrimitives("compositional", left.getCompositionalElement(), right.getCompositionalElement(), res.getMetadata(), IssueSeverity.WARNING, res); res.updatedMetadataState(ch);
comparePrimitives("versionNeeded", left.getVersionNeededElement(), right.getVersionNeededElement(), res.getMetadata(), IssueSeverity.INFORMATION, res); ch = false;
comparePrimitives("content", left.getContentElement(), right.getContentElement(), res.getMetadata(), IssueSeverity.WARNING, res); ch = comparePrimitives("caseSensitive", left.getCaseSensitiveElement(), right.getCaseSensitiveElement(), res.getMetadata(), IssueSeverity.ERROR, res) || ch;
ch = comparePrimitives("hierarchyMeaning", left.getHierarchyMeaningElement(), right.getHierarchyMeaningElement(), res.getMetadata(), IssueSeverity.ERROR, res) || ch;
compareConcepts(left.getConcept(), right.getConcept(), res.getCombined(), res.getUnion().getConcept(), res.getIntersection().getConcept(), res.getUnion(), res.getIntersection(), res, "CodeSystem.concept"); ch = comparePrimitives("content", left.getContentElement(), right.getContentElement(), res.getMetadata(), IssueSeverity.WARNING, res);
ch = compareConcepts(left.getConcept(), right.getConcept(), res.getCombined(), res.getUnion().getConcept(), res.getIntersection().getConcept(), res.getUnion(), res.getIntersection(), res, "CodeSystem.concept") || ch;
res.updateDefinitionsState(ch);
return res; return res;
} }
@ -153,13 +162,15 @@ public class CodeSystemComparer extends CanonicalResourceComparer {
} }
private void compareConcepts(List<ConceptDefinitionComponent> left, List<ConceptDefinitionComponent> right, StructuralMatch<ConceptDefinitionComponent> combined, private boolean compareConcepts(List<ConceptDefinitionComponent> left, List<ConceptDefinitionComponent> right, StructuralMatch<ConceptDefinitionComponent> combined,
List<ConceptDefinitionComponent> union, List<ConceptDefinitionComponent> intersection, CodeSystem csU, CodeSystem csI, CodeSystemComparison res, String path) { List<ConceptDefinitionComponent> union, List<ConceptDefinitionComponent> intersection, CodeSystem csU, CodeSystem csI, CodeSystemComparison res, String path) {
boolean result = false;
List<ConceptDefinitionComponent> matchR = new ArrayList<>(); List<ConceptDefinitionComponent> matchR = new ArrayList<>();
for (ConceptDefinitionComponent l : left) { for (ConceptDefinitionComponent l : left) {
ConceptDefinitionComponent r = findInList(right, l); ConceptDefinitionComponent r = findInList(right, l);
if (r == null) { if (r == null) {
union.add(l); union.add(l);
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(l, vmI(IssueSeverity.INFORMATION, "Removed this concept", path))); combined.getChildren().add(new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(l, vmI(IssueSeverity.INFORMATION, "Removed this concept", path)));
} else { } else {
matchR.add(r); matchR.add(r);
@ -168,17 +179,23 @@ public class CodeSystemComparer extends CanonicalResourceComparer {
union.add(cdM); union.add(cdM);
intersection.add(cdI); intersection.add(cdI);
StructuralMatch<ConceptDefinitionComponent> sm = new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(l, r); StructuralMatch<ConceptDefinitionComponent> sm = new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(l, r);
compare(sm.getMessages(), l, r, path+".where(code='"+l.getCode()+"')", res); if (compare(sm.getMessages(), l, r, path+".where(code='"+l.getCode()+"')", res)) {
result = true;
}
combined.getChildren().add(sm); combined.getChildren().add(sm);
compareConcepts(l.getConcept(), r.getConcept(), sm, cdM.getConcept(), cdI.getConcept(), csU, csI, res, path+".where(code='"+l.getCode()+"').concept"); if (compareConcepts(l.getConcept(), r.getConcept(), sm, cdM.getConcept(), cdI.getConcept(), csU, csI, res, path+".where(code='"+l.getCode()+"').concept")) {
result = true;
}
} }
} }
for (ConceptDefinitionComponent r : right) { for (ConceptDefinitionComponent r : right) {
if (!matchR.contains(r)) { if (!matchR.contains(r)) {
union.add(r); union.add(r);
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r)); combined.getChildren().add(new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r));
} }
} }
return result;
} }
private ConceptDefinitionComponent findInList(List<ConceptDefinitionComponent> list, ConceptDefinitionComponent item) { private ConceptDefinitionComponent findInList(List<ConceptDefinitionComponent> list, ConceptDefinitionComponent item) {
@ -190,24 +207,30 @@ public class CodeSystemComparer extends CanonicalResourceComparer {
return null; return null;
} }
private void compare(List<ValidationMessage> msgs, ConceptDefinitionComponent l, ConceptDefinitionComponent r, String path, CodeSystemComparison res) { private boolean compare(List<ValidationMessage> msgs, ConceptDefinitionComponent l, ConceptDefinitionComponent r, String path, CodeSystemComparison res) {
compareStrings(path, msgs, l.getDisplay(), r.getDisplay(), "display", IssueSeverity.WARNING, res); boolean result = false;
compareStrings(path, msgs, l.getDefinition(), r.getDefinition(), "definition", IssueSeverity.INFORMATION, res); result = compareStrings(path, msgs, l.getDisplay(), r.getDisplay(), "display", IssueSeverity.WARNING, res) || result;
result = compareStrings(path, msgs, l.getDefinition(), r.getDefinition(), "definition", IssueSeverity.INFORMATION, res) || result;
return result;
} }
private void compareStrings(String path, List<ValidationMessage> msgs, String left, String right, String name, IssueSeverity level, CodeSystemComparison res) { private boolean compareStrings(String path, List<ValidationMessage> msgs, String left, String right, String name, IssueSeverity level, CodeSystemComparison res) {
if (!Utilities.noString(right)) { if (!Utilities.noString(right)) {
if (Utilities.noString(left)) { if (Utilities.noString(left)) {
msgs.add(vmI(level, "Value for "+name+" added", path)); msgs.add(vmI(level, "Value for "+name+" added", path));
return true;
} else if (!left.equals(right)) { } else if (!left.equals(right)) {
if (level != IssueSeverity.NULL) { if (level != IssueSeverity.NULL) {
res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path+"."+name, "Changed value for "+name+": '"+left+"' vs '"+right+"'", level)); res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path+"."+name, "Changed value for "+name+": '"+left+"' vs '"+right+"'", level));
} }
msgs.add(vmI(level, name+" changed from left to right", path)); msgs.add(vmI(level, name+" changed from left to right", path));
return true;
} }
} else if (!Utilities.noString(left)) { } else if (!Utilities.noString(left)) {
msgs.add(vmI(level, "Value for "+name+" removed", path)); msgs.add(vmI(level, "Value for "+name+" removed", path));
return true;
} }
return false;
} }
private ConceptDefinitionComponent merge(ConceptDefinitionComponent l, ConceptDefinitionComponent r, List<PropertyComponent> destProps, CodeSystemComparison res) { private ConceptDefinitionComponent merge(ConceptDefinitionComponent l, ConceptDefinitionComponent r, List<PropertyComponent> destProps, CodeSystemComparison res) {

View File

@ -175,6 +175,7 @@ public class ComparisonRenderer implements IEvaluationContext {
vars.put("rightId", new StringType(comp.getRight().getId())); vars.put("rightId", new StringType(comp.getRight().getId()));
vars.put("leftUrl", new StringType(comp.getLeft().getUrl())); vars.put("leftUrl", new StringType(comp.getLeft().getUrl()));
vars.put("rightUrl", new StringType(comp.getRight().getUrl())); vars.put("rightUrl", new StringType(comp.getRight().getUrl()));
vars.put("summary", new StringType(comp.summary()));
vars.put("errors", new StringType(new XhtmlComposer(true).compose(cs.renderErrors(comp)))); vars.put("errors", new StringType(new XhtmlComposer(true).compose(cs.renderErrors(comp))));
vars.put("metadata", new StringType(new XhtmlComposer(true).compose(cs.renderMetadata(comp, "", "")))); vars.put("metadata", new StringType(new XhtmlComposer(true).compose(cs.renderMetadata(comp, "", ""))));
vars.put("concepts", new StringType(new XhtmlComposer(true).compose(cs.renderConcepts(comp, "", "")))); vars.put("concepts", new StringType(new XhtmlComposer(true).compose(cs.renderConcepts(comp, "", ""))));
@ -198,6 +199,7 @@ public class ComparisonRenderer implements IEvaluationContext {
vars.put("rightId", new StringType(comp.getRight().getId())); vars.put("rightId", new StringType(comp.getRight().getId()));
vars.put("leftUrl", new StringType(comp.getLeft().getUrl())); vars.put("leftUrl", new StringType(comp.getLeft().getUrl()));
vars.put("rightUrl", new StringType(comp.getRight().getUrl())); vars.put("rightUrl", new StringType(comp.getRight().getUrl()));
vars.put("summary", new StringType(comp.summary()));
vars.put("errors", new StringType(new XhtmlComposer(true).compose(cs.renderErrors(comp)))); vars.put("errors", new StringType(new XhtmlComposer(true).compose(cs.renderErrors(comp))));
vars.put("metadata", new StringType(new XhtmlComposer(true).compose(cs.renderMetadata(comp, "", "")))); vars.put("metadata", new StringType(new XhtmlComposer(true).compose(cs.renderMetadata(comp, "", ""))));
vars.put("compose", new StringType(new XhtmlComposer(true).compose(cs.renderCompose(comp, "", "")))); vars.put("compose", new StringType(new XhtmlComposer(true).compose(cs.renderCompose(comp, "", ""))));

View File

@ -32,6 +32,8 @@ public class ComparisonSession {
private String sessiondId; private String sessiondId;
private int count; private int count;
private boolean debug; private boolean debug;
private String forVersion;
private boolean annotate;
private String title; private String title;
private ProfileKnowledgeProvider pkpLeft; private ProfileKnowledgeProvider pkpLeft;
private ProfileKnowledgeProvider pkpRight; private ProfileKnowledgeProvider pkpRight;
@ -164,4 +166,22 @@ public class ComparisonSession {
public ProfileKnowledgeProvider getPkpRight() { public ProfileKnowledgeProvider getPkpRight() {
return pkpRight; return pkpRight;
} }
public String getForVersion() {
return forVersion;
}
public void setForVersion(String forVersion) {
this.forVersion = forVersion;
}
public boolean isAnnotate() {
return annotate;
}
public void setAnnotate(boolean annotate) {
this.annotate = annotate;
}
} }

View File

@ -114,6 +114,8 @@ public class StructuralMatch<T> {
return this; return this;
} }
public boolean isDifferent() {
return (left == null) != (right == null) || !messages.isEmpty();
}
} }

View File

@ -8,6 +8,7 @@ import java.util.List;
import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r5.comparison.ResourceComparer.MessageCounts; import org.hl7.fhir.r5.comparison.ResourceComparer.MessageCounts;
import org.hl7.fhir.r5.comparison.VersionComparisonAnnotation.AnotationType;
import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.CanonicalType; import org.hl7.fhir.r5.model.CanonicalType;
import org.hl7.fhir.r5.model.Element; import org.hl7.fhir.r5.model.Element;
@ -67,7 +68,12 @@ public class ValueSetComparer extends CanonicalResourceComparer {
@Override @Override
protected String summary() { protected String summary() {
return "ValueSet: "+left.present()+" vs "+right.present(); String res = "ValueSet: "+left.present()+" vs "+right.present();
String ch = changeSummary();
if (ch != null) {
res = res + ". "+ch;
}
return res;
} }
@Override @Override
@ -118,28 +124,34 @@ public class ValueSetComparer extends CanonicalResourceComparer {
vs1.setStatus(left.getStatus()); vs1.setStatus(left.getStatus());
vs1.setDate(new Date()); vs1.setDate(new Date());
compareMetadata(left, right, res.getMetadata(), res); var ch = compareMetadata(left, right, res.getMetadata(), res);
comparePrimitives("immutable", left.getImmutableElement(), right.getImmutableElement(), res.getMetadata(), IssueSeverity.WARNING, res); var def = false;
ch = comparePrimitives("immutable", left.getImmutableElement(), right.getImmutableElement(), res.getMetadata(), IssueSeverity.WARNING, res) || ch;
if (left.hasCompose() || right.hasCompose()) { if (left.hasCompose() || right.hasCompose()) {
comparePrimitives("compose.lockedDate", left.getCompose().getLockedDateElement(), right.getCompose().getLockedDateElement(), res.getMetadata(), IssueSeverity.WARNING, res); ch = comparePrimitives("compose.lockedDate", left.getCompose().getLockedDateElement(), right.getCompose().getLockedDateElement(), res.getMetadata(), IssueSeverity.WARNING, res) || ch;
comparePrimitives("compose.inactive", left.getCompose().getInactiveElement(), right.getCompose().getInactiveElement(), res.getMetadata(), IssueSeverity.WARNING, res); def = comparePrimitives("compose.inactive", left.getCompose().getInactiveElement(), right.getCompose().getInactiveElement(), res.getMetadata(), IssueSeverity.WARNING, res) || def;
} }
res.updatedMetadataState(ch);
compareCompose(left.getCompose(), right.getCompose(), res, res.getUnion().getCompose(), res.getIntersection().getCompose());
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);
return res; return res;
} }
private boolean compareCompose(ValueSetComposeComponent left, ValueSetComposeComponent right, ValueSetComparison res, ValueSetComposeComponent union, ValueSetComposeComponent intersection) {
boolean def = false;
private void compareCompose(ValueSetComposeComponent left, ValueSetComposeComponent right, ValueSetComparison res, ValueSetComposeComponent union, ValueSetComposeComponent intersection) {
// first, the includes // first, the includes
List<ConceptSetComponent> matchR = new ArrayList<>(); List<ConceptSetComponent> matchR = new ArrayList<>();
for (ConceptSetComponent l : left.getInclude()) { for (ConceptSetComponent l : left.getInclude()) {
ConceptSetComponent r = findInList(right.getInclude(), l, left.getInclude()); ConceptSetComponent r = findInList(right.getInclude(), l, left.getInclude());
if (r == null) { if (r == null) {
union.getInclude().add(l); union.getInclude().add(l);
res.updateContentState(true);
res.getIncludes().getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed Include", "ValueSet.compose.include"))); res.getIncludes().getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed Include", "ValueSet.compose.include")));
if (session.isAnnotate()) {
VersionComparisonAnnotation.markDeleted(right, session.getForVersion(), "include", l);
}
} else { } else {
matchR.add(r); matchR.add(r);
ConceptSetComponent csM = new ConceptSetComponent(); ConceptSetComponent csM = new ConceptSetComponent();
@ -148,13 +160,17 @@ public class ValueSetComparer extends CanonicalResourceComparer {
intersection.getInclude().add(csI); intersection.getInclude().add(csI);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r); StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
res.getIncludes().getChildren().add(sm); res.getIncludes().getChildren().add(sm);
compareDefinitions(l, r, sm, csM, csI); def = compareDefinitions(l, r, sm, csM, csI, res) || def;
} }
} }
for (ConceptSetComponent r : right.getInclude()) { for (ConceptSetComponent r : right.getInclude()) {
if (!matchR.contains(r)) { if (!matchR.contains(r)) {
union.getInclude().add(r); union.getInclude().add(r);
res.getIncludes().getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added Include", "ValueSet.compose.include"), r)); res.updateContentState(true);
res.getIncludes().getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added Include", "ValueSet.compose.include"), r));
if (session.isAnnotate()) {
VersionComparisonAnnotation.markAdded(r, session.getForVersion());
}
} }
} }
@ -164,6 +180,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
ConceptSetComponent r = findInList(right.getExclude(), l, left.getExclude()); ConceptSetComponent r = findInList(right.getExclude(), l, left.getExclude());
if (r == null) { if (r == null) {
union.getExclude().add(l); union.getExclude().add(l);
res.updateContentState(true);
res.getExcludes().getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed Exclude", "ValueSet.compose.exclude"))); res.getExcludes().getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed Exclude", "ValueSet.compose.exclude")));
} else { } else {
matchR.add(r); matchR.add(r);
@ -173,15 +190,17 @@ public class ValueSetComparer extends CanonicalResourceComparer {
intersection.getExclude().add(csI); intersection.getExclude().add(csI);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r); StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
res.getExcludes().getChildren().add(sm); res.getExcludes().getChildren().add(sm);
compareDefinitions(l, r, sm, csM, csI); def = compareDefinitions(l, r, sm, csM, csI, res) || def;
} }
} }
for (ConceptSetComponent r : right.getExclude()) { for (ConceptSetComponent r : right.getExclude()) {
if (!matchR.contains(r)) { if (!matchR.contains(r)) {
union.getExclude().add(r); union.getExclude().add(r);
res.updateContentState(true);
res.getExcludes().getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added Exclude", "ValueSet.compose.exclude"), r)); res.getExcludes().getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added Exclude", "ValueSet.compose.exclude"), r));
} }
} }
return def;
} }
private ConceptSetComponent findInList(List<ConceptSetComponent> matches, ConceptSetComponent item, List<ConceptSetComponent> source) { private ConceptSetComponent findInList(List<ConceptSetComponent> matches, ConceptSetComponent item, List<ConceptSetComponent> source) {
@ -218,13 +237,15 @@ public class ValueSetComparer extends CanonicalResourceComparer {
} }
private void compareDefinitions(ConceptSetComponent left, ConceptSetComponent right, StructuralMatch<Element> combined, ConceptSetComponent union, ConceptSetComponent intersection) { private boolean compareDefinitions(ConceptSetComponent left, ConceptSetComponent right, StructuralMatch<Element> combined, ConceptSetComponent union, ConceptSetComponent intersection, ValueSetComparison res) {
boolean def = false;
// system must match, but the rest might not. we're going to do the full comparison whatever, so the outcome looks consistent to the user // system must match, but the rest might not. we're going to do the full comparison whatever, so the outcome looks consistent to the user
List<CanonicalType> matchVSR = new ArrayList<>(); List<CanonicalType> matchVSR = new ArrayList<>();
for (CanonicalType l : left.getValueSet()) { for (CanonicalType l : left.getValueSet()) {
CanonicalType r = findInList(right.getValueSet(), l, left.getValueSet()); CanonicalType r = findInList(right.getValueSet(), l, left.getValueSet());
if (r == null) { if (r == null) {
union.getValueSet().add(l); union.getValueSet().add(l);
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed ValueSet", "ValueSet.compose.include.valueSet"))); combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed ValueSet", "ValueSet.compose.include.valueSet")));
} else { } else {
matchVSR.add(r); matchVSR.add(r);
@ -234,8 +255,10 @@ public class ValueSetComparer extends CanonicalResourceComparer {
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, null); StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, null);
combined.getChildren().add(sm); combined.getChildren().add(sm);
} else { } else {
// it's not possible to get here?
union.getValueSet().add(l); union.getValueSet().add(l);
union.getValueSet().add(r); union.getValueSet().add(r);
res.updateContentState(true);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, vmI(IssueSeverity.INFORMATION, "Values are different", "ValueSet.compose.include.valueSet")); StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, vmI(IssueSeverity.INFORMATION, "Values are different", "ValueSet.compose.include.valueSet"));
combined.getChildren().add(sm); combined.getChildren().add(sm);
} }
@ -244,6 +267,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
for (CanonicalType r : right.getValueSet()) { for (CanonicalType r : right.getValueSet()) {
if (!matchVSR.contains(r)) { if (!matchVSR.contains(r)) {
union.getValueSet().add(r); union.getValueSet().add(r);
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Add ValueSet", "ValueSet.compose.include.valueSet"), r)); combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Add ValueSet", "ValueSet.compose.include.valueSet"), r));
} }
} }
@ -253,6 +277,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
ConceptReferenceComponent r = findInList(right.getConcept(), l, left.getConcept()); ConceptReferenceComponent r = findInList(right.getConcept(), l, left.getConcept());
if (r == null) { if (r == null) {
union.getConcept().add(l); union.getConcept().add(l);
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed this Concept", "ValueSet.compose.include.concept"))); combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed this Concept", "ValueSet.compose.include.concept")));
} else { } else {
matchCR.add(r); matchCR.add(r);
@ -263,12 +288,14 @@ public class ValueSetComparer extends CanonicalResourceComparer {
intersection.getConcept().add(ci); intersection.getConcept().add(ci);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r); StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
combined.getChildren().add(sm); combined.getChildren().add(sm);
compareConcepts(l, r, sm, cu, ci); def = compareConcepts(l, r, sm, cu, ci) || def;
} else { } else {
// not that it's possible to get here?
union.getConcept().add(l); union.getConcept().add(l);
union.getConcept().add(r); union.getConcept().add(r);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, vmI(IssueSeverity.INFORMATION, "Concepts are different", "ValueSet.compose.include.concept")); StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, vmI(IssueSeverity.INFORMATION, "Concepts are different", "ValueSet.compose.include.concept"));
combined.getChildren().add(sm); combined.getChildren().add(sm);
res.updateContentState(true);
compareConcepts(l, r, sm, null, null); compareConcepts(l, r, sm, null, null);
} }
} }
@ -276,6 +303,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
for (ConceptReferenceComponent r : right.getConcept()) { for (ConceptReferenceComponent r : right.getConcept()) {
if (!matchCR.contains(r)) { if (!matchCR.contains(r)) {
union.getConcept().add(r); union.getConcept().add(r);
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this Concept", "ValueSet.compose.include.concept"), r)); combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this Concept", "ValueSet.compose.include.concept"), r));
} }
} }
@ -285,6 +313,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
ConceptSetFilterComponent r = findInList(right.getFilter(), l, left.getFilter()); ConceptSetFilterComponent r = findInList(right.getFilter(), l, left.getFilter());
if (r == null) { if (r == null) {
union.getFilter().add(l); union.getFilter().add(l);
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed this item", "ValueSet.compose.include.filter"))); combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed this item", "ValueSet.compose.include.filter")));
} else { } else {
matchFR.add(r); matchFR.add(r);
@ -295,11 +324,14 @@ public class ValueSetComparer extends CanonicalResourceComparer {
intersection.getFilter().add(ci); intersection.getFilter().add(ci);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r); StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
combined.getChildren().add(sm); combined.getChildren().add(sm);
compareFilters(l, r, sm, cu, ci); if (!compareFilters(l, r, sm, cu, ci)) {
res.updateContentState(true);
}
} else { } else {
union.getFilter().add(l); union.getFilter().add(l);
union.getFilter().add(r); union.getFilter().add(r);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, vmI(IssueSeverity.INFORMATION, "Codes are different", "ValueSet.compose.include.filter")); StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, vmI(IssueSeverity.INFORMATION, "Codes are different", "ValueSet.compose.include.filter"));
res.updateContentState(true);
combined.getChildren().add(sm); combined.getChildren().add(sm);
compareFilters(l, r, sm, null, null); compareFilters(l, r, sm, null, null);
} }
@ -308,12 +340,15 @@ public class ValueSetComparer extends CanonicalResourceComparer {
for (ConceptSetFilterComponent r : right.getFilter()) { for (ConceptSetFilterComponent r : right.getFilter()) {
if (!matchFR.contains(r)) { if (!matchFR.contains(r)) {
union.getFilter().add(r); union.getFilter().add(r);
res.updateContentState(true);
combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this item", "ValueSet.compose.include.filter"), r)); combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this item", "ValueSet.compose.include.filter"), r));
} }
} }
return def;
} }
private void compareConcepts(ConceptReferenceComponent l, ConceptReferenceComponent r, StructuralMatch<Element> sm, ConceptReferenceComponent cu, ConceptReferenceComponent ci) { private boolean compareConcepts(ConceptReferenceComponent l, ConceptReferenceComponent r, StructuralMatch<Element> sm, ConceptReferenceComponent cu, ConceptReferenceComponent ci) {
boolean def = false;
sm.getChildren().add(new StructuralMatch<Element>(l.getCodeElement(), r.getCodeElement(), l.getCode().equals(r.getCode()) ? null : vmI(IssueSeverity.INFORMATION, "Codes do not match", "ValueSet.compose.include.concept"))); sm.getChildren().add(new StructuralMatch<Element>(l.getCodeElement(), r.getCodeElement(), l.getCode().equals(r.getCode()) ? null : vmI(IssueSeverity.INFORMATION, "Codes do not match", "ValueSet.compose.include.concept")));
if (ci != null) { if (ci != null) {
ci.setCode(l.getCode()); ci.setCode(l.getCode());
@ -325,24 +360,28 @@ public class ValueSetComparer extends CanonicalResourceComparer {
ci.setDisplay(r.getDisplay()); ci.setDisplay(r.getDisplay());
cu.setDisplay(r.getDisplay()); cu.setDisplay(r.getDisplay());
} }
def = !l.getDisplay().equals(r.getDisplay());
} else if (l.hasDisplay()) { } else if (l.hasDisplay()) {
sm.getChildren().add(new StructuralMatch<Element>(l.getDisplayElement(), null, vmI(IssueSeverity.INFORMATION, "Display Removed", "ValueSet.compose.include.concept"))); sm.getChildren().add(new StructuralMatch<Element>(l.getDisplayElement(), null, vmI(IssueSeverity.INFORMATION, "Display Removed", "ValueSet.compose.include.concept")));
if (ci != null) { if (ci != null) {
ci.setDisplay(l.getDisplay()); ci.setDisplay(l.getDisplay());
cu.setDisplay(l.getDisplay()); cu.setDisplay(l.getDisplay());
} }
def = true;
} else if (r.hasDisplay()) { } else if (r.hasDisplay()) {
sm.getChildren().add(new StructuralMatch<Element>(null, r.getDisplayElement(), vmI(IssueSeverity.INFORMATION, "Display added", "ValueSet.compose.include.concept"))); sm.getChildren().add(new StructuralMatch<Element>(null, r.getDisplayElement(), vmI(IssueSeverity.INFORMATION, "Display added", "ValueSet.compose.include.concept")));
if (ci != null) { if (ci != null) {
ci.setDisplay(r.getDisplay()); ci.setDisplay(r.getDisplay());
cu.setDisplay(r.getDisplay()); cu.setDisplay(r.getDisplay());
} }
def = true;
} else { } else {
sm.getChildren().add(new StructuralMatch<Element>(null, null, vmI(IssueSeverity.INFORMATION, "No Display", "ValueSet.compose.include.concept"))); sm.getChildren().add(new StructuralMatch<Element>(null, null, vmI(IssueSeverity.INFORMATION, "No Display", "ValueSet.compose.include.concept")));
} }
return def;
} }
private void compareFilters(ConceptSetFilterComponent l, ConceptSetFilterComponent r, StructuralMatch<Element> sm, ConceptSetFilterComponent cu, ConceptSetFilterComponent ci) { private boolean compareFilters(ConceptSetFilterComponent l, ConceptSetFilterComponent r, StructuralMatch<Element> sm, ConceptSetFilterComponent cu, ConceptSetFilterComponent ci) {
sm.getChildren().add(new StructuralMatch<Element>(l.getPropertyElement(), r.getPropertyElement(), l.getProperty().equals(r.getProperty()) ? null : vmI(IssueSeverity.INFORMATION, "Properties do not match", "ValueSet.compose.include.concept"))); sm.getChildren().add(new StructuralMatch<Element>(l.getPropertyElement(), r.getPropertyElement(), l.getProperty().equals(r.getProperty()) ? null : vmI(IssueSeverity.INFORMATION, "Properties do not match", "ValueSet.compose.include.concept")));
sm.getChildren().add(new StructuralMatch<Element>(l.getOpElement(), r.getOpElement(), l.getOp().equals(r.getOp()) ? null : vmI(IssueSeverity.INFORMATION, "Filter Operations do not match", "ValueSet.compose.include.concept"))); sm.getChildren().add(new StructuralMatch<Element>(l.getOpElement(), r.getOpElement(), l.getOp().equals(r.getOp()) ? null : vmI(IssueSeverity.INFORMATION, "Filter Operations do not match", "ValueSet.compose.include.concept")));
sm.getChildren().add(new StructuralMatch<Element>(l.getValueElement(), r.getValueElement(), l.getValue().equals(r.getValue()) ? null : vmI(IssueSeverity.INFORMATION, "Values do not match", "ValueSet.compose.include.concept"))); sm.getChildren().add(new StructuralMatch<Element>(l.getValueElement(), r.getValueElement(), l.getValue().equals(r.getValue()) ? null : vmI(IssueSeverity.INFORMATION, "Values do not match", "ValueSet.compose.include.concept")));
@ -354,6 +393,7 @@ public class ValueSetComparer extends CanonicalResourceComparer {
cu.setOp(l.getOp()); cu.setOp(l.getOp());
cu.setValue(l.getValue()); cu.setValue(l.getValue());
} }
return !l.getProperty().equals(r.getProperty());
} }
private CanonicalType findInList(List<CanonicalType> matches, CanonicalType item, List<CanonicalType> source) { private CanonicalType findInList(List<CanonicalType> matches, CanonicalType item, List<CanonicalType> source) {

View File

@ -0,0 +1,143 @@
package org.hl7.fhir.r5.comparison;
import java.util.List;
import java.util.Map;
import java.util.ArrayList;
import java.util.HashMap;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
public class VersionComparisonAnnotation {
public enum AnotationType {
Added, Changed, Deleted;
}
public static final String USER_DATA_NAME = "versoin-annotation";
private AnotationType type;
// private String comment;
// private String link;
private Map<String, List<Base>> deletedChildren;
private String version;
private VersionComparisonAnnotation(AnotationType type, String version) {
super();
this.type = type;
this.version = version;
}
//
// private VersionComparisonAnnotation(AnotationType type, String comment) {
// super();
// this.type = type;
// this.comment = comment;
// }
// private VersionComparisonAnnotation(AnotationType type, String comment, String link) {
// super();
// this.type = type;
// this.comment = comment;
// this.link = link;
// }
//
public static void markAdded(Base focus, String version) {
focus.setUserData(USER_DATA_NAME, new VersionComparisonAnnotation(AnotationType.Added, version));
}
public static void markDeleted(Base parent, String name, String version, Base other) {
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);
parent.setUserData(USER_DATA_NAME, vca);
}
if (vca.deletedChildren == null) {
vca.deletedChildren = new HashMap<>();
}
if (!vca.deletedChildren.containsKey(name)) {
vca.deletedChildren.put(name, new ArrayList<>());
}
other.setUserData(USER_DATA_NAME, new VersionComparisonAnnotation(AnotationType.Deleted, version));
vca.deletedChildren.get(name).add(other);
}
public AnotationType getType() {
return type;
}
public void setType(AnotationType type) {
this.type = type;
}
// public String getComment() {
// return comment;
// }
// public void setComment(String comment) {
// this.comment = comment;
// }
// public String getLink() {
// return link;
// }
// public void setLink(String link) {
// this.link = link;
// }
public static XhtmlNode render(Base b, XhtmlNode x) {
if (b.hasUserData(USER_DATA_NAME)) {
VersionComparisonAnnotation self = (VersionComparisonAnnotation) b.getUserData(USER_DATA_NAME);
return self.render(x);
} else {
return x;
}
}
private XhtmlNode render(XhtmlNode x) {
switch (type) {
case Added:
XhtmlNode span = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", null);
span.img("icon-change-add.png", "This content has been added since version "+version);
span.tx(" Added:");
return x;
case Changed:
return x;
case Deleted:
span = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", null);
span.img("icon-change-remove.png", "This content has been removed since version "+version);
span.tx(" Removed:");
return x.strikethrough();
default:
return x;
}
}
public static boolean hasDeleted(Base base, String... names) {
boolean result = false;
if (base.hasUserData(USER_DATA_NAME)) {
VersionComparisonAnnotation self = (VersionComparisonAnnotation) base.getUserData(USER_DATA_NAME);
for (String name : names) {
if (self.deletedChildren != null && self.deletedChildren.containsKey(name)) {
result = true;
}
}
}
return result;
}
public static List<Base> getDeleted(Base base, String... names) {
List<Base> result = new ArrayList<>();
if (base.hasUserData(USER_DATA_NAME)) {
VersionComparisonAnnotation self = (VersionComparisonAnnotation) base.getUserData(USER_DATA_NAME);
for (String name : names) {
if (self.deletedChildren != null && self.deletedChildren.containsKey(name)) {
result.addAll(self.deletedChildren.get(name));
}
}
}
return result;
}
}

View File

@ -88,6 +88,17 @@ public abstract class Base implements Serializable, IBase, IElement {
} }
private static ThreadLocal<Boolean> copyUserData = new ThreadLocal<>();
public static boolean isCopyUserData() {
Boolean res = copyUserData.get();
return res != null && res;
}
public static void setCopyUserData(boolean value) {
copyUserData.set(value);
}
/** /**
* User appended data items - allow users to add extra information to the class * User appended data items - allow users to add extra information to the class
*/ */
@ -456,7 +467,11 @@ public abstract class Base implements Serializable, IBase, IElement {
public abstract Base copy(); public abstract Base copy();
public void copyValues(Base dst) { public void copyValues(Base dst) {
if (isCopyUserData() && userData != null) {
dst.userData = new HashMap<>();
dst.userData.putAll(userData);
}
} }
/** /**

View File

@ -14,8 +14,10 @@ import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError; import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.exceptions.TerminologyServiceException; import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r5.comparison.VersionComparisonAnnotation;
import org.hl7.fhir.r5.context.IWorkerContext.CodingValidationRequest; import org.hl7.fhir.r5.context.IWorkerContext.CodingValidationRequest;
import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult; import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.BooleanType; import org.hl7.fhir.r5.model.BooleanType;
import org.hl7.fhir.r5.model.CanonicalResource; import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CodeSystem; import org.hl7.fhir.r5.model.CodeSystem;
@ -924,7 +926,7 @@ public class ValueSetRenderer extends TerminologyRenderer {
generateCopyright(x, vs); generateCopyright(x, vs);
} }
int index = 0; int index = 0;
if (vs.getCompose().getInclude().size() == 1 && vs.getCompose().getExclude().size() == 0) { if (vs.getCompose().getInclude().size() == 1 && vs.getCompose().getExclude().size() == 0 && !VersionComparisonAnnotation.hasDeleted(vs.getCompose(), "include", "exclude")) {
hasExtensions = genInclude(x.ul(), vs.getCompose().getInclude().get(0), "Include", langs, doDesignations, maps, designations, index, vs) || hasExtensions; hasExtensions = genInclude(x.ul(), vs.getCompose().getInclude().get(0), "Include", langs, doDesignations, maps, designations, index, vs) || hasExtensions;
} else { } else {
XhtmlNode p = x.para(); XhtmlNode p = x.para();
@ -934,7 +936,11 @@ public class ValueSetRenderer extends TerminologyRenderer {
hasExtensions = genInclude(ul, inc, "Include", langs, doDesignations, maps, designations, index, vs) || hasExtensions; hasExtensions = genInclude(ul, inc, "Include", langs, doDesignations, maps, designations, index, vs) || hasExtensions;
index++; index++;
} }
if (vs.getCompose().hasExclude()) { for (Base inc : VersionComparisonAnnotation.getDeleted(vs.getCompose(), "include")) {
genInclude(ul, (ConceptSetComponent) inc, "Include", langs, doDesignations, maps, designations, index, vs);
index++;
}
if (vs.getCompose().hasExclude() || VersionComparisonAnnotation.hasDeleted(vs.getCompose(), "exclude")) {
p = x.para(); p = x.para();
p.tx("This value set excludes codes based on the following rules:"); p.tx("This value set excludes codes based on the following rules:");
ul = x.ul(); ul = x.ul();
@ -942,6 +948,10 @@ public class ValueSetRenderer extends TerminologyRenderer {
hasExtensions = genInclude(ul, exc, "Exclude", langs, doDesignations, maps, designations, index, vs) || hasExtensions; hasExtensions = genInclude(ul, exc, "Exclude", langs, doDesignations, maps, designations, index, vs) || hasExtensions;
index++; index++;
} }
for (Base inc : VersionComparisonAnnotation.getDeleted(vs.getCompose(), "exclude")) {
genInclude(ul, (ConceptSetComponent) inc, "Exclude", langs, doDesignations, maps, designations, index, vs);
index++;
}
} }
} }
@ -1131,6 +1141,8 @@ public class ValueSetRenderer extends TerminologyRenderer {
boolean hasExtensions = false; boolean hasExtensions = false;
XhtmlNode li; XhtmlNode li;
li = ul.li(); li = ul.li();
li = VersionComparisonAnnotation.render(inc, li);
Map<String, ConceptDefinitionComponent> definitions = new HashMap<>(); Map<String, ConceptDefinitionComponent> definitions = new HashMap<>();
if (inc.hasSystem()) { if (inc.hasSystem()) {
@ -1144,7 +1156,7 @@ public class ValueSetRenderer extends TerminologyRenderer {
addCsRef(inc, li, e); addCsRef(inc, li, e);
if (inc.hasVersion()) { if (inc.hasVersion()) {
li.addText(" version "); li.addText(" version ");
li.code(inc.getVersion()); li.code(inc.getVersion());
} }
// for performance reasons, we do all the fetching in one batch // for performance reasons, we do all the fetching in one batch

View File

@ -867,4 +867,9 @@ public class XhtmlNode extends XhtmlFluent implements IBaseXhtml {
return res; return res;
} }
public XhtmlNode strikethrough() {
return addTag("s");
}
} }