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