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