
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) { 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); 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) { 1282 return resolveCode(new Coding().setSystem(system).setCode(code)); 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 systemLink = cs != null ? cs.getWebPath() : null; 1302 systemName = cs != null ? crPresent(cs) : displaySystem(c.primitiveValue("system")); 1303 link = getLinkForCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1304 1305 hint = systemName+": "+display+(c.has("version") ? " "+ context.formatPhrase(RenderingContext.DATA_REND_VERSION, c.primitiveValue("version"), ")") : ""); 1306 return new CodeResolution(systemName, systemLink, link, display, hint); 1307 } 1308 1309 public CodeResolution resolveCode(Coding code) { 1310 return resolveCode(wrapNC(code)); 1311 } 1312 1313 public CodeResolution resolveCode(CodeableConcept code) { 1314 if (code.hasCoding()) { 1315 return resolveCode(code.getCodingFirstRep()); 1316 } else { 1317 return new CodeResolution(null, null, null, code.getText(), code.getText()); 1318 } 1319 } 1320 protected void renderCodingWithDetails(RenderingStatus status, XhtmlNode x, ResourceWrapper c) throws FHIRFormatError, DefinitionException, IOException { 1321 String s = ""; 1322 if (c.has("display")) 1323 s = context.getTranslated(c.child("display")); 1324 if (Utilities.noString(s)) 1325 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1326 1327 String sn = displaySystem(c.primitiveValue("system")); 1328 String link = getLinkForCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1329 XhtmlNode xi = link != null ? x.ah(context.prefixLocalHref(link)) : x; 1330 xi.tx(sn); 1331 xi.tx(" "); 1332 1333 xi.tx(c.primitiveValue("code")); 1334 1335 if (!Utilities.noString(s)) { 1336 x.tx(": "); 1337 x.tx(s); 1338 } 1339 if (c.has("version")) { 1340 x.tx(" "+context.formatPhrase(RenderingContext.DATA_REND_VERSION, c.primitiveValue("version"), ")")); 1341 } 1342 checkRenderExtensions(status, x, c); 1343 } 1344 1345 protected void renderCoding(RenderingStatus status, XhtmlNode x, ResourceWrapper c) { 1346 renderCoding(status, x, c, true); 1347 } 1348 1349 protected void renderCoding(RenderingStatus status, XhtmlNode x, ResourceWrapper c, boolean details) { 1350 String s = ""; 1351 if (c.has("display")) 1352 s = context.getTranslated(c.child("display")); 1353 if (Utilities.noString(s)) 1354 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1355 1356 if (Utilities.noString(s)) 1357 s = c.primitiveValue("code"); 1358 1359 if (context.isTechnicalMode() && details) { 1360 String d = c.primitiveValue("display") == null ? lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")): c.primitiveValue("display"); 1361 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); 1362 x.addText(s+" "+d); 1363 } else { 1364 x.span(null, "{"+c.primitiveValue("system")+" "+c.primitiveValue("code")+"}").addText(s); 1365 } 1366 } 1367 1368 public String displayCodeableConcept(ResourceWrapper cc) { 1369 String s = context.getTranslated(cc.child("Text")); 1370 if (Utilities.noString(s)) { 1371 for (ResourceWrapper c : cc.children("coding")) { 1372 if (c.has("display")) { 1373 s = context.getTranslated(c.child("display")); 1374 break; 1375 } 1376 } 1377 } 1378 if (Utilities.noString(s)) { 1379 // still? ok, let's try looking it up 1380 for (ResourceWrapper c : cc.children("coding")) { 1381 if (c.has("code") && c.has("system")) { 1382 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1383 if (!Utilities.noString(s)) 1384 break; 1385 } 1386 } 1387 } 1388 1389 if (Utilities.noString(s)) { 1390 if (!cc.has("coding")) 1391 s = ""; 1392 else 1393 s = cc.children("coding").get(0).primitiveValue("code"); 1394 } 1395 return s; 1396 } 1397 1398 1399 protected void renderCodeableReference(RenderingStatus status, XhtmlNode x, ResourceWrapper e) throws FHIRFormatError, DefinitionException, IOException { 1400 if (e.has("concept")) { 1401 renderCodeableConcept(status, x, e.child("concept")); 1402 } 1403 if (e.has("reference")) { 1404 renderReference(status, x, e.child("reference")); 1405 } 1406 } 1407 1408 protected void renderCodeableConcept(RenderingStatus status, XhtmlNode x, ResourceWrapper cc) throws FHIRFormatError, DefinitionException, IOException { 1409 if (cc.isEmpty()) { 1410 return; 1411 } 1412 1413 String s = context.getTranslated(cc.child("text")); 1414 if (Utilities.noString(s)) { 1415 for (ResourceWrapper c : cc.children("coding")) { 1416 if (c.has("display")) { 1417 s = context.getTranslated(c.child("display")); 1418 break; 1419 } 1420 } 1421 } 1422 if (Utilities.noString(s)) { 1423 // still? ok, let's try looking it up 1424 for (ResourceWrapper c : cc.children("coding")) { 1425 if (c.has("code") && c.has("system")) { 1426 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1427 if (!Utilities.noString(s)) 1428 break; 1429 } 1430 } 1431 } 1432 1433 if (Utilities.noString(s)) { 1434 if (!cc.has("coding")) 1435 s = ""; 1436 else 1437 s = cc.children("coding").get(0).primitiveValue("code"); 1438 } 1439 1440 if (status.isShowCodeDetails()) { 1441 x.addTextWithLineBreaks(s+" "); 1442 XhtmlNode sp = x.span("background: LightGoldenRodYellow; margin: 4px; border: 1px solid khaki", null); 1443 sp.tx(" ("); 1444 boolean first = true; 1445 for (ResourceWrapper c : cc.children("coding")) { 1446 if (first) { 1447 first = false; 1448 } else { 1449 sp.tx("; "); 1450 } 1451 String url = getLinkForSystem(c.primitiveValue("system"), c.primitiveValue("version")); 1452 if (url != null) { 1453 sp.ah(context.prefixLocalHref(url)).tx(displayCodeSource(c.primitiveValue("system"), c.primitiveValue("version"))); 1454 } else { 1455 sp.tx(displayCodeSource(c.primitiveValue("system"), c.primitiveValue("version"))); 1456 } 1457 if (c.has("code")) { 1458 sp.tx("#"+c.primitiveValue("code")); 1459 } 1460 if (c.has("display") && !s.equals(c.primitiveValue("display"))) { 1461 sp.tx(" \""+context.getTranslated(c.child("display"))+"\""); 1462 } 1463 } 1464 if (hasRenderableExtensions(cc)) { 1465 if (!first) { 1466 sp.tx("; "); 1467 } 1468 renderExtensionsInText(status, sp, cc, ";"); 1469 } 1470 sp.tx(")"); 1471 } else { 1472 1473 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1474 for (ResourceWrapper c : cc.children("coding")) { 1475 if (c.has("code") && c.has("system")) { 1476 b.append("{"+c.primitiveValue("system")+" "+c.primitiveValue("code")+"}"); 1477 } 1478 } 1479 1480 x.span(null, context.formatPhrase(RenderingContext.DATA_REND_CODES) +b.toString()).addTextWithLineBreaks(s); 1481 } 1482 checkRenderExtensions(status, x, cc); 1483 } 1484 1485 protected String displayIdentifier(ResourceWrapper ii) { 1486 String s = Utilities.noString(ii.primitiveValue("value")) ? "?ngen-9?" : ii.primitiveValue("value"); 1487 if ("urn:ietf:rfc:3986".equals(ii.primitiveValue("system")) && s.startsWith("urn:oid:")) { 1488 s = "OID:"+s.substring(8); 1489 } else if ("urn:ietf:rfc:3986".equals(ii.primitiveValue("system")) && s.startsWith("urn:uuid:")) { 1490 s = "UUID:"+s.substring(9); 1491 } else { 1492 NamingSystem ns = context.getContext().getNSUrlMap().get(ii.primitiveValue("system")); 1493 if (ns != null) { 1494 s = crPresent(ns)+"#"+s; 1495 } 1496 if (ii.has("type")) { 1497 ResourceWrapper type = ii.child("type"); 1498 if (type.has("text")) 1499 s = context.getTranslated(type.child("text"))+":\u00A0"+s; 1500 else if (type.has("coding") && type.children("coding").get(0).has("display")) 1501 s = context.getTranslated(type.children("coding").get(0).child("display"))+": "+s; 1502 else if (type.has("coding") && type.children("coding").get(0).has("code")) 1503 s = lookupCode(type.children("coding").get(0).primitiveValue("system"), type.children("coding").get(0).primitiveValue("version"), type.children("coding").get(0).primitiveValue("code")); 1504 } else if (ii.has("system")) { 1505 s = ii.primitiveValue("system")+"#"+s; 1506 } 1507 } 1508 1509 if (ii.has("use") || ii.has("period")) { 1510 s = s + "\u00A0("; 1511 if (ii.has("use")) { 1512 s = s + "use:\u00A0"+ii.primitiveValue("use"); 1513 } 1514 if (ii.has("use") || ii.has("period")) { 1515 s = s + ",\u00A0"; 1516 } 1517 if (ii.has("period")) { 1518 s = s + "period:\u00A0"+displayPeriod(ii.child("period")); 1519 } 1520 s = s + ")"; 1521 } 1522 return s; 1523 } 1524 1525 protected void renderIdentifier(RenderingStatus status, XhtmlNode x, ResourceWrapper ii) throws FHIRFormatError, DefinitionException, IOException { 1526 if (ii.has("type")) { 1527 ResourceWrapper type = ii.child("type"); 1528 if (type.has("text")) { 1529 x.tx(context.getTranslated(type.child("text"))); 1530 } else if (type.has("coding") && type.children("coding").get(0).has("display")) { 1531 x.tx(context.getTranslated(type.children("coding").get(0).child("display"))); 1532 } else if (type.has("coding") && type.children("coding").get(0).has("code")) { 1533 x.tx(lookupCode(type.children("coding").get(0).primitiveValue("system"), type.children("coding").get(0).primitiveValue("version"), type.children("coding").get(0).primitiveValue("code"))); 1534 } 1535 x.tx("/"); 1536 } else if (ii.has("system")) { 1537 NamingSystem ns = context.getContext().getNSUrlMap().get(ii.primitiveValue("system")); 1538 if (ns != null) { 1539 if (ns.hasWebPath()) { 1540 x.ah(context.prefixLocalHref(ns.getWebPath()), ns.getDescription()).tx(crPresent(ns)); 1541 } else { 1542 x.tx(crPresent(ns)); 1543 } 1544 } else { 1545 switch (ii.primitiveValue("system")) { 1546 case "urn:oid:2.51.1.3": 1547 x.ah("https://www.gs1.org/standards/id-keys/gln", context.formatPhrase(RenderingContext.DATA_REND_GLN)).tx("GLN"); 1548 break; 1549 default: 1550 x.code(ii.primitiveValue("system")); 1551 } 1552 } 1553 x.tx("/"); 1554 } 1555 x.tx(Utilities.noString(ii.primitiveValue("value")) ? "?ngen-9?" : ii.primitiveValue("value")); 1556 1557 if (ii.has("use") || ii.has("period")) { 1558 x.nbsp(); 1559 x.tx("("); 1560 if (ii.has("use")) { 1561 x.tx(context.formatPhrase(RenderingContext.DATA_REND_USE)); 1562 x.nbsp(); 1563 x.tx(ii.primitiveValue("use")); 1564 } 1565 if (ii.has("use") || ii.has("period")) { 1566 x.tx(","); 1567 x.nbsp(); 1568 } 1569 if (ii.has("period")) { 1570 x.tx(context.formatPhrase(RenderingContext.DATA_REND_PERIOD)); 1571 x.nbsp(); 1572 x.tx(displayPeriod(ii.child("period"))); 1573 } 1574 x.tx(")"); 1575 } 1576 checkRenderExtensions(status, x, ii); 1577 } 1578 1579 public static String displayHumanName(ResourceWrapper name) { 1580 StringBuilder s = new StringBuilder(); 1581 if (name.has("text")) 1582 s.append(name.primitiveValue("text")); 1583 else { 1584 for (ResourceWrapper p : name.children("given")) { 1585 s.append(p.primitiveValue()); 1586 s.append(" "); 1587 } 1588 if (name.has("family")) { 1589 s.append(name.primitiveValue("family")); 1590 s.append(" "); 1591 } 1592 } 1593 if (name.has("use") && !"usual".equals(name.primitiveValue("use"))) 1594 s.append("("+name.primitiveValue("use")+")"); 1595 return s.toString(); 1596 } 1597 1598 1599 protected void renderHumanName(RenderingStatus status, XhtmlNode x, ResourceWrapper name) throws FHIRFormatError, DefinitionException, IOException { 1600 StringBuilder s = new StringBuilder(); 1601 if (name.has("text")) 1602 s.append(context.getTranslated(name.child("text"))); 1603 else { 1604 for (ResourceWrapper p : name.children("given")) { 1605 s.append(context.getTranslated(p)); 1606 s.append(" "); 1607 } 1608 if (name.has("family")) { 1609 s.append(context.getTranslated(name.child("family"))); 1610 s.append(" "); 1611 } 1612 } 1613 if (name.has("use") && !"usual".equals(name.primitiveValue("use"))) { 1614 s.append("("+context.getTranslatedCode(name.primitiveValue("use"), "http://hl7.org/fhir/name-use")+")"); 1615 } 1616 x.addTextWithLineBreaks(s.toString()); 1617 checkRenderExtensions(status, x, name); 1618 } 1619 1620 private String displayAddress(ResourceWrapper address) { 1621 StringBuilder s = new StringBuilder(); 1622 if (address.has("text")) 1623 s.append(context.getTranslated(address.child("text"))); 1624 else { 1625 for (ResourceWrapper p : address.children("line")) { 1626 s.append(context.getTranslated(p)); 1627 s.append(" "); 1628 } 1629 if (address.has("city")) { 1630 s.append(context.getTranslated(address.child("city"))); 1631 s.append(" "); 1632 } 1633 if (address.has("state")) { 1634 s.append(context.getTranslated(address.child("state"))); 1635 s.append(" "); 1636 } 1637 1638 if (address.has("postalCode")) { 1639 s.append(context.getTranslated(address.child("postalCode"))); 1640 s.append(" "); 1641 } 1642 1643 if (address.has("country")) { 1644 s.append(context.getTranslated(address.child("country"))); 1645 s.append(" "); 1646 } 1647 } 1648 if (address.has("use")) { 1649 s.append("("+address.primitiveValue("use")+")"); 1650 } 1651 return s.toString(); 1652 } 1653 1654 protected void renderAddress(RenderingStatus status, XhtmlNode x, ResourceWrapper address) throws FHIRFormatError, DefinitionException, IOException { 1655 x.addTextWithLineBreaks(displayAddress(address)); 1656 checkRenderExtensions(status, x, address); 1657 } 1658 1659 1660 public String displayContactPoint(ResourceWrapper contact) { 1661 StringBuilder s = new StringBuilder(); 1662 s.append(describeSystem(contact.primitiveValue("system"))); 1663 if (Utilities.noString(contact.primitiveValue("value"))) 1664 s.append("-unknown-"); 1665 else 1666 s.append(contact.primitiveValue("value")); 1667 if (contact.has("use")) 1668 s.append("("+getTranslatedCode(contact.child("use"))+")"); 1669 return s.toString(); 1670 } 1671 1672 public String displayContactDetail(ResourceWrapper contact) { 1673 CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); 1674 for (ResourceWrapper cp : contact.children("telecom")) { 1675 s.append(displayContactPoint(cp)); 1676 } 1677 return contact.primitiveValue("name")+(s.length() == 0 ? "" : " ("+s.toString()+")"); 1678 } 1679 1680 protected String getLocalizedBigDecimalValue(BigDecimal input, Currency c) { 1681 NumberFormat numberFormat = NumberFormat.getNumberInstance(context.getLocale()); 1682 numberFormat.setGroupingUsed(true); 1683 numberFormat.setMaximumFractionDigits(c.getDefaultFractionDigits()); 1684 numberFormat.setMinimumFractionDigits(c.getDefaultFractionDigits()); 1685 return numberFormat.format(input); 1686 } 1687 1688 protected void renderMoney(RenderingStatus status, XhtmlNode x, ResourceWrapper money) { 1689 if (x.getName().equals("blockquote")) { 1690 x = x.para(); 1691 } 1692 Currency c = money.has("currency") ? Currency.getInstance(money.primitiveValue("currency")) : null; 1693 if (c != null) { 1694 XhtmlNode s = x.span(null, c.getDisplayName()); 1695 s.tx(c.getSymbol(context.getLocale())); 1696 s.tx(getLocalizedBigDecimalValue(new BigDecimal(money.primitiveValue("value")), c)); 1697 x.tx(" ("+c.getCurrencyCode()+")"); 1698 } else { 1699 if (money.has("currency")) { 1700 x.tx(money.primitiveValue("currency")); 1701 } 1702 x.tx(money.primitiveValue("value")); 1703 } 1704 } 1705 1706 protected void renderExpression(RenderingStatus status, XhtmlNode x, ResourceWrapper expr) { 1707 // there's two parts: what the expression is, and how it's described. 1708 // we start with what it is, and then how it's described 1709 XhtmlNode p = x; 1710 if (p.getName().equals("blockquote")) { 1711 p = p.para(); 1712 } 1713 if (expr.has("expression")) { 1714 if (expr.has("reference")) { 1715 p = x.ah(context.prefixLocalHref(expr.primitiveValue("reference"))); 1716 } 1717 XhtmlNode c = p; 1718 if (expr.has("language")) { 1719 c = c.span(null, expr.primitiveValue("language")); 1720 } 1721 c.code().tx(expr.primitiveValue("expression")); 1722 } else if (expr.has("reference")) { 1723 p.ah(context.prefixLocalHref(expr.primitiveValue("reference"))).tx(context.formatPhrase(RenderingContext.DATA_REND_SOURCE)); 1724 } 1725 if (expr.has("name") || expr.has("description")) { 1726 p.tx("("); 1727 if (expr.has("name")) { 1728 p.b().tx(expr.primitiveValue("name")); 1729 } 1730 if (expr.has("description")) { 1731 p.tx("\""); 1732 p.tx(context.getTranslated(expr.child("description"))); 1733 p.tx("\""); 1734 } 1735 p.tx(")"); 1736 } 1737 } 1738 1739 1740 protected void renderContactPoint(RenderingStatus status, XhtmlNode x, ResourceWrapper contact) { 1741 if (contact != null) { 1742 if (!contact.has("system")) { 1743 x.addText(displayContactPoint(contact)); 1744 } else { 1745 String v = contact.primitiveValue("value"); 1746 switch (contact.primitiveValue("system")) { 1747 case "email": 1748 x.ah("mailto:"+v).tx(v); 1749 break; 1750 case "fax": 1751 x.addText(displayContactPoint(contact)); 1752 break; 1753 case "other": 1754 x.addText(displayContactPoint(contact)); 1755 break; 1756 case "pager": 1757 x.addText(displayContactPoint(contact)); 1758 break; 1759 case "phone": 1760 if (contact.has("value") && v != null && v.startsWith("+")) { 1761 x.ah("tel:"+v.replace(" ", "")).tx(v); 1762 } else { 1763 x.addText(displayContactPoint(contact)); 1764 } 1765 break; 1766 case "sms": 1767 x.addText(displayContactPoint(contact)); 1768 break; 1769 case "url": 1770 x.ah(context.prefixLocalHref(v)).tx(v); 1771 break; 1772 default: 1773 break; 1774 } 1775 } 1776 } 1777 } 1778 1779 protected void displayContactPoint(XhtmlNode p, ContactPoint c) { 1780 if (c != null) { 1781 if (c.getSystem() == ContactPointSystem.PHONE) { 1782 p.tx(context.formatPhrase(RenderingContext.DATA_REND_PHONE, c.getValue()) + " "); 1783 } else if (c.getSystem() == ContactPointSystem.FAX) { 1784 p.tx(context.formatPhrase(RenderingContext.DATA_REND_FAX, c.getValue()) + " "); 1785 } else if (c.getSystem() == ContactPointSystem.EMAIL) { 1786 p.tx(c.getValue()); 1787 } else if (c.getSystem() == ContactPointSystem.URL) { 1788 if (c.getValue().length() > 30) { 1789 p.addText(c.getValue().substring(0, 30)+"..."); 1790 } else { 1791 p.addText(c.getValue()); 1792 } 1793 } 1794 } 1795 } 1796 1797 protected void addTelecom(XhtmlNode p, ResourceWrapper c) { 1798 String sys = c.primitiveValue("system"); 1799 String value = c.primitiveValue("value"); 1800 if (sys.equals("phone")) { 1801 p.tx(context.formatPhrase(RenderingContext.DATA_REND_PHONE, value) + " "); 1802 } else if (sys.equals("fax")) { 1803 p.tx(context.formatPhrase(RenderingContext.DATA_REND_FAX, value) + " "); 1804 } else if (sys.equals("email")) { 1805 p.ah("mailto:"+value).addText(value); 1806 } else if (sys.equals("url")) { 1807 if (value.length() > 30) 1808 p.ah(context.prefixLocalHref(value)).addText(value.substring(0, 30)+"..."); 1809 else 1810 p.ah(context.prefixLocalHref(value)).addText(value); 1811 } 1812 } 1813 private static String describeSystem(String system) { 1814 if (system == null) 1815 return ""; 1816 switch (system) { 1817 case "phone": return "ph: "; 1818 case "fax": return "fax: "; 1819 default: 1820 return ""; 1821 } 1822 } 1823 1824 protected String displayQuantity(ResourceWrapper q) { 1825 if (q == null) { 1826 return ""; 1827 } 1828 StringBuilder s = new StringBuilder(); 1829 1830 s.append(q.has("value") ? q.primitiveValue("value") : "?"); 1831 if (q.has("unit")) 1832 s.append(" ").append(q.primitiveValue("unit")); 1833 else if (q.has("code")) 1834 s.append(" ").append(q.primitiveValue("code")); 1835 1836 return s.toString(); 1837 } 1838 1839 protected void renderQuantity(RenderingStatus status, XhtmlNode x, ResourceWrapper q) throws FHIRFormatError, DefinitionException, IOException { 1840 if (q.has("comparator")) 1841 x.addText(q.primitiveValue("comparator")); 1842 if (q.has("value")) { 1843 x.addText(context.getTranslated(q.child("value"))); 1844 } 1845 if (q.has("unit")) 1846 x.tx(" "+context.getTranslated(q.child("unit"))); 1847 else if (q.has("code") && q.has("system")) { 1848 // 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 1849 if (q.has("system") && q.primitiveValue("system").equals("http://unitsofmeasure.org")) 1850 x.tx(" "+q.primitiveValue("code")); 1851 else 1852 x.tx("(unit "+q.primitiveValue("code")+" from "+q.primitiveValue("system")+")"); 1853 } 1854 if (context.isTechnicalMode() && q.has("code")) { 1855 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"))+"')"); 1856 } 1857 checkRenderExtensions(status, x, q); 1858 } 1859 1860 1861 protected void renderQuantity(HierarchicalTableGenerator gen, List<Piece> pieces, ResourceWrapper q, boolean showCodeDetails) { 1862 pieces.add(gen.new Piece(null, displayQuantity(q), null)); 1863 } 1864 1865 protected void renderQuantity(HierarchicalTableGenerator gen, List<Piece> pieces, Quantity q, boolean showCodeDetails) { 1866 pieces.add(gen.new Piece(null, displayQuantity(wrapNC(q)), null)); 1867 } 1868 1869 public String displayRange(ResourceWrapper q) { 1870 if (!q.has("low") && !q.has("high")) 1871 return "?"; 1872 1873 StringBuilder b = new StringBuilder(); 1874 1875 ResourceWrapper lowC = q.child("low"); 1876 ResourceWrapper highC = q.child("high"); 1877 boolean sameUnits = (lowC != null && highC != null) && ((lowC.has("unit") && highC.has("unit") && lowC.child("unit").matches(highC.child("unit"))) 1878 || (lowC.has("code") && highC.has("code") && lowC.child("code").matches(highC.child("code")))); 1879 String low = "?"; 1880 if (q.has("low") && lowC.has("value")) 1881 low = sameUnits ? lowC.primitiveValue("value").toString() : displayQuantity(lowC); 1882 String high = displayQuantity(highC); 1883 if (high.isEmpty()) 1884 high = "?"; 1885 b.append(low).append("\u00A0to\u00A0").append(high); 1886 return b.toString(); 1887 } 1888 1889 protected void renderRange(RenderingStatus status, XhtmlNode x, ResourceWrapper q) { 1890 if (q.has("low")) 1891 x.addText(q.child("low").primitiveValue("value").toString()); 1892 else 1893 x.tx("?"); 1894 x.tx("-"); 1895 if (q.has("high")) 1896 x.addText(q.child("high").primitiveValue("value").toString()); 1897 else 1898 x.tx("?"); 1899 if (q.has("low") && q.child("low").has("unit")) 1900 x.tx(" "+q.child("low").primitiveValue("unit")); 1901 } 1902 1903 public String displayPeriod(ResourceWrapper p) { 1904 String s = !p.has("start") ? "(?)" : displayDateTime(p.child("start")); 1905 s = s + " --> "; 1906 return s + (!p.has("end") ? context.formatPhrase(RenderingContext.DATA_REND_ONGOING) : displayDateTime(p.child("end"))); 1907 } 1908 1909 public void renderPeriod(RenderingStatus status, XhtmlNode x, ResourceWrapper p) { 1910 x.addText(!p.has("start") ? "??" : displayDateTime(p.child("start"))); 1911 x.tx(" --> "); 1912 x.addText(!p.has("end") ? context.formatPhrase(RenderingContext.DATA_REND_ONGOING) : displayDateTime(p.child("end"))); 1913 } 1914 1915 public void renderUsageContext(RenderingStatus status, XhtmlNode x, ResourceWrapper u) throws FHIRFormatError, DefinitionException, IOException { 1916 renderCoding(status, x, u.child("code"), false); 1917 x.tx(" = "); 1918 renderDataType(status, x, u.child("value")); 1919 } 1920 1921 1922 public void renderTriggerDefinition(RenderingStatus status, XhtmlNode x, ResourceWrapper td) throws FHIRFormatError, DefinitionException, IOException { 1923 if (x.isPara()) { 1924 x.b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 1925 x.tx(": "); 1926 x.tx(td.child("type").primitiveValue("display")); 1927 1928 if (td.has("name")) { 1929 x.tx(", "); 1930 x.b().tx(context.formatPhrase(RenderingContext.GENERAL_NAME)); 1931 x.tx(": "); 1932 x.tx(context.getTranslated(td.child("name"))); 1933 } 1934 if (td.has("code")) { 1935 x.tx(", "); 1936 x.b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 1937 x.tx(": "); 1938 renderCodeableConcept(status, x, td.child("code")); 1939 } 1940 if (td.has("timing")) { 1941 x.tx(", "); 1942 x.b().tx(context.formatPhrase(RenderingContext.DATA_REND_TIMING)); 1943 x.tx(": "); 1944 renderDataType(status, x, td.child("timing")); 1945 } 1946 if (td.has("condition")) { 1947 x.tx(", "); 1948 x.b().tx(context.formatPhrase(RenderingContext.DATA_REND_COND)); 1949 x.tx(": "); 1950 renderExpression(status, x, td.child("condition")); 1951 } 1952 } else { 1953 XhtmlNode tbl = x.table("grid", false); 1954 1955 XhtmlNode tr = tbl.tr(); 1956 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 1957 tr.td().tx(td.child("type").primitiveValue("display")); 1958 1959 if (td.has("name")) { 1960 tr = tbl.tr(); 1961 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_NAME)); 1962 tr.td().tx(context.getTranslated(td.child("name"))); 1963 } 1964 if (td.has("code")) { 1965 tr = tbl.tr(); 1966 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 1967 renderCodeableConcept(status, tr.td(), td.child("code")); 1968 } 1969 if (td.has("timing")) { 1970 tr = tbl.tr(); 1971 tr.td().b().tx(context.formatPhrase(RenderingContext.DATA_REND_TIMING)); 1972 renderDataType(status, tr.td(), td.child("timing")); 1973 } 1974 if (td.has("condition")) { 1975 tr = tbl.tr(); 1976 tr.td().b().tx(context.formatPhrase(RenderingContext.DATA_REND_COND)); 1977 renderExpression(status, tr.td(), td.child("condition")); 1978 } 1979 } 1980 } 1981 1982 public void renderDataRequirement(RenderingStatus status, XhtmlNode x, ResourceWrapper dr) throws FHIRFormatError, DefinitionException, IOException { 1983 XhtmlNode tbl = x.table("grid", false); 1984 XhtmlNode tr = tbl.tr(); 1985 XhtmlNode td = tr.td().colspan("2"); 1986 td.b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 1987 td.tx(": "); 1988 StructureDefinition sd = context.getWorker().fetchTypeDefinition(dr.primitiveValue("type")); 1989 if (sd != null && sd.hasWebPath()) { 1990 td.ah(context.prefixLocalHref(sd.getWebPath())).tx(dr.primitiveValue("type")); 1991 } else { 1992 td.tx(dr.primitiveValue("type")); 1993 } 1994 if (dr.has("profile")) { 1995 td.tx(" ("); 1996 boolean first = true; 1997 for (ResourceWrapper p : dr.children("profile")) { 1998 if (first) first = false; else td.tx(" | "); 1999 sd = context.getWorker().fetchResource(StructureDefinition.class, p.primitiveValue()); 2000 if (sd != null && sd.hasWebPath()) { 2001 td.ah(context.prefixLocalHref(sd.getWebPath())).tx(crPresent(sd)); 2002 } else { 2003 td.tx(p.primitiveValue()); 2004 } 2005 } 2006 td.tx(")"); 2007 } 2008 if (dr.has("subject")) { 2009 tr = tbl.tr(); 2010 td = tr.td().colspan("2"); 2011 td.b().tx(context.formatPhrase(RenderingContext.GENERAL_SUBJ)); 2012 ResourceWrapper subj = dr.child("subject"); 2013 if (subj.fhirType().equals("reference")) { 2014 renderReference(status, td, subj); 2015 } else { 2016 renderCodeableConcept(status, td, subj); 2017 } 2018 } 2019 if (dr.has("codeFilter") || dr.has("dateFilter")) { 2020 tr = tbl.tr().backgroundColor("#efefef"); 2021 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_FILTER)); 2022 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_VALUE)); 2023 } 2024 for (ResourceWrapper cf : dr.children("codeFilter")) { 2025 tr = tbl.tr(); 2026 if (cf.has("path")) { 2027 tr.td().tx(cf.primitiveValue("path")); 2028 } else { 2029 tr.td().tx(context.formatPhrase(RenderingContext.DATA_REND_SEARCH, cf.primitiveValue("searchParam")) + " "); 2030 } 2031 if (cf.has("valueSet")) { 2032 td = tr.td(); 2033 td.tx(context.formatPhrase(RenderingContext.DATA_REND_VALUESET) + " "); 2034 renderDataType(status, td, cf.child("valueSet")); 2035 } else { 2036 boolean first = true; 2037 td = tr.td(); 2038 td.tx(context.formatPhrase(RenderingContext.DATA_REND_THESE_CODES) + " "); 2039 for (ResourceWrapper c : cf.children("code")) { 2040 if (first) first = false; else td.tx(", "); 2041 renderDataType(status, td, c); 2042 } 2043 } 2044 } 2045 for (ResourceWrapper cf : dr.children("dateFilter")) { 2046 tr = tbl.tr(); 2047 if (cf.has("path")) { 2048 tr.td().tx(cf.primitiveValue("path")); 2049 } else { 2050 tr.td().tx(context.formatPhrase(RenderingContext.DATA_REND_SEARCH, cf.primitiveValue("searchParam")) + " "); 2051 } 2052 renderDataType(status, tr.td(), cf.child("value")); 2053 } 2054 if (dr.has("sort") || dr.has("limit")) { 2055 tr = tbl.tr(); 2056 td = tr.td().colspan("2"); 2057 if (dr.has("limit")) { 2058 td.b().tx(context.formatPhrase(RenderingContext.DATA_REND_LIMIT)); 2059 td.tx(": "); 2060 td.tx(dr.primitiveValue("limit")); 2061 if (dr.has("sort")) { 2062 td.tx(", "); 2063 } 2064 } 2065 if (dr.has("sort")) { 2066 td.b().tx(context.formatPhrase(RenderingContext.DATA_REND_SORT)); 2067 td.tx(": "); 2068 boolean first = true; 2069 for (ResourceWrapper p : dr.children("sort")) { 2070 if (first) first = false; else td.tx(" | "); 2071 td.tx(p.primitiveValue("direction").equals("ascending") ? "+" : "-"); 2072 td.tx(p.primitiveValue("path")); 2073 } 2074 } 2075 } 2076 } 2077 2078 2079 private String displayTiming(ResourceWrapper s) throws FHIRException { 2080 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 2081 if (s.has("code")) { 2082 b.append(context.formatPhrase(RenderingContext.GENERAL_CODE, displayCodeableConcept(s.child("code"))) + " "); 2083 } 2084 2085 if (s.has("event")) { 2086 CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder(); 2087 for (ResourceWrapper p : s.children("event")) { 2088 if (p.hasPrimitiveValue()) { 2089 c.append(displayDateTime(p)); 2090 } else if (!renderExpression(c, p)) { 2091 c.append("??"); 2092 } 2093 } 2094 b.append(context.formatPhrase(RenderingContext.DATA_REND_EVENTS, c.toString()) + " "); 2095 } 2096 2097 if (s.has("repeat")) { 2098 ResourceWrapper rep = s.child("repeat"); 2099 if (rep.has("boundsPeriod") && rep.child("boundsPeriod").has("start")) 2100 b.append(context.formatPhrase(RenderingContext.DATA_REND_STARTING, displayDateTime(rep.child("boundsPeriod").child("start"))) + " "); 2101 if (rep.has("count")) 2102 b.append(context.formatPhrase(RenderingContext.DATA_REND_COUNT, rep.primitiveValue("count")) + " " + " times"); 2103 if (rep.has("duration")) 2104 b.append(context.formatPhrase(RenderingContext.DATA_REND_DURATION, rep.primitiveValue("duration")+displayTimeUnits(rep.primitiveValue("periodUnit"), "1".equals(rep.primitiveValue("duration")))) + " "); 2105 2106 String st = ""; 2107 if (rep.has("offset")) { 2108 st = rep.primitiveValue("offset")+"min "; 2109 } 2110 if (!Utilities.noString(st)) { 2111 b.append(st); 2112 } 2113 for (ResourceWrapper wh : rep.children("when")) { 2114 b.append(displayEventCode(wh.primitiveValue())); 2115 } 2116 st = ""; 2117 if (!rep.has("frequency") || (!rep.has("frequencyMax") && rep.primitiveValue("frequency").equals("1"))) { 2118 st = context.formatPhrase(RenderingContext.DATA_REND_ONCE); 2119 } else { 2120 st = rep.primitiveValue("frequency"); 2121 if (rep.has("frequencyMax")) 2122 st = st + "-"+rep.primitiveValue("frequencyMax"); 2123 } 2124 if (rep.has("period")) { 2125 st = st + " "+ (context.formatPhrase(RenderingContext.DATA_REND_PER))+" "+rep.primitiveValue("period"); 2126 if (rep.has("periodMax")) { 2127 st = st + "-"+rep.primitiveValue("periodMax"); 2128 } 2129 st = st + " "+displayTimeUnits(rep.primitiveValue("periodUnit"), "1".equals(rep.primitiveValue("period"))); 2130 } 2131 if (!Utilities.noString(st)) { 2132 b.append(st); 2133 } 2134 if (rep.has("boundsPeriod") && rep.child("boundsPeriod").has("end")) { 2135 b.append(context.formatPhrase(RenderingContext.DATA_REND_UNTIL, displayDateTime(rep.child("boundsPeriod").child("end"))) + " "); 2136 } 2137 } 2138 return b.toString(); 2139 } 2140 2141 private boolean renderExpression(CommaSeparatedStringBuilder c, ResourceWrapper p) { 2142 ResourceWrapper exp = p.extensionValue("http://hl7.org/fhir/StructureDefinition/cqf-expression"); 2143 if (exp == null || !exp.has("value")) { 2144 return false; 2145 } 2146 c.append(exp.child("value").primitiveValue("expression")); 2147 return true; 2148 } 2149 2150 private String displayEventCode(String when) { 2151 if (when == null) 2152 return "??"; 2153 switch (when.toLowerCase()) { 2154 case "c": return (context.formatPhrase(RenderingContext.DATA_REND_MEALS)); 2155 case "cd": return (context.formatPhrase(RenderingContext.DATA_REND_ATLUNCH)); 2156 case "cm": return (context.formatPhrase(RenderingContext.DATA_REND_ATBKFST)); 2157 case "cv": return (context.formatPhrase(RenderingContext.DATA_REND_ATDINR)); 2158 case "ac": return (context.formatPhrase(RenderingContext.DATA_REND_BFMEALS)); 2159 case "acd": return (context.formatPhrase(RenderingContext.DATA_REND_BFLUNCH)); 2160 case "acm": return (context.formatPhrase(RenderingContext.DATA_REND_BFBKFST)); 2161 case "acv": return (context.formatPhrase(RenderingContext.DATA_REND_BFDINR)); 2162 case "hs": return (context.formatPhrase(RenderingContext.DATA_REND_BFSLEEP)); 2163 case "pc": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRMEALS)); 2164 case "pcd": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRLUNCH)); 2165 case "pcm": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRBKFST)); 2166 case "pcv": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRDINR)); 2167 case "wake": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRWKNG)); 2168 case "morn": return (context.formatPhrase(RenderingContext.DATA_REND_MORNING)); 2169 case "morn.early": return (context.formatPhrase(RenderingContext.DATA_REND_MORNING_EARLY)); 2170 case "morn.late": return (context.formatPhrase(RenderingContext.DATA_REND_MORNING_LATE)); 2171 case "noon": return (context.formatPhrase(RenderingContext.DATA_REND_NOON)); 2172 case "aft": return (context.formatPhrase(RenderingContext.DATA_REND_AFTERNOON)); 2173 case "aft.early": return (context.formatPhrase(RenderingContext.DATA_REND_AFTERNOON_EARLY)); 2174 case "aft.late": return (context.formatPhrase(RenderingContext.DATA_REND_AFTERNOON_LATE)); 2175 case "eve": return (context.formatPhrase(RenderingContext.DATA_REND_EVENING)); 2176 case "eve.early": return (context.formatPhrase(RenderingContext.DATA_REND_EVENING_EARLY)); 2177 case "eve.late": return (context.formatPhrase(RenderingContext.DATA_REND_EVENING_LATE)); 2178 case "night": return (context.formatPhrase(RenderingContext.DATA_REND_NIGHT)); 2179 case "phs": return (context.formatPhrase(RenderingContext.DATA_REND_AFTER_SLEEP)); 2180 case "imd": return (context.formatPhrase(RenderingContext.DATA_REND_IMMEDIATE)); 2181 2182 default: return "?"+when+"?"; 2183 } 2184 } 2185 2186 private String displayTimeUnits(String units, boolean singular) { 2187 if (units == null) 2188 return "??"; 2189 switch (units) { 2190 case "a": return singular ? "year" : "years"; 2191 case "d": return singular ? "day" : "days"; 2192 case "h": return singular ? "hour" : "hours"; 2193 case "min": return singular ? "minute" : "minutes"; 2194 case "mo": return singular ? "month" : "months"; 2195 case "s": return singular ? "second" : "seconds"; 2196 case "wk": return singular ? "week" : "weeks"; 2197 default: return "?"+units+"?"; 2198 } 2199 } 2200 2201 protected void renderTiming(RenderingStatus status, XhtmlNode x, ResourceWrapper s) throws FHIRException { 2202 x.addText(displayTiming(s)); 2203 } 2204 2205 2206 private String displaySampledData(ResourceWrapper s) { 2207 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 2208 if (s.has("origin")) 2209 b.append(context.formatPhrase(RenderingContext.DATA_REND_ORIGIN, displayQuantity(s.child("origin"))) + " "); 2210 2211 if (s.has("interval")) { 2212 b.append(context.formatPhrase(RenderingContext.DATA_REND_INT, s.primitiveValue("interval")) + " "); 2213 2214 if (s.has("intervalUnit")) 2215 b.append(s.primitiveValue("intervalUnit")); 2216 } 2217 2218 if (s.has("factor")) 2219 b.append(context.formatPhrase(RenderingContext.DATA_REND_FACT, s.primitiveValue("factor")) + " "); 2220 2221 if (s.has("lowerLimit")) 2222 b.append(context.formatPhrase(RenderingContext.DATA_REND_LOWER, s.primitiveValue("lowerLimit")) + " "); 2223 2224 if (s.has("upperLimit")) 2225 b.append(context.formatPhrase(RenderingContext.DATA_REND_UP, s.primitiveValue("upperLimit")) + " "); 2226 2227 if (s.has("dimensions")) 2228 b.append(context.formatPhrase(RenderingContext.DATA_REND_DIM, s.primitiveValue("dimensions")) + " "); 2229 2230 if (s.has("data")) 2231 b.append(context.formatPhrase(RenderingContext.DATA_REND_DATA, s.primitiveValue("data")) + " "); 2232 2233 return b.toString(); 2234 } 2235 2236 protected void renderSampledData(RenderingStatus status, XhtmlNode x, ResourceWrapper sampledData) { 2237 x.addText(displaySampledData(sampledData)); 2238 } 2239 2240 public RenderingContext getContext() { 2241 return context; 2242 } 2243 2244 2245 public XhtmlNode makeExceptionXhtml(Exception e, String function) { 2246 XhtmlNode xn; 2247 xn = new XhtmlNode(NodeType.Element, "div"); 2248 XhtmlNode p = xn.para(); 2249 p.b().tx((context.formatPhrase(RenderingContext.DATA_REND_EXCEPTION)) +function+": "+e.getMessage()); 2250 p.addComment(getStackTrace(e)); 2251 return xn; 2252 } 2253 2254 private String getStackTrace(Exception e) { 2255 StringBuilder b = new StringBuilder(); 2256 b.append("\r\n"); 2257 for (StackTraceElement t : e.getStackTrace()) { 2258 b.append(t.getClassName()+"."+t.getMethodName()+" ("+t.getFileName()+":"+t.getLineNumber()); 2259 b.append("\r\n"); 2260 } 2261 return b.toString(); 2262 } 2263 2264 protected String systemFromCanonical(String system) { 2265 if (system == null) { 2266 return null; 2267 } else if (system.contains("|")) { 2268 return system.substring(0, system.indexOf("|")); 2269 } else { 2270 return null; 2271 } 2272 } 2273 2274 protected String versionFromCanonical(String system) { 2275 if (system == null) { 2276 return null; 2277 } else if (system.contains("|")) { 2278 return system.substring(system.indexOf("|")+1); 2279 } else { 2280 return system; 2281 } 2282 } 2283 2284 2285 /** 2286 * when we run into an unknown (canonical) URL, we assume that it's a pointer to something we don't 2287 * know about, and render it as an 'a href=' in case it is valid. But in the 'known' domains, where 2288 * we reasonably expect to know everything , we don't make them links 2289 * @return 2290 */ 2291 protected boolean isInKnownUrlSpace(String url) { 2292 return Utilities.startsWithInList(url, 2293 "http://hl7.org/fhir", "http://fhir.org/guides", "http://ihe.net/fhir", "http://terminology.hl7.org", 2294 "https://hl7.org/fhir", "https://fhir.org/guides", "https://ihe.net/fhir", "https://terminology.hl7.org", 2295 "http://www.hl7.org/fhir", "http://www.fhir.org/guides", "http://www.ihe.net/fhir", 2296 "https://www.hl7.org/fhir", "https://www.fhir.org/guides", "https://www.ihe.net/fhir" 2297 ); 2298 } 2299}