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.model.Reference; 013import org.hl7.fhir.r5.model.Resource; 014import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; 015import org.hl7.fhir.r5.renderers.utils.RenderingContext; 016import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; 017import org.hl7.fhir.r5.utils.EOperationOutcome; 018import org.hl7.fhir.r5.utils.LiquidEngine; 019import org.hl7.fhir.r5.utils.LiquidEngine.ILiquidRenderingSupport; 020import org.hl7.fhir.r5.utils.LiquidEngine.LiquidDocument; 021import org.hl7.fhir.utilities.xhtml.NodeType; 022import org.hl7.fhir.utilities.xhtml.XhtmlComposer; 023import org.hl7.fhir.utilities.xhtml.XhtmlNode; 024import org.hl7.fhir.utilities.xhtml.XhtmlParser; 025 026public class LiquidRenderer extends ResourceRenderer implements ILiquidRenderingSupport { 027 028 public class LiquidRendererContxt { 029 030 private ResourceContext rcontext; 031 private ResourceWrapper resource; 032 033 public LiquidRendererContxt(ResourceContext rcontext, ResourceWrapper r) { 034 this.rcontext = rcontext; 035 this.resource = r; 036 } 037 038 public ResourceWrapper getResource() { 039 return resource; 040 } 041 042 } 043 044 private String liquidTemplate; 045 046 public LiquidRenderer(RenderingContext context, String liquidTemplate) { 047 super(context); 048 this.liquidTemplate = liquidTemplate; 049 } 050 051 public LiquidRenderer(RenderingContext context, ResourceContext rcontext, String liquidTemplate) { 052 super(context); 053 this.rcontext = rcontext; 054 this.liquidTemplate = liquidTemplate; 055 } 056 057 /** 058 * This class provides an implementation of the ILiquidEngineIncludeResolver that makes use of the 059 * template provider available in the rendering context to support resolving includes. 060 */ 061 private class LiquidRendererIncludeResolver implements LiquidEngine.ILiquidEngineIncludeResolver { 062 public LiquidRendererIncludeResolver(RenderingContext context) { 063 this.context = context; 064 } 065 066 private RenderingContext context; 067 068 @Override 069 public String fetchInclude(LiquidEngine engine, String name) { 070 return context.getTemplateProvider().findTemplate(context, name); 071 } 072 } 073 074 @Override 075 public boolean render(XhtmlNode x, Resource r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { 076 LiquidEngine engine = new LiquidEngine(context.getWorker(), context.getServices()); 077 XhtmlNode xn; 078 try { 079 engine.setIncludeResolver(new LiquidRendererIncludeResolver(context)); 080 engine.setRenderingSupport(this); 081 LiquidDocument doc = engine.parse(liquidTemplate, "template"); 082 String html = engine.evaluate(doc, r, rcontext); 083 xn = new XhtmlParser().parseFragment(html); 084 if (!x.getName().equals("div")) 085 throw new FHIRException("Error in template: Root element is not 'div'"); 086 } catch (FHIRException | IOException e) { 087 xn = new XhtmlNode(NodeType.Element, "div"); 088 xn.para().b().style("color: maroon").tx("Exception generating Narrative: "+e.getMessage()); 089 } 090 x.getChildNodes().addAll(xn.getChildNodes()); 091 return true; 092 } 093 094 @Override 095 public String display(Resource r) throws UnsupportedEncodingException, IOException { 096 return "not done yet"; 097 } 098 099 public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException { 100 if (r.has("title")) { 101 return r.children("title").get(0).getBase().primitiveValue(); 102 } 103 if (r.has("name")) { 104 return r.children("name").get(0).getBase().primitiveValue(); 105 } 106 return "??"; 107 } 108 109 @Override 110 public boolean render(XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { 111 LiquidEngine engine = new LiquidEngine(context.getWorker(), context.getServices()); 112 XhtmlNode xn; 113 try { 114 LiquidDocument doc = engine.parse(liquidTemplate, "template"); 115 engine.setRenderingSupport(this); 116 String html = engine.evaluate(doc, r.getBase(), new LiquidRendererContxt(rcontext, r)); 117 xn = new XhtmlParser().parseFragment(html); 118 if (!x.getName().equals("div")) 119 throw new FHIRException("Error in template: Root element is not 'div'"); 120 } catch (FHIRException | IOException e) { 121 xn = new XhtmlNode(NodeType.Element, "div"); 122 xn.para().b().style("color: maroon").tx("Exception generating Narrative: "+e.getMessage()); 123 } 124 x.getChildNodes().addAll(xn.getChildNodes()); 125 return true; 126 } 127 128 public RendererType getRendererType() { 129 return RendererType.LIQUID; 130 } 131 132 @Override 133 public String renderForLiquid(Object appContext, Base base) throws FHIRException { 134 try { 135 if (base instanceof Element) { 136 base = context.getParser().parseType((Element) base); 137 } 138 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 139 if (base instanceof Reference) { 140 renderReference(((LiquidRendererContxt) appContext).getResource(), x, (Reference) base); 141 } else if (base instanceof DataType) { 142 render(x, (DataType) base); 143 } else { 144 x.tx(base.toString()); 145 } 146 String res = new XhtmlComposer(true).compose(x).substring(5); 147 return res.substring(0, res.length()-6); 148 } catch (FHIRFormatError e) { 149 throw new FHIRException(e); 150 } catch (IOException e) { 151 throw new FHIRException(e); 152 } 153 } 154 155}