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