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