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.PrimitiveType; 027import org.hl7.fhir.r5.model.Narrative.NarrativeStatus; 028import org.hl7.fhir.r5.model.Reference; 029import org.hl7.fhir.r5.model.Resource; 030import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper; 031import org.hl7.fhir.r5.renderers.utils.BaseWrappers.PropertyWrapper; 032import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; 033import org.hl7.fhir.r5.renderers.utils.DirectWrappers.ResourceWrapperDirect; 034import org.hl7.fhir.r5.renderers.utils.ElementWrappers.ResourceWrapperMetaElement; 035import org.hl7.fhir.r5.renderers.utils.RenderingContext; 036import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules; 037import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; 038import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceReferenceKind; 039import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceWithReference; 040import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; 041import org.hl7.fhir.r5.utils.EOperationOutcome; 042import org.hl7.fhir.r5.utils.ToolingExtensions; 043import org.hl7.fhir.r5.utils.XVerExtensionManager; 044import org.hl7.fhir.utilities.Utilities; 045import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 046import org.hl7.fhir.utilities.xhtml.NodeType; 047import org.hl7.fhir.utilities.xhtml.XhtmlNode; 048import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece; 049 050public abstract class ResourceRenderer extends DataRenderer { 051 052 public enum RendererType { 053 NATIVE, PROFILE, LIQUID 054 055 } 056 057 protected ResourceContext rcontext; 058 protected XVerExtensionManager xverManager; 059 protected boolean multiLangMode; 060 061 062 public ResourceRenderer(RenderingContext context) { 063 super(context); 064 } 065 066 public ResourceRenderer(RenderingContext context, ResourceContext rcontext) { 067 super(context); 068 this.rcontext = rcontext; 069 } 070 071 public ResourceContext getRcontext() { 072 return rcontext; 073 } 074 075 public ResourceRenderer setRcontext(ResourceContext rcontext) { 076 this.rcontext = rcontext; 077 return this; 078 } 079 080 081 public boolean isMultiLangMode() { 082 return multiLangMode; 083 } 084 085 public ResourceRenderer setMultiLangMode(boolean multiLangMode) { 086 this.multiLangMode = multiLangMode; 087 return this; 088 } 089 090 public XhtmlNode build(Resource dr) throws FHIRFormatError, DefinitionException, FHIRException, IOException, EOperationOutcome { 091 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 092 render(x, dr); 093 return x; 094 } 095 /** 096 * given a resource, update it's narrative with the best rendering available 097 * 098 * @param r - the domain resource in question 099 * 100 * @throws IOException 101 * @throws EOperationOutcome 102 * @throws FHIRException 103 */ 104 105 public void render(DomainResource r) throws IOException, FHIRException, EOperationOutcome { 106 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 107 boolean hasExtensions; 108 hasExtensions = render(x, r); 109 String an = r.fhirType()+"_"+r.getId(); 110 if (context.isAddName()) { 111 if (!hasAnchorName(x, an)) { 112 injectAnchorName(x, an); 113 } 114 } 115 inject(r, x, hasExtensions ? NarrativeStatus.EXTENSIONS : NarrativeStatus.GENERATED); 116 } 117 118 public XhtmlNode render(ResourceWrapper r) throws IOException, FHIRException, EOperationOutcome { 119 assert r.getContext() == context; 120 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 121 boolean hasExtensions = render(x, r); 122 123 String an = r.fhirType()+"_"+r.getId(); 124 if (context.isAddName()) { 125 if (!hasAnchorName(x, an)) { 126 injectAnchorName(x, an); 127 } 128 } 129 if (r.hasNarrative()) { 130 r.injectNarrative(this, x, hasExtensions ? NarrativeStatus.EXTENSIONS : NarrativeStatus.GENERATED); 131 } 132 return x; 133 } 134 135 public XhtmlNode checkNarrative(ResourceWrapper r) throws IOException, FHIRException, EOperationOutcome { 136 assert r.getContext() == context; 137 XhtmlNode x = r.getNarrative(); 138 String an = r.fhirType()+"_"+r.getId(); 139 if (context.isAddName()) { 140 if (!hasAnchorName(x, an)) { 141 injectAnchorName(x, an); 142 } 143 } 144 return x; 145 } 146 147 private void injectAnchorName(XhtmlNode x, String an) { 148 XhtmlNode ip = x; 149 while (ip.hasChildren() && "div".equals(ip.getChildNodes().get(0).getName())) { 150 ip = ip.getChildNodes().get(0); 151 } 152 ip.addTag(0, "a").setAttribute("name", an).tx(" "); 153 } 154 155 protected boolean hasAnchorName(XhtmlNode x, String an) { 156 if ("a".equals(x.getName()) && an.equals(x.getAttribute("name"))) { 157 return true; 158 } 159 if (x.hasChildren()) { 160 for (XhtmlNode c : x.getChildNodes()) { 161 if (hasAnchorName(c, an)) { 162 return true; 163 } 164 } 165 } 166 return false; 167 } 168 169 public abstract boolean render(XhtmlNode x, Resource r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome; 170 171 public boolean render(XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { 172 ProfileDrivenRenderer pr = new ProfileDrivenRenderer(context); 173 return pr.render(x, r); 174 } 175 176 public void describe(XhtmlNode x, Resource r) throws UnsupportedEncodingException, IOException { 177 x.tx(display(r)); 178 } 179 180 public void describe(XhtmlNode x, ResourceWrapper r) throws UnsupportedEncodingException, IOException { 181 x.tx(display(r)); 182 } 183 184 public abstract String display(Resource r) throws UnsupportedEncodingException, IOException; 185 public abstract String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException; 186 187 public void inject(DomainResource r, XhtmlNode x, NarrativeStatus status) { 188 r.getText().setUserData("renderer.generated", true); 189 if (!r.hasText() || !r.getText().hasDiv()) { 190 r.setText(new Narrative()); 191 r.getText().setStatus(status); 192 } 193 if (multiLangMode) { 194 if (!r.getText().hasDiv()) { 195 XhtmlNode div = new XhtmlNode(NodeType.Element, "div"); 196 div.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); 197 r.getText().setDiv(div); 198 } else { 199 r.getText().getDiv().getChildNodes().removeIf(c -> !"div".equals(c.getName()) || !c.hasAttribute("xml:lang")); 200 } 201 markLanguage(x); 202 r.getText().getDiv().getChildNodes().add(x); 203 } else { 204 if (!x.hasAttribute("xmlns")) 205 x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml"); 206 if (r.hasLanguage()) { 207 // use both - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues 208 x.setAttribute("lang", r.getLanguage()); 209 x.setAttribute("xml:lang", r.getLanguage()); 210 } 211 r.getText().setDiv(x); 212 } 213 } 214 215 public void renderCanonical(Resource res, XhtmlNode x, String url) throws UnsupportedEncodingException, IOException { 216 ResourceWrapper rw = new ResourceWrapperDirect(this.context, res); 217 renderCanonical(rw, x, url); 218 } 219 220 public void renderCanonical(ResourceWrapper rw, XhtmlNode x, String url) throws UnsupportedEncodingException, IOException { 221 renderCanonical(rw, x, url, true, rw.getResource()); 222 } 223 224 public void renderCanonical(ResourceWrapper rw, XhtmlNode x, String url, boolean allowLinks, Resource src) throws UnsupportedEncodingException, IOException { 225 if (url == null) { 226 return; 227 } 228 Resource target = context.getWorker().fetchResource(Resource.class, url, src); 229 if (target == null || !(target instanceof CanonicalResource)) { 230 x.code().tx(url); 231 } else { 232 CanonicalResource cr = (CanonicalResource) target; 233 if (url.contains("|")) { 234 if (target.hasWebPath()) { 235 x.ah(target.getWebPath()).tx(cr.present()+ context.formatPhrase(RenderingContext.RES_REND_VER) +cr.getVersion()+")"); 236 } else { 237 url = url.substring(0, url.indexOf("|")); 238 x.code().tx(url); 239 x.tx(": "+cr.present()+ context.formatPhrase(RenderingContext.RES_REND_VER) +cr.getVersion()+")"); 240 } 241 } else { 242 if (target.hasWebPath()) { 243 x.ah(target.getWebPath()).tx(cr.present()); 244 } else { 245 x.code().tx(url); 246 x.tx(" ("+cr.present()+")"); 247 } 248 } 249 } 250 } 251 252 public void render(Resource res, XhtmlNode x, DataType type) throws FHIRFormatError, DefinitionException, IOException { 253 if (type instanceof Reference) { 254 renderReference(res, x, (Reference) type); 255 } else if (type instanceof CodeableReference) { 256 CodeableReference cr = (CodeableReference) type; 257 if (cr.hasReference()) { 258 renderReference(res, x, cr.getReference()); 259 } else { 260 render(x, type); 261 } 262 } else { 263 render(x, type); 264 } 265 } 266 267 public void render(ResourceWrapper res, XhtmlNode x, DataType type) throws FHIRFormatError, DefinitionException, IOException { 268 if (type instanceof Reference) { 269 renderReference(res, x, (Reference) type); 270 } else if (type instanceof CodeableReference) { 271 CodeableReference cr = (CodeableReference) type; 272 if (cr.hasReference()) { 273 renderReference(res, x, cr.getReference()); 274 } else { 275 render(x, type); 276 } 277 } else { 278 render(x, type); 279 } 280 } 281 282 public void renderReference(Resource res, XhtmlNode x, Reference r) throws UnsupportedEncodingException, IOException { 283 ResourceWrapper rw = new ResourceWrapperDirect(this.context, res); 284 renderReference(rw, x, r); 285 } 286 287 public void renderReference(ResourceWrapper rw, XhtmlNode x, Reference r) throws UnsupportedEncodingException, IOException { 288 renderReference(rw, x, r, true); 289 } 290 291 public void renderReference(Resource res, HierarchicalTableGenerator gen, List<Piece> pieces, Reference r, boolean allowLinks) throws UnsupportedEncodingException, IOException { 292 if (r == null) { 293 pieces.add(gen.new Piece(null, "null!", null)); 294 return; 295 } 296 ResourceWrapper rw = new ResourceWrapperDirect(this.context, res); 297 ResourceWithReference tr = null; 298 String link = null; 299 StringBuilder text = new StringBuilder(); 300 if (r.hasReferenceElement() && allowLinks) { 301 tr = resolveReference(rw, r.getReference()); 302 303 if (!r.getReference().startsWith("#")) { 304 if (tr != null && tr.getReference() != null) { 305 link = tr.getReference(); 306 } else if (r.getReference().contains("?")) { 307 text.append(context.formatPhrase(RenderingContext.RES_REND_COND_REF)+" "); 308 } else { 309 link = r.getReference(); 310 } 311 } 312 } 313 if (tr != null && tr.getReference() != null && tr.getReference().startsWith("#")) { 314 text.append(context.formatPhrase(RenderingContext.RES_REND_SEE_ON_THIS_PAGE)+" "); 315 } 316 // what to display: if text is provided, then that. if the reference was resolved, then show the name, or the generated narrative 317 String display = r.hasDisplayElement() ? r.getDisplay() : null; 318 String name = tr != null && tr.getResource() != null ? tr.getResource().getNameFromResource() : null; 319 320 if (display == null && (tr == null || tr.getResource() == null)) { 321 if (!Utilities.noString(r.getReference())) { 322 text.append(r.getReference()); 323 } else if (r.hasIdentifier()) { 324 text.append(displayIdentifier(r.getIdentifier())); 325 } else { 326 text.append("??"); 327 } 328 } else if (context.isTechnicalMode()) { 329 text.append(r.getReference()); 330 if (display != null) { 331 text.append(": "+display); 332 } 333 if ((tr == null || (tr.getReference() != null && !tr.getReference().startsWith("#"))) && name != null) { 334 text.append(" \""+name+"\""); 335 } 336 if (r.hasExtension(ToolingExtensions.EXT_TARGET_ID) || r.hasExtension(ToolingExtensions.EXT_TARGET_PATH)) { 337 text.append("("); 338 for (Extension ex : r.getExtensionsByUrl(ToolingExtensions.EXT_TARGET_ID)) { 339 if (ex.hasValue()) { 340 text.append(", "); 341 text.append("#"+ex.getValue().primitiveValue()); 342 } 343 } 344 for (Extension ex : r.getExtensionsByUrl(ToolingExtensions.EXT_TARGET_PATH)) { 345 if (ex.hasValue()) { 346 text.append(", "); 347 text.append("/#"+ex.getValue().primitiveValue()); 348 } 349 } 350 text.append(")"); 351 } 352 } else { 353 if (display != null) { 354 text.append(display); 355 } else if (name != null) { 356 text.append(name); 357 } else { 358 text.append(context.formatPhrase(RenderingContext.RES_REND_DESC)); 359 } 360 } 361 if (tr != null && tr.getReference() != null && tr.getReference().startsWith("#")) { 362 text.append(")"); 363 } 364 pieces.add(gen.new Piece(link,text.toString(), null)); 365 } 366 367 public void renderReference(ResourceWrapper rw, XhtmlNode x, Reference r, boolean allowLinks) throws UnsupportedEncodingException, IOException { 368 if (r == null) { 369 x.tx("null!"); 370 return; 371 } 372 XhtmlNode c = null; 373 ResourceWithReference tr = null; 374 boolean onPage = false; 375 if (r.hasReferenceElement() && allowLinks) { 376 tr = resolveReference(rw, r.getReference()); 377 378 if (!r.getReference().startsWith("#")) { 379 if (tr != null && tr.getReference() != null) { 380 if (tr.getReference().startsWith("#")) { 381 onPage = true; 382 if (context.getRules() == GenerationRules.IG_PUBLISHER || (tr != null && tr.getKind() != ResourceReferenceKind.BUNDLE)) { 383 c = x.ah("#hc"+tr.getReference().substring(1)); 384 } else { 385 c = x; 386 } 387 } else { 388 c = x.ah(tr.getReference()); 389 } 390 } else if (r.getReference().contains("?")) { 391 x.tx(context.formatPhrase(RenderingContext.RES_REND_COND_REF)+" "); 392 c = x.code(""); 393 } else { 394 c = x.ah(r.getReference()); 395 } 396 } else if ("#".equals(r.getReference())) { 397 c = x.ah("#"); 398 } else if (context.getRules() == GenerationRules.IG_PUBLISHER || (tr != null && tr.getKind() != ResourceReferenceKind.BUNDLE)) { 399 c = x.ah("#hc"+r.getReference().substring(1)); 400 onPage = true; 401 } else { 402 c = x; 403 } 404 } else { 405 c = x.span(null, null); 406 } 407 if (onPage) { 408 c.tx(context.formatPhrase(RenderingContext.RES_REND_SEE_ON_THIS_PAGE)+" "); 409 } 410 // what to display: if text is provided, then that. if the reference was resolved, then show the name, or the generated narrative 411 String display = r.hasDisplayElement() ? r.getDisplay() : null; 412 String name = tr != null && tr.getResource() != null ? tr.getResource().getNameFromResource() : null; 413 414 if (display == null && (tr == null || tr.getResource() == null)) { 415 if (!Utilities.noString(r.getReference())) { 416 c.addText(r.getReference()); 417 } else if (r.hasIdentifier()) { 418 renderIdentifier(c, r.getIdentifier()); 419 } else { 420 c.addText("??"); 421 } 422 } else if (context.isTechnicalMode()) { 423 c.addText(r.getReference()); 424 if (display != null) { 425 c.addText(": "+display); 426 } 427 if ((tr == null || (tr.getReference() != null && !tr.getReference().startsWith("#"))) && name != null) { 428 x.addText(" \""+name+"\""); 429 } 430 if (r.hasExtension(ToolingExtensions.EXT_TARGET_ID) || r.hasExtension(ToolingExtensions.EXT_TARGET_PATH)) { 431 x.addText("("); 432 for (Extension ex : r.getExtensionsByUrl(ToolingExtensions.EXT_TARGET_ID)) { 433 if (ex.hasValue()) { 434 x.sep(", "); 435 x.addText("#"+ex.getValue().primitiveValue()); 436 } 437 } 438 for (Extension ex : r.getExtensionsByUrl(ToolingExtensions.EXT_TARGET_PATH)) { 439 if (ex.hasValue()) { 440 x.sep(", "); 441 x.addText("/#"+ex.getValue().primitiveValue()); 442 } 443 } 444 x.addText(")"); 445 } 446 } else { 447 if (display != null) { 448 c.addText(display); 449 } else if (name != null) { 450 c.addText(name); 451 } else { 452 c.tx(context.formatPhrase(RenderingContext.RES_REND_GEN_SUM)+" "); 453 if (tr != null) { 454 new ProfileDrivenRenderer(context).generateResourceSummary(c, tr.getResource(), true, r.getReference().startsWith("#"), true); 455 } 456 } 457 } 458 } 459 460 public void renderReference(ResourceWrapper rw, XhtmlNode x, BaseWrapper r) throws UnsupportedEncodingException, IOException { 461 XhtmlNode c = x; 462 ResourceWithReference tr = null; 463 String v; 464 if (r.has("reference")) { 465 v = r.get("reference").primitiveValue(); 466 tr = resolveReference(rw, v); 467 468 if (!v.startsWith("#")) { 469 if (tr != null && tr.getReference() != null) 470 c = x.ah(tr.getReference()); 471 else 472 c = x.ah(v); 473 } 474 } else { 475 v = ""; 476 } 477 // what to display: if text is provided, then that. if the reference was resolved, then show the generated narrative 478 if (r.has("display")) { 479 c.addText(r.get("display").primitiveValue()); 480 if (tr != null && tr.getResource() != null) { 481 c.tx(context.formatPhrase(RenderingContext.RES_REND_GEN_SUM)+" "); 482 new ProfileDrivenRenderer(context).generateResourceSummary(c, tr.getResource(), true, v.startsWith("#"), false); 483 } 484 } else if (tr != null && tr.getResource() != null) { 485 new ProfileDrivenRenderer(context).generateResourceSummary(c, tr.getResource(), v.startsWith("#"), v.startsWith("#"), false); 486 } else { 487 c.addText(v); 488 } 489 } 490 491 protected ResourceWithReference resolveReference(ResourceWrapper res, String url) { 492 if (url == null) 493 return null; 494 if (url.startsWith("#") && res != null) { 495 for (ResourceWrapper r : res.getContained()) { 496 if (r.getId().equals(url.substring(1))) 497 return new ResourceWithReference(ResourceReferenceKind.CONTAINED, url, r); 498 } 499 return null; 500 } 501 String version = null; 502 if (url.contains("/_history/")) { 503 version = url.substring(url.indexOf("/_history/")+10); 504 url = url.substring(0, url.indexOf("/_history/")); 505 } 506 507 if (rcontext != null) { 508 BundleEntryComponent bundleResource = rcontext.resolve(url); 509 if (bundleResource != null) { 510 String id = bundleResource.getResource().getId(); 511 if (id == null) { 512 id = makeIdFromBundleEntry(bundleResource.getFullUrl()); 513 } 514 String bundleUrl = "#" + bundleResource.getResource().getResourceType().name() + "_" + id; 515 return new ResourceWithReference(ResourceReferenceKind.BUNDLE, bundleUrl, new ResourceWrapperDirect(this.context, bundleResource.getResource())); 516 } 517 org.hl7.fhir.r5.elementmodel.Element bundleElement = rcontext.resolveElement(url, version); 518 if (bundleElement != null) { 519 String bundleUrl = null; 520 Element br = bundleElement.getNamedChild("resource", false); 521 if (br.getChildValue("id") != null) { 522 if ("Bundle".equals(br.fhirType())) { 523 bundleUrl = "#"; 524 } else { 525 bundleUrl = "#" + br.fhirType() + "_" + br.getChildValue("id"); 526 } 527 } else { 528 bundleUrl = "#" +fullUrlToAnchor(bundleElement.getChildValue("fullUrl")); 529 } 530 return new ResourceWithReference(ResourceReferenceKind.BUNDLE, bundleUrl, new ResourceWrapperMetaElement(this.context, br)); 531 } 532 } 533 534 Resource ae = getContext().getWorker().fetchResource(null, url, version); 535 if (ae != null) 536 return new ResourceWithReference(ResourceReferenceKind.EXTERNAL, url, new ResourceWrapperDirect(this.context, ae)); 537 else if (context.getResolver() != null) { 538 return context.getResolver().resolve(context, url); 539 } else 540 return null; 541 } 542 543 544 protected String makeIdFromBundleEntry(String url) { 545 if (url == null) { 546 return null; 547 } 548 if (url.startsWith("urn:uuid:")) { 549 return url.substring(9).toLowerCase(); 550 } 551 return fullUrlToAnchor(url); 552 } 553 554 private String fullUrlToAnchor(String url) { 555 return url.replace(":", "").replace("/", "_"); 556 } 557 558 protected void generateCopyright(XhtmlNode x, CanonicalResource cs) { 559 XhtmlNode p = x.para(); 560 p.b().tx(getContext().formatPhrase(RenderingContext.RESOURCE_COPYRIGHT)); 561 smartAddText(p, " " + cs.getCopyright()); 562 } 563 564 public String displayReference(Resource res, Reference r) throws UnsupportedEncodingException, IOException { 565 return (context.formatPhrase(RenderingContext.GENERAL_TODO)); 566 } 567 568 569 public Base parseType(String string, String type) { 570 return null; 571 } 572 573 protected PropertyWrapper getProperty(ResourceWrapper res, String name) { 574 for (PropertyWrapper t : res.children()) { 575 if (t.getName().equals(name)) 576 return t; 577 } 578 return null; 579 } 580 581 protected PropertyWrapper getProperty(BaseWrapper res, String name) { 582 for (PropertyWrapper t : res.children()) { 583 if (t.getName().equals(name)) 584 return t; 585 } 586 return null; 587 } 588 589 protected boolean valued(PropertyWrapper pw) { 590 return pw != null && pw.hasValues(); 591 } 592 593 594 protected ResourceWrapper fetchResource(BaseWrapper subject) throws UnsupportedEncodingException, FHIRException, IOException { 595 if (context.getResolver() == null) 596 return null; 597 598 PropertyWrapper ref = subject.getChildByName("reference"); 599 if (ref == null || !ref.hasValues()) { 600 return null; 601 } 602 String url = ref.value().getBase().primitiveValue(); 603 ResourceWithReference rr = context.getResolver().resolve(context, url); 604 return rr == null ? null : rr.getResource(); 605 } 606 607 608 protected String describeStatus(PublicationStatus status, boolean experimental) { 609 switch (status) { 610 case ACTIVE: return experimental ? (context.formatPhrase(RenderingContext.GENERAL_EXPER)) : (context.formatPhrase(RenderingContext.RES_REND_ACT)); 611 case DRAFT: return (context.formatPhrase(RenderingContext.RES_REND_DRAFT)); 612 case RETIRED: return (context.formatPhrase(RenderingContext.RES_REND_RET)); 613 default: return (context.formatPhrase(RenderingContext.RES_REND_UNKNOWN)); 614 } 615 } 616 617 protected void renderCommitteeLink(XhtmlNode x, CanonicalResource cr) { 618 String code = ToolingExtensions.readStringExtension(cr, ToolingExtensions.EXT_WORKGROUP); 619 CodeSystem cs = context.getWorker().fetchCodeSystem("http://terminology.hl7.org/CodeSystem/hl7-work-group"); 620 if (cs == null || !cs.hasWebPath()) 621 x.tx(code); 622 else { 623 ConceptDefinitionComponent cd = CodeSystemUtilities.findCode(cs.getConcept(), code); 624 if (cd == null) { 625 x.tx(code); 626 } else { 627 x.ah(cs.getWebPath()+"#"+cs.getId()+"-"+cd.getCode()).tx(cd.getDisplay()); 628 } 629 } 630 } 631 632 public static String makeInternalBundleLink(String fullUrl) { 633 return fullUrl.replace(":", "-"); 634 } 635 636 public boolean canRender(Resource resource) { 637 return true; 638 } 639 640 protected void renderResourceHeader(ResourceWrapper r, XhtmlNode x, boolean doId) throws UnsupportedEncodingException, FHIRException, IOException { 641 XhtmlNode div = x.div().style("display: inline-block").style("background-color: #d9e0e7").style("padding: 6px") 642 .style("margin: 4px").style("border: 1px solid #8da1b4") 643 .style("border-radius: 5px").style("line-height: 60%"); 644 645 String id = getPrimitiveValue(r, "id"); 646 if (doId) { 647 div.an("hc"+id); 648 } 649 650 String lang = getPrimitiveValue(r, "language"); 651 String ir = getPrimitiveValue(r, "implicitRules"); 652 BaseWrapper meta = r.getChildByName("meta").hasValues() ? r.getChildByName("meta").getValues().get(0) : null; 653 String versionId = getPrimitiveValue(meta, "versionId"); 654 String lastUpdated = getPrimitiveValue(meta, "lastUpdated"); 655 String source = getPrimitiveValue(meta, "source"); 656 657 if (id != null || lang != null || versionId != null || lastUpdated != null) { 658 XhtmlNode p = plateStyle(div.para()); 659 p.tx(context.formatPhrase(RenderingContext.GENERAL_RESOURCE)); 660 p.tx(r.fhirType()); 661 p.tx(" "); 662 if (id != null) { 663 p.tx("\""+id+"\" "); 664 } 665 if (versionId != null) { 666 p.tx(context.formatPhrase(RenderingContext.GENERAL_VER) + "\""+versionId+"\" "); 667 } 668 if (lastUpdated != null) { 669 p.tx(context.formatPhrase(RenderingContext.RES_REND_UPDATED) + "\""); 670 renderDateTime(p, lastUpdated); 671 p.tx("\" "); 672 } 673 if (lang != null) { 674 p.tx(" " + context.formatPhrase(RenderingContext.RES_REND_LANGUAGE) + "\""+lang+"\") "); 675 } 676 } 677 if (ir != null) { 678 plateStyle(div.para()).b().tx(context.formatPhrase(RenderingContext.RES_REND_SPEC_RULES) + " "+ir+"!"); 679 } 680 if (source != null) { 681 plateStyle(div.para()).tx(context.formatPhrase(RenderingContext.RES_REND_INFO_SOURCE) + " "+source+"!"); 682 } 683 if (meta != null) { 684 PropertyWrapper pl = meta.getChildByName("profile"); 685 if (pl.hasValues()) { 686 XhtmlNode p = plateStyle(div.para()); 687 p.tx(Utilities.pluralize(context.formatPhrase(RenderingContext.GENERAL_PROF), pl.getValues().size())+": "); 688 boolean first = true; 689 for (BaseWrapper bw : pl.getValues()) { 690 if (first) first = false; else p.tx(", "); 691 renderCanonical(r, p, bw.getBase().primitiveValue()); 692 } 693 } 694 PropertyWrapper tl = meta.getChildByName("tag"); 695 if (tl.hasValues()) { 696 XhtmlNode p = plateStyle(div.para()); 697 p.tx(Utilities.pluralize(context.formatPhrase(RenderingContext.RES_REND_TAG), tl.getValues().size())+": "); 698 boolean first = true; 699 for (BaseWrapper bw : tl.getValues()) { 700 if (first) first = false; else p.tx(", "); 701 String system = getPrimitiveValue(bw, "system"); 702 String version = getPrimitiveValue(bw, "version"); 703 String code = getPrimitiveValue(bw, "system"); 704 String display = getPrimitiveValue(bw, "system"); 705 renderCoding(p, new Coding(system, version, code, display)); 706 } 707 } 708 PropertyWrapper sl = meta.getChildByName("security"); 709 if (sl.hasValues()) { 710 XhtmlNode p = plateStyle(div.para()); 711 p.tx(Utilities.pluralize(context.formatPhrase(RenderingContext.GENERAL_SECURITY_LABEL), tl.getValues().size())+": "); 712 boolean first = true; 713 for (BaseWrapper bw : sl.getValues()) { 714 if (first) first = false; else p.tx(", "); 715 String system = getPrimitiveValue(bw, "system"); 716 String version = getPrimitiveValue(bw, "version"); 717 String code = getPrimitiveValue(bw, "system"); 718 String display = getPrimitiveValue(bw, "system"); 719 renderCoding(p, new Coding(system, version, code, display)); 720 } 721 } 722 } 723 724 } 725 726 private XhtmlNode plateStyle(XhtmlNode para) { 727 return para.style("margin-bottom: 0px"); 728 } 729 730 private String getPrimitiveValue(BaseWrapper b, String name) throws UnsupportedEncodingException, FHIRException, IOException { 731 return b != null && b.has(name) && b.getChildByName(name).hasValues() ? b.getChildByName(name).getValues().get(0).getBase().primitiveValue() : null; 732 } 733 734 private String getPrimitiveValue(ResourceWrapper r, String name) throws UnsupportedEncodingException, FHIRException, IOException { 735 return r.has(name) && r.getChildByName(name).hasValues() ? r.getChildByName(name).getValues().get(0).getBase().primitiveValue() : null; 736 } 737 738 public void renderOrError(DomainResource dr) { 739 try { 740 render(dr); 741 } catch (Exception e) { 742 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 743 x.para().tx(context.formatPhrase(RenderingContext.RES_REND_ERROR, e.getMessage())+" "); 744 dr.setText(null); 745 inject(dr, x, NarrativeStatus.GENERATED); 746 } 747 748 } 749 750 public RendererType getRendererType() { 751 return RendererType.NATIVE; 752 } 753 754 public class TableRowData { 755 private Map<String, List<DataType>> cols = new HashMap<>(); 756 private TableData data; 757 758 public void value(String name, DataType value) { 759 if (!cols.containsKey(name)) { 760 cols.put(name, new ArrayList<>()); 761 } 762 if (!data.columns.contains(name)) { 763 data.columns.add(name); 764 } 765 cols.get(name).add(value); 766 } 767 768 public boolean hasCol(String name) { 769 return cols.containsKey(name); 770 } 771 772 public List<DataType> get(String name) { 773 return cols.get(name); 774 } 775 776 } 777 public class TableData { 778 private String title; 779 private List<String> columns = new ArrayList<>(); 780 private List<TableRowData> rows = new ArrayList<>(); 781 public TableData(String title) { 782 this.title = title; 783 } 784 public String getTitle() { 785 return title; 786 } 787 public List<String> getColumns() { 788 return columns; 789 } 790 public List<TableRowData> getRows() { 791 return rows; 792 } 793 public void addColumn(String name) { 794 columns.add(name); 795 } 796 public TableRowData addRow() { 797 TableRowData res = new TableRowData(); 798 rows.add(res); 799 res.data = this; 800 return res; 801 } 802 } 803 804 805 public void renderTable(TableData provider, XhtmlNode x) throws FHIRFormatError, DefinitionException, IOException { 806 List<String> columns = new ArrayList<>(); 807 for (String name : provider.getColumns()) { 808 boolean hasData = false; 809 for (TableRowData row : provider.getRows()) { 810 if (row.hasCol(name)) { 811 hasData = true; 812 } 813 } 814 if (hasData) { 815 columns.add(name); 816 } 817 } 818 if (columns.size() > 0) { 819 XhtmlNode table = x.table("grid"); 820 821 if (provider.getTitle() != null) { 822 table.tr().td().colspan(columns.size()).b().tx(provider.getTitle()); 823 } 824 XhtmlNode tr = table.tr(); 825 for (String col : columns) { 826 tr.th().b().tx(col); 827 } 828 for (TableRowData row : provider.getRows()) { 829 tr = table.tr(); 830 for (String col : columns) { 831 XhtmlNode td = tr.td(); 832 boolean first = true; 833 List<DataType> list = row.get(col); 834 if (list != null) { 835 for (DataType value : list) { 836 if (first) first = false; else td.tx(", "); 837 render(td, value); 838 } 839 } 840 } 841 } 842 } 843 } 844 845 public void markLanguage(XhtmlNode x) { 846 x.setAttribute("lang", context.getLocale().toString()); 847 x.setAttribute("xml:lang", context.getLocale().toString()); 848 x.addTag(0, "hr"); 849 x.addTag(0, "p").b().tx(context.getLocale().getDisplayName()); 850 x.addTag(0, "hr"); 851 } 852 853 854}