001package org.hl7.fhir.r5.renderers;
002
003import java.io.IOException;
004import java.io.UnsupportedEncodingException;
005
006import org.hl7.fhir.exceptions.DefinitionException;
007import org.hl7.fhir.exceptions.FHIRException;
008import org.hl7.fhir.exceptions.FHIRFormatError;
009import org.hl7.fhir.r5.elementmodel.Element;
010import org.hl7.fhir.r5.model.Base;
011import org.hl7.fhir.r5.model.DataType;
012import org.hl7.fhir.r5.renderers.utils.RenderingContext;
013import org.hl7.fhir.r5.renderers.utils.ResourceWrapper;
014import org.hl7.fhir.r5.utils.EOperationOutcome;
015import org.hl7.fhir.r5.utils.LiquidEngine;
016import org.hl7.fhir.r5.utils.LiquidEngine.ILiquidRenderingSupport;
017import org.hl7.fhir.r5.utils.LiquidEngine.LiquidDocument;
018import org.hl7.fhir.utilities.xhtml.NodeType;
019import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
020import org.hl7.fhir.utilities.xhtml.XhtmlNode;
021import org.hl7.fhir.utilities.xhtml.XhtmlParser;
022
023public class LiquidRenderer extends ResourceRenderer implements ILiquidRenderingSupport {
024
025  private String liquidTemplate;
026
027  private class LiquidRendererContext {
028    private RenderingStatus status;
029    private ResourceWrapper resource;
030    protected LiquidRendererContext(RenderingStatus status, ResourceWrapper resource) {
031      super();
032      this.status = status;
033      this.resource = resource;
034    }
035    
036  }
037  
038  public LiquidRenderer(RenderingContext context, String liquidTemplate) { 
039    super(context); 
040    this.liquidTemplate = liquidTemplate;
041  } 
042   
043  @Override
044  public String buildSummary(ResourceWrapper r) throws UnsupportedEncodingException, IOException {
045    return canonicalTitle(r);
046  }
047
048  /**
049   * This class provides an implementation of the ILiquidEngineIncludeResolver that makes use of the
050   * template provider available in the rendering context to support resolving includes.
051   */
052  private class LiquidRendererIncludeResolver implements LiquidEngine.ILiquidEngineIncludeResolver {
053    public LiquidRendererIncludeResolver(RenderingContext context) {
054      this.context = context;
055    }
056
057    private RenderingContext context;
058
059    @Override
060    public String fetchInclude(LiquidEngine engine, String name) {
061      return context.getTemplateProvider().findTemplate(context, name);
062    }
063  }
064  
065  @Override
066  public void buildNarrative(RenderingStatus status, XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome {
067    LiquidEngine engine = new LiquidEngine(context.getWorker(), context.getServices());
068    XhtmlNode xn;
069    try {
070      engine.setIncludeResolver(new LiquidRendererIncludeResolver(context));
071      engine.setRenderingSupport(this);
072      LiquidDocument doc = engine.parse(liquidTemplate, "template");
073      String html = engine.evaluate(doc, r.getBase(), new LiquidRendererContext(status, r));
074      xn = new XhtmlParser().parseFragment(html);
075      if (!x.getName().equals("div"))
076        throw new FHIRException("Error in template: Root element is not 'div'");
077    } catch (FHIRException | IOException e) {
078      xn = new XhtmlNode(NodeType.Element, "div");
079      xn.para().b().style("color: maroon").tx("Exception generating Narrative: "+e.getMessage());
080    }
081    x.addChildNodes(xn.getChildNodes());
082    status.setExtensions(true);
083  }
084
085  public RendererType getRendererType() {
086    return RendererType.LIQUID;
087  }
088
089  @Override
090  public String renderForLiquid(Object appContext, Base base) throws FHIRException {
091    try {
092      LiquidRendererContext ctxt = (LiquidRendererContext) appContext;
093      ResourceWrapper r = null;
094      if (base instanceof Element) {
095        r = ResourceWrapper.forType(context.getContextUtilities(), (Element) base);
096      } else if (base instanceof DataType) {
097        r = ResourceWrapper.forType(context.getContextUtilities(), (DataType) base);        
098      } else {
099        return base.toString(); 
100      }
101      XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 
102      renderDataType(ctxt.status, x, r);
103      String res = new XhtmlComposer(true).compose(x);
104      res = res.substring(5);
105      if (res.length() < 6) {
106        return "";
107      } else {
108        return res.substring(0, res.length()-6);
109      }
110    } catch (FHIRFormatError e) {
111      throw new FHIRException(e);
112    } catch (IOException e) {
113      throw new FHIRException(e);
114    }
115  }
116
117}