
001package org.hl7.fhir.r5.renderers; 002 003import static java.time.temporal.ChronoField.MONTH_OF_YEAR; 004import static java.time.temporal.ChronoField.YEAR; 005 006import java.io.IOException; 007import java.math.BigDecimal; 008import java.text.NumberFormat; 009import java.time.LocalDate; 010import java.time.ZoneId; 011import java.time.ZonedDateTime; 012import java.time.format.DateTimeFormatter; 013import java.time.format.DateTimeFormatterBuilder; 014import java.time.format.FormatStyle; 015import java.time.format.SignStyle; 016import java.util.Currency; 017import java.util.List; 018 019import org.hl7.fhir.exceptions.DefinitionException; 020import org.hl7.fhir.exceptions.FHIRException; 021import org.hl7.fhir.exceptions.FHIRFormatError; 022import org.hl7.fhir.r5.context.IWorkerContext; 023import org.hl7.fhir.r5.extensions.ExtensionDefinitions; 024import org.hl7.fhir.r5.model.BackboneType; 025import org.hl7.fhir.r5.model.Base; 026import org.hl7.fhir.r5.model.BaseDateTimeType; 027import org.hl7.fhir.r5.model.CanonicalResource; 028import org.hl7.fhir.r5.model.CodeSystem; 029import org.hl7.fhir.r5.model.CodeableConcept; 030import org.hl7.fhir.r5.model.Coding; 031import org.hl7.fhir.r5.model.ContactPoint; 032import org.hl7.fhir.r5.model.ContactPoint.ContactPointSystem; 033import org.hl7.fhir.r5.model.DataType; 034import org.hl7.fhir.r5.model.DateTimeType; 035import org.hl7.fhir.r5.model.ElementDefinition; 036import org.hl7.fhir.r5.model.Extension; 037import org.hl7.fhir.r5.model.NamingSystem; 038import org.hl7.fhir.r5.model.PrimitiveType; 039import org.hl7.fhir.r5.model.Quantity; 040import org.hl7.fhir.r5.model.Resource; 041import org.hl7.fhir.r5.model.StructureDefinition; 042import org.hl7.fhir.r5.model.ValueSet; 043import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; 044import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceDesignationComponent; 045import org.hl7.fhir.r5.renderers.utils.RenderingContext; 046import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules; 047import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; 048import org.hl7.fhir.r5.renderers.utils.ResourceWrapper; 049import org.hl7.fhir.r5.terminologies.JurisdictionUtilities; 050import org.hl7.fhir.r5.terminologies.utilities.SnomedUtilities; 051import org.hl7.fhir.r5.terminologies.utilities.ValidationResult; 052 053import org.hl7.fhir.r5.utils.UserDataNames; 054import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 055import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 056import org.hl7.fhir.utilities.Utilities; 057import org.hl7.fhir.utilities.VersionUtilities; 058import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 059import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece; 060import org.hl7.fhir.utilities.xhtml.NodeType; 061import org.hl7.fhir.utilities.xhtml.XhtmlNode; 062import org.hl7.fhir.utilities.xhtml.XhtmlParser; 063 064import ca.uhn.fhir.model.api.TemporalPrecisionEnum; 065 066@MarkedToMoveToAdjunctPackage 067public class DataRenderer extends Renderer implements CodeResolver { 068 069 // -- 1. context -------------------------------------------------------------- 070 071 public DataRenderer(RenderingContext context) { 072 super(context); 073 } 074 075 public DataRenderer(IWorkerContext worker) { 076 super(worker); 077 } 078 079 // -- 2. Markdown support ------------------------------------------------------- 080 081 public static String processRelativeUrls(String markdown, String path) { 082 if (markdown == null) { 083 return ""; 084 } 085 if (!Utilities.isAbsoluteUrl(path)) { 086 return markdown; 087 } 088 String basePath = path.contains("/") ? path.substring(0, path.lastIndexOf("/")+1) : path+"/"; 089 StringBuilder b = new StringBuilder(); 090 int i = 0; 091 while (i < markdown.length()) { 092 if (i < markdown.length()-3 && markdown.substring(i, i+2).equals("](")) { 093 int j = i + 2; 094 while (j < markdown.length() && markdown.charAt(j) != ')') 095 j++; 096 if (j < markdown.length()) { 097 String url = markdown.substring(i+2, j); 098 if (!Utilities.isAbsoluteUrl(url) && !url.startsWith("..")) { 099 // it's relative - so it's relative to the base URL 100 b.append("]("); 101 b.append(basePath); 102 } else { 103 b.append("]("); 104 } 105 i = i + 1; 106 } else 107 b.append(markdown.charAt(i)); 108 } else { 109 b.append(markdown.charAt(i)); 110 } 111 i++; 112 } 113 return b.toString(); 114 } 115 116 protected void addMarkdown(XhtmlNode x, String text, String path) throws FHIRFormatError, IOException, DefinitionException { 117 addMarkdown(x, processRelativeUrls(text, path)); 118 } 119 120 protected void addMarkdown(XhtmlNode x, String text) throws FHIRFormatError, IOException, DefinitionException { 121 if (text != null) { 122 // 1. custom FHIR extensions 123 while (text.contains("[[[")) { 124 String left = text.substring(0, text.indexOf("[[[")); 125 String link = text.substring(text.indexOf("[[[")+3, text.indexOf("]]]")); 126 String right = text.substring(text.indexOf("]]]")+3); 127 String path = null; 128 String url = link; 129 String[] parts = link.split("\\#"); 130 if (parts[0].contains(".")) { 131 path = parts[0]; 132 parts[0] = parts[0].substring(0, parts[0].indexOf(".")); 133 } 134 StructureDefinition p = getContext().getWorker().fetchResource(StructureDefinition.class, parts[0]); 135 if (p == null) { 136 p = getContext().getWorker().fetchTypeDefinition(parts[0]); 137 } 138 if (context.getTypeMap().containsKey(parts[0])) { 139 p = getContext().getWorker().fetchTypeDefinition(context.getTypeMap().get(parts[0])); 140 } 141 if (p == null) { 142 p = getContext().getWorker().fetchResource(StructureDefinition.class, link); 143 } 144 if (p != null) { 145 if ("Extension".equals(p.getType())) { 146 path = null; 147 } else if (p.hasSnapshot()) { 148 path = p.getSnapshot().getElementFirstRep().getPath(); 149 } else if (Utilities.isAbsoluteUrl(path)) { 150 path = null; 151 } 152 url = p.getWebPath(); 153 if (url == null) { 154 url = p.getUserString(UserDataNames.render_filename); 155 } 156 } else { 157 throw new DefinitionException(context.formatPhrase(RenderingContext.DATA_REND_MKDWN_LNK, link) + " "); 158 } 159 160 text = left+"["+link+"]("+url+(path == null ? "" : "#"+path)+")"+right; 161 } 162 163 // 2. markdown 164 String s = getContext().getMarkdown().process(text, "narrative generator"); 165 XhtmlParser p = new XhtmlParser(); 166 XhtmlNode m; 167 try { 168 m = p.parse("<div>"+s+"</div>", "div"); 169 } catch (org.hl7.fhir.exceptions.FHIRFormatError e) { 170 throw new FHIRFormatError(e.getMessage(), e); 171 } 172 x.addChildNodes(m.getChildNodes()); 173 } 174 } 175 176 177 // -- 3. General Purpose Terminology Support ----------------------------------------- 178 179 private static String snMonth(String m) { 180 switch (m) { 181 case "1" : return "Jan"; 182 case "2" : return "Feb"; 183 case "3" : return "Mar"; 184 case "4" : return "Apr"; 185 case "5" : return "May"; 186 case "6" : return "Jun"; 187 case "7" : return "Jul"; 188 case "8" : return "Aug"; 189 case "9" : return "Sep"; 190 case "10" : return "Oct"; 191 case "11" : return "Nov"; 192 case "12" : return "Dec"; 193 default: return null; 194 } 195 } 196 197 public static String describeVersion(String version) { 198 if (version.startsWith("http://snomed.info/sct")) { 199 String[] p = version.split("\\/"); 200 String ed = null; 201 String dt = ""; 202 203 if (p[p.length-2].equals("version")) { 204 ed = p[p.length-3]; 205 String y = p[p.length-3].substring(4, 8); 206 String m = p[p.length-3].substring(2, 4); 207 dt = " rel. "+snMonth(m)+" "+y; 208 } else { 209 ed = p[p.length-1]; 210 } 211 switch (ed) { 212 case "900000000000207008": return "Intl"+dt; 213 case "731000124108": return "US"+dt; 214 case "32506021000036107": return "AU"+dt; 215 case "449081005": return "ES/Intl"+dt; 216 case "554471000005108": return "DK"+dt; 217 case "11000146104": return "NL"+dt; 218 case "45991000052106": return "SE"+dt; 219 case "83821000000107": return "UK"+dt; 220 case "11000172109": return "BE"+dt; 221 case "11000221109" : return "AR"+dt; 222 case "11000234105" : return "AT"+dt; 223 case "20621000087109" : return "CA-EN"+dt; 224 case "20611000087101" : return "CA"+dt; // was FR, but was repurposed for the canadian edition early 2024 225 case "11000181102 " : return "EE"+dt; 226 case "11000229106" : return "FI"+dt; 227 case "11000274103" : return "DE"+dt; 228 case "1121000189102" : return "IN"+dt; 229 case "11000220105" : return "IE"+dt; 230 case "21000210109" : return "NZ"+dt; 231 case "51000202101 " : return "NO"+dt; 232 case "11000267109" : return "KR"+dt; 233 case "900000001000122104" : return "ES-ES"+dt; 234 case "2011000195101" : return "CH"+dt; 235 case "999000021000000109" : return "UK+Clinical"+dt; 236 case "5631000179106" : return "UY"+dt; 237 case "5991000124107" : return "US+ICD10CM"+dt; 238 case "21000325107": return "CH"+dt; 239 case "11000279109": return "CZ"+dt; 240 case "11000181102": return "ES"+dt; 241 case "51000202101": return "NO"+dt; 242 case "827022005": return "IPS"+dt; 243 default: return "??"+dt; 244 } 245 } else { 246 return version; 247 } 248 } 249 250 public String displaySystem(String system) { 251 if (system == null) 252 return (context.formatPhrase(RenderingContext.DATA_REND_NOT_STAT)); 253 if (system.equals("http://loinc.org")) 254 return (context.formatPhrase(RenderingContext.DATA_REND_LOINC)); 255 if (system.startsWith("http://snomed.info")) 256 return (context.formatPhrase(RenderingContext.DATA_REND_SNOMED)); 257 if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm")) 258 return (context.formatPhrase(RenderingContext.DATA_REND_RXNORM)); 259 if (system.equals("http://hl7.org/fhir/sid/icd-9")) 260 return (context.formatPhrase(RenderingContext.DATA_REND_ICD)); 261 if (system.equals("http://dicom.nema.org/resources/ontology/DCM")) 262 return (context.formatPhrase(RenderingContext.DATA_REND_DICOM)); 263 if (system.equals("http://unitsofmeasure.org")) 264 return (context.formatPhrase(RenderingContext.GENERAL_UCUM)); 265 if (system.equals("http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl")) 266 return (context.formatPhrase(RenderingContext.GENERAL_NCI_THES)); 267 268 269 CodeSystem cs = context.getContext().fetchCodeSystem(system); 270 if (cs != null) { 271 return crPresent(cs); 272 } 273 return tails(system); 274 } 275 276 private String crPresent(CanonicalResource cr) { 277 if (cr.hasUserData(UserDataNames.render_presentation)) { 278 return cr.getUserString(UserDataNames.render_presentation); 279 } 280 if (cr.hasTitle()) 281 return context.getTranslated(cr.getTitleElement()); 282 if (cr.hasName()) 283 return context.getTranslated(cr.getNameElement()); 284 return cr.toString(); 285 } 286 287 private String tails(String system) { 288 if (system.contains("/")) { 289 return system.substring(system.lastIndexOf("/")+1); 290 } else { 291 return (context.formatPhrase(RenderingContext.DATA_REND_UNKNWN)); 292 } 293 } 294 295 protected String makeAnchor(String codeSystem, String code) { 296 String s = codeSystem+'-'+code; 297 StringBuilder b = new StringBuilder(); 298 for (char c : s.toCharArray()) { 299 if (Utilities.isValidHtmlAnchorChar(c)) { 300 b.append(c); 301 } else { 302 b.append("|"+Integer.toHexString(c)); // not % to save double coding confusing users 303 } 304 } 305 return b.toString(); 306 } 307 308 public String lookupCode(String system, String version, String code) { 309 if (JurisdictionUtilities.isJurisdiction(system)) { 310 return JurisdictionUtilities.displayJurisdiction(system+"#"+code); 311 } 312 ValidationResult t = getContext().getWorker().validateCode(getContext().getTerminologyServiceOptions().withLanguage(context.getLocale().toLanguageTag()).withVersionFlexible(true), system, version, code, null); 313 314 if (t != null && t.getDisplay() != null) 315 return t.getDisplay(); 316 else 317 return code; 318 } 319 320 protected String describeLang(String lang) { 321 // special cases: 322 if ("fr-CA".equals(lang)) { 323 return "French (Canadian)"; // this one was omitted from the value set 324 } 325 ValueSet v = getContext().getWorker().findTxResource(ValueSet.class, "http://hl7.org/fhir/ValueSet/languages"); 326 if (v != null) { 327 ConceptReferenceComponent l = null; 328 for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) { 329 if (cc.getCode().equals(lang)) 330 l = cc; 331 } 332 if (l == null) { 333 if (lang.contains("-")) { 334 lang = lang.substring(0, lang.indexOf("-")); 335 } 336 for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) { 337 if (cc.getCode().equals(lang)) { 338 l = cc; 339 break; 340 } 341 } 342 if (l == null) { 343 for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) { 344 if (cc.getCode().startsWith(lang+"-")) { 345 l = cc; 346 break; 347 } 348 } 349 } 350 } 351 if (l != null) { 352 if (lang.contains("-")) 353 lang = lang.substring(0, lang.indexOf("-")); 354 String en = l.getDisplay(); 355 String nativelang = null; 356 for (ConceptReferenceDesignationComponent cd : l.getDesignation()) { 357 if (cd.getLanguage().equals(lang)) 358 nativelang = cd.getValue(); 359 } 360 if (nativelang == null) 361 return en+" ("+lang+")"; 362 else 363 return nativelang+" ("+en+", "+lang+")"; 364 } 365 } 366 return lang; 367 } 368 369 private boolean isCanonical(String path) { 370 if (!path.endsWith(".url")) 371 return false; 372 String t = path.substring(0, path.length()-4); 373 StructureDefinition sd = getContext().getWorker().fetchTypeDefinition(t); 374 if (sd == null) 375 return false; 376 if (VersionUtilities.getCanonicalResourceNames(getContext().getWorker().getVersion()).contains(t)) { 377 return true; 378 } 379 if (Utilities.existsInList(t, 380 "ActivityDefinition", "CapabilityStatement", "ChargeItemDefinition", "Citation", "CodeSystem", 381 "CompartmentDefinition", "ConceptMap", "ConditionDefinition", "EventDefinition", "Evidence", "EvidenceReport", "EvidenceVariable", 382 "ExampleScenario", "GraphDefinition", "ImplementationGuide", "Library", "Measure", "MessageDefinition", "NamingSystem", "PlanDefinition" 383 )) 384 return true; 385 return false; 386 } 387 388 // -- 4. Language support ------------------------------------------------------ 389 390 public String gt(@SuppressWarnings("rawtypes") PrimitiveType value) { 391 return context.getTranslated(value); 392 } 393 394 public String gt(ResourceWrapper value) { 395 return context.getTranslated(value); 396 } 397 398 // -- 6. General purpose extension rendering ---------------------------------------------- 399 400 public boolean hasRenderableExtensions(DataType element) { 401 for (Extension ext : element.getExtension()) { 402 if (canRender(ext)) { 403 return true; 404 } 405 } 406 return false; 407 } 408 409 public boolean hasRenderableExtensions(BackboneType element) { 410 for (Extension ext : element.getExtension()) { 411 if (canRender(ext)) { 412 return true; 413 } 414 } 415 return element.hasModifierExtension(); 416 } 417 418 public boolean hasRenderableExtensions(ResourceWrapper element) { 419 for (ResourceWrapper ext : element.extensions()) { 420 if (canRender(ext)) { 421 return true; 422 } 423 } 424 return false; 425 } 426 427 private String getExtensionLabel(Extension ext) { 428 StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, ext.getUrl()); 429 if (sd != null && ext.hasValue() && ext.getValue().isPrimitive() && sd.hasSnapshot()) { 430 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 431 if (Utilities.existsInList(ed.getPath(), "Extension", "Extension.value[x]") && ed.hasLabel()) { 432 return context.getTranslated(ed.getLabelElement()); 433 } 434 } 435 } 436 return null; 437 } 438 439 private String getExtensionLabel(ResourceWrapper ext) { 440 StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, ext.primitiveValue("url")); 441 if (sd != null && ext.has("value") && ext.child("value").isPrimitive() && sd.hasSnapshot()) { 442 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 443 if (Utilities.existsInList(ed.getPath(), "Extension", "Extension.value[x]") && ed.hasLabel()) { 444 return context.getTranslated(ed.getLabelElement()); 445 } 446 } 447 } 448 return null; 449 } 450 451 private boolean canRender(Extension ext) { 452 return getExtensionLabel(ext) != null; 453 } 454 455 456 private boolean canRender(ResourceWrapper ext) { 457 StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, ext.primitiveValue("url")); 458 if (sd != null && ext.has("value") && ext.isPrimitive("value") && sd.hasSnapshot()) { 459 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 460 if (Utilities.existsInList(ed.getPath(), "Extension", "Extension.value[x]") && ed.hasLabel()) { 461 return true; 462 } 463 } 464 } 465 return false; 466 } 467 468 public void renderExtensionsInList(RenderingStatus status, XhtmlNode ul, ResourceWrapper element) throws FHIRFormatError, DefinitionException, IOException { 469 for (ResourceWrapper ext : element.extensions()) { 470 if (canRender(ext)) { 471 String lbl = getExtensionLabel(ext); 472 XhtmlNode li = ul.li(); 473 li.tx(lbl); 474 li.tx(": "); 475 renderDataType(status, li, ext.child("value")); 476 } 477 } 478 } 479 480 // public void renderExtensionsInList(XhtmlNode ul, BackboneType element) throws FHIRFormatError, DefinitionException, IOException { 481 // for (Extension ext : element.getModifierExtension()) { 482 // if (canRender(ext)) { 483 // String lbl = getExtensionLabel(ext); 484 // XhtmlNode li = ul.li(); 485 // li = li.b(); 486 // li.tx(lbl); 487 // li.tx(": "); 488 // render(li, ext.getValue()); 489 // } else { 490 // // somehow have to do better than this 491 // XhtmlNode li = ul.li(); 492 // li.b().tx(context.formatPhrase(RenderingContext.DATA_REND_UNRD_EX)); 493 // } 494 // } 495 // for (Extension ext : element.getExtension()) { 496 // if (canRender(ext)) { 497 // String lbl = getExtensionLabel(ext); 498 // XhtmlNode li = ul.li(); 499 // li.tx(lbl); 500 // li.tx(": "); 501 // render(li, ext.getValue()); 502 // } 503 // } 504 // } 505 // 506 public void renderExtensionsInText(RenderingStatus status, XhtmlNode x, ResourceWrapper element, String sep) throws FHIRFormatError, DefinitionException, IOException { 507 boolean first = true; 508 for (ResourceWrapper ext : element.extensions()) { 509 if (canRender(ext)) { 510 status.setExtensions(true); 511 if (first) { 512 first = false; 513 } else { 514 x.tx(sep); 515 x.tx(" "); 516 } 517 518 String lbl = getExtensionLabel(ext); 519 x.tx(lbl); 520 x.tx(": "); 521 renderDataType(status, x, ext.child("value")); 522 } 523 } 524 } 525 526 527 protected void checkRenderExtensions(RenderingStatus status, XhtmlNode x, ResourceWrapper element) throws FHIRFormatError, DefinitionException, IOException { 528 if (element.has("extension")) { 529 boolean someCanRender = false; 530 for (ResourceWrapper ext : element.children("extension")) { 531 ResourceWrapper value = ext.child("value"); 532 if (canRender(ext) && value.isPrimitive()) { 533 someCanRender = true; 534 } 535 } 536 if (someCanRender) { 537 status.setExtensions(true); 538 x.tx(" ("); 539 renderExtensionsInText(status, x, element, ", "); 540 x.tx(")"); 541 } 542 } 543 544 } 545 546 // public void renderExtensionsInList(XhtmlNode div, BackboneType element, String sep) throws FHIRFormatError, DefinitionException, IOException { 547 // boolean first = true; 548 // for (Extension ext : element.getModifierExtension()) { 549 // if (first) { 550 // first = false; 551 // } else { 552 // div.tx(sep); 553 // div.tx(" "); 554 // } 555 // if (canRender(ext)) { 556 // String lbl = getExtensionLabel(ext); 557 // XhtmlNode b = div.b(); 558 // b.tx(lbl); 559 // b.tx(": "); 560 // render(div, ext.getValue()); 561 // } else { 562 // // somehow have to do better than this 563 // div.b().tx(context.formatPhrase(RenderingContext.DATA_REND_UNRD_EX)); 564 // } 565 // } 566 // for (Extension ext : element.getExtension()) { 567 // if (canRender(ext)) { 568 // if (first) { 569 // first = false; 570 // } else { 571 // div.tx(sep); 572 // div.tx(" "); 573 // } 574 // 575 // String lbl = getExtensionLabel(ext); 576 // div.tx(lbl); 577 // div.tx(": "); 578 // render(div, ext.getValue()); 579 // } 580 // } 581 // 582 // } 583 584 // -- 6. Data type Rendering ---------------------------------------------- 585 586 public static String display(IWorkerContext context, DataType type) { 587 return new DataRenderer(new RenderingContext(context, null, null, "http://hl7.org/fhir/R4", "", context.getLocale(), ResourceRendererMode.END_USER, GenerationRules.VALID_RESOURCE)).displayDataType(type); 588 } 589 590 public String displayBase(Base b) { 591 if (b instanceof DataType) { 592 return displayDataType((DataType) b); 593 } else { 594 return (context.formatPhrase(RenderingContext.DATA_REND_NO_DISP, b.fhirType()) + " "); 595 } 596 } 597 598 public String displayDataType(DataType type) { 599 if (type == null) { 600 return ""; 601 } 602 return displayDataType(wrapNC(type)); 603 } 604 605 public String displayDataType(ResourceWrapper type) { 606 if (type == null || type.isEmpty()) { 607 return ""; 608 } 609 610 switch (type.fhirType()) { 611 case "Coding": return displayCoding(type); 612 case "CodeableConcept": return displayCodeableConcept(type); 613 case "CodeableReference": return displayCodeableReference(type); 614 case "Identifier": return displayIdentifier(type); 615 case "HumanName": return displayHumanName(type); 616 case "Address": return displayAddress(type); 617 case "ContactPoint": return displayContactPoint(type); 618 case "Quantity": return displayQuantity(type); 619 case "Range": return displayRange(type); 620 case "Period": return displayPeriod(type); 621 case "Timing": return displayTiming(type); 622 case "SampledData": return displaySampledData(type); 623 case "ContactDetail": return displayContactDetail(type); 624 case "Annotation": return displayAnnotation(type); 625 case "Ratio": return displayRatio(type); 626 case "Reference" : return displayReference(type); 627 case "Money" : return displayMoney(type); 628 case "dateTime": 629 case "date" : 630 case "instant" : 631 return displayDateTime(type); 632 default: 633 if (type.isPrimitive()) { 634 return context.getTranslated(type); 635 } else if (Utilities.existsInList(type.fhirType(), "Meta", "Dosage", "Signature", "UsageContext", "RelatedArtifact", "ElementDefinition", "Base64BinaryType", "Attachment")) { 636 return ""; 637 } else if ("Extension".equals(type.fhirType())) { 638 return displayDataType(type.child("value")); 639 } else { 640 return (context.formatPhrase(RenderingContext.DATA_REND_NO_DISP, type.fhirType()) + " "); 641 } 642 } 643 } 644 645 private String displayMoney(ResourceWrapper type) { 646 String currency = type.primitiveValue("currency"); 647 String value = type.primitiveValue("value"); 648 return context.formatPhrase(RenderingContext.DATA_REND_CURRENCY, currency, value); 649 } 650 651 private String displayAnnotation(ResourceWrapper type) { 652 return type.primitiveValue("text"); 653 } 654 655 private String displayCodeableReference(ResourceWrapper type) { 656 if (type.has("reference")) { 657 return displayReference(type.child("reference")); 658 } else { 659 return displayCodeableConcept(type.child("concept")); 660 } 661 } 662 663 664 protected String displayReference(ResourceWrapper type) { 665 if (type.has("display")) { 666 return type.primitiveValue("display"); 667 } else if (type.has("reference")) { 668 // ResourceWithReference tr = resolveReference(res, r.getReference()); 669 // x.addText(tr == null ? r.getReference() : "?ngen-3"); // getDisplayForReference(tr.getReference())); 670 return "?ngen-3"; 671 } else { 672 return "?ngen-4?"; 673 } 674 } 675 676 private String displayRatio(ResourceWrapper type) { 677 return displayQuantity(type.child("numerator"))+" / "+displayQuantity(type.child("denominator")); 678 } 679 680 protected String displayDateTime(ResourceWrapper type) { 681 if (!type.hasPrimitiveValue()) { 682 return ""; 683 } 684 685 BaseDateTimeType t = new DateTimeType(type.primitiveValue()); 686 // relevant inputs in rendering context: 687 // timeZone, dateTimeFormat, locale, mode 688 // timezone - application specified timezone to use. 689 // null = default to the time of the date/time itself 690 // dateTimeFormat - application specified format for date times 691 // null = default to ... depends on mode 692 // mode - if rendering mode is technical, format defaults to XML format 693 // locale - otherwise, format defaults to SHORT for the Locale (which defaults to default Locale) 694 if (isOnlyDate(t.getPrecision())) { 695 696 DateTimeFormatter fmt = getDateFormatForPrecision(t); 697 LocalDate date = LocalDate.of(t.getYear(), t.getMonth()+1, t.getDay()); 698 return fmt.format(date); 699 } 700 701 DateTimeFormatter fmt = context.getDateTimeFormat(); 702 if (fmt == null) { 703 if (context.isTechnicalMode()) { 704 fmt = DateTimeFormatter.ISO_OFFSET_DATE_TIME; 705 } else { 706 fmt = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).withLocale(context.getLocale()); 707 } 708 } 709 ZonedDateTime zdt = ZonedDateTime.parse(t.primitiveValue()); 710 ZoneId zone = context.getTimeZoneId(); 711 if (zone != null) { 712 zdt = zdt.withZoneSameInstant(zone); 713 } 714 return fmt.format(zdt); 715 } 716 717 private DateTimeFormatter getDateFormatForPrecision(BaseDateTimeType type) { 718 DateTimeFormatter fmt = getContextDateFormat(type); 719 if (fmt != null) { 720 return fmt; 721 } 722 if (context.isTechnicalMode()) { 723 switch (type.getPrecision()) { 724 case YEAR: 725 return new DateTimeFormatterBuilder().appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD).toFormatter(); 726 case MONTH: 727 return new DateTimeFormatterBuilder().appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD).appendLiteral('-').appendValue(MONTH_OF_YEAR, 2).toFormatter(); 728 default: 729 return DateTimeFormatter.ISO_DATE; 730 } 731 } else { 732 switch (type.getPrecision()) { 733 case YEAR: 734 return DateTimeFormatter.ofPattern("uuuu"); 735 case MONTH: 736 return DateTimeFormatter.ofPattern("MMM uuuu"); 737 default: 738 return DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(context.getLocale()); 739 } 740 } 741 } 742 743 private DateTimeFormatter getContextDateFormat(BaseDateTimeType type) { 744 switch (type.getPrecision()) { 745 case YEAR: 746 return context.getDateYearFormat(); 747 case MONTH: 748 return context.getDateYearMonthFormat(); 749 default: 750 return context.getDateFormat(); 751 } 752 } 753 754 private boolean isOnlyDate(TemporalPrecisionEnum temporalPrecisionEnum) { 755 return temporalPrecisionEnum == TemporalPrecisionEnum.YEAR || temporalPrecisionEnum == TemporalPrecisionEnum.MONTH || temporalPrecisionEnum == TemporalPrecisionEnum.DAY; 756 } 757 758 759 // public void renderDataType(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException { 760 //// Base base = null; 761 //// try { 762 //// base = type.getBase(); 763 //// } catch (FHIRException | IOException e) { 764 //// x.tx(context.formatPhrase(RenderingContext.DATA_REND_ERROR, e.getMessage()) + " "); // this shouldn't happen - it's an error in the library itself 765 //// return; 766 //// } 767 //// if (base instanceof DataType) { 768 //// render(x, (DataType) base); 769 //// } else { 770 //// x.tx(context.formatPhrase(RenderingContext.DATA_REND_TO_DO, base.fhirType())); 771 //// } 772 // } 773 774 public void renderBase(RenderingStatus status, XhtmlNode x, Base b) throws FHIRFormatError, DefinitionException, IOException { 775 if (b instanceof DataType) { 776 renderDataType(status, x, wrapNC((DataType) b)); 777 } else { 778 x.tx(context.formatPhrase(RenderingContext.DATA_REND_NO_DISP, b.fhirType()) + " "); 779 } 780 } 781 782 public boolean canRenderDataType(String type) { 783 return context.getContextUtilities().isPrimitiveType(type) || Utilities.existsInList(type, "Annotation", "Coding", "CodeableConcept", "Identifier", "HumanName", "Address", "Dosage", 784 "Expression", "Money", "ContactPoint", "Quantity", "Range", "Period", "Timing", "SampledData", "Reference", "UsageContext", "ContactDetail", "Ratio", "Attachment", "CodeableReference"); 785 } 786 787 public boolean renderDataType(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException { 788 return renderDataType(status, x, x, type); 789 } 790 791 public boolean renderDataType(RenderingStatus status, XhtmlNode parent, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException { 792 if (type == null) { 793 return false; 794 } 795 switch (type.fhirType()) { 796 case "dateTime": 797 case "date" : 798 case "instant" : 799 renderDateTime(status, x, type); 800 break; 801 case "uri" : 802 case "url" : 803 renderUri(status, x, type); 804 break; 805 case "canonical" : 806 renderCanonical(status, x, type); 807 break; 808 case "Annotation": 809 renderAnnotation(status, parent, x, type); 810 break; 811 case "Coding": 812 renderCodingWithDetails(status, x, type); 813 break; 814 case "CodeableConcept": 815 renderCodeableConcept(status, x, type); 816 break; 817 case "Identifier": 818 renderIdentifier(status, x, type); 819 break; 820 case "HumanName": 821 renderHumanName(status, x, type); 822 break; 823 case "Address": 824 renderAddress(status, x, type); 825 break; 826 case "Expression": 827 renderExpression(status, x, type); 828 break; 829 case "Money": 830 renderMoney(status, x, type); 831 break; 832 case "ContactPoint": 833 renderContactPoint(status, x, type); 834 break; 835 case "Quantity": 836 case "Age": 837 renderQuantity(status, x, type); 838 break; 839 case "Range": 840 renderRange(status, x, type); 841 break; 842 case "Period": 843 renderPeriod(status, x, type); 844 break; 845 case "Timing": 846 renderTiming(status, x, type); 847 break; 848 case "SampledData": 849 renderSampledData(status, x, type); 850 break; 851 case "Reference": 852 renderReference(status, x, type); 853 break; 854 case "UsageContext": 855 renderUsageContext(status, x, type); 856 break; 857 case "ContactDetail": 858 renderContactDetail(status, x, type); 859 break; 860 case "Ratio": 861 renderRatio(status, x, type); 862 break; 863 case "Attachment": 864 renderAttachment(status, x, type); 865 break; 866 case "CodeableReference": 867 if (type.has("concept")) { 868 renderCodeableConcept(status, x, type.child("concept")); 869 } else { 870 renderReference(status, x, type.child("reference")); 871 } 872 break; 873 case "code": 874 x.tx(getTranslatedCode(type)); 875 break; 876 case "markdown": 877 addMarkdown(parent == null ? x : parent, context.getTranslated(type)); // note parent not focus, because of paragraph issues and markdown 878 break; 879 case "base64Binary": 880 int length = type.primitiveValue().length(); 881 if (length >= context.getBase64Limit()) { 882 x.tx(context.formatPhrase(RenderingContext.DATA_REND_BASE64, length)); 883 } else { 884 x.code(type.primitiveValue()); 885 } 886 break; 887 default: 888 if (type.isPrimitive()) { 889 if (!renderPrimitiveWithNoValue(status, x, type)) { 890 x.tx(context.getTranslated(type)); 891 } 892 } else { 893 x.tx(context.formatPhrase(RenderingContext.DATA_REND_NO_DISP, type.fhirType()) + " "); 894 return false; 895 } 896 } 897 return true; 898 } 899 900 // overide in ResourceRenderer 901 protected void renderCanonical(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException { 902 renderUri(status, x, type); 903 } 904 905 private void renderRatio(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException { 906 renderQuantity(status, x, type.child("numerator")); 907 x.tx("/"); 908 renderQuantity(status, x, type.child("denominator")); 909 checkRenderExtensions(status, x, type); 910 } 911 912 private void renderAttachment(RenderingStatus status, XhtmlNode x, ResourceWrapper att) { 913 String ct = att.primitiveValue("contentType"); 914 if (att.has("url")) { 915 x.tx(context.formatMessage(RenderingContext.DATA_REND_ATT_URL, ct, att.primitiveValue("url"))); 916 } else if (att.has("data")) { 917 x.tx(context.formatMessage(RenderingContext.DATA_REND_ATT_DATA, ct, displayDataType(att.child("data")))); 918 } 919 } 920 921 private void renderContactDetail(RenderingStatus status, XhtmlNode x, ResourceWrapper cd) { 922 if (cd.has("name")) { 923 x.tx(cd.primitiveValue("name")+": "); 924 } 925 boolean first = true; 926 for (ResourceWrapper c : cd.children("telecom")) { 927 if (first) first = false; else x.tx(","); 928 renderContactPoint(status, x, c); 929 } 930 } 931 932 private void renderDateTime(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException { 933 if (!renderPrimitiveWithNoValue(status, x, type)) { 934 x.tx(displayDateTime(type)); 935 checkRenderExtensions(status, x, type); 936 } 937 } 938 939 /** 940 * this is overridden in ResourceRenderer where a better rendering is performed 941 * @param status 942 * @param x 943 * @param ref 944 * @throws IOException 945 * @throws DefinitionException 946 * @throws FHIRFormatError 947 */ 948 protected void renderReference(RenderingStatus status, XhtmlNode x, ResourceWrapper ref) throws FHIRFormatError, DefinitionException, IOException { 949 if (ref.has("display")) { 950 x.tx(context.getTranslated(ref.child("display"))); 951 } else if (ref.has("reference")) { 952 x.tx(ref.primitiveValue("reference")); 953 } else { 954 x.tx("??"); 955 } 956 } 957 // 958 // public void renderDateTime(RenderingStatus status, XhtmlNode x, Base e) { 959 // if (e.hasPrimitiveValue()) { 960 // x.addText(displayDateTime((DateTimeType) e)); 961 // } 962 // } 963 // 964 // public void renderDate(RenderingStatus status, XhtmlNode x, Base e) { 965 // if (e.hasPrimitiveValue()) { 966 // x.addText(displayDateTime((DateType) e)); 967 // } 968 // } 969 // 970 // public void renderDateTime(XhtmlNode x, String s) { 971 // if (s != null) { 972 // DateTimeType dt = new DateTimeType(s); 973 // x.addText(displayDateTime(dt)); 974 // } 975 // } 976 977 978 protected boolean renderPrimitiveWithNoValue(RenderingStatus status, XhtmlNode x, PrimitiveType<?> prim) throws FHIRFormatError, DefinitionException, IOException { 979 if (prim.hasPrimitiveValue()) { 980 return false; 981 } 982 boolean first = true; 983 for (Extension ext : prim.getExtension()) { 984 if (first) first = false; else x.tx(", "); 985 String url = ext.getUrl(); 986 if (url.equals(ExtensionDefinitions.EXT_DAR)) { 987 x.tx("Absent because : "); 988 displayCode(x, wrapNC(ext.getValue())); 989 } else if (url.equals(ExtensionDefinitions.EXT_NF)) { 990 x.tx("Null because: "); 991 displayCode(x, wrapNC(ext.getValue())); 992 } else if (url.equals(ExtensionDefinitions.EXT_OT)) { 993 x.code().tx("Text: "); 994 displayCode(x, wrapNC(ext.getValue())); 995 } else if (url.equals(ExtensionDefinitions.EXT_CQF_EXP)) { 996 x.code().tx("Value calculated by: "); 997 renderExpression(status, x, wrapNC(ext.getValue())); 998 } else { 999 StructureDefinition def = context.getContext().fetchResource(StructureDefinition.class, url); 1000 if (def == null) { 1001 x.code().tx(tail(url)+": "); 1002 } else { 1003 x.code().tx(def.present()+": "); 1004 } 1005 renderDataType(status, x, wrapNC(ext.getValue())); 1006 } 1007 } 1008 status.setExtensions(true); 1009 return true; 1010 } 1011 1012 protected boolean renderPrimitiveWithNoValue(RenderingStatus status, XhtmlNode x, ResourceWrapper prim) throws FHIRFormatError, DefinitionException, IOException { 1013 if (prim.hasPrimitiveValue()) { 1014 return false; 1015 } 1016 boolean first = true; 1017 for (ResourceWrapper ext : prim.extensions()) { 1018 if (first) first = false; else x.tx(", "); 1019 String url = ext.primitiveValue("url"); 1020 if (url.equals(ExtensionDefinitions.EXT_DAR)) { 1021 x.tx("Absent because : "); 1022 displayCode(x, ext.child("value")); 1023 } else if (url.equals(ExtensionDefinitions.EXT_NF)) { 1024 x.tx("Null because: "); 1025 displayCode(x, ext.child("value")); 1026 } else if (url.equals(ExtensionDefinitions.EXT_OT)) { 1027 x.code().tx("Text: "); 1028 displayCode(x, ext.child("value")); 1029 } else if (url.equals(ExtensionDefinitions.EXT_CQF_EXP)) { 1030 x.code().tx("Value calculated by: "); 1031 renderExpression(status, x, ext.child("value")); 1032 } else { 1033 StructureDefinition def = context.getContext().fetchResource(StructureDefinition.class, url); 1034 if (def == null) { 1035 x.code().tx(tail(url)+": "); 1036 } else { 1037 x.code().tx(def.present()+": "); 1038 } 1039 renderDataType(status, x, ext.child("value")); 1040 } 1041 } 1042 status.setExtensions(true); 1043 return true; 1044 } 1045 1046 protected String tail(String url) { 1047 return url.substring(url.lastIndexOf(".")+1); 1048 } 1049 1050 protected String utail(String url) { 1051 return url.contains("/") ? url.substring(url.lastIndexOf("/")+1) : url; 1052 } 1053 1054 private void displayCode(XhtmlNode x, ResourceWrapper code) { 1055 x.tx(code.primitiveValue()); 1056 } 1057 1058 protected void renderUri(RenderingStatus status, XhtmlNode x, ResourceWrapper uri) throws FHIRFormatError, DefinitionException, IOException { 1059 if (!renderPrimitiveWithNoValue(status, x, uri)) { 1060 String v = uri.primitiveValue(); 1061 1062 if (v != null) { 1063 if (context.getContextUtilities().isResource(v)) { 1064 v = "http://hl7.org/fhir/"+v; 1065 } 1066 if (v.startsWith("mailto:")) { 1067 x.ah(v).addText(v.substring(7)); 1068 } else { 1069 Resource r = context.getContext().fetchResource(Resource.class, v); 1070 if (r != null && r.getWebPath() != null) { 1071 if (r instanceof CanonicalResource) { 1072 x.ah(context.prefixLocalHref(r.getWebPath())).addText(crPresent((CanonicalResource) r)); 1073 } else { 1074 x.ah(context.prefixLocalHref(r.getWebPath())).addText(v); 1075 } 1076 } else { 1077 String url = context.getResolver() != null ? context.getResolver().resolveUri(context, v) : null; 1078 if (url != null) { 1079 x.ah(context.prefixLocalHref(url)).addText(v); 1080 } else if (Utilities.isAbsoluteUrlLinkable(v) && !uri.fhirType().equals("id")) { 1081 x.ah(context.prefixLocalHref(v)).addText(v); 1082 } else { 1083 x.addText(v); 1084 } 1085 } 1086 } 1087 } 1088 } 1089 checkRenderExtensions(status, x, uri); 1090 } 1091 1092 protected void renderAnnotation(RenderingStatus status, XhtmlNode parent, XhtmlNode x, ResourceWrapper a) throws FHIRException, IOException { 1093 if (a.has("text")) { 1094 addMarkdown(parent.blockquote(), context.getTranslated(a.child("text"))); 1095 } 1096 1097 if (a.has("author")) { 1098 x.tx(context.formatPhrase(RenderingContext.DATA_REND_BY) + " "); 1099 ResourceWrapper auth = a.child("author"); 1100 if (auth.fhirType().equals("Reference")) { 1101 x.tx(auth.primitiveValue("reference")); 1102 } else if (auth.fhirType().equals("string")) { 1103 x.tx(context.getTranslated(auth)); 1104 } 1105 } 1106 1107 1108 if (a.has("time")) { 1109 if (a.has("author")) { 1110 x.tx(" "); 1111 } 1112 x.tx("@"); 1113 x.tx(displayDateTime(a.child("time"))); 1114 } 1115 1116 } 1117 1118 public String displayCoding(ResourceWrapper c) { 1119 String s = ""; 1120 if (context.isTechnicalMode()) { 1121 s = context.getTranslated(c.child("display")); 1122 if (Utilities.noString(s)) { 1123 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1124 } 1125 if (Utilities.noString(s)) { 1126 s = displayCodeTriple(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1127 } else if (c.has("system")) { 1128 s = s + " ("+displayCodeTriple(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code"))+")"; 1129 } else if (c.has("code")) { 1130 s = s + " ("+c.primitiveValue("code")+")"; 1131 } 1132 } else { 1133 if (c.has("display")) 1134 return context.getTranslated(c.child("display")); 1135 if (Utilities.noString(s)) 1136 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1137 if (Utilities.noString(s)) 1138 s = c.primitiveValue("code"); 1139 } 1140 return s; 1141 } 1142 1143 public String displayCodeSource(String system, String version) { 1144 String s = displaySystem(system); 1145 if (version != null) { 1146 s = s + "["+describeVersion(version)+"]"; 1147 } 1148 return s; 1149 } 1150 1151 private String displayCodeTriple(String system, String version, String code) { 1152 if (system == null) { 1153 if (code == null) { 1154 return ""; 1155 } else { 1156 return "#"+code; 1157 } 1158 } else { 1159 String s = displayCodeSource(system, version); 1160 if (code != null) { 1161 s = s + "#"+code; 1162 } 1163 return s; 1164 } 1165 } 1166 1167 public String displayCoding(List<Coding> list) { 1168 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1169 for (Coding c : list) { 1170 b.append(displayCoding(wrapNC(c))); 1171 } 1172 return b.toString(); 1173 } 1174 1175 protected void renderCoding(HierarchicalTableGenerator gen, List<Piece> pieces, Coding c) { 1176 if (c.isEmpty()) { 1177 return; 1178 } 1179 1180 String url = getLinkForSystem(c.getSystem(), c.getVersion()); 1181 String name = displayCodeSource(c.getSystem(), c.getVersion()); 1182 if (!Utilities.noString(url)) { 1183 pieces.add(gen.new Piece(url, name, c.getSystem()+(c.hasVersion() ? "#"+c.getVersion() : ""))); 1184 } else { 1185 pieces.add(gen.new Piece(null, name, c.getSystem()+(c.hasVersion() ? "#"+c.getVersion() : ""))); 1186 } 1187 pieces.add(gen.new Piece(null, "#"+c.getCode(), null)); 1188 String s = context.getTranslated(c.getDisplayElement()); 1189 if (Utilities.noString(s)) { 1190 s = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); 1191 } 1192 if (!Utilities.noString(s)) { 1193 pieces.add(gen.new Piece(null, " \""+s+"\"", null)); 1194 } 1195 } 1196 1197 protected void renderCoding(HierarchicalTableGenerator gen, List<Piece> pieces, ResourceWrapper c) { 1198 if (c.isEmpty()) { 1199 return; 1200 } 1201 1202 String url = getLinkForSystem(c.primitiveValue("system"), c.primitiveValue("version")); 1203 String name = displayCodeSource(c.primitiveValue("system"), c.primitiveValue("version")); 1204 if (!Utilities.noString(url)) { 1205 pieces.add(gen.new Piece(url, name, c.primitiveValue("system")+(c.has("version") ? "#"+c.primitiveValue("version") : ""))); 1206 } else { 1207 pieces.add(gen.new Piece(null, name, c.primitiveValue("system")+(c.has("version") ? "#"+c.primitiveValue("version") : ""))); 1208 } 1209 pieces.add(gen.new Piece(null, "#"+c.primitiveValue("code"), null)); 1210 String s = context.getTranslated(c.child("display")); 1211 if (Utilities.noString(s)) { 1212 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1213 } 1214 if (!Utilities.noString(s)) { 1215 pieces.add(gen.new Piece(null, " \""+s+"\"", null)); 1216 } 1217 } 1218 1219 public String getLinkForSystem(String system, String version) { 1220 if ("http://snomed.info/sct".equals(system)) { 1221 return "https://browser.ihtsdotools.org/"; 1222 } else if ("http://loinc.org".equals(system)) { 1223 return "https://loinc.org/"; 1224 } else if ("http://unitsofmeasure.org".equals(system)) { 1225 return "http://ucum.org"; 1226 } else if ("http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl".equals(system)) { 1227 return "https://ncit.nci.nih.gov/ncitbrowser/pages/home.jsf"; 1228 } else { 1229 String url = system; 1230 if (version != null) { 1231 url = url + "|"+version; 1232 } 1233 CodeSystem cs = context.getWorker().fetchCodeSystem(url); 1234 if (cs != null && cs.hasWebPath()) { 1235 return cs.getWebPath(); 1236 } 1237 return null; 1238 } 1239 } 1240 1241 public String getLinkForCode(String system, String version, String code, Resource source) { 1242 if ("http://snomed.info/sct".equals(system)) { 1243 return SnomedUtilities.getSctLink(version, code, context.getContext().getExpansionParameters()); 1244 } else if ("http://loinc.org".equals(system)) { 1245 if (!Utilities.noString(code)) { 1246 return "https://loinc.org/"+code; 1247 } else { 1248 return "https://loinc.org/"; 1249 } 1250 } else if ("http://www.nlm.nih.gov/research/umls/rxnorm".equals(system)) { 1251 if (!Utilities.noString(code)) { 1252 return "https://mor.nlm.nih.gov/RxNav/search?searchBy=RXCUI&searchTerm="+code; 1253 } else { 1254 return "https://www.nlm.nih.gov/research/umls/rxnorm/index.html"; 1255 } 1256 } else if ("http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl".equals(system)) { 1257 if (!Utilities.noString(code)) { 1258 return "https://ncit.nci.nih.gov/ncitbrowser/ConceptReport.jsp?code="+code; 1259 } else { 1260 return "https://ncit.nci.nih.gov/ncitbrowser/pages/home.jsf"; 1261 } 1262 } else if ("urn:iso:std:iso:3166".equals(system)) { 1263 if (!Utilities.noString(code)) { 1264 return "https://en.wikipedia.org/wiki/ISO_3166-2:"+code; 1265 } else { 1266 return "https://en.wikipedia.org/wiki/ISO_3166-2"; 1267 } 1268 } else { 1269 CodeSystem cs = context.getWorker().fetchCodeSystem(system, version, source); 1270 if (cs != null && cs.hasWebPath()) { 1271 if (!Utilities.noString(code)) { 1272 return cs.getWebPath()+"#"+cs.getId()+"-"+Utilities.nmtokenize(code); 1273 } else { 1274 return cs.getWebPath(); 1275 } 1276 } 1277 } 1278 return null; 1279 } 1280 1281 public CodeResolution resolveCode(String system, String code, Resource source) { 1282 return resolveCode(new Coding().setSystem(system).setCode(code), source); 1283 } 1284 1285 public CodeResolution resolveCode(ResourceWrapper c) { 1286 String systemName; 1287 String systemLink; 1288 String link; 1289 String display = null; 1290 String hint; 1291 1292 if (c.has("display")) 1293 display = context.getTranslated(c.child("display")); 1294 if (Utilities.noString(display)) 1295 display = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1296 if (Utilities.noString(display)) { 1297 display = c.primitiveValue("code"); 1298 } 1299 1300 CodeSystem cs = context.getWorker().fetchCodeSystem(c.primitiveValue("system")); 1301 NamingSystem ns = null; 1302 if (cs == null) { 1303 ns = context.getContextUtilities().fetchNamingSystem(c.primitiveValue("system")); 1304 } 1305 if (ns != null) { 1306 systemLink = null; 1307 systemName = ns.present(); 1308 } else { 1309 systemLink = cs != null ? cs.getWebPath() : null; 1310 systemName = cs != null ? crPresent(cs) : displaySystem(c.primitiveValue("system")); 1311 } 1312 link = getLinkForCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code"), c.getResourceNative()); 1313 1314 hint = systemName+": "+display+(c.has("version") ? " "+ context.formatPhrase(RenderingContext.DATA_REND_VERSION, c.primitiveValue("version"), ")") : ""); 1315 return new CodeResolution(systemName, systemLink, link, display, hint); 1316 } 1317 1318 public CodeResolution resolveCode(Coding code, Resource source) { 1319 return resolveCode(wrapNC(code)); 1320 } 1321 1322 public CodeResolution resolveCode(CodeableConcept code, Resource source) { 1323 if (code.hasCoding()) { 1324 return resolveCode(code.getCodingFirstRep(), source); 1325 } else { 1326 return new CodeResolution(null, null, null, code.getText(), code.getText()); 1327 } 1328 } 1329 protected void renderCodingWithDetails(RenderingStatus status, XhtmlNode x, ResourceWrapper c) throws FHIRFormatError, DefinitionException, IOException { 1330 String s = ""; 1331 if (c.has("display")) 1332 s = context.getTranslated(c.child("display")); 1333 if (Utilities.noString(s)) 1334 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1335 1336 String sn = displaySystem(c.primitiveValue("system")); 1337 String link = getLinkForCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code"), c.getResourceNative()); 1338 XhtmlNode xi = link != null ? x.ah(context.prefixLocalHref(link)) : x; 1339 xi.tx(sn); 1340 xi.tx(": "); 1341 1342 xi.tx(c.primitiveValue("code")); 1343 1344 if (!Utilities.noString(s)) { 1345 x.tx(" ("); 1346 x.tx(s); 1347 x.tx(")"); 1348 } 1349 if (c.has("version")) { 1350 x.tx(" "+context.formatPhrase(RenderingContext.DATA_REND_VERSION, c.primitiveValue("version"), ")")); 1351 } 1352 checkRenderExtensions(status, x, c); 1353 } 1354 1355 protected void renderCoding(RenderingStatus status, XhtmlNode x, ResourceWrapper c) { 1356 renderCoding(status, x, c, true); 1357 } 1358 1359 protected void renderCoding(RenderingStatus status, XhtmlNode x, ResourceWrapper c, boolean details) { 1360 String s = ""; 1361 if (c.has("display")) 1362 s = context.getTranslated(c.child("display")); 1363 if (Utilities.noString(s)) 1364 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1365 1366 if (Utilities.noString(s)) 1367 s = c.primitiveValue("code"); 1368 1369 if (context.isTechnicalMode() && details) { 1370 String d = c.primitiveValue("display") == null ? lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")): c.primitiveValue("display"); 1371 d = context.formatPhrase(d == null || d.equals(c.primitiveValue("code")) ? RenderingContext.DATA_REND_DETAILS_STATED_ND : RenderingContext.DATA_REND_DETAILS_STATED, displaySystem(c.primitiveValue("system")), c.primitiveValue("code"), d); 1372 x.addText(s+" "+d); 1373 } else { 1374 x.span(null, "{"+c.primitiveValue("system")+" "+c.primitiveValue("code")+"}").addText(s); 1375 } 1376 } 1377 1378 public String displayCodeableConcept(ResourceWrapper cc) { 1379 String s = context.getTranslated(cc.child("Text")); 1380 if (Utilities.noString(s)) { 1381 for (ResourceWrapper c : cc.children("coding")) { 1382 if (c.has("display")) { 1383 s = context.getTranslated(c.child("display")); 1384 break; 1385 } 1386 } 1387 } 1388 if (Utilities.noString(s)) { 1389 // still? ok, let's try looking it up 1390 for (ResourceWrapper c : cc.children("coding")) { 1391 if (c.has("code") && c.has("system")) { 1392 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1393 if (!Utilities.noString(s)) 1394 break; 1395 } 1396 } 1397 } 1398 1399 if (Utilities.noString(s)) { 1400 if (!cc.has("coding")) 1401 s = ""; 1402 else 1403 s = cc.children("coding").get(0).primitiveValue("code"); 1404 } 1405 return s; 1406 } 1407 1408 1409 protected void renderCodeableReference(RenderingStatus status, XhtmlNode x, ResourceWrapper e) throws FHIRFormatError, DefinitionException, IOException { 1410 if (e.has("concept")) { 1411 renderCodeableConcept(status, x, e.child("concept")); 1412 } 1413 if (e.has("reference")) { 1414 renderReference(status, x, e.child("reference")); 1415 } 1416 } 1417 1418 protected void renderCodeableConcept(RenderingStatus status, XhtmlNode x, ResourceWrapper cc) throws FHIRFormatError, DefinitionException, IOException { 1419 if (cc.isEmpty()) { 1420 return; 1421 } 1422 1423 String s = context.getTranslated(cc.child("text")); 1424 if (Utilities.noString(s)) { 1425 for (ResourceWrapper c : cc.children("coding")) { 1426 if (c.has("display")) { 1427 s = context.getTranslated(c.child("display")); 1428 break; 1429 } 1430 } 1431 } 1432 if (Utilities.noString(s)) { 1433 // still? ok, let's try looking it up 1434 for (ResourceWrapper c : cc.children("coding")) { 1435 if (c.has("code") && c.has("system")) { 1436 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1437 if (!Utilities.noString(s)) 1438 break; 1439 } 1440 } 1441 } 1442 1443 if (Utilities.noString(s)) { 1444 if (!cc.has("coding")) 1445 s = ""; 1446 else 1447 s = cc.children("coding").get(0).primitiveValue("code"); 1448 } 1449 1450 if (status.isShowCodeDetails()) { 1451 x.addTextWithLineBreaks(s+" "); 1452 XhtmlNode sp = x.span("background: LightGoldenRodYellow; margin: 4px; border: 1px solid khaki", null); 1453 sp.tx(" ("); 1454 boolean first = true; 1455 for (ResourceWrapper c : cc.children("coding")) { 1456 if (first) { 1457 first = false; 1458 } else { 1459 sp.tx("; "); 1460 } 1461 String url = getLinkForSystem(c.primitiveValue("system"), c.primitiveValue("version")); 1462 if (url != null) { 1463 sp.ah(context.prefixLocalHref(url)).tx(displayCodeSource(c.primitiveValue("system"), c.primitiveValue("version"))); 1464 } else { 1465 sp.tx(displayCodeSource(c.primitiveValue("system"), c.primitiveValue("version"))); 1466 } 1467 if (c.has("code")) { 1468 sp.tx("#"+c.primitiveValue("code")); 1469 } 1470 if (c.has("display") && !s.equals(c.primitiveValue("display"))) { 1471 sp.tx(" \""+context.getTranslated(c.child("display"))+"\""); 1472 } 1473 } 1474 if (hasRenderableExtensions(cc)) { 1475 if (!first) { 1476 sp.tx("; "); 1477 } 1478 renderExtensionsInText(status, sp, cc, ";"); 1479 } 1480 sp.tx(")"); 1481 } else { 1482 1483 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1484 for (ResourceWrapper c : cc.children("coding")) { 1485 if (c.has("code") && c.has("system")) { 1486 b.append("{"+c.primitiveValue("system")+" "+c.primitiveValue("code")+"}"); 1487 } 1488 } 1489 1490 x.span(null, context.formatPhrase(RenderingContext.DATA_REND_CODES) +b.toString()).addTextWithLineBreaks(s); 1491 } 1492 checkRenderExtensions(status, x, cc); 1493 } 1494 1495 protected String displayIdentifier(ResourceWrapper ii) { 1496 String s = Utilities.noString(ii.primitiveValue("value")) ? "?ngen-9?" : ii.primitiveValue("value"); 1497 if ("urn:ietf:rfc:3986".equals(ii.primitiveValue("system")) && s.startsWith("urn:oid:")) { 1498 s = "OID:"+s.substring(8); 1499 } else if ("urn:ietf:rfc:3986".equals(ii.primitiveValue("system")) && s.startsWith("urn:uuid:")) { 1500 s = "UUID:"+s.substring(9); 1501 } else { 1502 NamingSystem ns = context.getContext().getNSUrlMap().get(ii.primitiveValue("system")); 1503 if (ns != null) { 1504 s = crPresent(ns)+"#"+s; 1505 } 1506 if (ii.has("type")) { 1507 ResourceWrapper type = ii.child("type"); 1508 if (type.has("text")) 1509 s = context.getTranslated(type.child("text"))+":\u00A0"+s; 1510 else if (type.has("coding") && type.children("coding").get(0).has("display")) 1511 s = context.getTranslated(type.children("coding").get(0).child("display"))+": "+s; 1512 else if (type.has("coding") && type.children("coding").get(0).has("code")) 1513 s = lookupCode(type.children("coding").get(0).primitiveValue("system"), type.children("coding").get(0).primitiveValue("version"), type.children("coding").get(0).primitiveValue("code")); 1514 } else if (ii.has("system")) { 1515 s = ii.primitiveValue("system")+"#"+s; 1516 } 1517 } 1518 1519 if (ii.has("use") || ii.has("period")) { 1520 s = s + "\u00A0("; 1521 if (ii.has("use")) { 1522 s = s + "use:\u00A0"+ii.primitiveValue("use"); 1523 } 1524 if (ii.has("use") || ii.has("period")) { 1525 s = s + ",\u00A0"; 1526 } 1527 if (ii.has("period")) { 1528 s = s + "period:\u00A0"+displayPeriod(ii.child("period")); 1529 } 1530 s = s + ")"; 1531 } 1532 return s; 1533 } 1534 1535 protected void renderIdentifier(RenderingStatus status, XhtmlNode x, ResourceWrapper ii) throws FHIRFormatError, DefinitionException, IOException { 1536 if (ii.has("type")) { 1537 ResourceWrapper type = ii.child("type"); 1538 if (type.has("text")) { 1539 x.tx(context.getTranslated(type.child("text"))); 1540 } else if (type.has("coding") && type.children("coding").get(0).has("display")) { 1541 x.tx(context.getTranslated(type.children("coding").get(0).child("display"))); 1542 } else if (type.has("coding") && type.children("coding").get(0).has("code")) { 1543 x.tx(lookupCode(type.children("coding").get(0).primitiveValue("system"), type.children("coding").get(0).primitiveValue("version"), type.children("coding").get(0).primitiveValue("code"))); 1544 } 1545 x.tx("/"); 1546 } else if (ii.has("system")) { 1547 NamingSystem ns = context.getContext().getNSUrlMap().get(ii.primitiveValue("system")); 1548 if (ns != null) { 1549 if (ns.hasWebPath()) { 1550 x.ah(context.prefixLocalHref(ns.getWebPath()), ns.getDescription()).tx(crPresent(ns)); 1551 } else { 1552 x.tx(crPresent(ns)); 1553 } 1554 } else { 1555 switch (ii.primitiveValue("system")) { 1556 case "urn:oid:2.51.1.3": 1557 x.ah("https://www.gs1.org/standards/id-keys/gln", context.formatPhrase(RenderingContext.DATA_REND_GLN)).tx("GLN"); 1558 break; 1559 default: 1560 x.code(ii.primitiveValue("system")); 1561 } 1562 } 1563 x.tx("/"); 1564 } 1565 x.tx(Utilities.noString(ii.primitiveValue("value")) ? "?ngen-9?" : ii.primitiveValue("value")); 1566 1567 if (ii.has("use") || ii.has("period")) { 1568 x.nbsp(); 1569 x.tx("("); 1570 if (ii.has("use")) { 1571 x.tx(context.formatPhrase(RenderingContext.DATA_REND_USE)); 1572 x.nbsp(); 1573 x.tx(ii.primitiveValue("use")); 1574 } 1575 if (ii.has("use") || ii.has("period")) { 1576 x.tx(","); 1577 x.nbsp(); 1578 } 1579 if (ii.has("period")) { 1580 x.tx(context.formatPhrase(RenderingContext.DATA_REND_PERIOD)); 1581 x.nbsp(); 1582 x.tx(displayPeriod(ii.child("period"))); 1583 } 1584 x.tx(")"); 1585 } 1586 checkRenderExtensions(status, x, ii); 1587 } 1588 1589 public static String displayHumanName(ResourceWrapper name) { 1590 StringBuilder s = new StringBuilder(); 1591 if (name.has("text")) 1592 s.append(name.primitiveValue("text")); 1593 else { 1594 for (ResourceWrapper p : name.children("given")) { 1595 s.append(p.primitiveValue()); 1596 s.append(" "); 1597 } 1598 if (name.has("family")) { 1599 s.append(name.primitiveValue("family")); 1600 s.append(" "); 1601 } 1602 } 1603 if (name.has("use") && !"usual".equals(name.primitiveValue("use"))) 1604 s.append("("+name.primitiveValue("use")+")"); 1605 return s.toString(); 1606 } 1607 1608 1609 protected void renderHumanName(RenderingStatus status, XhtmlNode x, ResourceWrapper name) throws FHIRFormatError, DefinitionException, IOException { 1610 StringBuilder s = new StringBuilder(); 1611 if (name.has("text")) 1612 s.append(context.getTranslated(name.child("text"))); 1613 else { 1614 for (ResourceWrapper p : name.children("given")) { 1615 s.append(context.getTranslated(p)); 1616 s.append(" "); 1617 } 1618 if (name.has("family")) { 1619 s.append(context.getTranslated(name.child("family"))); 1620 s.append(" "); 1621 } 1622 } 1623 if (name.has("use") && !"usual".equals(name.primitiveValue("use"))) { 1624 s.append("("+context.getTranslatedCode(name.primitiveValue("use"), "http://hl7.org/fhir/name-use")+")"); 1625 } 1626 x.addTextWithLineBreaks(s.toString()); 1627 checkRenderExtensions(status, x, name); 1628 } 1629 1630 private String displayAddress(ResourceWrapper address) { 1631 StringBuilder s = new StringBuilder(); 1632 if (address.has("text")) 1633 s.append(context.getTranslated(address.child("text"))); 1634 else { 1635 for (ResourceWrapper p : address.children("line")) { 1636 s.append(context.getTranslated(p)); 1637 s.append(" "); 1638 } 1639 if (address.has("city")) { 1640 s.append(context.getTranslated(address.child("city"))); 1641 s.append(" "); 1642 } 1643 if (address.has("state")) { 1644 s.append(context.getTranslated(address.child("state"))); 1645 s.append(" "); 1646 } 1647 1648 if (address.has("postalCode")) { 1649 s.append(context.getTranslated(address.child("postalCode"))); 1650 s.append(" "); 1651 } 1652 1653 if (address.has("country")) { 1654 s.append(context.getTranslated(address.child("country"))); 1655 s.append(" "); 1656 } 1657 } 1658 if (address.has("use")) { 1659 s.append("("+address.primitiveValue("use")+")"); 1660 } 1661 return s.toString(); 1662 } 1663 1664 protected void renderAddress(RenderingStatus status, XhtmlNode x, ResourceWrapper address) throws FHIRFormatError, DefinitionException, IOException { 1665 x.addTextWithLineBreaks(displayAddress(address)); 1666 checkRenderExtensions(status, x, address); 1667 } 1668 1669 1670 public String displayContactPoint(ResourceWrapper contact) { 1671 StringBuilder s = new StringBuilder(); 1672 s.append(describeSystem(contact.primitiveValue("system"))); 1673 if (Utilities.noString(contact.primitiveValue("value"))) 1674 s.append("-unknown-"); 1675 else 1676 s.append(contact.primitiveValue("value")); 1677 if (contact.has("use")) 1678 s.append("("+getTranslatedCode(contact.child("use"))+")"); 1679 return s.toString(); 1680 } 1681 1682 public String displayContactDetail(ResourceWrapper contact) { 1683 CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); 1684 for (ResourceWrapper cp : contact.children("telecom")) { 1685 s.append(displayContactPoint(cp)); 1686 } 1687 return contact.primitiveValue("name")+(s.length() == 0 ? "" : " ("+s.toString()+")"); 1688 } 1689 1690 protected String getLocalizedBigDecimalValue(BigDecimal input, Currency c) { 1691 NumberFormat numberFormat = NumberFormat.getNumberInstance(context.getLocale()); 1692 numberFormat.setGroupingUsed(true); 1693 numberFormat.setMaximumFractionDigits(c.getDefaultFractionDigits()); 1694 numberFormat.setMinimumFractionDigits(c.getDefaultFractionDigits()); 1695 return numberFormat.format(input); 1696 } 1697 1698 protected void renderMoney(RenderingStatus status, XhtmlNode x, ResourceWrapper money) { 1699 if (x.getName().equals("blockquote")) { 1700 x = x.para(); 1701 } 1702 Currency c = money.has("currency") ? Currency.getInstance(money.primitiveValue("currency")) : null; 1703 if (c != null) { 1704 XhtmlNode s = x.span(null, c.getDisplayName()); 1705 s.tx(c.getSymbol(context.getLocale())); 1706 s.tx(getLocalizedBigDecimalValue(new BigDecimal(money.primitiveValue("value")), c)); 1707 x.tx(" ("+c.getCurrencyCode()+")"); 1708 } else { 1709 if (money.has("currency")) { 1710 x.tx(money.primitiveValue("currency")); 1711 } 1712 x.tx(money.primitiveValue("value")); 1713 } 1714 } 1715 1716 protected void renderExpression(RenderingStatus status, XhtmlNode x, ResourceWrapper expr) { 1717 // there's two parts: what the expression is, and how it's described. 1718 // we start with what it is, and then how it's described 1719 XhtmlNode p = x; 1720 if (p.getName().equals("blockquote")) { 1721 p = p.para(); 1722 } 1723 if (expr.has("expression")) { 1724 if (expr.has("reference")) { 1725 p = x.ah(context.prefixLocalHref(expr.primitiveValue("reference"))); 1726 } 1727 XhtmlNode c = p; 1728 if (expr.has("language")) { 1729 c = c.span(null, expr.primitiveValue("language")); 1730 } 1731 c.code().tx(expr.primitiveValue("expression")); 1732 } else if (expr.has("reference")) { 1733 p.ah(context.prefixLocalHref(expr.primitiveValue("reference"))).tx(context.formatPhrase(RenderingContext.DATA_REND_SOURCE)); 1734 } 1735 if (expr.has("name") || expr.has("description")) { 1736 p.tx("("); 1737 if (expr.has("name")) { 1738 p.b().tx(expr.primitiveValue("name")); 1739 } 1740 if (expr.has("description")) { 1741 p.tx("\""); 1742 p.tx(context.getTranslated(expr.child("description"))); 1743 p.tx("\""); 1744 } 1745 p.tx(")"); 1746 } 1747 } 1748 1749 1750 protected void renderContactPoint(RenderingStatus status, XhtmlNode x, ResourceWrapper contact) { 1751 if (contact != null) { 1752 if (!contact.has("system")) { 1753 x.addText(displayContactPoint(contact)); 1754 } else { 1755 String v = contact.primitiveValue("value"); 1756 switch (contact.primitiveValue("system")) { 1757 case "email": 1758 x.ah("mailto:"+v).tx(v); 1759 break; 1760 case "fax": 1761 x.addText(displayContactPoint(contact)); 1762 break; 1763 case "other": 1764 x.addText(displayContactPoint(contact)); 1765 break; 1766 case "pager": 1767 x.addText(displayContactPoint(contact)); 1768 break; 1769 case "phone": 1770 if (contact.has("value") && v != null && v.startsWith("+")) { 1771 x.ah("tel:"+v.replace(" ", "")).tx(v); 1772 } else { 1773 x.addText(displayContactPoint(contact)); 1774 } 1775 break; 1776 case "sms": 1777 x.addText(displayContactPoint(contact)); 1778 break; 1779 case "url": 1780 x.ah(context.prefixLocalHref(v)).tx(v); 1781 break; 1782 default: 1783 break; 1784 } 1785 } 1786 } 1787 } 1788 1789 protected void displayContactPoint(XhtmlNode p, ContactPoint c) { 1790 if (c != null) { 1791 if (c.getSystem() == ContactPointSystem.PHONE) { 1792 p.tx(context.formatPhrase(RenderingContext.DATA_REND_PHONE, c.getValue()) + " "); 1793 } else if (c.getSystem() == ContactPointSystem.FAX) { 1794 p.tx(context.formatPhrase(RenderingContext.DATA_REND_FAX, c.getValue()) + " "); 1795 } else if (c.getSystem() == ContactPointSystem.EMAIL) { 1796 p.tx(c.getValue()); 1797 } else if (c.getSystem() == ContactPointSystem.URL) { 1798 if (c.getValue().length() > 30) { 1799 p.addText(c.getValue().substring(0, 30)+"..."); 1800 } else { 1801 p.addText(c.getValue()); 1802 } 1803 } 1804 } 1805 } 1806 1807 protected void addTelecom(XhtmlNode p, ResourceWrapper c) { 1808 String sys = c.primitiveValue("system"); 1809 String value = c.primitiveValue("value"); 1810 if (sys.equals("phone")) { 1811 p.tx(context.formatPhrase(RenderingContext.DATA_REND_PHONE, value) + " "); 1812 } else if (sys.equals("fax")) { 1813 p.tx(context.formatPhrase(RenderingContext.DATA_REND_FAX, value) + " "); 1814 } else if (sys.equals("email")) { 1815 p.ah("mailto:"+value).addText(value); 1816 } else if (sys.equals("url")) { 1817 if (value.length() > 30) 1818 p.ah(context.prefixLocalHref(value)).addText(value.substring(0, 30)+"..."); 1819 else 1820 p.ah(context.prefixLocalHref(value)).addText(value); 1821 } 1822 } 1823 private static String describeSystem(String system) { 1824 if (system == null) 1825 return ""; 1826 switch (system) { 1827 case "phone": return "ph: "; 1828 case "fax": return "fax: "; 1829 default: 1830 return ""; 1831 } 1832 } 1833 1834 protected String displayQuantity(ResourceWrapper q) { 1835 if (q == null) { 1836 return ""; 1837 } 1838 StringBuilder s = new StringBuilder(); 1839 1840 s.append(q.has("value") ? q.primitiveValue("value") : "?"); 1841 if (q.has("unit")) 1842 s.append(" ").append(q.primitiveValue("unit")); 1843 else if (q.has("code")) 1844 s.append(" ").append(q.primitiveValue("code")); 1845 1846 return s.toString(); 1847 } 1848 1849 protected void renderQuantity(RenderingStatus status, XhtmlNode x, ResourceWrapper q) throws FHIRFormatError, DefinitionException, IOException { 1850 if (q.has("comparator")) 1851 x.addText(q.primitiveValue("comparator")); 1852 if (q.has("value")) { 1853 x.addText(context.getTranslated(q.child("value"))); 1854 } 1855 if (q.has("unit")) 1856 x.tx(" "+context.getTranslated(q.child("unit"))); 1857 else if (q.has("code") && q.has("system")) { 1858 // if there's a code there *shall* be a system, so if we've got one and not the other, things are invalid and we won't bother trying to render 1859 if (q.has("system") && q.primitiveValue("system").equals("http://unitsofmeasure.org")) 1860 x.tx(" "+q.primitiveValue("code")); 1861 else 1862 x.tx("(unit "+q.primitiveValue("code")+" from "+q.primitiveValue("system")+")"); 1863 } 1864 if (context.isTechnicalMode() && q.has("code")) { 1865 x.span("background: LightGoldenRodYellow", null).tx(" "+ (context.formatPhrase(RenderingContext.DATA_REND_DETAILS, displaySystem(q.primitiveValue("system")))) +q.primitiveValue("code")+" = '"+lookupCode(q.primitiveValue("system"), null, q.primitiveValue("code"))+"')"); 1866 } 1867 checkRenderExtensions(status, x, q); 1868 } 1869 1870 1871 protected void renderQuantity(HierarchicalTableGenerator gen, List<Piece> pieces, ResourceWrapper q, boolean showCodeDetails) { 1872 pieces.add(gen.new Piece(null, displayQuantity(q), null)); 1873 } 1874 1875 protected void renderQuantity(HierarchicalTableGenerator gen, List<Piece> pieces, Quantity q, boolean showCodeDetails) { 1876 pieces.add(gen.new Piece(null, displayQuantity(wrapNC(q)), null)); 1877 } 1878 1879 public String displayRange(ResourceWrapper q) { 1880 if (!q.has("low") && !q.has("high")) 1881 return "?"; 1882 1883 StringBuilder b = new StringBuilder(); 1884 1885 ResourceWrapper lowC = q.child("low"); 1886 ResourceWrapper highC = q.child("high"); 1887 boolean sameUnits = (lowC != null && highC != null) && ((lowC.has("unit") && highC.has("unit") && lowC.child("unit").matches(highC.child("unit"))) 1888 || (lowC.has("code") && highC.has("code") && lowC.child("code").matches(highC.child("code")))); 1889 String low = "?"; 1890 if (q.has("low") && lowC.has("value")) 1891 low = sameUnits ? lowC.primitiveValue("value").toString() : displayQuantity(lowC); 1892 String high = displayQuantity(highC); 1893 if (high.isEmpty()) 1894 high = "?"; 1895 b.append(low).append("\u00A0to\u00A0").append(high); 1896 return b.toString(); 1897 } 1898 1899 protected void renderRange(RenderingStatus status, XhtmlNode x, ResourceWrapper q) { 1900 if (q.has("low")) 1901 x.addText(q.child("low").primitiveValue("value").toString()); 1902 else 1903 x.tx("?"); 1904 x.tx("-"); 1905 if (q.has("high")) 1906 x.addText(q.child("high").primitiveValue("value").toString()); 1907 else 1908 x.tx("?"); 1909 if (q.has("low") && q.child("low").has("unit")) 1910 x.tx(" "+q.child("low").primitiveValue("unit")); 1911 } 1912 1913 public String displayPeriod(ResourceWrapper p) { 1914 String s = !p.has("start") ? "(?)" : displayDateTime(p.child("start")); 1915 s = s + " --> "; 1916 return s + (!p.has("end") ? context.formatPhrase(RenderingContext.DATA_REND_ONGOING) : displayDateTime(p.child("end"))); 1917 } 1918 1919 public void renderPeriod(RenderingStatus status, XhtmlNode x, ResourceWrapper p) { 1920 x.addText(!p.has("start") ? "??" : displayDateTime(p.child("start"))); 1921 x.tx(" --> "); 1922 x.addText(!p.has("end") ? context.formatPhrase(RenderingContext.DATA_REND_ONGOING) : displayDateTime(p.child("end"))); 1923 } 1924 1925 public void renderUsageContext(RenderingStatus status, XhtmlNode x, ResourceWrapper u) throws FHIRFormatError, DefinitionException, IOException { 1926 renderCoding(status, x, u.child("code"), false); 1927 x.tx(" = "); 1928 renderDataType(status, x, u.child("value")); 1929 } 1930 1931 1932 public void renderTriggerDefinition(RenderingStatus status, XhtmlNode x, ResourceWrapper td) throws FHIRFormatError, DefinitionException, IOException { 1933 if (x.isPara()) { 1934 x.b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 1935 x.tx(": "); 1936 x.tx(td.child("type").primitiveValue("display")); 1937 1938 if (td.has("name")) { 1939 x.tx(", "); 1940 x.b().tx(context.formatPhrase(RenderingContext.GENERAL_NAME)); 1941 x.tx(": "); 1942 x.tx(context.getTranslated(td.child("name"))); 1943 } 1944 if (td.has("code")) { 1945 x.tx(", "); 1946 x.b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 1947 x.tx(": "); 1948 renderCodeableConcept(status, x, td.child("code")); 1949 } 1950 if (td.has("timing")) { 1951 x.tx(", "); 1952 x.b().tx(context.formatPhrase(RenderingContext.DATA_REND_TIMING)); 1953 x.tx(": "); 1954 renderDataType(status, x, td.child("timing")); 1955 } 1956 if (td.has("condition")) { 1957 x.tx(", "); 1958 x.b().tx(context.formatPhrase(RenderingContext.DATA_REND_COND)); 1959 x.tx(": "); 1960 renderExpression(status, x, td.child("condition")); 1961 } 1962 } else { 1963 XhtmlNode tbl = x.table("grid", false); 1964 1965 XhtmlNode tr = tbl.tr(); 1966 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 1967 tr.td().tx(td.child("type").primitiveValue("display")); 1968 1969 if (td.has("name")) { 1970 tr = tbl.tr(); 1971 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_NAME)); 1972 tr.td().tx(context.getTranslated(td.child("name"))); 1973 } 1974 if (td.has("code")) { 1975 tr = tbl.tr(); 1976 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 1977 renderCodeableConcept(status, tr.td(), td.child("code")); 1978 } 1979 if (td.has("timing")) { 1980 tr = tbl.tr(); 1981 tr.td().b().tx(context.formatPhrase(RenderingContext.DATA_REND_TIMING)); 1982 renderDataType(status, tr.td(), td.child("timing")); 1983 } 1984 if (td.has("condition")) { 1985 tr = tbl.tr(); 1986 tr.td().b().tx(context.formatPhrase(RenderingContext.DATA_REND_COND)); 1987 renderExpression(status, tr.td(), td.child("condition")); 1988 } 1989 } 1990 } 1991 1992 public void renderDataRequirement(RenderingStatus status, XhtmlNode x, ResourceWrapper dr) throws FHIRFormatError, DefinitionException, IOException { 1993 XhtmlNode tbl = x.table("grid", false); 1994 XhtmlNode tr = tbl.tr(); 1995 XhtmlNode td = tr.td().colspan("2"); 1996 td.b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 1997 td.tx(": "); 1998 StructureDefinition sd = context.getWorker().fetchTypeDefinition(dr.primitiveValue("type")); 1999 if (sd != null && sd.hasWebPath()) { 2000 td.ah(context.prefixLocalHref(sd.getWebPath())).tx(dr.primitiveValue("type")); 2001 } else { 2002 td.tx(dr.primitiveValue("type")); 2003 } 2004 if (dr.has("profile")) { 2005 td.tx(" ("); 2006 boolean first = true; 2007 for (ResourceWrapper p : dr.children("profile")) { 2008 if (first) first = false; else td.tx(" | "); 2009 sd = context.getWorker().fetchResource(StructureDefinition.class, p.primitiveValue()); 2010 if (sd != null && sd.hasWebPath()) { 2011 td.ah(context.prefixLocalHref(sd.getWebPath())).tx(crPresent(sd)); 2012 } else { 2013 td.tx(p.primitiveValue()); 2014 } 2015 } 2016 td.tx(")"); 2017 } 2018 if (dr.has("subject")) { 2019 tr = tbl.tr(); 2020 td = tr.td().colspan("2"); 2021 td.b().tx(context.formatPhrase(RenderingContext.GENERAL_SUBJ)); 2022 ResourceWrapper subj = dr.child("subject"); 2023 if (subj.fhirType().equals("reference")) { 2024 renderReference(status, td, subj); 2025 } else { 2026 renderCodeableConcept(status, td, subj); 2027 } 2028 } 2029 if (dr.has("codeFilter") || dr.has("dateFilter")) { 2030 tr = tbl.tr().backgroundColor("#efefef"); 2031 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_FILTER)); 2032 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_VALUE)); 2033 } 2034 for (ResourceWrapper cf : dr.children("codeFilter")) { 2035 tr = tbl.tr(); 2036 if (cf.has("path")) { 2037 tr.td().tx(cf.primitiveValue("path")); 2038 } else { 2039 tr.td().tx(context.formatPhrase(RenderingContext.DATA_REND_SEARCH, cf.primitiveValue("searchParam")) + " "); 2040 } 2041 if (cf.has("valueSet")) { 2042 td = tr.td(); 2043 td.tx(context.formatPhrase(RenderingContext.DATA_REND_VALUESET) + " "); 2044 renderDataType(status, td, cf.child("valueSet")); 2045 } else { 2046 boolean first = true; 2047 td = tr.td(); 2048 td.tx(context.formatPhrase(RenderingContext.DATA_REND_THESE_CODES) + " "); 2049 for (ResourceWrapper c : cf.children("code")) { 2050 if (first) first = false; else td.tx(", "); 2051 renderDataType(status, td, c); 2052 } 2053 } 2054 } 2055 for (ResourceWrapper cf : dr.children("dateFilter")) { 2056 tr = tbl.tr(); 2057 if (cf.has("path")) { 2058 tr.td().tx(cf.primitiveValue("path")); 2059 } else { 2060 tr.td().tx(context.formatPhrase(RenderingContext.DATA_REND_SEARCH, cf.primitiveValue("searchParam")) + " "); 2061 } 2062 renderDataType(status, tr.td(), cf.child("value")); 2063 } 2064 if (dr.has("sort") || dr.has("limit")) { 2065 tr = tbl.tr(); 2066 td = tr.td().colspan("2"); 2067 if (dr.has("limit")) { 2068 td.b().tx(context.formatPhrase(RenderingContext.DATA_REND_LIMIT)); 2069 td.tx(": "); 2070 td.tx(dr.primitiveValue("limit")); 2071 if (dr.has("sort")) { 2072 td.tx(", "); 2073 } 2074 } 2075 if (dr.has("sort")) { 2076 td.b().tx(context.formatPhrase(RenderingContext.DATA_REND_SORT)); 2077 td.tx(": "); 2078 boolean first = true; 2079 for (ResourceWrapper p : dr.children("sort")) { 2080 if (first) first = false; else td.tx(" | "); 2081 td.tx(p.primitiveValue("direction").equals("ascending") ? "+" : "-"); 2082 td.tx(p.primitiveValue("path")); 2083 } 2084 } 2085 } 2086 } 2087 2088 2089 private String displayTiming(ResourceWrapper s) throws FHIRException { 2090 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 2091 if (s.has("code")) { 2092 b.append(context.formatPhrase(RenderingContext.GENERAL_CODE, displayCodeableConcept(s.child("code"))) + " "); 2093 } 2094 2095 if (s.has("event")) { 2096 CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder(); 2097 for (ResourceWrapper p : s.children("event")) { 2098 if (p.hasPrimitiveValue()) { 2099 c.append(displayDateTime(p)); 2100 } else if (!renderExpression(c, p)) { 2101 c.append("??"); 2102 } 2103 } 2104 b.append(context.formatPhrase(RenderingContext.DATA_REND_EVENTS, c.toString()) + " "); 2105 } 2106 2107 if (s.has("repeat")) { 2108 ResourceWrapper rep = s.child("repeat"); 2109 if (rep.has("boundsPeriod") && rep.child("boundsPeriod").has("start")) 2110 b.append(context.formatPhrase(RenderingContext.DATA_REND_STARTING, displayDateTime(rep.child("boundsPeriod").child("start"))) + " "); 2111 if (rep.has("count")) 2112 b.append(context.formatPhrase(RenderingContext.DATA_REND_COUNT, rep.primitiveValue("count")) + " " + " times"); 2113 if (rep.has("duration")) 2114 b.append(context.formatPhrase(RenderingContext.DATA_REND_DURATION, rep.primitiveValue("duration")+displayTimeUnits(rep.primitiveValue("periodUnit"), "1".equals(rep.primitiveValue("duration")))) + " "); 2115 2116 String st = ""; 2117 if (rep.has("offset")) { 2118 st = rep.primitiveValue("offset")+"min "; 2119 } 2120 if (!Utilities.noString(st)) { 2121 b.append(st); 2122 } 2123 for (ResourceWrapper wh : rep.children("when")) { 2124 b.append(displayEventCode(wh.primitiveValue())); 2125 } 2126 st = ""; 2127 if (!rep.has("frequency") || (!rep.has("frequencyMax") && rep.primitiveValue("frequency").equals("1"))) { 2128 st = context.formatPhrase(RenderingContext.DATA_REND_ONCE); 2129 } else { 2130 st = rep.primitiveValue("frequency"); 2131 if (rep.has("frequencyMax")) 2132 st = st + "-"+rep.primitiveValue("frequencyMax"); 2133 } 2134 if (rep.has("period")) { 2135 st = st + " "+ (context.formatPhrase(RenderingContext.DATA_REND_PER))+" "+rep.primitiveValue("period"); 2136 if (rep.has("periodMax")) { 2137 st = st + "-"+rep.primitiveValue("periodMax"); 2138 } 2139 st = st + " "+displayTimeUnits(rep.primitiveValue("periodUnit"), "1".equals(rep.primitiveValue("period"))); 2140 } 2141 if (!Utilities.noString(st)) { 2142 b.append(st); 2143 } 2144 if (rep.has("boundsPeriod") && rep.child("boundsPeriod").has("end")) { 2145 b.append(context.formatPhrase(RenderingContext.DATA_REND_UNTIL, displayDateTime(rep.child("boundsPeriod").child("end"))) + " "); 2146 } 2147 } 2148 return b.toString(); 2149 } 2150 2151 private boolean renderExpression(CommaSeparatedStringBuilder c, ResourceWrapper p) { 2152 ResourceWrapper exp = p.extensionValue("http://hl7.org/fhir/StructureDefinition/cqf-expression"); 2153 if (exp == null || !exp.has("value")) { 2154 return false; 2155 } 2156 c.append(exp.child("value").primitiveValue("expression")); 2157 return true; 2158 } 2159 2160 private String displayEventCode(String when) { 2161 if (when == null) 2162 return "??"; 2163 switch (when.toLowerCase()) { 2164 case "c": return (context.formatPhrase(RenderingContext.DATA_REND_MEALS)); 2165 case "cd": return (context.formatPhrase(RenderingContext.DATA_REND_ATLUNCH)); 2166 case "cm": return (context.formatPhrase(RenderingContext.DATA_REND_ATBKFST)); 2167 case "cv": return (context.formatPhrase(RenderingContext.DATA_REND_ATDINR)); 2168 case "ac": return (context.formatPhrase(RenderingContext.DATA_REND_BFMEALS)); 2169 case "acd": return (context.formatPhrase(RenderingContext.DATA_REND_BFLUNCH)); 2170 case "acm": return (context.formatPhrase(RenderingContext.DATA_REND_BFBKFST)); 2171 case "acv": return (context.formatPhrase(RenderingContext.DATA_REND_BFDINR)); 2172 case "hs": return (context.formatPhrase(RenderingContext.DATA_REND_BFSLEEP)); 2173 case "pc": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRMEALS)); 2174 case "pcd": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRLUNCH)); 2175 case "pcm": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRBKFST)); 2176 case "pcv": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRDINR)); 2177 case "wake": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRWKNG)); 2178 case "morn": return (context.formatPhrase(RenderingContext.DATA_REND_MORNING)); 2179 case "morn.early": return (context.formatPhrase(RenderingContext.DATA_REND_MORNING_EARLY)); 2180 case "morn.late": return (context.formatPhrase(RenderingContext.DATA_REND_MORNING_LATE)); 2181 case "noon": return (context.formatPhrase(RenderingContext.DATA_REND_NOON)); 2182 case "aft": return (context.formatPhrase(RenderingContext.DATA_REND_AFTERNOON)); 2183 case "aft.early": return (context.formatPhrase(RenderingContext.DATA_REND_AFTERNOON_EARLY)); 2184 case "aft.late": return (context.formatPhrase(RenderingContext.DATA_REND_AFTERNOON_LATE)); 2185 case "eve": return (context.formatPhrase(RenderingContext.DATA_REND_EVENING)); 2186 case "eve.early": return (context.formatPhrase(RenderingContext.DATA_REND_EVENING_EARLY)); 2187 case "eve.late": return (context.formatPhrase(RenderingContext.DATA_REND_EVENING_LATE)); 2188 case "night": return (context.formatPhrase(RenderingContext.DATA_REND_NIGHT)); 2189 case "phs": return (context.formatPhrase(RenderingContext.DATA_REND_AFTER_SLEEP)); 2190 case "imd": return (context.formatPhrase(RenderingContext.DATA_REND_IMMEDIATE)); 2191 2192 default: return "?"+when+"?"; 2193 } 2194 } 2195 2196 private String displayTimeUnits(String units, boolean singular) { 2197 if (units == null) 2198 return "??"; 2199 switch (units) { 2200 case "a": return singular ? "year" : "years"; 2201 case "d": return singular ? "day" : "days"; 2202 case "h": return singular ? "hour" : "hours"; 2203 case "min": return singular ? "minute" : "minutes"; 2204 case "mo": return singular ? "month" : "months"; 2205 case "s": return singular ? "second" : "seconds"; 2206 case "wk": return singular ? "week" : "weeks"; 2207 default: return "?"+units+"?"; 2208 } 2209 } 2210 2211 protected void renderTiming(RenderingStatus status, XhtmlNode x, ResourceWrapper s) throws FHIRException { 2212 x.addText(displayTiming(s)); 2213 } 2214 2215 2216 private String displaySampledData(ResourceWrapper s) { 2217 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 2218 if (s.has("origin")) 2219 b.append(context.formatPhrase(RenderingContext.DATA_REND_ORIGIN, displayQuantity(s.child("origin"))) + " "); 2220 2221 if (s.has("interval")) { 2222 b.append(context.formatPhrase(RenderingContext.DATA_REND_INT, s.primitiveValue("interval")) + " "); 2223 2224 if (s.has("intervalUnit")) 2225 b.append(s.primitiveValue("intervalUnit")); 2226 } 2227 2228 if (s.has("factor")) 2229 b.append(context.formatPhrase(RenderingContext.DATA_REND_FACT, s.primitiveValue("factor")) + " "); 2230 2231 if (s.has("lowerLimit")) 2232 b.append(context.formatPhrase(RenderingContext.DATA_REND_LOWER, s.primitiveValue("lowerLimit")) + " "); 2233 2234 if (s.has("upperLimit")) 2235 b.append(context.formatPhrase(RenderingContext.DATA_REND_UP, s.primitiveValue("upperLimit")) + " "); 2236 2237 if (s.has("dimensions")) 2238 b.append(context.formatPhrase(RenderingContext.DATA_REND_DIM, s.primitiveValue("dimensions")) + " "); 2239 2240 if (s.has("data")) 2241 b.append(context.formatPhrase(RenderingContext.DATA_REND_DATA, s.primitiveValue("data")) + " "); 2242 2243 return b.toString(); 2244 } 2245 2246 protected void renderSampledData(RenderingStatus status, XhtmlNode x, ResourceWrapper sampledData) { 2247 x.addText(displaySampledData(sampledData)); 2248 } 2249 2250 public RenderingContext getContext() { 2251 return context; 2252 } 2253 2254 2255 public XhtmlNode makeExceptionXhtml(Exception e, String function) { 2256 XhtmlNode xn; 2257 xn = new XhtmlNode(NodeType.Element, "div"); 2258 XhtmlNode p = xn.para(); 2259 p.b().tx((context.formatPhrase(RenderingContext.DATA_REND_EXCEPTION)) +function+": "+e.getMessage()); 2260 p.addComment(getStackTrace(e)); 2261 return xn; 2262 } 2263 2264 private String getStackTrace(Exception e) { 2265 StringBuilder b = new StringBuilder(); 2266 b.append("\r\n"); 2267 for (StackTraceElement t : e.getStackTrace()) { 2268 b.append(t.getClassName()+"."+t.getMethodName()+" ("+t.getFileName()+":"+t.getLineNumber()); 2269 b.append("\r\n"); 2270 } 2271 return b.toString(); 2272 } 2273 2274 protected String systemFromCanonical(String system) { 2275 if (system == null) { 2276 return null; 2277 } else if (system.contains("|")) { 2278 return system.substring(0, system.indexOf("|")); 2279 } else { 2280 return null; 2281 } 2282 } 2283 2284 protected String versionFromCanonical(String system) { 2285 if (system == null) { 2286 return null; 2287 } else if (system.contains("|")) { 2288 return system.substring(system.indexOf("|")+1); 2289 } else { 2290 return system; 2291 } 2292 } 2293 2294 2295 /** 2296 * when we run into an unknown (canonical) URL, we assume that it's a pointer to something we don't 2297 * know about, and render it as an 'a href=' in case it is valid. But in the 'known' domains, where 2298 * we reasonably expect to know everything , we don't make them links 2299 * @return 2300 */ 2301 protected boolean isInKnownUrlSpace(String url) { 2302 return Utilities.startsWithInList(url, 2303 "http://hl7.org/fhir", "http://fhir.org/guides", "http://ihe.net/fhir", "http://terminology.hl7.org", 2304 "https://hl7.org/fhir", "https://fhir.org/guides", "https://ihe.net/fhir", "https://terminology.hl7.org", 2305 "http://www.hl7.org/fhir", "http://www.fhir.org/guides", "http://www.ihe.net/fhir", 2306 "https://www.hl7.org/fhir", "https://www.fhir.org/guides", "https://www.ihe.net/fhir" 2307 ); 2308 } 2309}