001package org.hl7.fhir.r5.renderers;
002
003import java.io.IOException;
004import java.io.UnsupportedEncodingException;
005import java.util.List;
006
007import lombok.extern.slf4j.Slf4j;
008import org.apache.commons.codec.binary.Base64;
009import org.hl7.fhir.exceptions.DefinitionException;
010import org.hl7.fhir.exceptions.FHIRException;
011import org.hl7.fhir.exceptions.FHIRFormatError;
012import org.hl7.fhir.r5.model.CanonicalResource;
013import org.hl7.fhir.r5.model.Resource;
014import org.hl7.fhir.r5.model.StructureDefinition;
015import org.hl7.fhir.r5.model.ValueSet;
016import org.hl7.fhir.r5.renderers.Renderer.RenderingStatus;
017import org.hl7.fhir.r5.renderers.utils.RenderingContext;
018import org.hl7.fhir.r5.renderers.utils.ResourceWrapper;
019import org.hl7.fhir.r5.terminologies.utilities.ValidationResult;
020import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules;
021import org.hl7.fhir.r5.renderers.utils.RenderingContext.KnownLinkType;
022import org.hl7.fhir.r5.utils.EOperationOutcome;
023import org.hl7.fhir.r5.utils.ToolingExtensions;
024import org.hl7.fhir.r5.utils.UserDataNames;
025import org.hl7.fhir.r5.utils.sql.Column;
026import org.hl7.fhir.r5.utils.sql.ColumnKind;
027import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
028import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
029import org.hl7.fhir.utilities.Utilities;
030import org.hl7.fhir.utilities.VersionUtilities;
031import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
032import org.hl7.fhir.utilities.xhtml.NodeType;
033import org.hl7.fhir.utilities.xhtml.XhtmlNode;
034import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
035import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
036import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
037import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
038import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Title;
039
040@MarkedToMoveToAdjunctPackage
041@Slf4j
042public class WebTemplateRenderer extends ResourceRenderer {
043  
044  public WebTemplateRenderer(RenderingContext context) { 
045    super(context); 
046  } 
047
048  @Override
049  public boolean renderingUsesValidation() {
050    return true;
051  }
052  
053  @Override
054  public String buildSummary(ResourceWrapper r) throws UnsupportedEncodingException, IOException {
055    return canonicalTitle(r);
056  }
057
058  @Override
059  public void buildNarrative(RenderingStatus status, XhtmlNode x, ResourceWrapper wt) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome {
060    renderResourceTechDetails(wt, x);
061
062    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context, context.getDestDir(), context.isInlineGraphics(), true, ""); 
063    TableModel model = gen.new TableModel("wt="+wt.getId(), context.getRules() == GenerationRules.IG_PUBLISHER);     
064    model.setAlternating(true); 
065    if (context.getRules() == GenerationRules.VALID_RESOURCE || context.isInlineGraphics()) { 
066      model.setDocoImg(HierarchicalTableGenerator.help16AsData());     
067    } else { 
068      model.setDocoImg(Utilities.pathURL(context.getLink(KnownLinkType.SPEC, true), "help16.png")); 
069    }  
070    model.getTitles().add(gen.new Title(null, model.getDocoRef(), ("Name"), (context.formatPhrase(RenderingContext.QUEST_LINK)), null, 0)); 
071    model.getTitles().add(gen.new Title(null, model.getDocoRef(), ("Card."), (context.formatPhrase(RenderingContext.QUEST_TEXTFOR)), null, 0)); 
072    model.getTitles().add(gen.new Title(null, model.getDocoRef(), ("Definition"), (context.formatPhrase(RenderingContext.QUEST_TIMES)), null, 0)); 
073    model.getTitles().add(gen.new Title(null, model.getDocoRef(), ("Type"), (context.formatPhrase(RenderingContext.QUEST_TIMES)), null, 0)); 
074    model.getTitles().add(gen.new Title(null, model.getDocoRef(), ("Inputs"), (context.formatPhrase(RenderingContext.QUEST_TYPE_ITEM)), null, 0)); 
075 
076    // first we add a root for the WebTemplate itself 
077    Row row = addItem(gen, model.getRows(), wt.child("tree")); 
078//    for (ResourceWrapper select : vd.children("select")) { 
079//      renderSelect(status, gen, row.getSubRows(), vd, select); 
080//    } 
081    XhtmlNode xn = gen.generate(model, context.getLocalPrefix(), 1, null); 
082    x.addChildNode(xn); 
083 
084    
085  } 
086
087  private Row addItem(HierarchicalTableGenerator gen, List<Row> rows, ResourceWrapper item) {
088    Row r = gen.new Row(); 
089    rows.add(r); 
090
091    r.setIcon("icon_vd_view.png", context.formatPhrase(RenderingContext.QUEST_ROOT)); 
092    r.getCells().add(gen.new Cell(null, null, item.primitiveValue("name"), null, null)); 
093    
094    r.getCells().add(gen.new Cell(null, null, item.primitiveValue("min")+".."+("-1".equals(item.primitiveValue("max")) ? "*" : item.primitiveValue("max")), null, null));
095    
096    String def = (item.primitiveValue("archetype_id") != null ? item.primitiveValue("archetype_id")+"/" : "")+(item.primitiveValue("nodeId") != null ?item.primitiveValue("nodeId") : "");
097    Cell cell = gen.new Cell(null, null, Utilities.noString(def) ? "--" : def, item.primitiveValue("aqlPath"), null);
098    r.getCells().add(cell);
099    addTermBindings(gen, cell, item);
100    
101    
102    r.getCells().add(gen.new Cell(null, linkForType(item.primitiveValue("rmType")), item.primitiveValue("rmType"), null, null));
103    
104    cell = gen.new Cell(null, null, null, null, null);
105    r.getCells().add(cell);
106    boolean first = true;
107    for (ResourceWrapper input : item.children("inputs")) {
108      if (first) {
109        first = false;
110      } else {
111        cell.getPieces().add(gen.new Piece("br"));
112      }
113      addInput(gen, cell, input);
114    }
115    
116    
117    for (ResourceWrapper child : item.children("children")) {
118      addItem(gen, r.getSubRows(), child);
119    }
120    return r;     
121  }
122
123  private void addInput(HierarchicalTableGenerator gen, Cell cell, ResourceWrapper input) {
124    if (input.has("suffix")) {
125      cell.getPieces().add(gen.new Piece(null, input.primitiveValue("suffix"), null));
126      cell.getPieces().add(gen.new Piece(null, " ", null));      
127    }
128    cell.getPieces().add(gen.new Piece(null, input.primitiveValue("type"), null).addStyle("font-weight: bold"));
129    if (input.has("defaultValue")) {
130      cell.getPieces().add(gen.new Piece(null, "(=", null));
131      cell.getPieces().add(gen.new Piece(null, input.primitiveValue("defaultValue"), null));
132      cell.getPieces().add(gen.new Piece(null, ")", null));
133    }
134    if (input.has("validation")) {
135      addValidation(gen, cell, input.child("validation"));
136    }
137    if (input.has("list")) {
138      addList(gen, cell, input.children("list"));
139    }
140  }
141
142  private void addValidation(HierarchicalTableGenerator gen, Cell cell, ResourceWrapper validation) {
143    cell.getPieces().add(gen.new Piece(null, ": ", null));
144    
145    if (validation.has("range") && !validation.has("precision")) {
146      addRange(gen, cell, validation.child("range"));
147    } else {
148      boolean first = true;
149      if (validation.has("range")) {
150        cell.getPieces().add(gen.new Piece(null, "range: ", null));
151        addRange(gen, cell, validation.child("range"));
152        first = false;
153      }        
154      if (validation.has("precision")) {
155        if (!first) {          
156          cell.getPieces().add(gen.new Piece(null, "; ", null));
157        }
158        cell.getPieces().add(gen.new Piece(null, "precision: ", null));
159        addRange(gen, cell, validation.child("precision"));
160      }        
161    }
162  }
163
164  private void addRange(HierarchicalTableGenerator gen, Cell cell, ResourceWrapper range) {
165    String min = range.primitiveValue("min");
166    String minOp = range.primitiveValue("minOp");
167    String max = range.primitiveValue("max");
168    String maxOp = range.primitiveValue("maxOp");
169    String summ;
170    if ("0".equals(min) && "0".equals(max)) {
171      summ = "0";
172    } else {
173      summ = minOp+min;
174      if (!Utilities.noString(max)) {
175        summ = summ+","+maxOp+max;
176      }
177    } 
178    cell.getPieces().add(gen.new Piece(null, summ, null));
179    
180  }
181
182  private void addList(HierarchicalTableGenerator gen, Cell cell, List<ResourceWrapper> list) {
183    for (ResourceWrapper item : list) {
184      cell.getPieces().add(gen.new Piece("br"));
185      cell.getPieces().add(gen.new Piece(null, "? ", null));
186      cell.getPieces().add(gen.new Piece(null, item.primitiveValue("label"), item.primitiveValue("value")));      
187    }
188    
189  }
190
191  private void addTermBindings(HierarchicalTableGenerator gen, Cell cell, ResourceWrapper item) {
192    for (ResourceWrapper tb : item.children("termBindings")) {
193      String code = tb.primitiveValue("code");
194      ResourceWrapper v = tb.child("value");
195      String value = v.primitiveValue("value");
196      if (value.contains("::")) {
197        value = value.substring(value.indexOf("::")+2).replace("]", "");
198      }
199      String tid = v.primitiveValue("terminologyId");
200      String pfx = "";
201      String link = null;
202      String hint = null;
203      switch (code) {
204      case "SNOMED-CT" : 
205        pfx = "SCT:";
206        link = getLinkForCode("http://snomed.info/sct", null, value);
207        ValidationResult vr = context.getContext().validateCode(context.getTerminologyServiceOptions(), "http://snomed.info/sct", null, value, null);
208        if (vr.isOk()) {
209          hint = "SNOMED CT "+value+": "+vr.getDisplay();
210        }
211        break;
212      case "LOINC" :
213        pfx = "LN:";
214        link = getLinkForCode("http://loinc.org", null, value);
215        vr = context.getContext().validateCode(context.getTerminologyServiceOptions(), "http://loinc.org", null, value, null);
216        if (vr.isOk()) {
217          hint = "LOINC "+value+": "+vr.getDisplay();
218        }
219        break;
220      case "LNC205" :
221        // what is this?
222        break;
223      default: 
224        log.warn("?");
225      }
226      cell.addPiece(gen.new Piece(null, " ", null));
227      cell.addPiece(gen.new Piece(link, pfx+value, hint));      
228    }
229  }
230
231  private String linkForType(String t) {
232    if (t == null) {
233      return null;
234    }
235    StructureDefinition sd = context.getContext().fetchResource(StructureDefinition.class, "http://openehr.org/fhir/StructureDefinition/"+t);
236    if (sd == null) {
237      sd = context.getContext().fetchTypeDefinition(t);
238    }
239    if (sd != null) {
240      return sd.getWebPath();
241    }
242    return null;
243  }
244
245  protected XhtmlNode renderResourceTechDetails(ResourceWrapper r, XhtmlNode x) throws UnsupportedEncodingException, FHIRException, IOException {
246    return renderResourceTechDetails(r, x, (context.isContained() && r.getId() != null ? "#"+r.getId() : r.getId()));
247  }
248  
249  protected XhtmlNode renderResourceTechDetails(ResourceWrapper r, XhtmlNode x, String desc) throws UnsupportedEncodingException, FHIRException, IOException {
250    XhtmlNode p = x.para().attribute("class", "res-header-id");
251    if (desc == null) { 
252      p.b().tx(context.formatPhrase(context.isTechnicalMode() && !isInner() ? RenderingContext.PROF_DRIV_GEN_NARR_TECH : RenderingContext.PROF_DRIV_GEN_NARR, "WebTemplate", ""));      
253    } else {
254      p.b().tx(context.formatPhrase(context.isTechnicalMode() && !isInner() ? RenderingContext.PROF_DRIV_GEN_NARR_TECH : RenderingContext.PROF_DRIV_GEN_NARR, "WebTemplate", desc));
255    }
256
257    // first thing we do is lay down the resource anchors. 
258    String tid = r.primitiveValue("templateId");
259    if (!Utilities.noString(tid)) {
260      String sid = "hc"+tid;
261      if (!context.hasAnchor(sid)) {
262        context.addAnchor(sid);
263        x.an(context.prefixAnchor(sid));
264      }
265    }
266
267    if (context.isTechnicalMode()) {
268      RenderingStatus status = new RenderingStatus();
269
270      String lang = r.primitiveValue("defaultLanguage"); 
271      ResourceWrapper versionId = r.child("semver");
272
273      if (lang != null || versionId != null) {
274        XhtmlNode div = x.div().style("display: inline-block").style("background-color: #d9e0e7").style("padding: 6px")
275            .style("margin: 4px").style("border: 1px solid #8da1b4")
276            .style("border-radius: 5px").style("line-height: 60%");
277
278        boolean sfirst = true;
279        p = plateStyle(div.para());
280        if (tid != null) {
281          p.tx(context.formatPhrase(RenderingContext.RES_REND_TEMPLATE_ID, tid));
282          sfirst = false;
283        }
284        if (versionId != null) {
285          p.tx(context.formatPhrase(RenderingContext.RES_REND_VER, versionId.primitiveValue()));
286          sfirst = false;
287        }
288        if (lang != null) {
289          if (!sfirst) {
290            p.tx("; ");
291          }
292          p.tx(context.formatPhrase(RenderingContext.RES_REND_LANGUAGE, lang));
293          sfirst = false;
294        }
295      }
296    }
297    return null;
298  }
299
300}