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