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