001package org.hl7.fhir.r5.renderers; 002 003import java.util.List; 004import java.util.Map; 005import java.util.HashMap; 006import java.util.ArrayList; 007import java.io.IOException; 008import java.io.UnsupportedEncodingException; 009 010import org.hl7.fhir.exceptions.DefinitionException; 011import org.hl7.fhir.exceptions.FHIRException; 012import org.hl7.fhir.exceptions.FHIRFormatError; 013import org.hl7.fhir.r5.elementmodel.Element; 014import org.hl7.fhir.r5.model.Base; 015import org.hl7.fhir.r5.model.Bundle.BundleEntryComponent; 016import org.hl7.fhir.r5.model.CanonicalResource; 017import org.hl7.fhir.r5.model.CodeSystem; 018import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 019import org.hl7.fhir.r5.model.CodeableReference; 020import org.hl7.fhir.r5.model.Coding; 021import org.hl7.fhir.r5.model.DataType; 022import org.hl7.fhir.r5.model.DomainResource; 023import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; 024import org.hl7.fhir.r5.model.Extension; 025import org.hl7.fhir.r5.model.Narrative; 026import org.hl7.fhir.r5.model.Narrative.NarrativeStatus; 027import org.hl7.fhir.r5.model.Reference; 028import org.hl7.fhir.r5.model.Resource; 029import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper; 030import org.hl7.fhir.r5.renderers.utils.BaseWrappers.PropertyWrapper; 031import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; 032import org.hl7.fhir.r5.renderers.utils.DirectWrappers.ResourceWrapperDirect; 033import org.hl7.fhir.r5.renderers.utils.ElementWrappers.ResourceWrapperMetaElement; 034import org.hl7.fhir.r5.renderers.utils.RenderingContext; 035import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; 036import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceWithReference; 037import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; 038import org.hl7.fhir.r5.utils.EOperationOutcome; 039import org.hl7.fhir.r5.utils.ToolingExtensions; 040import org.hl7.fhir.r5.utils.XVerExtensionManager; 041import org.hl7.fhir.utilities.Utilities; 042import org.hl7.fhir.utilities.xhtml.NodeType; 043import org.hl7.fhir.utilities.xhtml.XhtmlNode; 044 045public abstract class ResourceRenderer extends DataRenderer { 046 047 public enum RendererType { 048 NATIVE, PROFILE, LIQUID 049 050 } 051 052 protected ResourceContext rcontext; 053 protected XVerExtensionManager xverManager; 054 055 056 public ResourceRenderer(RenderingContext context) { 057 super(context); 058 } 059 060 public ResourceRenderer(RenderingContext context, ResourceContext rcontext) { 061 super(context); 062 this.rcontext = rcontext; 063 } 064 065 public ResourceContext getRcontext() { 066 return rcontext; 067 } 068 069 public ResourceRenderer setRcontext(ResourceContext rcontext) { 070 this.rcontext = rcontext; 071 return this; 072 } 073 074 public XhtmlNode build(Resource dr) throws FHIRFormatError, DefinitionException, FHIRException, IOException, EOperationOutcome { 075 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 076 render(x, dr); 077 return x; 078 } 079 /** 080 * given a resource, update it's narrative with the best rendering available 081 * 082 * @param r - the domain resource in question 083 * 084 * @throws IOException 085 * @throws EOperationOutcome 086 * @throws FHIRException 087 */ 088 089 public void render(DomainResource r) throws IOException, FHIRException, EOperationOutcome { 090 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 091 boolean hasExtensions; 092 hasExtensions = render(x, r); 093 inject(r, x, hasExtensions ? NarrativeStatus.EXTENSIONS : NarrativeStatus.GENERATED); 094 } 095 096 public XhtmlNode render(ResourceWrapper r) throws IOException, FHIRException, EOperationOutcome { 097 assert r.getContext() == context; 098 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 099 boolean hasExtensions = render(x, r); 100 if (r.hasNarrative()) { 101 r.injectNarrative(x, hasExtensions ? NarrativeStatus.EXTENSIONS : NarrativeStatus.GENERATED); 102 } 103 return x; 104 } 105 106 public abstract boolean render(XhtmlNode x, Resource r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome; 107 108 public boolean render(XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { 109 ProfileDrivenRenderer pr = new ProfileDrivenRenderer(context); 110 return pr.render(x, r); 111 } 112 113 public void describe(XhtmlNode x, Resource r) throws UnsupportedEncodingException, IOException { 114 x.tx(display(r)); 115 } 116 117 public void describe(XhtmlNode x, ResourceWrapper r) throws UnsupportedEncodingException, IOException { 118 x.tx(display(r)); 119 } 120 121 public abstract String display(Resource r) throws UnsupportedEncodingException, IOException; 122 public abstract String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException; 123 124 public static void inject(DomainResource r, XhtmlNode x, NarrativeStatus status) { 125 if (!x.hasAttribute("xmlns")) 126 x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); 127 if (r.hasLanguage()) { 128 // use both - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues 129 x.setAttribute("lang", r.getLanguage()); 130 x.setAttribute("xml:lang", r.getLanguage()); 131 } 132 r.getText().setUserData("renderer.generated", true); 133 if (!r.hasText() || !r.getText().hasDiv() || r.getText().getDiv().getChildNodes().isEmpty()) { 134 r.setText(new Narrative()); 135 r.getText().setDiv(x); 136 r.getText().setStatus(status); 137 } else { 138 XhtmlNode n = r.getText().getDiv(); 139 n.clear(); 140 n.getChildNodes().addAll(x.getChildNodes()); 141 } 142 } 143 144 public void renderCanonical(Resource res, XhtmlNode x, String url) throws UnsupportedEncodingException, IOException { 145 ResourceWrapper rw = new ResourceWrapperDirect(this.context, res); 146 renderCanonical(rw, x, url); 147 } 148 149 public void renderCanonical(ResourceWrapper rw, XhtmlNode x, String url) throws UnsupportedEncodingException, IOException { 150 renderCanonical(rw, x, url, true, rw.getResource()); 151 } 152 153 public void renderCanonical(ResourceWrapper rw, XhtmlNode x, String url, boolean allowLinks, Resource src) throws UnsupportedEncodingException, IOException { 154 if (url == null) { 155 return; 156 } 157 Resource target = context.getWorker().fetchResource(Resource.class, url, src); 158 if (target == null || !(target instanceof CanonicalResource)) { 159 x.code().tx(url); 160 } else { 161 CanonicalResource cr = (CanonicalResource) target; 162 if (url.contains("|")) { 163 if (target.hasWebPath()) { 164 x.ah(target.getWebPath()).tx(cr.present()+" (version "+cr.getVersion()+")"); 165 } else { 166 url = url.substring(0, url.indexOf("|")); 167 x.code().tx(url); 168 x.tx(": "+cr.present()+" (version "+cr.getVersion()+")"); 169 } 170 } else { 171 if (target.hasWebPath()) { 172 x.ah(target.getWebPath()).tx(cr.present()); 173 } else { 174 x.code().tx(url); 175 x.tx(" ("+cr.present()+")"); 176 } 177 } 178 } 179 } 180 181 public void render(Resource res, XhtmlNode x, DataType type) throws FHIRFormatError, DefinitionException, IOException { 182 if (type instanceof Reference) { 183 renderReference(res, x, (Reference) type); 184 } else if (type instanceof CodeableReference) { 185 CodeableReference cr = (CodeableReference) type; 186 if (cr.hasReference()) { 187 renderReference(res, x, cr.getReference()); 188 } else { 189 render(x, type); 190 } 191 } else { 192 render(x, type); 193 } 194 } 195 196 public void render(ResourceWrapper res, XhtmlNode x, DataType type) throws FHIRFormatError, DefinitionException, IOException { 197 if (type instanceof Reference) { 198 renderReference(res, x, (Reference) type); 199 } else if (type instanceof CodeableReference) { 200 CodeableReference cr = (CodeableReference) type; 201 if (cr.hasReference()) { 202 renderReference(res, x, cr.getReference()); 203 } else { 204 render(x, type); 205 } 206 } else { 207 render(x, type); 208 } 209 } 210 211 public void renderReference(Resource res, XhtmlNode x, Reference r) throws UnsupportedEncodingException, IOException { 212 ResourceWrapper rw = new ResourceWrapperDirect(this.context, res); 213 renderReference(rw, x, r); 214 } 215 216 public void renderReference(ResourceWrapper rw, XhtmlNode x, Reference r) throws UnsupportedEncodingException, IOException { 217 renderReference(rw, x, r, true); 218 } 219 220 public void renderReference(ResourceWrapper rw, XhtmlNode x, Reference r, boolean allowLinks) throws UnsupportedEncodingException, IOException { 221 if (r == null) { 222 x.tx("null!"); 223 return; 224 } 225 XhtmlNode c = null; 226 ResourceWithReference tr = null; 227 if (r.hasReferenceElement() && allowLinks) { 228 tr = resolveReference(rw, r.getReference()); 229 230 if (!r.getReference().startsWith("#")) { 231 if (tr != null && tr.getReference() != null) { 232 c = x.ah(tr.getReference()); 233 } else if (r.getReference().contains("?")) { 234 x.tx("Conditional Reference: "); 235 c = x.code(""); 236 } else { 237 c = x.ah(r.getReference()); 238 } 239 } else { 240 c = x.ah(r.getReference()); 241 } 242 } else { 243 c = x.span(null, null); 244 } 245 if (tr != null && tr.getReference() != null && tr.getReference().startsWith("#")) { 246 c.tx("See above ("); 247 } 248 // what to display: if text is provided, then that. if the reference was resolved, then show the name, or the generated narrative 249 String display = r.hasDisplayElement() ? r.getDisplay() : null; 250 String name = tr != null && tr.getResource() != null ? tr.getResource().getNameFromResource() : null; 251 252 if (display == null && (tr == null || tr.getResource() == null)) { 253 if (!Utilities.noString(r.getReference())) { 254 c.addText(r.getReference()); 255 } else if (r.hasIdentifier()) { 256 renderIdentifier(c, r.getIdentifier()); 257 } else { 258 c.addText("??"); 259 } 260 } else if (context.isTechnicalMode()) { 261 c.addText(r.getReference()); 262 if (display != null) { 263 c.addText(": "+display); 264 } 265 if ((tr == null || (tr.getReference() != null && !tr.getReference().startsWith("#"))) && name != null) { 266 x.addText(" \""+name+"\""); 267 } 268 if (r.hasExtension(ToolingExtensions.EXT_TARGET_ID) || r.hasExtension(ToolingExtensions.EXT_TARGET_PATH)) { 269 x.addText("("); 270 for (Extension ex : r.getExtensionsByUrl(ToolingExtensions.EXT_TARGET_ID)) { 271 if (ex.hasValue()) { 272 x.sep(", "); 273 x.addText("#"+ex.getValue().primitiveValue()); 274 } 275 } 276 for (Extension ex : r.getExtensionsByUrl(ToolingExtensions.EXT_TARGET_PATH)) { 277 if (ex.hasValue()) { 278 x.sep(", "); 279 x.addText("/#"+ex.getValue().primitiveValue()); 280 } 281 } 282 x.addText(")"); 283 } 284 } else { 285 if (display != null) { 286 c.addText(display); 287 } else if (name != null) { 288 c.addText(name); 289 } else { 290 c.tx(". Generated Summary: "); 291 if (tr != null) { 292 new ProfileDrivenRenderer(context).generateResourceSummary(c, tr.getResource(), true, r.getReference().startsWith("#"), true); 293 } 294 } 295 } 296 if (tr != null && tr.getReference() != null && tr.getReference().startsWith("#")) { 297 c.tx(")"); 298 } 299 } 300 301 public void renderReference(ResourceWrapper rw, XhtmlNode x, BaseWrapper r) throws UnsupportedEncodingException, IOException { 302 XhtmlNode c = x; 303 ResourceWithReference tr = null; 304 String v; 305 if (r.has("reference")) { 306 v = r.get("reference").primitiveValue(); 307 tr = resolveReference(rw, v); 308 309 if (!v.startsWith("#")) { 310 if (tr != null && tr.getReference() != null) 311 c = x.ah(tr.getReference()); 312 else 313 c = x.ah(v); 314 } 315 } else { 316 v = ""; 317 } 318 // what to display: if text is provided, then that. if the reference was resolved, then show the generated narrative 319 if (r.has("display")) { 320 c.addText(r.get("display").primitiveValue()); 321 if (tr != null && tr.getResource() != null) { 322 c.tx(". Generated Summary: "); 323 new ProfileDrivenRenderer(context).generateResourceSummary(c, tr.getResource(), true, v.startsWith("#"), false); 324 } 325 } else if (tr != null && tr.getResource() != null) { 326 new ProfileDrivenRenderer(context).generateResourceSummary(c, tr.getResource(), v.startsWith("#"), v.startsWith("#"), false); 327 } else { 328 c.addText(v); 329 } 330 } 331 332 protected ResourceWithReference resolveReference(ResourceWrapper res, String url) { 333 if (url == null) 334 return null; 335 if (url.startsWith("#") && res != null) { 336 for (ResourceWrapper r : res.getContained()) { 337 if (r.getId().equals(url.substring(1))) 338 return new ResourceWithReference(null, r); 339 } 340 return null; 341 } 342 String version = null; 343 if (url.contains("/_history/")) { 344 version = url.substring(url.indexOf("/_history/")+10); 345 url = url.substring(0, url.indexOf("/_history/")); 346 } 347 348 if (rcontext != null) { 349 BundleEntryComponent bundleResource = rcontext.resolve(url); 350 if (bundleResource != null) { 351 String id = bundleResource.getResource().getId(); 352 if (id == null) { 353 id = makeIdFromBundleEntry(bundleResource.getFullUrl()); 354 } 355 String bundleUrl = "#" + bundleResource.getResource().getResourceType().name() + "_" + id; 356 return new ResourceWithReference(bundleUrl, new ResourceWrapperDirect(this.context, bundleResource.getResource())); 357 } 358 org.hl7.fhir.r5.elementmodel.Element bundleElement = rcontext.resolveElement(url, version); 359 if (bundleElement != null) { 360 String bundleUrl = null; 361 Element br = bundleElement.getNamedChild("resource"); 362 if (br.getChildValue("id") != null) { 363 bundleUrl = "#" + br.fhirType() + "_" + br.getChildValue("id"); 364 } else { 365 bundleUrl = "#" +fullUrlToAnchor(bundleElement.getChildValue("fullUrl")); 366 } 367 return new ResourceWithReference(bundleUrl, new ResourceWrapperMetaElement(this.context, br)); 368 } 369 } 370 371 Resource ae = getContext().getWorker().fetchResource(null, url, version); 372 if (ae != null) 373 return new ResourceWithReference(url, new ResourceWrapperDirect(this.context, ae)); 374 else if (context.getResolver() != null) { 375 return context.getResolver().resolve(context, url); 376 } else 377 return null; 378 } 379 380 381 protected String makeIdFromBundleEntry(String url) { 382 if (url == null) { 383 return null; 384 } 385 if (url.startsWith("urn:uuid:")) { 386 return url.substring(9).toLowerCase(); 387 } 388 return fullUrlToAnchor(url); 389 } 390 391 private String fullUrlToAnchor(String url) { 392 return url.replace(":", "").replace("/", "_"); 393 } 394 395 protected void generateCopyright(XhtmlNode x, CanonicalResource cs) { 396 XhtmlNode p = x.para(); 397 p.b().tx(getContext().getWorker().translator().translate("xhtml-gen-cs", "Copyright Statement:", context.getLang())); 398 smartAddText(p, " " + cs.getCopyright()); 399 } 400 401 public String displayReference(Resource res, Reference r) throws UnsupportedEncodingException, IOException { 402 return "todo"; 403 } 404 405 406 public Base parseType(String string, String type) { 407 return null; 408 } 409 410 protected PropertyWrapper getProperty(ResourceWrapper res, String name) { 411 for (PropertyWrapper t : res.children()) { 412 if (t.getName().equals(name)) 413 return t; 414 } 415 return null; 416 } 417 418 protected PropertyWrapper getProperty(BaseWrapper res, String name) { 419 for (PropertyWrapper t : res.children()) { 420 if (t.getName().equals(name)) 421 return t; 422 } 423 return null; 424 } 425 426 protected boolean valued(PropertyWrapper pw) { 427 return pw != null && pw.hasValues(); 428 } 429 430 431 protected ResourceWrapper fetchResource(BaseWrapper subject) throws UnsupportedEncodingException, FHIRException, IOException { 432 if (context.getResolver() == null) 433 return null; 434 435 PropertyWrapper ref = subject.getChildByName("reference"); 436 if (ref == null || !ref.hasValues()) { 437 return null; 438 } 439 String url = ref.value().getBase().primitiveValue(); 440 ResourceWithReference rr = context.getResolver().resolve(context, url); 441 return rr == null ? null : rr.getResource(); 442 } 443 444 445 protected String describeStatus(PublicationStatus status, boolean experimental) { 446 switch (status) { 447 case ACTIVE: return experimental ? "Experimental" : "Active"; 448 case DRAFT: return "draft"; 449 case RETIRED: return "retired"; 450 default: return "Unknown"; 451 } 452 } 453 454 protected void renderCommitteeLink(XhtmlNode x, CanonicalResource cr) { 455 String code = ToolingExtensions.readStringExtension(cr, ToolingExtensions.EXT_WORKGROUP); 456 CodeSystem cs = context.getWorker().fetchCodeSystem("http://terminology.hl7.org/CodeSystem/hl7-work-group"); 457 if (cs == null || !cs.hasWebPath()) 458 x.tx(code); 459 else { 460 ConceptDefinitionComponent cd = CodeSystemUtilities.findCode(cs.getConcept(), code); 461 if (cd == null) { 462 x.tx(code); 463 } else { 464 x.ah(cs.getWebPath()+"#"+cs.getId()+"-"+cd.getCode()).tx(cd.getDisplay()); 465 } 466 } 467 } 468 469 public static String makeInternalBundleLink(String fullUrl) { 470 return fullUrl.replace(":", "-"); 471 } 472 473 public boolean canRender(Resource resource) { 474 return true; 475 } 476 477 protected void renderResourceHeader(ResourceWrapper r, XhtmlNode x, boolean doId) throws UnsupportedEncodingException, FHIRException, IOException { 478 XhtmlNode div = x.div().style("display: inline-block").style("background-color: #d9e0e7").style("padding: 6px") 479 .style("margin: 4px").style("border: 1px solid #8da1b4") 480 .style("border-radius: 5px").style("line-height: 60%"); 481 482 String id = getPrimitiveValue(r, "id"); 483 if (doId) { 484 div.an(id); 485 } 486 487 String lang = getPrimitiveValue(r, "language"); 488 String ir = getPrimitiveValue(r, "implicitRules"); 489 BaseWrapper meta = r.getChildByName("meta").hasValues() ? r.getChildByName("meta").getValues().get(0) : null; 490 String versionId = getPrimitiveValue(meta, "versionId"); 491 String lastUpdated = getPrimitiveValue(meta, "lastUpdated"); 492 String source = getPrimitiveValue(meta, "source"); 493 494 if (id != null || lang != null || versionId != null || lastUpdated != null) { 495 XhtmlNode p = plateStyle(div.para()); 496 p.tx("Resource "); 497 p.tx(r.fhirType()); 498 p.tx(" "); 499 if (id != null) { 500 p.tx("\""+id+"\" "); 501 } 502 if (versionId != null) { 503 p.tx("Version \""+versionId+"\" "); 504 } 505 if (lastUpdated != null) { 506 p.tx("Updated \""); 507 renderDateTime(p, lastUpdated); 508 p.tx("\" "); 509 } 510 if (lang != null) { 511 p.tx(" (Language \""+lang+"\") "); 512 } 513 } 514 if (ir != null) { 515 plateStyle(div.para()).b().tx("Special rules apply: "+ir+"!"); 516 } 517 if (source != null) { 518 plateStyle(div.para()).tx("Information Source: "+source+"!"); 519 } 520 if (meta != null) { 521 PropertyWrapper pl = meta.getChildByName("profile"); 522 if (pl.hasValues()) { 523 XhtmlNode p = plateStyle(div.para()); 524 p.tx(Utilities.pluralize("Profile", pl.getValues().size())+": "); 525 boolean first = true; 526 for (BaseWrapper bw : pl.getValues()) { 527 if (first) first = false; else p.tx(", "); 528 renderCanonical(r, p, bw.getBase().primitiveValue()); 529 } 530 } 531 PropertyWrapper tl = meta.getChildByName("tag"); 532 if (tl.hasValues()) { 533 XhtmlNode p = plateStyle(div.para()); 534 p.tx(Utilities.pluralize("Tag", tl.getValues().size())+": "); 535 boolean first = true; 536 for (BaseWrapper bw : tl.getValues()) { 537 if (first) first = false; else p.tx(", "); 538 String system = getPrimitiveValue(bw, "system"); 539 String version = getPrimitiveValue(bw, "version"); 540 String code = getPrimitiveValue(bw, "system"); 541 String display = getPrimitiveValue(bw, "system"); 542 renderCoding(p, new Coding(system, version, code, display)); 543 } 544 } 545 PropertyWrapper sl = meta.getChildByName("security"); 546 if (sl.hasValues()) { 547 XhtmlNode p = plateStyle(div.para()); 548 p.tx(Utilities.pluralize("Security Label", tl.getValues().size())+": "); 549 boolean first = true; 550 for (BaseWrapper bw : sl.getValues()) { 551 if (first) first = false; else p.tx(", "); 552 String system = getPrimitiveValue(bw, "system"); 553 String version = getPrimitiveValue(bw, "version"); 554 String code = getPrimitiveValue(bw, "system"); 555 String display = getPrimitiveValue(bw, "system"); 556 renderCoding(p, new Coding(system, version, code, display)); 557 } 558 } 559 } 560 561 } 562 563 private XhtmlNode plateStyle(XhtmlNode para) { 564 return para.style("margin-bottom: 0px"); 565 } 566 567 private String getPrimitiveValue(BaseWrapper b, String name) throws UnsupportedEncodingException, FHIRException, IOException { 568 return b != null && b.has(name) && b.getChildByName(name).hasValues() ? b.getChildByName(name).getValues().get(0).getBase().primitiveValue() : null; 569 } 570 571 private String getPrimitiveValue(ResourceWrapper r, String name) throws UnsupportedEncodingException, FHIRException, IOException { 572 return r.has(name) && r.getChildByName(name).hasValues() ? r.getChildByName(name).getValues().get(0).getBase().primitiveValue() : null; 573 } 574 575 public void renderOrError(DomainResource dr) { 576 try { 577 render(dr); 578 } catch (Exception e) { 579 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 580 x.para().tx("Error rendering: "+e.getMessage()); 581 dr.setText(null); 582 inject(dr, x, NarrativeStatus.GENERATED); 583 } 584 585 } 586 587 public RendererType getRendererType() { 588 return RendererType.NATIVE; 589 } 590 591 public class TableRowData { 592 private Map<String, List<DataType>> cols = new HashMap<>(); 593 private TableData data; 594 595 public void value(String name, DataType value) { 596 if (!cols.containsKey(name)) { 597 cols.put(name, new ArrayList<>()); 598 } 599 if (!data.columns.contains(name)) { 600 data.columns.add(name); 601 } 602 cols.get(name).add(value); 603 } 604 605 public boolean hasCol(String name) { 606 return cols.containsKey(name); 607 } 608 609 public List<DataType> get(String name) { 610 return cols.get(name); 611 } 612 613 } 614 public class TableData { 615 private String title; 616 private List<String> columns = new ArrayList<>(); 617 private List<TableRowData> rows = new ArrayList<>(); 618 public TableData(String title) { 619 this.title = title; 620 } 621 public String getTitle() { 622 return title; 623 } 624 public List<String> getColumns() { 625 return columns; 626 } 627 public List<TableRowData> getRows() { 628 return rows; 629 } 630 public void addColumn(String name) { 631 columns.add(name); 632 } 633 public TableRowData addRow() { 634 TableRowData res = new TableRowData(); 635 rows.add(res); 636 res.data = this; 637 return res; 638 } 639 } 640 641 642 public void renderTable(TableData provider, XhtmlNode x) throws FHIRFormatError, DefinitionException, IOException { 643 List<String> columns = new ArrayList<>(); 644 for (String name : provider.getColumns()) { 645 boolean hasData = false; 646 for (TableRowData row : provider.getRows()) { 647 if (row.hasCol(name)) { 648 hasData = true; 649 } 650 } 651 if (hasData) { 652 columns.add(name); 653 } 654 } 655 if (columns.size() > 0) { 656 XhtmlNode table = x.table("grid"); 657 658 if (provider.getTitle() != null) { 659 table.tr().td().colspan(columns.size()).b().tx(provider.getTitle()); 660 } 661 XhtmlNode tr = table.tr(); 662 for (String col : columns) { 663 tr.th().b().tx(col); 664 } 665 for (TableRowData row : provider.getRows()) { 666 tr = table.tr(); 667 for (String col : columns) { 668 XhtmlNode td = tr.td(); 669 boolean first = true; 670 List<DataType> list = row.get(col); 671 if (list != null) { 672 for (DataType value : list) { 673 if (first) first = false; else td.tx(", "); 674 render(td, value); 675 } 676 } 677 } 678 } 679 } 680 } 681}