001package org.hl7.fhir.r5.comparison; 002 003import java.io.File; 004import java.io.FileOutputStream; 005import java.io.IOException; 006import java.io.PrintWriter; 007import java.io.StringWriter; 008import java.util.ArrayList; 009import java.util.Collections; 010import java.util.HashMap; 011import java.util.List; 012import java.util.Map; 013import java.util.Set; 014 015import org.hl7.fhir.exceptions.FHIRException; 016import org.hl7.fhir.exceptions.PathEngineException; 017import org.hl7.fhir.r5.comparison.CapabilityStatementComparer.CapabilityStatementComparison; 018import org.hl7.fhir.r5.comparison.CodeSystemComparer.CodeSystemComparison; 019import org.hl7.fhir.r5.comparison.ResourceComparer.PlaceHolderComparison; 020import org.hl7.fhir.r5.comparison.ResourceComparer.ResourceComparison; 021import org.hl7.fhir.r5.comparison.StructureDefinitionComparer.ProfileComparison; 022import org.hl7.fhir.r5.comparison.ValueSetComparer.ValueSetComparison; 023import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; 024import org.hl7.fhir.r5.context.IWorkerContext; 025import org.hl7.fhir.r5.formats.IParser.OutputStyle; 026import org.hl7.fhir.r5.model.Base; 027import org.hl7.fhir.r5.model.ExpressionNode.CollectionStatus; 028import org.hl7.fhir.r5.model.StringType; 029import org.hl7.fhir.r5.model.Tuple; 030import org.hl7.fhir.r5.model.TypeDetails; 031import org.hl7.fhir.r5.model.ValueSet; 032import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext; 033import org.hl7.fhir.r5.utils.FHIRPathUtilityClasses.FunctionDetails; 034import org.hl7.fhir.r5.utils.LiquidEngine; 035import org.hl7.fhir.r5.utils.LiquidEngine.LiquidDocument; 036import org.hl7.fhir.utilities.TextFile; 037import org.hl7.fhir.utilities.Utilities; 038import org.hl7.fhir.utilities.xhtml.XhtmlComposer; 039 040public class ComparisonRenderer implements IEvaluationContext { 041 042 private IWorkerContext contextLeft; 043 private IWorkerContext contextRight; 044 private ComparisonSession session; 045 private Map<String, String> templates = new HashMap<>(); 046 private String folder; 047 private String preamble; 048 049 public ComparisonRenderer(IWorkerContext contextLeft, IWorkerContext contextRight, String folder, ComparisonSession session) { 050 super(); 051 this.contextLeft = contextLeft; 052 this.contextRight = contextRight; 053 this.folder = folder; 054 this.session = session; 055 } 056 057 public String getPreamble() { 058 return preamble; 059 } 060 061 public void setPreamble(String preamble) { 062 this.preamble = preamble; 063 } 064 065 public Map<String, String> getTemplates() { 066 return templates; 067 } 068 069 public File render(String leftName, String rightName) throws IOException { 070 dumpBinaries(); 071 StringBuilder b = new StringBuilder(); 072 if (preamble != null) { 073 b.append(preamble); 074 } 075 b.append("<table class=\"grid\">\r\n"); 076 b.append(" <tr>\r\n"); 077 b.append(" <td width=\"260\"><b>"+Utilities.escapeXml(leftName)+"</b></td>\r\n"); 078 b.append(" <td width=\"260\"><b>"+Utilities.escapeXml(rightName)+"</b></td>\r\n"); 079 b.append(" <td width=\"100\"><b>Difference</b></td>\r\n"); 080 b.append(" <td width=\"260\"><b>Notes</b></td>\r\n"); 081 b.append(" </tr>\r\n"); 082 083 List<String> list = sorted(session.getCompares().keySet()); 084 processList(list, b, "CodeSystem"); 085 processList(list, b, "ValueSet"); 086 processList(list, b, "StructureDefinition"); 087 processList(list, b, "CapabilityStatement"); 088 b.append("</table>\r\n"); 089 090 Map<String, Base> vars = new HashMap<>(); 091 vars.put("title", new StringType(session.getTitle())); 092 vars.put("list", new StringType(b.toString())); 093 String template = templates.get("Index"); 094 String cnt = processTemplate(template, "CodeSystem", vars); 095 TextFile.stringToFile(cnt, file("index.html")); 096 return new File(file("index.html")); 097 } 098 099 private void processList(List<String> list, StringBuilder b, String name) throws IOException { 100 // TODO Auto-generated method stub 101 boolean first = true; 102 for (String id : list) { 103 ResourceComparison comp = session.getCompares().get(id); 104 if (comp.fhirType().equals(name)) { 105 if (first) { 106 first = false; 107 b.append("<tr><td colspan=\"4\"><b>"+Utilities.pluralize(name, 2)+"</b></td></tr>\r\n"); 108 } 109 try { 110 renderComparison(id, comp); 111 } catch (Exception e) { 112 System.out.println("Exception rendering "+id+": "+e.getMessage()); 113 e.printStackTrace(); 114 } 115 b.append(comp.toTable()); 116 //"<li><a href=\""+comp.getId()+".html\">"+Utilities.escapeXml(comp.summary())+"</a></li>\r\n" 117 } 118 } 119 } 120 121 private List<String> sorted(Set<String> keySet) { 122 List<String> list = new ArrayList<>(); 123 list.addAll(keySet); 124 Collections.sort(list); 125 return list; 126 } 127 128 private void dumpBinaries() throws IOException { 129 if (contextLeft != null && contextLeft.getBinaryKeysAsSet() != null) { 130 for (String k : contextLeft.getBinaryKeysAsSet()) { 131 TextFile.bytesToFile(contextLeft.getBinaryForKey(k), Utilities.path(folder, k)); 132 } 133 } 134 if (contextRight != null && contextRight.getBinaryKeysAsSet() != null) { 135 for (String k : contextRight.getBinaryKeysAsSet()) { 136 TextFile.bytesToFile(contextRight.getBinaryForKey(k), Utilities.path(folder, k)); 137 } 138 } 139 } 140 141 private void renderComparison(String id, ResourceComparison comp) throws IOException { 142 if (comp instanceof ProfileComparison) { 143 renderProfile(id, (ProfileComparison) comp); 144 } else if (comp instanceof ValueSetComparison) { 145 renderValueSet(id, (ValueSetComparison) comp); 146 } else if (comp instanceof CodeSystemComparison) { 147 renderCodeSystem(id, (CodeSystemComparison) comp); 148 } else if (comp instanceof CapabilityStatementComparison) { 149 renderCapabilityStatement(id, (CapabilityStatementComparison) comp); 150 } else if (comp instanceof PlaceHolderComparison) { 151 renderPlaceHolder(id, (PlaceHolderComparison) comp); 152 } 153 } 154 155 private void renderPlaceHolder(String id, PlaceHolderComparison comp) throws IOException { 156 String cnt = ""; 157 if (comp.getE() != null) { 158 StringWriter sw = new StringWriter(); 159 PrintWriter pw = new PrintWriter(sw); 160 comp.getE().printStackTrace(pw); 161 cnt = sw.toString(); 162 } 163 cnt = "<html><body><pre>"+cnt+"</pre></body></html>\r\n"; 164 TextFile.stringToFile(cnt, file(comp.getId()+".html")); 165 } 166 167 private void renderCodeSystem(String id, CodeSystemComparison comp) throws IOException { 168 String template = templates.get("CodeSystem"); 169 Map<String, Base> vars = new HashMap<>(); 170 CodeSystemComparer cs = new CodeSystemComparer(session); 171 vars.put("left", new StringType(comp.getLeft().present())); 172 vars.put("right", new StringType(comp.getRight().present())); 173 vars.put("leftId", new StringType(comp.getLeft().getId())); 174 vars.put("rightId", new StringType(comp.getRight().getId())); 175 vars.put("leftUrl", new StringType(comp.getLeft().getUrl())); 176 vars.put("rightUrl", new StringType(comp.getRight().getUrl())); 177 vars.put("summary", new StringType(comp.summary())); 178 vars.put("errors", new StringType(new XhtmlComposer(true).compose(cs.renderErrors(comp)))); 179 vars.put("metadata", new StringType(new XhtmlComposer(true).compose(cs.renderMetadata(comp, "", "")))); 180 vars.put("concepts", new StringType(new XhtmlComposer(true).compose(cs.renderConcepts(comp, "", "")))); 181 String cnt = processTemplate(template, "CodeSystem", vars); 182 TextFile.stringToFile(cnt, file(comp.getId()+".html")); 183 new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(folder, comp.getId() + "-union.json")), comp.getUnion()); 184 new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(folder, comp.getId() + "-intersection.json")), comp.getIntersection()); 185 } 186 187 private String file(String name) throws IOException { 188 return Utilities.path(folder, name); 189 } 190 191 private void renderValueSet(String id, ValueSetComparison comp) throws FHIRException, IOException { 192 String template = templates.get("ValueSet"); 193 Map<String, Base> vars = new HashMap<>(); 194 ValueSetComparer cs = new ValueSetComparer(session); 195 vars.put("left", new StringType(comp.getLeft().present())); 196 vars.put("right", new StringType(comp.getRight().present())); 197 vars.put("leftId", new StringType(comp.getLeft().getId())); 198 vars.put("rightId", new StringType(comp.getRight().getId())); 199 vars.put("leftUrl", new StringType(comp.getLeft().getUrl())); 200 vars.put("rightUrl", new StringType(comp.getRight().getUrl())); 201 vars.put("summary", new StringType(comp.summary())); 202 vars.put("errors", new StringType(new XhtmlComposer(true).compose(cs.renderErrors(comp)))); 203 vars.put("metadata", new StringType(new XhtmlComposer(true).compose(cs.renderMetadata(comp, "", "")))); 204 vars.put("compose", new StringType(new XhtmlComposer(true).compose(cs.renderCompose(comp, "", "")))); 205 vars.put("expansion", new StringType(new XhtmlComposer(true).compose(cs.renderExpansion(comp, "", "")))); 206 String cnt = processTemplate(template, "ValueSet", vars); 207 TextFile.stringToFile(cnt, file(comp.getId()+".html")); 208 new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(folder, comp.getId() + "-union.json")), comp.getUnion()); 209 new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(folder, comp.getId() + "-intersection.json")), comp.getIntersection()); 210 } 211 212 private void renderProfile(String id, ProfileComparison comp) throws IOException { 213 String template = templates.get("Profile"); 214 Map<String, Base> vars = new HashMap<>(); 215 StructureDefinitionComparer cs = new StructureDefinitionComparer(session, new ProfileUtilities(session.getContextLeft(), null, session.getPkpLeft()), 216 new ProfileUtilities(session.getContextRight(), null, session.getPkpRight())); 217 vars.put("left", new StringType(comp.getLeft().present())); 218 vars.put("right", new StringType(comp.getRight().present())); 219 vars.put("leftId", new StringType(comp.getLeft().getId())); 220 vars.put("rightId", new StringType(comp.getRight().getId())); 221 vars.put("leftUrl", new StringType(comp.getLeft().getUrl())); 222 vars.put("rightUrl", new StringType(comp.getRight().getUrl())); 223 vars.put("errors", new StringType(new XhtmlComposer(true).compose(cs.renderErrors(comp)))); 224 vars.put("metadata", new StringType(new XhtmlComposer(true).compose(cs.renderMetadata(comp, "", "")))); 225 vars.put("structure", new StringType(new XhtmlComposer(true).compose(cs.renderStructure(comp, "", "", "http://hl7.org/fhir")))); 226 String cnt = processTemplate(template, "CodeSystem", vars); 227 TextFile.stringToFile(cnt, file(comp.getId()+".html")); 228 new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(folder, comp.getId() + "-union.json")), comp.getUnion()); 229 new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(folder, comp.getId() + "-intersection.json")), comp.getIntersection()); 230 } 231 232 private void renderCapabilityStatement(String id, CapabilityStatementComparison comp) throws IOException { 233 String template = templates.get("CapabilityStatement"); 234 Map<String, Base> vars = new HashMap<>(); 235 CapabilityStatementComparer cs = new CapabilityStatementComparer(session); 236 vars.put("left", new StringType(comp.getLeft().present())); 237 vars.put("right", new StringType(comp.getRight().present())); 238 vars.put("leftId", new StringType(comp.getLeft().getId())); 239 vars.put("rightId", new StringType(comp.getRight().getId())); 240 vars.put("leftUrl", new StringType(comp.getLeft().getUrl())); 241 vars.put("rightUrl", new StringType(comp.getRight().getUrl())); 242 vars.put("errors", new StringType(new XhtmlComposer(true).compose(cs.renderErrors(comp)))); 243 vars.put("metadata", new StringType(new XhtmlComposer(true).compose(cs.renderMetadata(comp, "", "")))); 244 vars.put("statement", new StringType(new XhtmlComposer(true).compose(cs.renderStatements(comp, "", "")))); 245 String cnt = processTemplate(template, "CapabilityStatement", vars); 246 TextFile.stringToFile(cnt, file(comp.getId()+".html")); 247 new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(folder, comp.getId() + "-union.json")), comp.getUnion()); 248 new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(folder, comp.getId() + "-intersection.json")), comp.getIntersection()); 249 } 250 251 private String processTemplate(String template, String name, Map<String, Base> vars) { 252 LiquidEngine engine = new LiquidEngine(contextRight, this); 253 LiquidDocument doc = engine.parse(template, name+".template"); 254 return engine.evaluate(doc, Tuple.fromMap(vars), vars); 255 } 256 257 @Override 258 public List<Base> resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException { 259 @SuppressWarnings("unchecked") 260 Map<String, Base> vars = (Map<String, Base>) appContext; 261 List<Base> res = new ArrayList<>(); 262 if (vars.containsKey(name)) { 263 res.add(vars.get(name)); 264 } 265 return res; 266 } 267 268 @Override 269 public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException { 270 @SuppressWarnings("unchecked") 271 Map<String, Base> vars = (Map<String, Base>) appContext; 272 Base b = vars.get(name); 273 return new TypeDetails(CollectionStatus.SINGLETON, b == null ? "Base" : b.fhirType()); 274 } 275 276 @Override 277 public boolean log(String argument, List<Base> focus) { 278 return false; 279 } 280 281 @Override 282 public FunctionDetails resolveFunction(String functionName) { 283 return null; 284 } 285 286 @Override 287 public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException { 288 return null; 289 } 290 291 @Override 292 public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) { 293 return null; 294 } 295 296 @Override 297 public Base resolveReference(Object appContext, String url, Base refContext) throws FHIRException { 298 return null; 299 } 300 301 @Override 302 public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException { 303 return false; 304 } 305 306 @Override 307 public ValueSet resolveValueSet(Object appContext, String url) { 308 return null; 309 } 310 311}