
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}