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