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