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}