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