![](/hapi-fhir/images/logos/raccoon-forwards.png)
001package org.hl7.fhir.r5.renderers; 002 003import java.io.IOException; 004import java.io.UnsupportedEncodingException; 005import java.util.ArrayList; 006import java.util.HashMap; 007import java.util.List; 008import java.util.Map; 009 010import org.hl7.fhir.exceptions.DefinitionException; 011import org.hl7.fhir.exceptions.FHIRException; 012import org.hl7.fhir.exceptions.FHIRFormatError; 013import org.hl7.fhir.r5.model.Base; 014import org.hl7.fhir.r5.model.CanonicalResource; 015import org.hl7.fhir.r5.model.CanonicalType; 016import org.hl7.fhir.r5.model.CodeSystem; 017import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 018import org.hl7.fhir.r5.model.ContactDetail; 019import org.hl7.fhir.r5.model.ContactPoint; 020import org.hl7.fhir.r5.model.ContactPoint.ContactPointSystem; 021import org.hl7.fhir.r5.model.DateTimeType; 022import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; 023import org.hl7.fhir.r5.model.Extension; 024import org.hl7.fhir.r5.model.Narrative.NarrativeStatus; 025import org.hl7.fhir.r5.model.Reference; 026import org.hl7.fhir.r5.model.Resource; 027import org.hl7.fhir.r5.model.StructureDefinition; 028import org.hl7.fhir.r5.model.UriType; 029import org.hl7.fhir.r5.renderers.utils.RenderingContext; 030import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceReferenceKind; 031import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceWithReference; 032import org.hl7.fhir.r5.renderers.utils.ResourceWrapper; 033import org.hl7.fhir.r5.renderers.utils.ResourceWrapper.ElementKind; 034import org.hl7.fhir.r5.terminologies.CodeSystemUtilities; 035import org.hl7.fhir.r5.utils.EOperationOutcome; 036import org.hl7.fhir.r5.utils.ToolingExtensions; 037import org.hl7.fhir.r5.utils.XVerExtensionManager; 038import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 039 040import org.hl7.fhir.utilities.Utilities; 041import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 042import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece; 043import org.hl7.fhir.utilities.xhtml.NodeType; 044import org.hl7.fhir.utilities.xhtml.XhtmlNode; 045 046public abstract class ResourceRenderer extends DataRenderer { 047 048 public enum RendererType { 049 NATIVE, PROFILE, LIQUID 050 051 } 052 053 protected XVerExtensionManager xverManager; 054 protected boolean multiLangMode; 055 056 057 public ResourceRenderer(RenderingContext context) { 058 super(context); 059 } 060 061 public boolean isMultiLangMode() { 062 return multiLangMode; 063 } 064 065 public ResourceRenderer setMultiLangMode(boolean multiLangMode) { 066 this.multiLangMode = multiLangMode; 067 return this; 068 } 069 070 /** 071 * Just build the narrative that would go in the resource (per @renderResource()), but don't put it in the resource 072 * @param dr 073 * @return 074 * @throws FHIRFormatError 075 * @throws DefinitionException 076 * @throws FHIRException 077 * @throws IOException 078 * @throws EOperationOutcome 079 */ 080 public XhtmlNode buildNarrative(ResourceWrapper dr) throws FHIRFormatError, DefinitionException, FHIRException, IOException, EOperationOutcome { 081 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 082 buildNarrative(new RenderingStatus(), x, dr); 083 return x; 084 } 085 086 /** 087 * given a resource, update it's narrative with the best rendering available. 088 * 089 * ResourceWrapper is a facade to either a org.hl7.fhir.r5.model Resource, or 090 * to a org.hl7.fhir.r5.elementModel (which might a resource of any version). 091 * 092 * Note that some resource renderers - only canonical ones - only render native 093 * resources, and not element model ones. These may be migrated in the future 094 * (only reason not to is the sheer size of the task, though performance might 095 * be a factor) 096 * 097 * @param r - the domain resource in question 098 * 099 * @throws IOException 100 * @throws EOperationOutcome 101 * @throws FHIRException 102 */ 103 public void renderResource(ResourceWrapper r) throws IOException, FHIRException, EOperationOutcome { 104 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 105 RenderingStatus status = new RenderingStatus(); 106 buildNarrative(status, x, r); 107 String an = r.fhirType()+"_"+r.getId(); 108 if (context.isAddName()) { 109 if (!hasAnchorName(x, an)) { 110 injectAnchorName(x, an); 111 } 112 } 113 inject(r, x, status.getExtensions() ? NarrativeStatus.EXTENSIONS : NarrativeStatus.GENERATED); 114 } 115 116 public XhtmlNode checkNarrative(ResourceWrapper r) throws IOException, FHIRException, EOperationOutcome { 117 XhtmlNode x = r.getNarrative(); 118 String an = r.fhirType()+"_"+r.getId(); 119 if (context.isAddName()) { 120 if (!hasAnchorName(x, an)) { 121 injectAnchorName(x, an); 122 } 123 } 124 return x; 125 } 126 127 private void injectAnchorName(XhtmlNode x, String an) { 128 XhtmlNode ip = x; 129 while (ip.hasChildren() && "div".equals(ip.getChildNodes().get(0).getName())) { 130 ip = ip.getChildNodes().get(0); 131 } 132 ip.addTag(0, "a").setAttribute("name", an).tx(" "); 133 } 134 135 protected boolean hasAnchorName(XhtmlNode x, String an) { 136 if ("a".equals(x.getName()) && an.equals(x.getAttribute("name"))) { 137 return true; 138 } 139 if (x.hasChildren()) { 140 for (XhtmlNode c : x.getChildNodes()) { 141 if (hasAnchorName(c, an)) { 142 return true; 143 } 144 } 145 } 146 return false; 147 } 148 149 // these three are what the descendants of this class override 150 public abstract void buildNarrative(RenderingStatus status, XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome; 151 public abstract String buildSummary(ResourceWrapper r) throws UnsupportedEncodingException, IOException; 152 153 154 public String canonicalTitle(ResourceWrapper r) { 155 if (r.has("title")) { 156 return r.primitiveValue("title"); 157 } 158 if (r.has("name")) { 159 return r.primitiveValue("name"); 160 } 161 if (r.has("id")) { 162 return r.primitiveValue("id"); 163 } 164 return "??"; 165 } 166 167 public void describe(XhtmlNode x, ResourceWrapper r) throws UnsupportedEncodingException, IOException { 168 x.tx(displayDataType(r)); 169 } 170 171 public void inject(ResourceWrapper r, XhtmlNode x, NarrativeStatus status) throws IOException { 172 r.setNarrative(x, status.toCode(), multiLangMode, context.getLocale(), context.isPretty()); 173 } 174 175 public void markLanguage(XhtmlNode x) { 176 x.setAttribute("lang", context.getLocale().toString()); 177 x.setAttribute("xml:lang", context.getLocale().toString()); 178 x.addTag(0, "hr"); 179 x.addTag(0, "p").b().tx(context.getLocale().getDisplayName()); 180 x.addTag(0, "hr"); 181 } 182 183 @Override 184 protected void renderCanonical(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException { 185 renderCanonical(status, x, Resource.class, type); 186 } 187 188 public <T extends Resource> void renderCanonical(RenderingStatus status, XhtmlNode x, Class<T> class_, ResourceWrapper canonical) throws UnsupportedEncodingException, IOException { 189 if (!renderPrimitiveWithNoValue(status, x, canonical)) { 190 CanonicalResource target = (CanonicalResource) context.getWorker().fetchResource(class_, canonical.primitiveValue(), canonical.getResourceNative()); 191 if (target != null && target.hasWebPath()) { 192 if (canonical.primitiveValue().contains("|")) { 193 x.ah(context.prefixLocalHref(target.getWebPath())).tx(target.present()+ context.formatPhrase(RenderingContext.RES_REND_VER) +target.getVersion()+")"); 194 } else { 195 x.ah(context.prefixLocalHref(target.getWebPath())).tx(target.present()); 196 } 197 return; 198 } 199 // we can't resolve it as a canonical in the context. We'll try to do a local resolution instead 200 ResourceWithReference rr = resolveReference(canonical); 201 if (rr == null) { 202 x.code(canonical.primitiveValue()); 203 } else if (rr.getResource() == null) { 204 x.ah(context.prefixLocalHref(rr.getWebPath())).tx(canonical.primitiveValue()); 205 } else { 206 x.ah(context.prefixLocalHref(rr.getWebPath())).tx(RendererFactory.factory(rr.getResource(), context.forContained()).buildSummary(rr.getResource())); 207 } 208 } 209 } 210 211 212 protected String displayCanonical(ResourceWrapper canonical) { 213 if (canonical == null || !canonical.hasPrimitiveValue()) { 214 return ""; 215 } 216 String url = canonical.primitiveValue(); 217 Resource target = context.getWorker().fetchResource(Resource.class, url, canonical.getResourceNative()); 218 if (target == null || !(target instanceof CanonicalResource)) { 219 return url; 220 } else { 221 CanonicalResource cr = (CanonicalResource) target; 222 return "->"+cr.present(); 223 } 224 } 225 226 protected String displayReference(ResourceWrapper type) { 227 if (type == null) { 228 return ""; 229 } 230 ResourceWrapper display = null; 231 ResourceWrapper actual = null; 232 ResourceWrapper id = null; 233 if (type.fhirType().equals("CodeableReference")) { 234 if (type.has("reference")) { 235 type = type.child("reference"); 236 } else { 237 return displayCodeableConcept(type.child("concept")); 238 } 239 } 240 if (type.fhirType().equals("Reference")) { 241 display = type.child("display"); 242 actual = type.child("reference"); 243 id = type.child("identifier"); 244 } else { 245 actual = type; 246 } 247 if (actual != null && actual.hasPrimitiveValue()) { 248 if ("#".equals(actual.primitiveValue())) { 249 return "this resource"; 250 } else { 251 ResourceWithReference rr = resolveReference(actual); 252 if (rr == null) { 253 String disp = display != null && display.hasPrimitiveValue() ? displayDataType(display) : actual.primitiveValue(); 254 return "->"+disp; 255 } else { 256 String disp; 257 try { 258 disp = display != null && display.hasPrimitiveValue() ? displayDataType(display) : RendererFactory.factory(rr.getResource(), context.forContained()).buildSummary(rr.getResource()); 259 } catch (IOException e) { 260 disp = e.getMessage(); 261 } 262 return "->"+disp; 263 } 264 } 265 } else if (display != null) { 266 return "->"+display; 267 } else if (id != null) { 268 return "id: "+displayIdentifier(id); 269 } else { 270 return "??"; 271 } 272 } 273 274 275 /** 276 * @param <T> 277 * @param status 278 * @param res 279 * @param x 280 * @param class_ - makes resolution faster, but can just be Resource.class 281 * @param canonical 282 * @throws UnsupportedEncodingException 283 * @throws IOException 284 */ 285 public <T extends Resource> void renderCanonical(RenderingStatus status, ResourceWrapper res, XhtmlNode x, Class<T> class_, CanonicalType canonical) throws UnsupportedEncodingException, IOException { 286 if (canonical == null || !canonical.hasPrimitiveValue()) { 287 return; 288 } 289 String url = canonical.asStringValue(); 290 Resource target = context.getWorker().fetchResource(Resource.class, url, res.getResourceNative()); 291 if (target == null || !(target instanceof CanonicalResource)) { 292 x.code().tx(url); 293 } else { 294 CanonicalResource cr = (CanonicalResource) target; 295 if (!target.hasWebPath()) { 296 if (url.contains("|")) { 297 x.code().tx(cr.getUrl()); 298 x.tx(context.formatPhrase(RenderingContext.RES_REND_VER, cr.getVersion())); 299 x.tx(" ("+cr.present()+")"); 300 } else { 301 x.code().tx(url); 302 x.tx(" ("+cr.present()+")"); 303 } 304 } else { 305 if (url.contains("|")) { 306 x.ah(context.prefixLocalHref(target.getWebPath())).tx(cr.present()+ context.formatPhrase(RenderingContext.RES_REND_VER) +cr.getVersion()+")"); 307 } else { 308 x.ah(context.prefixLocalHref(target.getWebPath())).tx(cr.present()); 309 } 310 } 311 } 312 } 313 314 // todo: if (r.hasExtension(ToolingExtensions.EXT_TARGET_ID) || r.hasExtension(ToolingExtensions.EXT_TARGET_PATH)) { 315 @Override 316 public void renderReference(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException { 317 if (type == null) { 318 return; 319 } 320 ResourceWrapper display = null; 321 ResourceWrapper actual = null; 322 ResourceWrapper id = null; 323 if (type.fhirType().equals("CodeableReference")) { 324 if (type.has("reference")) { 325 type = type.child("reference"); 326 } else { 327 renderCodeableConcept(status, x, type.child("concept")); 328 return; 329 } 330 } 331 if (type.fhirType().equals("Reference")) { 332 display = type.child("display"); 333 actual = type.child("reference"); 334 id = type.child("identifier"); 335 } else { 336 actual = type; 337 } 338 if (actual != null && actual.hasPrimitiveValue()) { 339 ResourceWithReference rr = resolveReference(actual); 340 if (rr == null) { 341 String disp = display != null && display.hasPrimitiveValue() ? displayDataType(display) : actual.primitiveValue(); 342 x.ah(context.prefixLocalHref(actual.primitiveValue())).tx(disp); 343 } else if (rr.getResource() == null) { 344 String disp = display != null && display.hasPrimitiveValue() ? displayDataType(display) : "??"; 345 x.ah(context.prefixLocalHref(rr.getWebPath())).tx(disp); 346 } else if (rr.getResource() != null) { 347 String disp = display != null && display.hasPrimitiveValue() ? displayDataType(display) : RendererFactory.factory(rr.getResource(), context.forContained()).buildSummary(rr.getResource()); 348 x.ah(context.prefixLocalHref(rr.getWebPath())).tx(disp); 349 } else { 350 String disp = display != null && display.hasPrimitiveValue() ? displayDataType(display) : "??"; 351 x.ah(context.prefixLocalHref(rr.getWebPath())).tx(disp); 352 } 353 } else if (display != null && id != null) { 354 renderDataType(status, x, display); 355 x.tx(" (Identifier: "); 356 renderIdentifier(status, x, id); 357 x.tx(")"); 358 } else if (display != null) { 359 renderDataType(status, x, display); 360 } else if (id != null) { 361 x.tx("Identifier: "); 362 renderIdentifier(status, x, id); 363 } else { 364 x.tx("??"); 365 } 366 } 367 368 public void renderReference(ResourceWrapper res, HierarchicalTableGenerator gen, List<Piece> pieces, Reference r, boolean allowLinks) throws UnsupportedEncodingException, IOException { 369 if (r == null) { 370 pieces.add(gen.new Piece(null, "null!", null)); 371 return; 372 } 373 ResourceWithReference tr = null; 374 String link = null; 375 StringBuilder text = new StringBuilder(); 376 if (r.hasReferenceElement() && allowLinks) { 377 tr = resolveReference(res, r.getReference(), true); 378 379 if (!r.getReference().startsWith("#")) { 380 if (tr != null && tr.getWebPath() != null) { 381 link = tr.getWebPath(); 382 } else if (r.getReference().contains("?")) { 383 text.append(context.formatPhrase(RenderingContext.RES_REND_COND_REF)+" "); 384 } else { 385 link = r.getReference(); 386 } 387 } 388 } 389 if (tr != null && tr.getWebPath() != null && tr.getWebPath().startsWith("#")) { 390 text.append(context.formatPhrase(RenderingContext.RES_REND_SEE_ON_THIS_PAGE)+" "); 391 } 392 // what to display: if text is provided, then that. if the reference was resolved, then show the name, or the generated narrative 393 String display = r.hasDisplayElement() ? r.getDisplay() : null; 394 String name = tr != null && tr.getResource() != null ? getNameForResource(tr.getResource()) : null; 395 396 if (display == null && (tr == null || tr.getResource() == null)) { 397 if (!Utilities.noString(r.getReference())) { 398 text.append(r.getReference()); 399 } else if (r.hasIdentifier()) { 400 text.append(displayIdentifier(wrapWC(res, r.getIdentifier()))); 401 } else { 402 text.append("??"); 403 } 404 } else if (context.isTechnicalMode()) { 405 text.append(r.getReference()); 406 if (display != null) { 407 text.append(": "+display); 408 } 409 if ((tr == null || (tr.getWebPath() != null && !tr.getWebPath().startsWith("#"))) && name != null) { 410 text.append(" \""+name+"\""); 411 } 412 if (r.hasExtension(ToolingExtensions.EXT_TARGET_ID) || r.hasExtension(ToolingExtensions.EXT_TARGET_PATH)) { 413 text.append("("); 414 for (Extension ex : r.getExtensionsByUrl(ToolingExtensions.EXT_TARGET_ID)) { 415 if (ex.hasValue()) { 416 text.append(", "); 417 text.append("#"+ex.getValue().primitiveValue()); 418 } 419 } 420 for (Extension ex : r.getExtensionsByUrl(ToolingExtensions.EXT_TARGET_PATH)) { 421 if (ex.hasValue()) { 422 text.append(", "); 423 text.append("/#"+ex.getValue().primitiveValue()); 424 } 425 } 426 text.append(")"); 427 } 428 } else { 429 if (display != null) { 430 text.append(display); 431 } else if (name != null) { 432 text.append(name); 433 } else { 434 text.append(context.formatPhrase(RenderingContext.RES_REND_DESC)); 435 } 436 } 437 if (tr != null && tr.getWebPath() != null && tr.getWebPath().startsWith("#")) { 438 text.append(")"); 439 } 440 pieces.add(gen.new Piece(link,text.toString(), null)); 441 } 442 443 public void renderReference(ResourceWrapper res, HierarchicalTableGenerator gen, List<Piece> pieces, ResourceWrapper r, boolean allowLinks) throws UnsupportedEncodingException, IOException { 444 if (r == null) { 445 pieces.add(gen.new Piece(null, "null!", null)); 446 return; 447 } 448 ResourceWithReference trt = null; 449 String link = null; 450 StringBuilder text = new StringBuilder(); 451 if (r.has("reference") && allowLinks) { 452 trt = resolveReference(res, r.primitiveValue("reference"), true); 453 454 if (!r.primitiveValue("reference").startsWith("#")) { 455 if (trt != null && trt.getWebPath() != null) { 456 link = trt.getWebPath(); 457 } else if (r.primitiveValue("reference").contains("?")) { 458 text.append(context.formatPhrase(RenderingContext.RES_REND_COND_REF)+" "); 459 } else { 460 link = r.primitiveValue("reference"); 461 } 462 } 463 } 464 if (trt != null && trt.getWebPath() != null && trt.getWebPath().startsWith("#")) { 465 text.append(context.formatPhrase(RenderingContext.RES_REND_SEE_ON_THIS_PAGE)+" "); 466 } 467 // what to display: if text is provided, then that. if the reference was resolved, then show the name, or the generated narrative 468 String display = r.has("display") ? r.primitiveValue("display") : null; 469 String name = trt != null && trt.getResource() != null ? getNameForResource(trt.getResource()) : null; 470 471 if (display == null && (trt == null || trt.getResource() == null)) { 472 if (!Utilities.noString(r.primitiveValue("reference"))) { 473 text.append(r.primitiveValue("reference")); 474 } else if (r.has("identifier")) { 475 text.append(displayIdentifier(r.child("identifier"))); 476 } else { 477 text.append("??"); 478 } 479 } else if (context.isTechnicalMode()) { 480 text.append(r.primitiveValue("reference")); 481 if (display != null) { 482 text.append(": "+display); 483 } 484 if ((trt == null || (trt.getWebPath() != null && !trt.getWebPath().startsWith("#"))) && name != null) { 485 text.append(" \""+name+"\""); 486 } 487 if (r.hasExtension(ToolingExtensions.EXT_TARGET_ID) || r.hasExtension(ToolingExtensions.EXT_TARGET_PATH)) { 488 text.append("("); 489 for (ResourceWrapper ex : r.extensions(ToolingExtensions.EXT_TARGET_ID)) { 490 if (ex.has("value")) { 491 text.append(", "); 492 text.append("#"+ex.primitiveValue("value")); 493 } 494 } 495 for (ResourceWrapper ex : r.extensions(ToolingExtensions.EXT_TARGET_PATH)) { 496 if (ex.has("value")) { 497 text.append(", "); 498 text.append("#"+ex.primitiveValue("value")); 499 } 500 } 501 text.append(")"); 502 } 503 } else { 504 if (display != null) { 505 text.append(display); 506 } else if (name != null) { 507 text.append(name); 508 } else { 509 text.append(context.formatPhrase(RenderingContext.RES_REND_DESC)); 510 } 511 } 512 if (trt != null && trt.getWebPath() != null && trt.getWebPath().startsWith("#")) { 513 text.append(")"); 514 } 515 pieces.add(gen.new Piece(link,text.toString(), null)); 516 } 517 518 protected String getNameForResource(ResourceWrapper resource) { 519 ResourceWrapper name = resource.firstChild("name"); 520 if (name != null && !name.isEmpty()) { 521 if (name.isPrimitive()) { 522 return name.primitiveValue(); 523 } else if (name.fhirType().equals("HumanName")) { 524 String family = name.primitiveValue("family"); 525 String given = name.firstPrimitiveValue("given"); 526 return (family == null) ? given : given == null ? family : family+" "+given; 527 } else { 528 String n = name.primitiveValueMN("name", "text", "value"); 529 if (n != null) { 530 return n; 531 } 532 } 533 } 534 String n = resource.primitiveValue("productName"); 535 if (n == null) { 536 throw new Error("What to render for 'name'? Type is "+resource.fhirType()); 537 } else { 538 return n; 539 } 540 } 541 542 protected void renderUri(RenderingStatus status, ResourceWrapper resource, XhtmlNode x, UriType uri) throws FHIRFormatError, DefinitionException, IOException { 543 if (!renderPrimitiveWithNoValue(status, x, uri)) { 544 String v = uri.primitiveValue(); 545 546 if (v.startsWith("mailto:")) { 547 x.ah(context.prefixLocalHref(v)).addText(v.substring(7)); 548 } else { 549 ResourceWithReference rr = resolveReference(resource, uri.primitiveValue(), false); 550 if (rr != null) { 551 x.ah(rr.getWebPath()).addText(RendererFactory.factory(rr.getResource(), context.forContained()).buildSummary(rr.getResource())); 552 } else { 553 Resource r = context.getContext().fetchResource(Resource.class, v); 554 if (r != null && r.getWebPath() != null) { 555 x.ah(context.prefixLocalHref(r.getWebPath())).addText(RendererFactory.factory(r, context.forContained()).buildSummary(wrap(r))); 556 } else { 557 String url = context.getResolver() != null ? context.getResolver().resolveUri(context, v) : null; 558 if (url != null) { 559 x.ah(context.prefixLocalHref(url)).addText(v); 560 } else if (Utilities.isAbsoluteUrlLinkable(v) && !uri.fhirType().equals("id")) { 561 x.ah(context.prefixLocalHref(v)).addText(v); 562 } else { 563 x.addText(v); 564 } 565 } 566 } 567 } 568 } 569 } 570 571 @Override 572 protected void renderUri(RenderingStatus status, XhtmlNode x, ResourceWrapper uri) throws FHIRFormatError, DefinitionException, IOException { 573 if (!renderPrimitiveWithNoValue(status, x, uri)) { 574 String v = uri.primitiveValue(); 575 576 if (context.getContextUtilities().isResource(v)) { 577 v = "http://hl7.org/fhir/StructureDefinition/"+v; 578 } 579 if (v.startsWith("mailto:")) { 580 x.ah(v).addText(v.substring(7)); 581 } else { 582 ResourceWithReference rr = resolveReference(uri); 583 if (rr != null) { 584 if (rr.getResource() == null) { 585 x.ah(context.prefixLocalHref(rr.getWebPath())).addText(rr.getUrlReference()); 586 } else { 587 x.ah(context.prefixLocalHref(rr.getWebPath())).addText(RendererFactory.factory(rr.getResource(), context.forContained()).buildSummary(rr.getResource())); 588 } 589 } else { 590 Resource r = context.getContext().fetchResource(Resource.class, v); 591 if (r != null && r.getWebPath() != null) { 592 x.ah(context.prefixLocalHref(r.getWebPath())).addText(RendererFactory.factory(r, context.forContained()).buildSummary(wrap(r))); 593 } else if (r != null) { 594 x.ah(context.prefixLocalHref(v)).addText(RendererFactory.factory(r, context.forContained()).buildSummary(wrap(r))); 595 } else { 596 String url = context.getResolver() != null ? context.getResolver().resolveUri(context, v) : null; 597 if (url != null) { 598 x.ah(context.prefixLocalHref(url)).addText(v); 599 } else if (Utilities.isAbsoluteUrlLinkable(v) && !uri.fhirType().equals("id")) { 600 x.ah(context.prefixLocalHref(v)).addText(v); 601 } else { 602 x.addText(v); 603 } 604 } 605 } 606 } 607 } 608 } 609 610 /** 611 * Eventually this will be retired if and when there's no more direct renderers 612 * 613 * @param <T> 614 */ 615 protected <T extends Resource> T findCanonical(Class<T> class_, UriType canonical, ResourceWrapper sourceOfReference) { 616 return context.getContext().fetchResource(class_, canonical.asStringValue(), sourceOfReference.getResourceNative()); 617 } 618 619 protected <T extends Resource> T findCanonical(Class<T> class_, String canonical, ResourceWrapper sourceOfReference) { 620 return context.getContext().fetchResource(class_, canonical, sourceOfReference.getResourceNative()); 621 } 622 623 624 private ResourceWithReference resolveContained(ResourceWrapper resource, String url) { 625 ResourceWrapper container = findContainer(resource); 626 if (container == null) { 627 return null; 628 } else if ("#".equals(url)) { 629 return new ResourceWithReference(ResourceReferenceKind.CONTAINER, url, "#hc"+container.getScopedId(), container); 630 } else { 631 String tid = url.substring(1); 632 for (ResourceWrapper c : container.children("contained")) { 633 if (tid.equals(c.getId())) { 634 return new ResourceWithReference(ResourceReferenceKind.CONTAINED, url, "#hc"+c.getScopedId(), c); 635 } 636 } 637 } 638 return null; 639 } 640 641 private ResourceWrapper findContainer(ResourceWrapper resource) { 642 ResourceWrapper container = resource; 643 while (container != null) { 644 if (container.isResource() && container.kind() != ElementKind.ContainedResource) { 645 break; 646 } 647 container = container.parent(); 648 } 649 return container; 650 } 651 652 private ResourceWithReference resolveOutside(ResourceWrapper resource, String url, String version, boolean followLinks) { 653 ResourceWrapper container = findContainer(resource); 654 if (container != null) { 655 while (container != null) { 656 if (container.isResource() && container.fhirType().equals("Bundle")) { 657 ResourceWithReference rr = findInBundle(resource, url); 658 if (rr != null) { 659 return null; 660 } 661 } 662 container = container.parent(); 663 } 664 } 665 if (followLinks) { 666 // ok, we didn't find it in the current instance, so we go look elsewhere 667 if (context.getResolver() != null) { 668 ResourceWithReference rr = context.getResolver().resolve(context, url, version); 669 if (rr != null) { 670 return rr; 671 } 672 } 673 Resource r = context.getWorker().fetchResource(Resource.class, url, version); 674 if (r != null) { 675 return new ResourceWithReference(ResourceReferenceKind.EXTERNAL, url, r.getWebPath(), wrap(r)); 676 } 677 } 678 return null; 679 } 680 681 private ResourceWithReference findInBundle(ResourceWrapper resource, String url) { 682 if (url.equals("Bundle/"+resource.getId())) { 683 return new ResourceWithReference(ResourceReferenceKind.BUNDLE, url, "#"+resource.getScopedId(), resource); 684 } 685 for (ResourceWrapper entry : resource.children("entry")) { 686 if (entry.has("resource")) { 687 ResourceWrapper res = entry.child("resource"); 688 if (entry.has("fullUrl")) { 689 String fu = entry.primitiveValue("fullUrl"); 690 if (url.equals(fu)) { 691 return new ResourceWithReference(ResourceReferenceKind.BUNDLE, url, "#"+res.getScopedId(), res); 692 } 693 } 694 if ("Bundle".equals(res.fhirType())) { 695 ResourceWithReference rr = findInBundle(res, url); 696 if (rr != null) { 697 return rr; 698 } 699 } 700 } 701 } 702 return null; 703 } 704 705 protected ResourceWithReference resolveReference(ResourceWrapper resource, String url, boolean followLinks) { 706 if (url == null) { 707 return null; 708 } 709 710 if (url.startsWith("#")) { 711 return resolveContained(resource, url); 712 } else { 713 String version = null; 714 if (url.contains("/_history/")) { 715 version = url.substring(url.indexOf("/_history/")+10); 716 url = url.substring(0, url.indexOf("/_history/")); 717 } 718 719 return resolveOutside(resource, url, version, followLinks); 720 } 721 722 } 723 724 protected ResourceWithReference resolveReference(ResourceWrapper reference) { 725 if (reference.fhirType().equals("CodeableReference")) { 726 if (reference.has("reference")) { 727 return resolveReference(reference.child("reference")); 728 } else { 729 return null; 730 } 731 } else if (reference.fhirType().equals("Reference")) { 732 return resolveReference(reference.getResourceWrapper(), reference.primitiveValue("reference"), true); 733 } else { 734 return resolveReference(reference.getResourceWrapper(), reference.primitiveValue(), true); 735 } 736 } 737 738 739 protected String makeIdFromBundleEntry(String url) { 740 if (url == null) { 741 return null; 742 } 743 if (url.startsWith("urn:uuid:")) { 744 return url.substring(9).toLowerCase(); 745 } 746 return fullUrlToAnchor(url); 747 } 748 749 private String fullUrlToAnchor(String url) { 750 return url.replace(":", "").replace("/", "_"); 751 } 752 753 protected void generateCopyright(XhtmlNode x, ResourceWrapper cs) { 754 XhtmlNode p = x.para(); 755 p.b().tx(getContext().formatPhrase(RenderingContext.RESOURCE_COPYRIGHT)); 756 smartAddText(p, " " + context.getTranslated(cs.child("copyright"))); 757 } 758 759 protected void generateCopyrightTableRow(XhtmlNode tbl, ResourceWrapper cs) { 760 XhtmlNode tr = tbl.tr(); 761 tr.td().b().tx(getContext().formatPhrase(RenderingContext.RESOURCE_COPYRIGHT)); 762 smartAddText(tr.td(), " " + context.getTranslated(cs.child("copyright"))); 763 } 764 765 public String displayReference(Resource res, Reference r) throws UnsupportedEncodingException, IOException { 766 return (context.formatPhrase(RenderingContext.GENERAL_TODO)); 767 } 768 769 public Base parseType(String string, String type) { 770 return null; 771 } 772 773 protected String describeStatus(PublicationStatus status, boolean experimental) { 774 switch (status) { 775 case ACTIVE: return experimental ? (context.formatPhrase(RenderingContext.GENERAL_EXPER)) : (context.formatPhrase(RenderingContext.RES_REND_ACT)); 776 case DRAFT: return (context.formatPhrase(RenderingContext.RES_REND_DRAFT)); 777 case RETIRED: return (context.formatPhrase(RenderingContext.RES_REND_RET)); 778 default: return (context.formatPhrase(RenderingContext.RES_REND_UNKNOWN)); 779 } 780 } 781 782 protected void renderCommitteeLink(XhtmlNode x, CanonicalResource cr) { 783 String code = ToolingExtensions.readStringExtension(cr, ToolingExtensions.EXT_WORKGROUP); 784 CodeSystem cs = context.getWorker().fetchCodeSystem("http://terminology.hl7.org/CodeSystem/hl7-work-group"); 785 if (cs == null || !cs.hasWebPath()) 786 x.tx(code); 787 else { 788 ConceptDefinitionComponent cd = CodeSystemUtilities.findCode(cs.getConcept(), code); 789 if (cd == null) { 790 x.tx(code); 791 } else { 792 x.ah(context.prefixLocalHref(cs.getWebPath()+"#"+cs.getId()+"-"+cd.getCode())).tx(cd.getDisplay()); 793 } 794 } 795 } 796 797 public static String makeInternalBundleLink(ResourceWrapper bundle, String fullUrl) { 798 // are we in a bundle in a bundle? Then the link is scoped 799 boolean inBundle = false; 800 ResourceWrapper rw = bundle.parent(); 801 while (rw != null) { 802 if (rw.fhirType().equals("Bundle")) { 803 inBundle = true; 804 } 805 rw = rw.parent(); 806 } 807 if (inBundle) { 808 return bundle.getScopedId()+"/"+fullUrl.replace(":", "-"); 809 } else { 810 return fullUrl.replace(":", "-"); 811 } 812 } 813 814 public boolean canRender(Resource resource) { 815 return true; 816 } 817 818 protected XhtmlNode renderResourceTechDetails(ResourceWrapper r, XhtmlNode x) throws UnsupportedEncodingException, FHIRException, IOException { 819 return renderResourceTechDetails(r, x, (context.isContained() ? " #"+r.getId() : r.getId())); 820 } 821 822 protected XhtmlNode renderResourceTechDetails(ResourceWrapper r, XhtmlNode x, String desc) throws UnsupportedEncodingException, FHIRException, IOException { 823 XhtmlNode p = x.para().attribute("class", "res-header-id"); 824 String ft = context.getTranslatedCode(r.fhirType(), "http://hl7.org/fhir/fhir-types"); 825 if (desc == null) { 826 p.b().tx(context.formatPhrase(context.isTechnicalMode() ? RenderingContext.PROF_DRIV_GEN_NARR_TECH : RenderingContext.PROF_DRIV_GEN_NARR, ft, "")); 827 } else { 828 p.b().tx(context.formatPhrase(context.isTechnicalMode() ? RenderingContext.PROF_DRIV_GEN_NARR_TECH : RenderingContext.PROF_DRIV_GEN_NARR, ft, desc)); 829 } 830 831 // first thing we do is lay down the resource anchors. 832 if (!Utilities.noString(r.getId())) { 833 if (!context.isSecondaryLang()) { 834 String sid = r.getScopedId(); 835 if (!context.hasAnchor(sid)) { 836 context.addAnchor(sid); 837 x.an(context.prefixAnchor(sid)); 838 } 839 sid = "hc"+sid; 840 if (!context.hasAnchor(sid)) { 841 context.addAnchor(sid); 842 x.an(context.prefixAnchor(sid)); 843 } 844 } 845 if (context.getLocale() != null) { 846 String langSuffix = "-"+context.getLocale().toLanguageTag(); 847 String sid = r.getScopedId()+langSuffix; 848 if (!context.hasAnchor(sid)) { 849 context.addAnchor(sid); 850 x.an(context.prefixAnchor(sid)); 851 } 852 } 853 } 854 855 if (context.isTechnicalMode()) { 856 RenderingStatus status = new RenderingStatus(); 857 858 String lang = r.primitiveValue("language"); 859 String ir = r.primitiveValue("implicitRules"); 860 ResourceWrapper meta = r.has("meta") && !r.child("meta").isEmpty() ? r.child("meta") : null; 861 ResourceWrapper versionId = meta == null ? null : meta.child("versionId"); 862 ResourceWrapper lastUpdated = meta == null ? null : meta.child("lastUpdated"); 863 ResourceWrapper source = meta == null ? null : meta.child("source"); 864 865 if (lang != null || versionId != null || lastUpdated != null || ir != null || source != null) { 866 XhtmlNode div = x.div().style("display: inline-block").style("background-color: #d9e0e7").style("padding: 6px") 867 .style("margin: 4px").style("border: 1px solid #8da1b4") 868 .style("border-radius: 5px").style("line-height: 60%"); 869 870 boolean sfirst = true; 871 p = plateStyle(div.para()); 872 if (versionId != null) { 873 p.tx(context.formatPhrase(RenderingContext.RES_REND_VER, versionId)); 874 sfirst = false; 875 } 876 if (lastUpdated != null) { 877 if (!sfirst) { 878 p.tx("; "); 879 } 880 p.tx(context.formatPhrase(RenderingContext.RES_REND_UPDATED, displayDataType(lastUpdated))); 881 sfirst = false; 882 } 883 if (lang != null) { 884 if (!sfirst) { 885 p.tx("; "); 886 } 887 p.tx(context.formatPhrase(RenderingContext.RES_REND_LANGUAGE, lang)); 888 sfirst = false; 889 } 890 if (source != null) { 891 if (!sfirst) { 892 p.tx("; "); 893 } 894 XhtmlNode pp = plateStyle(div.para()); 895 pp.startScript("source"); 896 renderDataType(status, pp.param("source"), source); 897 pp.execScript(context.formatPhrase(RenderingContext.RES_REND_INFO_SOURCE)); 898 pp.closeScript(); 899 sfirst = false; 900 } 901 if (ir != null) { 902 if (!sfirst) { 903 p.tx("; "); 904 } 905 plateStyle(div.para()).b().tx(context.formatPhrase(RenderingContext.RES_REND_SPEC_RULES, ir)); 906 sfirst = false; 907 } 908 if (meta != null) { 909 List<ResourceWrapper> items = meta.children("profile"); 910 if (!items.isEmpty()) { 911 p = plateStyle(div.para()); 912 p.tx(Utilities.pluralize(context.formatPhrase(RenderingContext.GENERAL_PROF), items.size())+": "); 913 boolean first = true; 914 for (ResourceWrapper bw : items) { 915 if (first) first = false; else p.tx(", "); 916 renderCanonical(status, p, StructureDefinition.class, bw); 917 } 918 } 919 items = meta.children("tag"); 920 if (!items.isEmpty()) { 921 p = plateStyle(div.para()); 922 p.tx(Utilities.pluralize(context.formatPhrase(RenderingContext.RES_REND_TAG), items.size())+": "); 923 boolean first = true; 924 for (ResourceWrapper bw : items) { 925 if (first) first = false; else p.tx(", "); 926 renderCoding(status, p, bw); 927 } 928 } 929 items = meta.children("security"); 930 if (!items.isEmpty()) { 931 p = plateStyle(div.para()); 932 p.tx(Utilities.pluralize(context.formatPhrase(RenderingContext.GENERAL_SECURITY_LABEL), items.size())+": "); 933 boolean first = true; 934 for (ResourceWrapper bw : items) { 935 if (first) first = false; else p.tx(", "); 936 renderCoding(status, p, bw); 937 } 938 } 939 } 940 return div; 941 } 942 } 943 return null; 944 } 945 946 private XhtmlNode plateStyle(XhtmlNode para) { 947 return para.style("margin-bottom: 0px"); 948 } 949 950// public void renderOrError(DomainResource dr) { 951// try { 952// render(dr); 953// } catch (Exception e) { 954// XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 955// x.para().tx(context.formatPhrase(RenderingContext.RES_REND_ERROR, e.getMessage())+" "); 956// dr.setText(null); 957// inject(dr, x, NarrativeStatus.GENERATED); 958// } 959// 960// } 961 962 public RendererType getRendererType() { 963 return RendererType.NATIVE; 964 } 965 966 public class TableRowData { 967 private Map<String, List<ResourceWrapper>> cols = new HashMap<>(); 968 private TableData data; 969 970 public void value(String name, ResourceWrapper value) { 971 if (!cols.containsKey(name)) { 972 cols.put(name, new ArrayList<>()); 973 } 974 if (!data.columns.contains(name)) { 975 data.columns.add(name); 976 } 977 cols.get(name).add(value); 978 } 979 980 public boolean hasCol(String name) { 981 return cols.containsKey(name); 982 } 983 984 public List<ResourceWrapper> get(String name) { 985 return cols.get(name); 986 } 987 988 } 989 public class TableData { 990 private String title; 991 private List<String> columns = new ArrayList<>(); 992 private List<TableRowData> rows = new ArrayList<>(); 993 public TableData(String title) { 994 this.title = title; 995 } 996 public String getTitle() { 997 return title; 998 } 999 public List<String> getColumns() { 1000 return columns; 1001 } 1002 public List<TableRowData> getRows() { 1003 return rows; 1004 } 1005 public void addColumn(String name) { 1006 columns.add(name); 1007 } 1008 public TableRowData addRow() { 1009 TableRowData res = new TableRowData(); 1010 rows.add(res); 1011 res.data = this; 1012 return res; 1013 } 1014 } 1015 1016 1017 public void renderTable(RenderingStatus status, TableData provider, XhtmlNode x) throws FHIRFormatError, DefinitionException, IOException { 1018 List<String> columns = new ArrayList<>(); 1019 for (String name : provider.getColumns()) { 1020 boolean hasData = false; 1021 for (TableRowData row : provider.getRows()) { 1022 if (row.hasCol(name)) { 1023 hasData = true; 1024 } 1025 } 1026 if (hasData) { 1027 columns.add(name); 1028 } 1029 } 1030 if (columns.size() > 0) { 1031 XhtmlNode table = x.table("grid"); 1032 1033 if (provider.getTitle() != null) { 1034 table.tr().td().colspan(columns.size()).b().tx(provider.getTitle()); 1035 } 1036 XhtmlNode tr = table.tr(); 1037 for (String col : columns) { 1038 tr.th().b().tx(col); 1039 } 1040 for (TableRowData row : provider.getRows()) { 1041 tr = table.tr(); 1042 for (String col : columns) { 1043 XhtmlNode td = tr.td(); 1044 boolean first = true; 1045 List<ResourceWrapper> list = row.get(col); 1046 if (list != null) { 1047 for (ResourceWrapper value : list) { 1048 if (first) first = false; else td.tx(", "); 1049 renderDataType(status, td, value); 1050 } 1051 } 1052 } 1053 } 1054 } 1055 } 1056 1057 public void renderOrError(ResourceWrapper dr) throws IOException { 1058 try { 1059 renderResource(dr); 1060 } catch (Exception e) { 1061 XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); 1062 x.para().tx("Error rendering: " + e.getMessage()); 1063 inject(dr, x, NarrativeStatus.GENERATED); 1064 } 1065 } 1066 1067 1068 public void genSummaryTable(RenderingStatus status, XhtmlNode x, ResourceWrapper cr) throws IOException { 1069 if (context.isShowSummaryTable() && cr != null) { 1070 XhtmlNode tbl = x.table("grid"); 1071 genSummaryTableContent(status, tbl, cr); 1072 } 1073 } 1074 1075 1076 protected void genSummaryTableContent(RenderingStatus status, XhtmlNode tbl, ResourceWrapper cr) throws IOException { 1077 XhtmlNode tr; 1078 if (cr.has("url")) { 1079 tr = tbl.tr(); 1080 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_DEFINING_URL)+":"); 1081 tr.td().code().tx(cr.primitiveValue("url")); 1082 } else if (cr.hasExtension("http://hl7.org/fhir/5.0/StructureDefinition/extension-NamingSystem.url")) { 1083 status.setExtensions(true); 1084 tr = tbl.tr(); 1085 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_DEFINING_URL)+":"); 1086 tr.td().code().tx(cr.extensionString("http://hl7.org/fhir/5.0/StructureDefinition/extension-NamingSystem.url")); 1087 } else if (!context.isContained()) { 1088 tr = tbl.tr(); 1089 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_DEFINING_URL)); 1090 tr.td(); 1091 } 1092 if (cr.has("version")) { 1093 tr = tbl.tr(); 1094 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_VER)+":"); 1095 renderDataType(status, tr.td(), cr.child("version")); 1096 } else if (cr.hasExtension("http://terminology.hl7.org/StructureDefinition/ext-namingsystem-version")) { 1097 status.setExtensions(true); 1098 tr = tbl.tr(); 1099 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_VER)+":"); 1100 renderDataType(status, tr.td(), cr.extensionValue("http://hl7.org/fhir/5.0/StructureDefinition/extension-NamingSystem.version")); 1101 } 1102 1103 String name = context.getTranslated(cr.child("name")); 1104 String title = context.getTranslated(cr.child("title")); 1105 1106 if (name != null) { 1107 tr = tbl.tr(); 1108 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_NAME)+":"); 1109 tr.td().tx(name); 1110 } 1111 1112 if (title != null && !title.equalsIgnoreCase(name)) { 1113 tr = tbl.tr(); 1114 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_TITLE)+":"); 1115 tr.td().tx(title); 1116 } 1117 1118 if (cr.has("status") && !context.isContained()) { 1119 tr = tbl.tr(); 1120 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_STATUS)+":"); 1121 tr.td().tx(describeStatus(status, cr)); 1122 } 1123 1124 if (cr.has("description")) { 1125 tr = tbl.tr(); 1126 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_DEFINITION)+":"); 1127 tr.td().markdown(context.getTranslated(cr.child("description")), "description"); 1128 } 1129 1130 if (cr.has("publisher")) { 1131 tr = tbl.tr(); 1132 tr.td().tx(context.formatPhrase(RenderingContext.CANON_REND_PUBLISHER)+":"); 1133 buildPublisherLinks( tr.td(), cr); 1134 } 1135 1136 if (cr.hasExtension(ToolingExtensions.EXT_WORKGROUP)) { 1137 status.setExtensions(true); 1138 tr = tbl.tr(); 1139 tr.td().tx(context.formatPhrase(RenderingContext.CANON_REND_COMMITTEE)+":"); 1140 renderCommitteeLink(tr.td(), cr); 1141 } 1142 1143 if (cr.has("copyright")) { 1144 tr = tbl.tr(); 1145 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_COPYRIGHT)+":"); 1146 tr.td().markdown(context.getTranslated(cr.child("copyright")), "copyright"); 1147 } 1148 1149 if (cr.hasExtension(ToolingExtensions.EXT_FMM_LEVEL)) { 1150 status.setExtensions(true); 1151 // Use hard-coded spec link to point to current spec because DSTU2 had maturity listed on a different page 1152 tr = tbl.tr(); 1153 tr.td().ah("http://hl7.org/fhir/versions.html#maturity", "Maturity Level").attribute("class", "fmm").tx(context.formatPhrase(RenderingContext.CANON_REND_COMMITTEE)+":"); 1154 renderDataType(status, tr.td(), cr.extensionValue(ToolingExtensions.EXT_FMM_LEVEL)); 1155 } 1156 } 1157 1158 1159 1160 public void genSummaryTable(RenderingStatus status, XhtmlNode x, CanonicalResource cr) throws IOException { 1161 if (context.isShowSummaryTable() && cr != null) { 1162 XhtmlNode tbl = x.table("grid"); 1163 genSummaryTableContent(status, tbl, cr); 1164 } 1165 } 1166 1167 1168 protected void genSummaryTableContent(RenderingStatus status, XhtmlNode tbl, CanonicalResource cr) throws IOException { 1169 XhtmlNode tr; 1170 if (cr.hasUrl()) { 1171 tr = tbl.tr(); 1172 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_DEFINING_URL)+":"); 1173 tr.td().code().tx(cr.getUrl()); 1174 } else if (cr.hasExtension("http://hl7.org/fhir/5.0/StructureDefinition/extension-NamingSystem.url")) { 1175 status.setExtensions(true); 1176 tr = tbl.tr(); 1177 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_DEFINING_URL)); 1178 tr.td().code().tx(ToolingExtensions.readStringExtension(cr, "http://hl7.org/fhir/5.0/StructureDefinition/extension-NamingSystem.url")+":"); 1179 } else if (!context.isContained()) { 1180 tr = tbl.tr(); 1181 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_DEFINING_URL)); 1182 tr.td(); 1183 } 1184 if (cr.hasVersion()) { 1185 tr = tbl.tr(); 1186 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_VER)+":"); 1187 tr.td().tx(cr.getVersion()); 1188 } else if (cr.hasExtension("http://terminology.hl7.org/StructureDefinition/ext-namingsystem-version")) { 1189 status.setExtensions(true); 1190 tr = tbl.tr(); 1191 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_VER)+":"); 1192 tr.td().tx(ToolingExtensions.readStringExtension(cr, "http://hl7.org/fhir/5.0/StructureDefinition/extension-NamingSystem.version")); 1193 } 1194 1195 String name = cr.hasName() ? context.getTranslated(cr.getNameElement()) : null; 1196 String title = cr.hasTitle() ? context.getTranslated(cr.getTitleElement()) : null; 1197 1198 if (name != null) { 1199 tr = tbl.tr(); 1200 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_NAME)+":"); 1201 tr.td().tx(name); 1202 } 1203 1204 if (title != null && !title.equalsIgnoreCase(name)) { 1205 tr = tbl.tr(); 1206 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_TITLE)+":"); 1207 tr.td().tx(title); 1208 } 1209 1210 if (cr.hasStatus() && !context.isContained()) { 1211 tr = tbl.tr(); 1212 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_STATUS)+":"); 1213 tr.td().tx(describeStatus(status, cr)); 1214 } 1215 1216 if (cr.hasDescription()) { 1217 tr = tbl.tr(); 1218 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_DEFINITION)+":"); 1219 tr.td().markdown(cr.getDescription(), "description"); 1220 } 1221 1222 if (cr.hasPublisher()) { 1223 tr = tbl.tr(); 1224 tr.td().tx(context.formatPhrase(RenderingContext.CANON_REND_PUBLISHER)+":"); 1225 buildPublisherLinks(tr.td(), cr); 1226 } 1227 1228 if (cr.hasExtension(ToolingExtensions.EXT_WORKGROUP)) { 1229 status.setExtensions(true); 1230 tr = tbl.tr(); 1231 tr.td().tx(context.formatPhrase(RenderingContext.CANON_REND_COMMITTEE)+":"); 1232 renderCommitteeLink(tr.td(), cr); 1233 } 1234 1235 if (cr.hasCopyright()) { 1236 tr = tbl.tr(); 1237 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_COPYRIGHT)+":"); 1238 tr.td().markdown(cr.getDescription(), "copyright"); 1239 } 1240 1241 if (ToolingExtensions.hasExtension(cr, ToolingExtensions.EXT_FMM_LEVEL)) { 1242 status.setExtensions(true); 1243 // Use hard-coded spec link to point to current spec because DSTU2 had maturity listed on a different page 1244 tr = tbl.tr(); 1245 tr.td().ah("http://hl7.org/fhir/versions.html#maturity", "Maturity Level").attribute("class", "fmm").tx(context.formatPhrase(RenderingContext.CANON_REND_COMMITTEE)+":"); 1246 tr.td().tx(ToolingExtensions.readStringExtension(cr, ToolingExtensions.EXT_FMM_LEVEL)); 1247 } 1248 } 1249 1250 1251 protected void renderCommitteeLink(XhtmlNode x, ResourceWrapper cr) { 1252 String code = cr.extensionString(ToolingExtensions.EXT_WORKGROUP); 1253 CodeSystem cs = context.getContext().fetchCodeSystem("http://terminology.hl7.org/CodeSystem/hl7-work-group"); 1254 if (cs == null || !cs.hasWebPath()) 1255 x.tx(code); 1256 else { 1257 ConceptDefinitionComponent cd = CodeSystemUtilities.findCode(cs.getConcept(), code); 1258 if (cd == null) { 1259 x.tx(code); 1260 } else { 1261 x.ah(cs.getWebPath()+"#"+cs.getId()+"-"+cd.getCode()).tx(cd.getDisplay()); 1262 } 1263 } 1264 } 1265 1266 private void buildPublisherLinks(XhtmlNode x, CanonicalResource cr) { 1267 boolean useName = false; 1268 for (ContactDetail cd : cr.getContact()) { 1269 if (!cd.hasName()) { 1270 useName = true; 1271 } 1272 } 1273 boolean first = true; 1274 if (!useName) { 1275 x.tx(Utilities.escapeXml(cr.getPublisher())); 1276 first = false; 1277 } 1278 for (ContactDetail cd : cr.getContact()) { 1279 String name = cd.hasName() ? cd.getName() : cr.getPublisher(); 1280 if (cd.hasTelecom()) { 1281 if (first) first = false; else x.tx(". "); 1282 renderContact(x, name, cd.getTelecom()); 1283 } 1284 } 1285 } 1286 1287 private void buildPublisherLinks(XhtmlNode x, ResourceWrapper cr) { 1288 boolean useName = false; 1289 for (ResourceWrapper cd : cr.children("contact")) { 1290 if (!cd.has("name")) { 1291 useName = true; 1292 } 1293 } 1294 boolean first = true; 1295 if (!useName) { 1296 x.tx(Utilities.escapeXml(cr.primitiveValue("publisher"))); 1297 first = false; 1298 } 1299 for (ResourceWrapper cd : cr.children("contact")) { 1300 String name = cd.has("name") ? cd.primitiveValue("name") : cr.primitiveValue("publisher"); 1301 if (cd.has("telecom")) { 1302 if (first) first = false; else x.tx(". "); 1303 renderContactW(x, name, cd.children("telecom")); 1304 } 1305 } 1306 } 1307 1308 1309 private void renderContactW(XhtmlNode x, String name, List<ResourceWrapper> telecom) { 1310 List<String> urls = new ArrayList<>(); 1311 for (ResourceWrapper t : telecom) { 1312 if ("url".equals(t.primitiveValue()) && t.has("value")) { 1313 urls.add(t.primitiveValue("value")); 1314 } 1315 } 1316 if (urls.size() == 1) { 1317 x.ah(urls.get(0)).tx(name); 1318 } else { // if (urls.size() == 0) { 1319 x.tx(name); 1320 } 1321 for (ResourceWrapper t : telecom) { 1322 String system = t.primitiveValue("system"); 1323 String value = t.primitiveValue("value"); 1324 if ("url".equals(system) && value != null && urls.size() != 1) { 1325 x.tx(", "); 1326 x.ah(t.primitiveValue("value")).tx("Link"); 1327 } 1328 if ("email".equals(system) && value != null) { 1329 x.tx(", "); 1330 x.ah("mailto:"+t.primitiveValue("value")).tx("Email"); 1331 } 1332 if ("phone".equals(system) && value != null) { 1333 x.tx(", "); 1334 x.tx(t.primitiveValue("value")); 1335 } 1336 if ("fax".equals(system) && value != null) { 1337 x.tx(", "); 1338 x.tx("Fax:"+t.primitiveValue("value")); 1339 } 1340 } 1341 } 1342 1343 private void renderContact(XhtmlNode x, String name, List<ContactPoint> telecom) { 1344 List<String> urls = new ArrayList<>(); 1345 for (ContactPoint t : telecom) { 1346 if (t.getSystem() == ContactPointSystem.URL && t.hasValue()) { 1347 urls.add(t.getValue()); 1348 } 1349 } 1350 if (urls.size() == 1) { 1351 x.ah(urls.get(0)).tx(name); 1352 } else { // if (urls.size() == 0) { 1353 x.tx(name); 1354 } 1355 for (ContactPoint t : telecom) { 1356 if (t.getSystem() == ContactPointSystem.URL && t.hasValue() && urls.size() != 1) { 1357 x.tx(", "); 1358 x.ah(t.getValue()).tx("Link"); 1359 } 1360 if (t.getSystem() == ContactPointSystem.EMAIL && t.hasValue()) { 1361 x.tx(", "); 1362 x.ah("mailto:"+t.getValue()).tx("Email"); 1363 } 1364 if (t.getSystem() == ContactPointSystem.PHONE && t.hasValue()) { 1365 x.tx(", "); 1366 x.tx(t.getValue()); 1367 } 1368 if (t.getSystem() == ContactPointSystem.FAX && t.hasValue()) { 1369 x.tx(", "); 1370 x.tx("Fax:"+t.getValue()); 1371 } 1372 } 1373 } 1374 1375 protected String describeStatus(RenderingStatus status, ResourceWrapper cr) { 1376 String s = describeStatus(cr.primitiveValue("status"), cr.primitiveValue("experimental"), cr.child("date"), cr.extensionString("http://hl7.org/fhir/StructureDefinition/valueset-deprecated")); 1377 if (cr.hasExtension(ToolingExtensions.EXT_STANDARDS_STATUS)) { 1378 status.setExtensions(true); 1379 s = s + presentStandardsStatus(cr.extensionString(ToolingExtensions.EXT_STANDARDS_STATUS)); 1380 } 1381 return s; 1382 } 1383 1384 protected String describeStatus(RenderingStatus status, CanonicalResource cr) { 1385 String s = describeStatus(cr.getStatus(), cr.hasExperimental() ? cr.getExperimental() : false, cr.hasDate() ? cr.getDateElement() : null, ToolingExtensions.readBooleanExtension(cr, "http://hl7.org/fhir/StructureDefinition/valueset-deprecated")); 1386 if (cr.hasExtension(ToolingExtensions.EXT_STANDARDS_STATUS)) { 1387 status.setExtensions(true); 1388 s = s + presentStandardsStatus(ToolingExtensions.readStringExtension(cr, ToolingExtensions.EXT_STANDARDS_STATUS)); 1389 } 1390 return s; 1391 } 1392 1393 private String presentStandardsStatus(String code) { 1394 String pfx = " (<a href=\"http://hl7.org/fhir/codesystem-standards-status.html#"+code+"\">Standards Status</a>: "; 1395 switch (code) { 1396 case "draft" : return pfx+"Draft)"; 1397 case "normative" : return pfx+"Normative)"; 1398 case "trial-use" : return pfx+"Trial Use)"; 1399 case "informative" : return pfx+"Informative)"; 1400 case "deprecated" : return pfx+"<span style=\"color: maroon; font-weight: bold\">Deprecated</span>)"; 1401 case "external" : return pfx+"External)"; 1402 } 1403 return ""; 1404 } 1405 1406 protected String describeStatus(PublicationStatus status, boolean experimental, DateTimeType dt, Boolean deprecated) { 1407 String sfx = dt != null ? " as of "+displayDataType(dt) : ""; 1408 if (deprecated != null && deprecated) { 1409 if (status == PublicationStatus.RETIRED) { 1410 return "Deprecated + Retired"+sfx; 1411 } else { 1412 return "Deprecated"+sfx; 1413 } 1414 } else { 1415 switch (status) { 1416 case ACTIVE: return (experimental ? "Experimental" : "Active")+sfx; 1417 case DRAFT: return "Draft"+sfx; 1418 case RETIRED: return "Retired"+sfx; 1419 default: return "Unknown"+sfx; 1420 } 1421 } 1422 } 1423 1424 protected String describeStatus(String status, String experimental, ResourceWrapper dt, String deprecated) { 1425 String sfx = dt != null ? " as of "+displayDataType(dt) : ""; 1426 if ("true".equals(deprecated)) { 1427 if ("retired".equals(status)) { 1428 return "Deprecated + Retired"+sfx; 1429 } else { 1430 return "Deprecated"+sfx; 1431 } 1432 } else { 1433 switch (status) { 1434 case "active": return ("true".equals(experimental) ? "Experimental" : "Active")+sfx; 1435 case "draft": return "Draft"+sfx; 1436 case "retired": return "Retired"+sfx; 1437 default: return "Unknown"+sfx; 1438 } 1439 } 1440 } 1441}