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}