first release of functional Profile comparison on new framework

This commit is contained in:
Grahame Grieve 2020-06-11 16:40:57 +10:00
parent a09c4c3cea
commit 5f1d6e193c
12 changed files with 412 additions and 1754 deletions

View File

@ -50,6 +50,11 @@ public class CodeSystemComparer extends CanonicalResourceComparer {
protected String abbreviation() {
return "cs";
}
@Override
protected String summary() {
return "CodeSystem: "+left.present()+" vs "+right.present();
}
}
private CodeSystem right;

View File

@ -2,9 +2,12 @@ package org.hl7.fhir.r5.comparison;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException;
@ -12,6 +15,7 @@ import org.hl7.fhir.r5.comparison.CodeSystemComparer.CodeSystemComparison;
import org.hl7.fhir.r5.comparison.ProfileComparer.ProfileComparison;
import org.hl7.fhir.r5.comparison.ResourceComparer.ResourceComparison;
import org.hl7.fhir.r5.comparison.ValueSetComparer.ValueSetComparison;
import org.hl7.fhir.r5.conformance.ProfileUtilities;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.model.Base;
@ -47,9 +51,27 @@ public class ComparisonRenderer implements IEvaluationContext {
public void render() throws IOException {
dumpBinaries();
for (String id : session.getCompares().keySet()) {
renderComparison(id, session.getCompares().get(id));
StringBuilder b = new StringBuilder();
for (String id : sorted(session.getCompares().keySet())) {
ResourceComparison comp = session.getCompares().get(id);
renderComparison(id, comp);
b.append("<li><a href=\""+comp.getId()+".html\">"+Utilities.escapeXml(comp.summary())+"</a></li>\r\n");
}
Map<String, Base> vars = new HashMap<>();
CodeSystemComparer cs = new CodeSystemComparer(session);
vars.put("title", new StringType(session.getTitle()));
vars.put("list", new StringType(b.toString()));
String template = templates.get("Index");
String cnt = processTemplate(template, "CodeSystem", vars);
TextFile.stringToFile(cnt, file("index.html"));
}
private List<String> sorted(Set<String> keySet) {
List<String> list = new ArrayList<>();
list.addAll(keySet);
Collections.sort(list);
return list;
}
private void dumpBinaries() throws IOException {
@ -115,7 +137,7 @@ public class ComparisonRenderer implements IEvaluationContext {
private void renderProfile(String id, ProfileComparison comp) throws IOException {
String template = templates.get("Profile");
Map<String, Base> vars = new HashMap<>();
ProfileComparer cs = new ProfileComparer(session);
ProfileComparer cs = new ProfileComparer(session, new ProfileUtilities(session.getContext(), null, null));
vars.put("left", new StringType(comp.getLeft().present()));
vars.put("right", new StringType(comp.getRight().present()));
vars.put("leftId", new StringType(comp.getLeft().getId()));
@ -124,7 +146,7 @@ public class ComparisonRenderer implements IEvaluationContext {
vars.put("rightUrl", new StringType(comp.getRight().getUrl()));
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("concepts", new StringType(new XhtmlComposer(true).compose(cs.renderConcepts(comp, "", ""))));
vars.put("structure", new StringType(new XhtmlComposer(true).compose(cs.renderStructure(comp, "", "", "http://hl7.org/fhir"))));
String cnt = processTemplate(template, "CodeSystem", vars);
TextFile.stringToFile(cnt, file(comp.getId()+".html"));
new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path("[tmp]", "comparison", comp.getId() + "-union.json")), comp.getUnion());

View File

@ -12,6 +12,7 @@ import org.hl7.fhir.r5.comparison.CodeSystemComparer.CodeSystemComparison;
import org.hl7.fhir.r5.comparison.ProfileComparer.ProfileComparison;
import org.hl7.fhir.r5.comparison.ResourceComparer.ResourceComparison;
import org.hl7.fhir.r5.comparison.ValueSetComparer.ValueSetComparison;
import org.hl7.fhir.r5.conformance.ProfileUtilities;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CodeSystem;
@ -27,17 +28,25 @@ public class ComparisonSession {
private String sessiondId;
private int count;
private boolean debug;
private String title;
public ComparisonSession(IWorkerContext context) {
public ComparisonSession(IWorkerContext context, String title) {
super();
this.context = context;
this.sessiondId = UUID.randomUUID().toString().toLowerCase();
this.title = title;
}
public IWorkerContext getContext() {
return context;
}
public String getTitle() {
return title;
}
public ResourceComparison compare(String left, String right) throws DefinitionException, FHIRFormatError, IOException {
CanonicalResource l = (CanonicalResource) context.fetchResource(Resource.class, left);
if (l == null) {
@ -69,7 +78,7 @@ public class ComparisonSession {
compares.put(key, csc);
return csc;
} else if (left instanceof StructureDefinition && right instanceof StructureDefinition) {
ProfileComparer cs = new ProfileComparer(this);
ProfileComparer cs = new ProfileComparer(this, new ProfileUtilities(context, null, null));
ProfileComparison csc = cs.compare((StructureDefinition) left, (StructureDefinition) right);
compares.put(key, csc);
return csc;
@ -91,9 +100,6 @@ public class ComparisonSession {
public void identify(ResourceComparison res) {
count++;
if (!Utilities.isValidId(res.getId())) {
res.setId(sessiondId+"-"+count);
}
}
public boolean isDebug() {

View File

@ -7,8 +7,11 @@ import java.util.Date;
import java.util.List;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.comparison.ValueSetComparer.ValueSetComparison;
import org.hl7.fhir.r5.conformance.ProfileUtilities;
import org.hl7.fhir.r5.conformance.ProfileUtilities.UnusedTracker;
import org.hl7.fhir.r5.formats.IParser;
import org.hl7.fhir.r5.model.Base;
import org.hl7.fhir.r5.model.Coding;
@ -30,6 +33,11 @@ import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
public class ProfileComparer extends CanonicalResourceComparer {
@ -50,10 +58,21 @@ public class ProfileComparer extends CanonicalResourceComparer {
protected String abbreviation() {
return "sd";
}
@Override
protected String summary() {
return "Profile: "+left.present()+" vs "+right.present();
}
}
public ProfileComparer(ComparisonSession session) {
private ProfileUtilities utils;
public ProfileComparer(ComparisonSession session, ProfileUtilities utils) {
super(session);
this.utils = utils;
}
@Override
@ -95,6 +114,7 @@ public class ProfileComparer extends CanonicalResourceComparer {
DefinitionNavigator rn = new DefinitionNavigator(session.getContext(), right);
StructuralMatch<ElementDefinition> sm = new StructuralMatch<ElementDefinition>(ln.current(), rn.current());
compareElements(res, sm, ln.path(), null, ln, rn);
res.combined = sm;
}
return res;
}
@ -143,17 +163,21 @@ public class ProfileComparer extends CanonicalResourceComparer {
subset.setIsSummary(left.current().getIsSummary());
// descriptive properties from ElementDefinition - merge them:
subset.setLabel(mergeText(comp, res, path, "label", left.current().getLabel(), right.current().getLabel()));
subset.setShort(mergeText(comp, res, path, "short", left.current().getShort(), right.current().getShort()));
subset.setDefinition(mergeText(comp, res, path, "definition", left.current().getDefinition(), right.current().getDefinition()));
subset.setComment(mergeText(comp, res, path, "comments", left.current().getComment(), right.current().getComment()));
subset.setRequirements(mergeText(comp, res, path, "requirements", left.current().getRequirements(), right.current().getRequirements()));
subset.setLabel(mergeText(comp, res, path, "label", left.current().getLabel(), right.current().getLabel(), false));
subset.setShort(mergeText(comp, res, path, "short", left.current().getShort(), right.current().getShort(), false));
subset.setDefinition(mergeText(comp, res, path, "definition", left.current().getDefinition(), right.current().getDefinition(), false));
subset.setComment(mergeText(comp, res, path, "comments", left.current().getComment(), right.current().getComment(), false));
subset.setRequirements(mergeText(comp, res, path, "requirements", left.current().getRequirements(), right.current().getRequirements(), false));
subset.getCode().addAll(mergeCodings(left.current().getCode(), right.current().getCode()));
subset.getAlias().addAll(mergeStrings(left.current().getAlias(), right.current().getAlias()));
subset.getMapping().addAll(mergeMappings(left.current().getMapping(), right.current().getMapping()));
// left will win for example
subset.setExample(left.current().hasExample() ? left.current().getExample() : right.current().getExample());
if (left.current().getMustSupport() != right.current().getMustSupport()) {
vm(IssueSeverity.ERROR, "Elements differ in definition for mustSupport:\r\n \""+left.current().getMustSupport()+"\"\r\n \""+right.current().getMustSupport()+"\"", path, comp.getMessages(), res.getMessages());
}
subset.setMustSupport(left.current().getMustSupport() || right.current().getMustSupport());
ElementDefinition superset = subset.copy();
@ -278,7 +302,7 @@ public class ProfileComparer extends CanonicalResourceComparer {
private DefinitionNavigator findInList(List<DefinitionNavigator> rc, DefinitionNavigator l) {
for (DefinitionNavigator t : rc) {
if (t.current().getPath().equals(l.current().getPath())) {
if (tail(t.current().getPath()).equals(tail(l.current().getPath()))) {
return t;
}
}
@ -292,8 +316,8 @@ public class ProfileComparer extends CanonicalResourceComparer {
vm(IssueSeverity.ERROR, "Added "+name, path, comp.getMessages(), res.getMessages());
} else if (vRight == null) {
vm(IssueSeverity.ERROR, "Removed "+name, path, comp.getMessages(), res.getMessages());
} else if (Base.compareDeep(vLeft, vRight, false)) {
vm(IssueSeverity.ERROR, name+" be the same ("+toString(vLeft)+"/"+toString(vRight)+")", path, comp.getMessages(), res.getMessages());
} else if (!Base.compareDeep(vLeft, vRight, false)) {
vm(IssueSeverity.ERROR, name+" must be the same ("+toString(vLeft)+"/"+toString(vRight)+")", path, comp.getMessages(), res.getMessages());
}
}
@ -324,7 +348,7 @@ public class ProfileComparer extends CanonicalResourceComparer {
return test;
}
private String mergeText(ProfileComparison comp, StructuralMatch<ElementDefinition> res, String path, String name, String left, String right) {
private String mergeText(ProfileComparison comp, StructuralMatch<ElementDefinition> res, String path, String name, String left, String right, boolean isError) {
if (left == null && right == null)
return null;
if (left == null)
@ -336,7 +360,7 @@ public class ProfileComparer extends CanonicalResourceComparer {
if (left.equalsIgnoreCase(right))
return left;
if (path != null) {
vm(IssueSeverity.ERROR, "Elements differ in definition for "+name+":\r\n \""+left+"\"\r\n \""+right+"\"", path, comp.getMessages(), res.getMessages());
vm(isError ? IssueSeverity.ERROR : IssueSeverity.WARNING, "Elements differ in "+name+":\r\n \""+left+"\"\r\n \""+right+"\"", path, comp.getMessages(), res.getMessages());
}
return "left: "+left+"; right: "+right;
}
@ -661,8 +685,8 @@ public class ProfileComparer extends CanonicalResourceComparer {
subset.setBinding(subBinding);
ElementDefinitionBindingComponent superBinding = new ElementDefinitionBindingComponent();
superset.setBinding(superBinding);
subBinding.setDescription(mergeText(comp, res, path, "description", left.getDescription(), right.getDescription()));
superBinding.setDescription(mergeText(comp, res, path, "description", left.getDescription(), right.getDescription()));
subBinding.setDescription(mergeText(comp, res, path, "description", left.getDescription(), right.getDescription(), false));
superBinding.setDescription(mergeText(comp, res, path, "description", left.getDescription(), right.getDescription(), false));
if (left.getStrength() == BindingStrength.REQUIRED || right.getStrength() == BindingStrength.REQUIRED)
subBinding.setStrength(BindingStrength.REQUIRED);
else
@ -776,7 +800,7 @@ public class ProfileComparer extends CanonicalResourceComparer {
union.setStrength(left.getStrength());
else
union.setStrength(right.getStrength());
union.setDescription(mergeText(comp, res, path, "binding.description", left.getDescription(), right.getDescription()));
union.setDescription(mergeText(comp, res, path, "binding.description", left.getDescription(), right.getDescription(), false));
if (Base.compareDeep(left.getValueSet(), right.getValueSet(), false))
union.setValueSet(left.getValueSet());
else {
@ -802,8 +826,165 @@ public class ProfileComparer extends CanonicalResourceComparer {
return session.getContext().fetchResource(ValueSet.class, vsRef);
}
public XhtmlNode renderStructure(ProfileComparison comp, String id, String prefix, String corePath) throws FHIRException, IOException {
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(Utilities.path("[tmp]", "compare"), false, true);
gen.setTranslator(session.getContext().translator());
TableModel model = gen.initComparisonTable(corePath, id);
genElementComp(null /* oome back to this later */, gen, model.getRows(), comp.combined, corePath, prefix, null, true);
return gen.generate(model, prefix, 0, null);
}
private void genElementComp(String defPath, HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<ElementDefinition> combined, String corePath, String prefix, Row slicingRow, boolean root) throws IOException {
Row originalRow = slicingRow;
Row typesRow = null;
List<StructuralMatch<ElementDefinition>> children = combined.getChildren();
Row row = gen.new Row();
rows.add(row);
String path = combined.either().getPath();
row.setAnchor(path);
row.setColor(utils.getRowColor(combined.either(), false));
if (eitherHasSlicing(combined))
row.setLineColor(1);
else if (eitherHasSliceName(combined))
row.setLineColor(2);
else
row.setLineColor(0);
boolean ext = false;
if (tail(path).equals("extension")) {
if (elementIsComplex(combined))
row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
else
row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
ext = true;
} else if (tail(path).equals("modifierExtension")) {
if (elementIsComplex(combined))
row.setIcon("icon_modifier_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
else
row.setIcon("icon_modifier_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
} else if (hasChoice(combined)) {
if (allAreReference(combined))
row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
else {
row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE);
typesRow = row;
}
} else if (combined.either().hasContentReference())
row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE);
else if (isPrimitive(combined))
row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
else if (hasTarget(combined))
row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
else if (isDataType(combined))
row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
else
row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
String ref = defPath == null ? null : defPath + combined.either().getId();
String sName = tail(path);
String sn = getSliceName(combined);
if (sn != null)
sName = sName +":"+sn;
UnusedTracker used = new UnusedTracker();
Cell nc;
String leftColor = !combined.hasLeft() ? COLOR_NO_ROW_LEFT : combined.hasErrors() ? COLOR_DIFFERENT : null;
String rightColor = !combined.hasRight() ? COLOR_NO_ROW_LEFT : combined.hasErrors() ? COLOR_DIFFERENT : null;
if (combined.hasLeft()) {
nc = utils.genElementNameCell(gen, combined.getLeft(), "??", true, corePath, prefix, root, false, false, null, typesRow, row, false, ext, used , ref, sName);
} else {
nc = utils.genElementNameCell(gen, combined.getRight(), "??", true, corePath, prefix, root, false, false, null, typesRow, row, false, ext, used , ref, sName);
}
if (combined.hasLeft()) {
frame(utils.genElementCells(gen, combined.getLeft(), "??", true, corePath, prefix, root, false, false, null, typesRow, row, false, ext, used , ref, sName, nc), leftColor);
} else {
frame(spacers(row, 4, gen), leftColor);
}
if (combined.hasRight()) {
frame(utils.genElementCells(gen, combined.getRight(), "??", true, corePath, prefix, root, false, false, null, typesRow, row, false, ext, used, ref, sName, nc), rightColor);
} else {
frame(spacers(row, 4, gen), rightColor);
}
row.getCells().add(cellForMessages(gen, combined.getMessages()));
for (StructuralMatch<ElementDefinition> child : children) {
genElementComp(defPath, gen, row.getSubRows(), child, corePath, prefix, originalRow, false);
}
}
private void frame(List<Cell> cells, String color) {
for (Cell cell : cells) {
if (color != null) {
cell.setStyle("background-color: "+color);
}
}
cells.get(0).setStyle("border-left: 1px grey solid"+(color == null ? "" : "; background-color: "+color));
cells.get(cells.size()-1).setStyle("border-right: 1px grey solid"+(color == null ? "" : "; background-color: "+color));
}
private List<Cell> spacers(Row row, int count, HierarchicalTableGenerator gen) {
List<Cell> res = new ArrayList<>();
for (int i = 0; i < count; i++) {
Cell c = gen.new Cell();
res.add(c);
row.getCells().add(c);
}
return res;
}
private String getSliceName(StructuralMatch<ElementDefinition> combined) {
// TODO Auto-generated method stub
return null;
}
private boolean isDataType(StructuralMatch<ElementDefinition> combined) {
// TODO Auto-generated method stub
return false;
}
private boolean hasTarget(StructuralMatch<ElementDefinition> combined) {
// TODO Auto-generated method stub
return false;
}
private boolean isPrimitive(StructuralMatch<ElementDefinition> combined) {
// TODO Auto-generated method stub
return false;
}
private boolean allAreReference(StructuralMatch<ElementDefinition> combined) {
// TODO Auto-generated method stub
return false;
}
private boolean hasChoice(StructuralMatch<ElementDefinition> combined) {
// TODO Auto-generated method stub
return false;
}
private boolean elementIsComplex(StructuralMatch<ElementDefinition> combined) {
// TODO Auto-generated method stub velement.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue()
return false;
}
private boolean eitherHasSliceName(StructuralMatch<ElementDefinition> combined) {
// TODO Auto-generated method stub
return false;
}
private boolean eitherHasSlicing(StructuralMatch<ElementDefinition> combined) {
// TODO Auto-generated method stub
return false;
}
private String tail(String path) {
if (path.contains("."))
return path.substring(path.lastIndexOf('.')+1);
else
return path;
}
}

View File

@ -47,9 +47,7 @@ public class ResourceComparer {
return id;
}
public void setId(String id) {
this.id = abbreviation()+"-"+id;
}
protected abstract String summary();
}
public final static String COLOR_NO_ROW_LEFT = "#ffffb3";
@ -84,6 +82,7 @@ public class ResourceComparer {
XhtmlNode tbl = div.table("grid");
for (ValidationMessage vm : csc.messages) {
XhtmlNode tr = tbl.tr();
tr.style("background-color: "+colorForLevel(vm.getLevel()));
tr.td().tx(vm.getLocation());
tr.td().tx(vm.getMessage());
tr.td().tx(vm.getLevel().getDisplay());

View File

@ -4,6 +4,7 @@ import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
public class StructuralMatch<T> {
@ -77,5 +78,14 @@ public class StructuralMatch<T> {
return messages;
}
public boolean hasErrors() {
for (ValidationMessage vm : messages) {
if (vm.getLevel() == IssueSeverity.ERROR) {
return true;
}
}
return false;
}
}

View File

@ -60,7 +60,12 @@ public class ValueSetComparer extends CanonicalResourceComparer {
@Override
protected String abbreviation() {
return "sd";
return "vs";
}
@Override
protected String summary() {
return "ValueSet: "+left.present()+" vs "+right.present();
}
}

View File

@ -286,7 +286,7 @@ public class ProfileUtilities extends TranslatingUtilities {
this.pkp = pkp;
}
private class UnusedTracker {
public static class UnusedTracker {
private boolean used;
}
@ -3219,7 +3219,7 @@ public class ProfileUtilities extends TranslatingUtilities {
return (!min.hasValue() ? "" : Integer.toString(min.getValue())) + ".." + (!max.hasValue() ? "" : max.getValue());
}
private void genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef, UnusedTracker tracker, ElementDefinition fallback) {
private Cell genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef, UnusedTracker tracker, ElementDefinition fallback) {
IntegerType min = !hasDef ? new IntegerType() : definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
StringType max = !hasDef ? new StringType() : definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
if (min.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) {
@ -3249,8 +3249,9 @@ public class ProfileUtilities extends TranslatingUtilities {
if (!min.isEmpty() || !max.isEmpty()) {
cell.addPiece(checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), null)));
cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", null)));
cell.addPiece(checkForNoChange(min, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), null)));
cell.addPiece(checkForNoChange(max, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), null)));
}
return cell;
}
@ -3393,13 +3394,9 @@ public class ProfileUtilities extends TranslatingUtilities {
private Row genElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element, List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName, Boolean extensions, boolean snapshot, String corePath, String imagePath, boolean root, boolean logicalModel, boolean isConstraintMode, boolean allInvariants, Row slicingRow) throws IOException, FHIRException {
Row originalRow = slicingRow;
StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1);
String s = tail(element.getPath());
if (element.hasSliceName())
s = s +":"+element.getSliceName();
Row typesRow = null;
List<ElementDefinition> children = getChildren(all, element);
boolean isExtension = (s.equals("extension") || s.equals("modifierExtension"));
// if (!snapshot && isExtension && extensions != null && extensions != isExtension)
// return;
@ -3449,71 +3446,16 @@ public class ProfileUtilities extends TranslatingUtilities {
row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
else
row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
String ref = defPath == null ? null : defPath + element.getId();
UnusedTracker used = new UnusedTracker();
String ref = defPath == null ? null : defPath + element.getId();
String sName = tail(element.getPath());
if (element.hasSliceName())
sName = sName +":"+element.getSliceName();
used.used = true;
if (logicalModel && element.hasRepresentation(PropertyRepresentation.XMLATTR))
s = "@"+s;
String hint = "";
hint = checkAdd(hint, (element.hasSliceName() ? translate("sd.table", "Slice")+" "+element.getSliceName() : ""));
if (hasDef && element.hasDefinition()) {
hint = checkAdd(hint, (hasDef && element.hasSliceName() ? ": " : ""));
hint = checkAdd(hint, !hasDef ? null : gt(element.getDefinitionElement()));
}
Cell left = gen.new Cell(null, ref, s, hint, null);
row.getCells().add(left);
Cell gc = gen.new Cell();
row.getCells().add(gc);
if (element != null && element.getIsModifier())
checkForNoChange(element.getIsModifierElement(), gc.addStyledText(translate("sd.table", "This element is a modifier element"), "?!", null, null, null, false));
if (element != null && element.getMustSupport())
checkForNoChange(element.getMustSupportElement(), gc.addStyledText(translate("sd.table", "This element must be supported"), "S", "white", "red", null, false));
if (element != null && element.getIsSummary())
checkForNoChange(element.getIsSummaryElement(), gc.addStyledText(translate("sd.table", "This element is included in summaries"), "\u03A3", null, null, null, false));
if (element != null && (!element.getConstraint().isEmpty() || !element.getCondition().isEmpty()))
gc.addStyledText(translate("sd.table", "This element has or is affected by some invariants ("+listConstraintsAndConditions(element)+")"), "I", null, null, null, false);
ExtensionContext extDefn = null;
if (ext) {
if (element != null && element.getType().size() == 1 && element.getType().get(0).hasProfile()) {
String eurl = element.getType().get(0).getProfile().get(0).getValue();
extDefn = locateExtension(StructureDefinition.class, eurl);
if (extDefn == null) {
genCardinality(gen, element, row, hasDef, used, null);
row.getCells().add(gen.new Cell(null, null, "?gen-e1? "+element.getType().get(0).getProfile(), null, null));
generateDescription(gen, row, element, (ElementDefinition) element.getUserData(DERIVATION_POINTER), used.used, profile.getUrl(), eurl, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot);
} else {
String name = urltail(eurl);
left.getPieces().get(0).setText(name);
// left.getPieces().get(0).setReference((String) extDefn.getExtensionStructure().getTag("filename"));
left.getPieces().get(0).setHint(translate("sd.table", "Extension URL")+" = "+extDefn.getUrl());
genCardinality(gen, element, row, hasDef, used, extDefn.getElement());
ElementDefinition valueDefn = extDefn.getExtensionValueDefinition();
if (valueDefn != null && !"0".equals(valueDefn.getMax()))
genTypes(gen, row, valueDefn, profileBaseFileName, profile, corePath, imagePath, root);
else // if it's complex, we just call it nothing
// genTypes(gen, row, extDefn.getSnapshot().getElement().get(0), profileBaseFileName, profile);
row.getCells().add(gen.new Cell(null, null, "("+translate("sd.table", "Complex")+")", null, null));
generateDescription(gen, row, element, extDefn.getElement(), used.used, null, extDefn.getUrl(), profile, corePath, imagePath, root, logicalModel, allInvariants, valueDefn, snapshot);
}
} else {
genCardinality(gen, element, row, hasDef, used, null);
if ("0".equals(element.getMax()))
row.getCells().add(gen.new Cell());
else
genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath, root);
generateDescription(gen, row, element, (ElementDefinition) element.getUserData(DERIVATION_POINTER), used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot);
}
} else {
genCardinality(gen, element, row, hasDef, used, null);
if (element.hasSlicing())
row.getCells().add(gen.new Cell(null, corePath+"profiling.html#slicing", "(Slice Definition)", null, null));
else if (hasDef && !"0".equals(element.getMax()) && typesRow == null)
genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath, root);
else
row.getCells().add(gen.new Cell());
generateDescription(gen, row, element, (ElementDefinition) element.getUserData(DERIVATION_POINTER), used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot);
}
sName = "@"+sName;
Cell nc = genElementNameCell(gen, element, profileBaseFileName, snapshot, corePath, imagePath, root, logicalModel, allInvariants, profile, typesRow, row, hasDef, ext, used, ref, sName);
genElementCells(gen, element, profileBaseFileName, snapshot, corePath, imagePath, root, logicalModel, allInvariants, profile, typesRow, row, hasDef, ext, used, ref, sName, nc);
if (element.hasSlicing()) {
if (standardExtensionSlicing(element)) {
used.used = true; // doesn't matter whether we have a type, we're used if we're setting up slicing ... element.hasType() && element.getType().get(0).hasProfile();
@ -3545,7 +3487,7 @@ public class ProfileUtilities extends TranslatingUtilities {
hrow.setColor(getRowColor(element, isConstraintMode));
hrow.setLineColor(1);
hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
hrow.getCells().add(gen.new Cell(null, null, s+":All Slices", "", null));
hrow.getCells().add(gen.new Cell(null, null, sName+":All Slices", "", null));
hrow.getCells().add(gen.new Cell());
hrow.getCells().add(gen.new Cell());
hrow.getCells().add(gen.new Cell());
@ -3560,7 +3502,7 @@ public class ProfileUtilities extends TranslatingUtilities {
hrow.setColor(getRowColor(element, isConstraintMode));
hrow.setLineColor(1);
hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
hrow.getCells().add(gen.new Cell(null, null, s+":All Types", "", null));
hrow.getCells().add(gen.new Cell(null, null, sName+":All Types", "", null));
hrow.getCells().add(gen.new Cell());
hrow.getCells().add(gen.new Cell());
hrow.getCells().add(gen.new Cell());
@ -3569,7 +3511,8 @@ public class ProfileUtilities extends TranslatingUtilities {
row = hrow;
}
Row currRow = row;
Row currRow = row;
boolean isExtension = Utilities.existsInList(tail(element.getPath()), "extension", "modifierExtension");
for (ElementDefinition child : children) {
if (!child.hasSliceName())
currRow = row;
@ -3588,6 +3531,85 @@ public class ProfileUtilities extends TranslatingUtilities {
return slicingRow;
}
public Cell genElementNameCell(HierarchicalTableGenerator gen, ElementDefinition element, String profileBaseFileName, boolean snapshot, String corePath,
String imagePath, boolean root, boolean logicalModel, boolean allInvariants, StructureDefinition profile, Row typesRow, Row row, boolean hasDef,
boolean ext, UnusedTracker used, String ref, String sName) throws IOException {
String hint = "";
hint = checkAdd(hint, (element.hasSliceName() ? translate("sd.table", "Slice")+" "+element.getSliceName() : ""));
if (hasDef && element.hasDefinition()) {
hint = checkAdd(hint, (hasDef && element.hasSliceName() ? ": " : ""));
hint = checkAdd(hint, !hasDef ? null : gt(element.getDefinitionElement()));
}
Cell left = gen.new Cell(null, ref, sName, hint, null);
row.getCells().add(left);
return left;
}
public List<Cell> genElementCells(HierarchicalTableGenerator gen, ElementDefinition element, String profileBaseFileName, boolean snapshot, String corePath,
String imagePath, boolean root, boolean logicalModel, boolean allInvariants, StructureDefinition profile, Row typesRow, Row row, boolean hasDef,
boolean ext, UnusedTracker used, String ref, String sName, Cell nameCell) throws IOException {
List<Cell> res = new ArrayList<>();
Cell gc = gen.new Cell();
row.getCells().add(gc);
res.add(gc);
if (element != null && element.getIsModifier())
checkForNoChange(element.getIsModifierElement(), gc.addStyledText(translate("sd.table", "This element is a modifier element"), "?!", null, null, null, false));
if (element != null && element.getMustSupport())
checkForNoChange(element.getMustSupportElement(), gc.addStyledText(translate("sd.table", "This element must be supported"), "S", "white", "red", null, false));
if (element != null && element.getIsSummary())
checkForNoChange(element.getIsSummaryElement(), gc.addStyledText(translate("sd.table", "This element is included in summaries"), "\u03A3", null, null, null, false));
if (element != null && (!element.getConstraint().isEmpty() || !element.getCondition().isEmpty()))
gc.addStyledText(translate("sd.table", "This element has or is affected by some invariants ("+listConstraintsAndConditions(element)+")"), "I", null, null, null, false);
ExtensionContext extDefn = null;
if (ext) {
if (element != null && element.getType().size() == 1 && element.getType().get(0).hasProfile()) {
String eurl = element.getType().get(0).getProfile().get(0).getValue();
extDefn = locateExtension(StructureDefinition.class, eurl);
if (extDefn == null) {
res.add(genCardinality(gen, element, row, hasDef, used, null));
res.add(addCell(row, gen.new Cell(null, null, "?gen-e1? "+element.getType().get(0).getProfile(), null, null)));
res.add(generateDescription(gen, row, element, (ElementDefinition) element.getUserData(DERIVATION_POINTER), used.used, profile.getUrl(), eurl, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot));
} else {
String name = urltail(eurl);
nameCell.getPieces().get(0).setText(name);
// left.getPieces().get(0).setReference((String) extDefn.getExtensionStructure().getTag("filename"));
nameCell.getPieces().get(0).setHint(translate("sd.table", "Extension URL")+" = "+extDefn.getUrl());
res.add(genCardinality(gen, element, row, hasDef, used, extDefn.getElement()));
ElementDefinition valueDefn = extDefn.getExtensionValueDefinition();
if (valueDefn != null && !"0".equals(valueDefn.getMax()))
res.add(genTypes(gen, row, valueDefn, profileBaseFileName, profile, corePath, imagePath, root));
else // if it's complex, we just call it nothing
// genTypes(gen, row, extDefn.getSnapshot().getElement().get(0), profileBaseFileName, profile);
res.add(addCell(row, gen.new Cell(null, null, "("+translate("sd.table", "Complex")+")", null, null)));
res.add(generateDescription(gen, row, element, extDefn.getElement(), used.used, null, extDefn.getUrl(), profile, corePath, imagePath, root, logicalModel, allInvariants, valueDefn, snapshot));
}
} else {
res.add(genCardinality(gen, element, row, hasDef, used, null));
if ("0".equals(element.getMax()))
res.add(addCell(row, gen.new Cell()));
else
res.add(genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath, root));
res.add(generateDescription(gen, row, element, (ElementDefinition) element.getUserData(DERIVATION_POINTER), used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot));
}
} else {
res.add(genCardinality(gen, element, row, hasDef, used, null));
if (element.hasSlicing())
res.add(addCell(row, gen.new Cell(null, corePath+"profiling.html#slicing", "(Slice Definition)", null, null)));
else if (hasDef && !"0".equals(element.getMax()) && typesRow == null)
res.add(genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath, root));
else
res.add(addCell(row, gen.new Cell()));
res.add(generateDescription(gen, row, element, (ElementDefinition) element.getUserData(DERIVATION_POINTER), used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot));
}
return res;
}
private Cell addCell(Row row, Cell cell) {
row.getCells().add(cell);
return (cell);
}
private String checkAdd(String src, String app) {
return app == null ? src : src + app;
}
@ -3800,7 +3822,7 @@ public class ProfileUtilities extends TranslatingUtilities {
}
private String getRowColor(ElementDefinition element, boolean isConstraintMode) {
public String getRowColor(ElementDefinition element, boolean isConstraintMode) {
switch (element.getUserInt(UD_ERROR_STATUS)) {
case STATUS_HINT: return ROW_COLOR_HINT;
case STATUS_WARNING: return ROW_COLOR_WARNING;
@ -3849,7 +3871,12 @@ public class ProfileUtilities extends TranslatingUtilities {
c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null));
}
}
if (root) {
if (profile.getAbstract()) {
if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
c.addPiece(gen.new Piece(null, "This is an abstract profile", null));
}
}
if (definition.getPath().endsWith("url") && definition.hasFixed()) {
c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen")));
} else {

View File

@ -201,6 +201,11 @@ public class DefinitionNavigator {
public StructureDefinition getStructure() {
return structure;
}
@Override
public String toString() {
return current().getId();
}
}

View File

@ -415,6 +415,10 @@ public class HierarchicalTableGenerator extends TranslatingUtilities {
this.span = span;
}
public Title setStyle(String value) {
super.setStyle(value);
return this;
}
}
public class Row {
@ -569,6 +573,26 @@ public class HierarchicalTableGenerator extends TranslatingUtilities {
return model;
}
public TableModel initComparisonTable(String prefix, String id) {
TableModel model = new TableModel(id, true);
model.setAlternating(true);
model.setDocoImg(prefix+"help16.png");
model.setDocoRef(prefix+"formats.html#table");
model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "Name"), translate("sd.hint", "The logical name of the element"), null, 0));
model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "L Flags"), translate("sd.hint", "Information about the use of the element - Left Structure"), null, 0).setStyle("border-left: 1px grey solid"));
model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "L Card."), translate("sd.hint", "Minimum and Maximum # of times the the element can appear in the instance - Left Structure"), null, 0));
model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "L Type"), translate("sd.hint", "Reference to the type of the element - Left Structure"), null, 100));
model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "L Description & Constraints"), translate("sd.hint", "Additional information about the element - Left Structure"), null, 0).setStyle("border-right: 1px grey solid"));
model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "R Flags"), translate("sd.hint", "Information about the use of the element - Left Structure"), null, 0).setStyle("border-left: 1px grey solid"));
model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "R Card."), translate("sd.hint", "Minimum and Maximum # of times the the element can appear in the instance - Left Structure"), null, 0));
model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "L Type"), translate("sd.hint", "Reference to the type of the element - Left Structure"), null, 100));
model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "L Description & Constraints"), translate("sd.hint", "Additional information about the element - Left Structure"), null, 0).setStyle("border-right: 1px grey solid"));
model.getTitles().add(new Title(null, model.getDocoRef(), translate("sd.head", "Comments"), translate("sd.hint", "Comments about the comparison"), null, 0));
return model;
}
public TableModel initGridTable(String prefix, String id) {
TableModel model = new TableModel(id, false);

View File

@ -14,8 +14,11 @@ import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.comparison.CodeSystemComparer;
import org.hl7.fhir.r5.comparison.CodeSystemComparer.CodeSystemComparison;
import org.hl7.fhir.r5.comparison.ComparisonSession;
import org.hl7.fhir.r5.comparison.ProfileComparer;
import org.hl7.fhir.r5.comparison.ProfileComparer.ProfileComparison;
import org.hl7.fhir.r5.comparison.ValueSetComparer;
import org.hl7.fhir.r5.comparison.ValueSetComparer.ValueSetComparison;
import org.hl7.fhir.r5.conformance.ProfileUtilities;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.formats.IParser.OutputStyle;
import org.hl7.fhir.r5.formats.JsonParser;
@ -24,6 +27,7 @@ import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CodeSystem;
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.test.utils.TestingUtilities;
import org.hl7.fhir.utilities.TextFile;
@ -103,7 +107,7 @@ public class ComparisonTests {
System.out.println("---- Set up Output ----------------------------------------------------------");
Utilities.createDirectory(Utilities.path("[tmp]", "comparison"));
FilesystemPackageCacheManager pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION);
NpmPackage npm = pcm.loadPackage("hl7.fhir.pubpack", "0.0.5");
NpmPackage npm = pcm.loadPackage("hl7.fhir.pubpack", "0.0.6");
for (String f : npm.list("other")) {
TextFile.streamToFile(npm.load("other", f), Utilities.path("[tmp]", "comparison", f));
}
@ -112,7 +116,7 @@ public class ComparisonTests {
CanonicalResource left = load("left");
CanonicalResource right = load("right");
ComparisonSession session = new ComparisonSession(context);
ComparisonSession session = new ComparisonSession(context, "Comparison Tests");
if (left instanceof CodeSystem && right instanceof CodeSystem) {
CodeSystemComparer cs = new CodeSystemComparer(session);
@ -138,11 +142,31 @@ 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);
} else if (left instanceof StructureDefinition && right instanceof StructureDefinition) {
ProfileUtilities utils = new ProfileUtilities(context, null, null);
genSnapshot(utils, (StructureDefinition) left);
genSnapshot(utils, (StructureDefinition) right);
ProfileComparer pc = new ProfileComparer(session, utils);
ProfileComparison csc = pc.compare((StructureDefinition) left, (StructureDefinition) right);
new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path("[tmp]", "comparison", name + "-union.json")), csc.getUnion());
new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path("[tmp]", "comparison", name + "-intersection.json")), csc.getIntersection());
String xmle = new XhtmlComposer(true).compose(pc.renderErrors(csc));
String xml1 = new XhtmlComposer(true).compose(pc.renderMetadata(csc, "", ""));
String xml2 = new XhtmlComposer(true).compose(pc.renderStructure(csc, "", "", "http://hl7.org/fhir"));
// 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);
} else {
throw new FHIRException("Can't compare " + left.fhirType() + " to " + right.fhirType());
}
}
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());
}
private String hd(String text) {
return "<h2>" + text + "</h2>\r\n";
}