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