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