
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.context.IWorkerContext.ValidationResult; 024import org.hl7.fhir.r5.model.Address; 025import org.hl7.fhir.r5.model.Annotation; 026import org.hl7.fhir.r5.model.BackboneType; 027import org.hl7.fhir.r5.model.Base; 028import org.hl7.fhir.r5.model.Base64BinaryType; 029import org.hl7.fhir.r5.model.BaseDateTimeType; 030import org.hl7.fhir.r5.model.CanonicalResource; 031import org.hl7.fhir.r5.model.CanonicalType; 032import org.hl7.fhir.r5.model.CodeSystem; 033import org.hl7.fhir.r5.model.CodeableConcept; 034import org.hl7.fhir.r5.model.CodeableReference; 035import org.hl7.fhir.r5.model.Coding; 036import org.hl7.fhir.r5.model.ContactPoint; 037import org.hl7.fhir.r5.model.ContactPoint.ContactPointSystem; 038import org.hl7.fhir.r5.model.DataRequirement; 039import org.hl7.fhir.r5.model.DataRequirement.DataRequirementCodeFilterComponent; 040import org.hl7.fhir.r5.model.DataRequirement.DataRequirementDateFilterComponent; 041import org.hl7.fhir.r5.model.DataRequirement.DataRequirementSortComponent; 042import org.hl7.fhir.r5.model.DataRequirement.SortDirection; 043import org.hl7.fhir.r5.model.DataType; 044import org.hl7.fhir.r5.model.DateTimeType; 045import org.hl7.fhir.r5.model.DateType; 046import org.hl7.fhir.r5.model.ElementDefinition; 047import org.hl7.fhir.r5.model.Enumeration; 048import org.hl7.fhir.r5.model.Expression; 049import org.hl7.fhir.r5.model.Extension; 050import org.hl7.fhir.r5.model.HumanName; 051import org.hl7.fhir.r5.model.HumanName.NameUse; 052import org.hl7.fhir.r5.model.IdType; 053import org.hl7.fhir.r5.model.Identifier; 054import org.hl7.fhir.r5.model.MarkdownType; 055import org.hl7.fhir.r5.model.Money; 056import org.hl7.fhir.r5.model.Period; 057import org.hl7.fhir.r5.model.PrimitiveType; 058import org.hl7.fhir.r5.model.Quantity; 059import org.hl7.fhir.r5.model.Range; 060import org.hl7.fhir.r5.model.Reference; 061import org.hl7.fhir.r5.model.Resource; 062import org.hl7.fhir.r5.model.SampledData; 063import org.hl7.fhir.r5.model.StringType; 064import org.hl7.fhir.r5.model.StructureDefinition; 065import org.hl7.fhir.r5.model.Timing; 066import org.hl7.fhir.r5.model.Timing.EventTiming; 067import org.hl7.fhir.r5.model.Timing.TimingRepeatComponent; 068import org.hl7.fhir.r5.model.Timing.UnitsOfTime; 069import org.hl7.fhir.r5.model.UriType; 070import org.hl7.fhir.r5.model.UsageContext; 071import org.hl7.fhir.r5.model.ValueSet; 072import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; 073import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceDesignationComponent; 074import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper; 075import org.hl7.fhir.r5.renderers.CodeResolver.CodeResolution; 076import org.hl7.fhir.r5.renderers.utils.RenderingContext; 077import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules; 078import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; 079import org.hl7.fhir.r5.terminologies.JurisdictionUtilities; 080import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 081import org.hl7.fhir.utilities.Utilities; 082import org.hl7.fhir.utilities.VersionUtilities; 083import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 084import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece; 085import org.hl7.fhir.utilities.xhtml.NodeType; 086import org.hl7.fhir.utilities.xhtml.XhtmlNode; 087import org.hl7.fhir.utilities.xhtml.XhtmlParser; 088 089import ca.uhn.fhir.model.api.TemporalPrecisionEnum; 090 091public class DataRenderer extends Renderer implements CodeResolver { 092 093 // -- 1. context -------------------------------------------------------------- 094 095 public DataRenderer(RenderingContext context) { 096 super(context); 097 } 098 099 public DataRenderer(IWorkerContext worker) { 100 super(worker); 101 } 102 103 // -- 2. Markdown support ------------------------------------------------------- 104 105 public static String processRelativeUrls(String markdown, String path) { 106 if (markdown == null) { 107 return ""; 108 } 109 if (!Utilities.isAbsoluteUrl(path)) { 110 return markdown; 111 } 112 String basePath = path.contains("/") ? path.substring(0, path.lastIndexOf("/")+1) : path+"/"; 113 StringBuilder b = new StringBuilder(); 114 int i = 0; 115 while (i < markdown.length()) { 116 if (i < markdown.length()-3 && markdown.substring(i, i+2).equals("](")) { 117 int j = i + 2; 118 while (j < markdown.length() && markdown.charAt(j) != ')') 119 j++; 120 if (j < markdown.length()) { 121 String url = markdown.substring(i+2, j); 122 if (!Utilities.isAbsoluteUrl(url) && !url.startsWith("..")) { 123 // it's relative - so it's relative to the base URL 124 b.append("]("); 125 b.append(basePath); 126 } else { 127 b.append("]("); 128 } 129 i = i + 1; 130 } else 131 b.append(markdown.charAt(i)); 132 } else { 133 b.append(markdown.charAt(i)); 134 } 135 i++; 136 } 137 return b.toString(); 138 } 139 140 protected void addMarkdown(XhtmlNode x, String text, String path) throws FHIRFormatError, IOException, DefinitionException { 141 addMarkdown(x, processRelativeUrls(text, path)); 142 } 143 144 protected void addMarkdown(XhtmlNode x, String text) throws FHIRFormatError, IOException, DefinitionException { 145 if (text != null) { 146 // 1. custom FHIR extensions 147 while (text.contains("[[[")) { 148 String left = text.substring(0, text.indexOf("[[[")); 149 String link = text.substring(text.indexOf("[[[")+3, text.indexOf("]]]")); 150 String right = text.substring(text.indexOf("]]]")+3); 151 String path = null; 152 String url = link; 153 String[] parts = link.split("\\#"); 154 if (parts[0].contains(".")) { 155 path = parts[0]; 156 parts[0] = parts[0].substring(0, parts[0].indexOf(".")); 157 } 158 StructureDefinition p = getContext().getWorker().fetchResource(StructureDefinition.class, parts[0]); 159 if (p == null) 160 p = getContext().getWorker().fetchTypeDefinition(parts[0]); 161 if (p == null) 162 p = getContext().getWorker().fetchResource(StructureDefinition.class, link); 163 if (p != null) { 164 url = p.getWebPath(); 165 if (url == null) 166 url = p.getUserString("filename"); 167 } else 168 throw new DefinitionException("Unable to resolve markdown link "+link); 169 170 text = left+"["+link+"]("+url+(path == null ? "" : "#"+path)+")"+right; 171 } 172 173 // 2. markdown 174 String s = getContext().getMarkdown().process(text, "narrative generator"); 175 XhtmlParser p = new XhtmlParser(); 176 XhtmlNode m; 177 try { 178 m = p.parse("<div>"+s+"</div>", "div"); 179 } catch (org.hl7.fhir.exceptions.FHIRFormatError e) { 180 throw new FHIRFormatError(e.getMessage(), e); 181 } 182 x.getChildNodes().addAll(m.getChildNodes()); 183 } 184 } 185 186 protected void smartAddText(XhtmlNode p, String text) { 187 if (text == null) 188 return; 189 190 String[] lines = text.split("\\r\\n"); 191 for (int i = 0; i < lines.length; i++) { 192 if (i > 0) 193 p.br(); 194 p.addText(lines[i]); 195 } 196 } 197 198 // -- 3. General Purpose Terminology Support ----------------------------------------- 199 200 private static String month(String m) { 201 switch (m) { 202 case "1" : return "Jan"; 203 case "2" : return "Feb"; 204 case "3" : return "Mar"; 205 case "4" : return "Apr"; 206 case "5" : return "May"; 207 case "6" : return "Jun"; 208 case "7" : return "Jul"; 209 case "8" : return "Aug"; 210 case "9" : return "Sep"; 211 case "10" : return "Oct"; 212 case "11" : return "Nov"; 213 case "12" : return "Dec"; 214 default: return null; 215 } 216 } 217 218 public static String describeVersion(String version) { 219 if (version.startsWith("http://snomed.info/sct")) { 220 String[] p = version.split("\\/"); 221 String ed = null; 222 String dt = ""; 223 224 if (p[p.length-2].equals("version")) { 225 ed = p[p.length-3]; 226 String y = p[p.length-3].substring(4, 8); 227 String m = p[p.length-3].substring(2, 4); 228 dt = " rel. "+month(m)+" "+y; 229 } else { 230 ed = p[p.length-1]; 231 } 232 switch (ed) { 233 case "900000000000207008": return "Intl"+dt; 234 case "731000124108": return "US"+dt; 235 case "32506021000036107": return "AU"+dt; 236 case "449081005": return "ES"+dt; 237 case "554471000005108": return "DK"+dt; 238 case "11000146104": return "NL"+dt; 239 case "45991000052106": return "SE"+dt; 240 case "999000041000000102": return "UK"+dt; 241 case "20611000087101": return "CA"+dt; 242 case "11000172109": return "BE"+dt; 243 default: return "??"+dt; 244 } 245 } else { 246 return version; 247 } 248 } 249 250 public static String describeSystem(String system) { 251 if (system == null) 252 return "[not stated]"; 253 if (system.equals("http://loinc.org")) 254 return "LOINC"; 255 if (system.startsWith("http://snomed.info")) 256 return "SNOMED CT"; 257 if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm")) 258 return "RxNorm"; 259 if (system.equals("http://hl7.org/fhir/sid/icd-9")) 260 return "ICD-9"; 261 if (system.equals("http://dicom.nema.org/resources/ontology/DCM")) 262 return "DICOM"; 263 if (system.equals("http://unitsofmeasure.org")) 264 return "UCUM"; 265 266 return system; 267 } 268 269 public String displaySystem(String system) { 270 if (system == null) 271 return "[not stated]"; 272 if (system.equals("http://loinc.org")) 273 return "LOINC"; 274 if (system.startsWith("http://snomed.info")) 275 return "SNOMED CT"; 276 if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm")) 277 return "RxNorm"; 278 if (system.equals("http://hl7.org/fhir/sid/icd-9")) 279 return "ICD-9"; 280 if (system.equals("http://dicom.nema.org/resources/ontology/DCM")) 281 return "DICOM"; 282 if (system.equals("http://unitsofmeasure.org")) 283 return "UCUM"; 284 285 CodeSystem cs = context.getContext().fetchCodeSystem(system); 286 if (cs != null) { 287 return cs.present(); 288 } 289 return tails(system); 290 } 291 292 private String tails(String system) { 293 if (system.contains("/")) { 294 return system.substring(system.lastIndexOf("/")+1); 295 } else { 296 return "unknown"; 297 } 298 } 299 300 protected String makeAnchor(String codeSystem, String code) { 301 String s = codeSystem+'-'+code; 302 StringBuilder b = new StringBuilder(); 303 for (char c : s.toCharArray()) { 304 if (Character.isAlphabetic(c) || Character.isDigit(c) || c == '.') 305 b.append(c); 306 else 307 b.append('-'); 308 } 309 return b.toString(); 310 } 311 312 private String lookupCode(String system, String version, String code) { 313 if (JurisdictionUtilities.isJurisdiction(system)) { 314 return JurisdictionUtilities.displayJurisdiction(system+"#"+code); 315 } 316 ValidationResult t = getContext().getWorker().validateCode(getContext().getTerminologyServiceOptions().withVersionFlexible(true), system, version, code, null); 317 318 if (t != null && t.getDisplay() != null) 319 return t.getDisplay(); 320 else 321 return code; 322 } 323 324 protected String describeLang(String lang) { 325 // special cases: 326 if ("fr-CA".equals(lang)) { 327 return "French (Canadian)"; // this one was omitted from the value set 328 } 329 ValueSet v = getContext().getWorker().fetchResource(ValueSet.class, "http://hl7.org/fhir/ValueSet/languages"); 330 if (v != null) { 331 ConceptReferenceComponent l = null; 332 for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) { 333 if (cc.getCode().equals(lang)) 334 l = cc; 335 } 336 if (l == null) { 337 if (lang.contains("-")) { 338 lang = lang.substring(0, lang.indexOf("-")); 339 } 340 for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) { 341 if (cc.getCode().equals(lang)) { 342 l = cc; 343 break; 344 } 345 } 346 if (l == null) { 347 for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) { 348 if (cc.getCode().startsWith(lang+"-")) { 349 l = cc; 350 break; 351 } 352 } 353 } 354 } 355 if (l != null) { 356 if (lang.contains("-")) 357 lang = lang.substring(0, lang.indexOf("-")); 358 String en = l.getDisplay(); 359 String nativelang = null; 360 for (ConceptReferenceDesignationComponent cd : l.getDesignation()) { 361 if (cd.getLanguage().equals(lang)) 362 nativelang = cd.getValue(); 363 } 364 if (nativelang == null) 365 return en+" ("+lang+")"; 366 else 367 return nativelang+" ("+en+", "+lang+")"; 368 } 369 } 370 return lang; 371 } 372 373 private boolean isCanonical(String path) { 374 if (!path.endsWith(".url")) 375 return false; 376 String t = path.substring(0, path.length()-4); 377 StructureDefinition sd = getContext().getWorker().fetchTypeDefinition(t); 378 if (sd == null) 379 return false; 380 if (VersionUtilities.getCanonicalResourceNames(getContext().getWorker().getVersion()).contains(t)) { 381 return true; 382 } 383 if (Utilities.existsInList(t, 384 "ActivityDefinition", "CapabilityStatement", "ChargeItemDefinition", "Citation", "CodeSystem", 385 "CompartmentDefinition", "ConceptMap", "ConditionDefinition", "EventDefinition", "Evidence", "EvidenceReport", "EvidenceVariable", 386 "ExampleScenario", "GraphDefinition", "ImplementationGuide", "Library", "Measure", "MessageDefinition", "NamingSystem", "PlanDefinition" 387 )) 388 return true; 389 return false; 390 } 391 392 // -- 4. Language support ------------------------------------------------------ 393 394 protected String translate(String source, String content) { 395 return content; 396 } 397 398 public String gt(@SuppressWarnings("rawtypes") PrimitiveType value) { 399 return value.primitiveValue(); 400 } 401 402 // -- 6. General purpose extension rendering ---------------------------------------------- 403 404 public boolean hasRenderableExtensions(DataType element) { 405 for (Extension ext : element.getExtension()) { 406 if (canRender(ext)) { 407 return true; 408 } 409 } 410 return false; 411 } 412 413 public boolean hasRenderableExtensions(BackboneType element) { 414 for (Extension ext : element.getExtension()) { 415 if (canRender(ext)) { 416 return true; 417 } 418 } 419 return element.hasModifierExtension(); 420 } 421 422 private String getExtensionLabel(Extension ext) { 423 StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, ext.getUrl()); 424 if (sd != null && ext.hasValue() && ext.getValue().isPrimitive() && sd.hasSnapshot()) { 425 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 426 if (Utilities.existsInList(ed.getPath(), "Extension", "Extension.value[x]") && ed.hasLabel()) { 427 return ed.getLabel(); 428 } 429 } 430 } 431 return null; 432 } 433 434 private boolean canRender(Extension ext) { 435 return getExtensionLabel(ext) != null; 436 } 437 438 public void renderExtensionsInList(XhtmlNode ul, DataType element) throws FHIRFormatError, DefinitionException, IOException { 439 for (Extension ext : element.getExtension()) { 440 if (canRender(ext)) { 441 String lbl = getExtensionLabel(ext); 442 XhtmlNode li = ul.li(); 443 li.tx(lbl); 444 li.tx(": "); 445 render(li, ext.getValue()); 446 } 447 } 448 } 449 450 public void renderExtensionsInList(XhtmlNode ul, BackboneType element) throws FHIRFormatError, DefinitionException, IOException { 451 for (Extension ext : element.getModifierExtension()) { 452 if (canRender(ext)) { 453 String lbl = getExtensionLabel(ext); 454 XhtmlNode li = ul.li(); 455 li = li.b(); 456 li.tx(lbl); 457 li.tx(": "); 458 render(li, ext.getValue()); 459 } else { 460 // somehow have to do better than this 461 XhtmlNode li = ul.li(); 462 li.b().tx("WARNING: Unrenderable Modifier Extension!"); 463 } 464 } 465 for (Extension ext : element.getExtension()) { 466 if (canRender(ext)) { 467 String lbl = getExtensionLabel(ext); 468 XhtmlNode li = ul.li(); 469 li.tx(lbl); 470 li.tx(": "); 471 render(li, ext.getValue()); 472 } 473 } 474 } 475 476 public void renderExtensionsInText(XhtmlNode div, DataType element, String sep) throws FHIRFormatError, DefinitionException, IOException { 477 boolean first = true; 478 for (Extension ext : element.getExtension()) { 479 if (canRender(ext)) { 480 if (first) { 481 first = false; 482 } else { 483 div.tx(sep); 484 div.tx(" "); 485 } 486 487 String lbl = getExtensionLabel(ext); 488 div.tx(lbl); 489 div.tx(": "); 490 render(div, ext.getValue()); 491 } 492 } 493 } 494 495 public void renderExtensionsInList(XhtmlNode div, BackboneType element, String sep) throws FHIRFormatError, DefinitionException, IOException { 496 boolean first = true; 497 for (Extension ext : element.getModifierExtension()) { 498 if (first) { 499 first = false; 500 } else { 501 div.tx(sep); 502 div.tx(" "); 503 } 504 if (canRender(ext)) { 505 String lbl = getExtensionLabel(ext); 506 XhtmlNode b = div.b(); 507 b.tx(lbl); 508 b.tx(": "); 509 render(div, ext.getValue()); 510 } else { 511 // somehow have to do better than this 512 div.b().tx("WARNING: Unrenderable Modifier Extension!"); 513 } 514 } 515 for (Extension ext : element.getExtension()) { 516 if (canRender(ext)) { 517 if (first) { 518 first = false; 519 } else { 520 div.tx(sep); 521 div.tx(" "); 522 } 523 524 String lbl = getExtensionLabel(ext); 525 div.tx(lbl); 526 div.tx(": "); 527 render(div, ext.getValue()); 528 } 529 } 530 531 } 532 533 // -- 6. Data type Rendering ---------------------------------------------- 534 535 public static String display(IWorkerContext context, DataType type) { 536 return new DataRenderer(new RenderingContext(context, null, null, "http://hl7.org/fhir/R4", "", null, ResourceRendererMode.END_USER, GenerationRules.VALID_RESOURCE)).display(type); 537 } 538 539 public String displayBase(Base b) { 540 if (b instanceof DataType) { 541 return display((DataType) b); 542 } else { 543 return "No display for "+b.fhirType(); 544 } 545 } 546 547 public String display(DataType type) { 548 if (type == null || type.isEmpty()) { 549 return ""; 550 } 551 552 if (type instanceof Coding) { 553 return displayCoding((Coding) type); 554 } else if (type instanceof CodeableConcept) { 555 return displayCodeableConcept((CodeableConcept) type); 556 } else if (type instanceof Identifier) { 557 return displayIdentifier((Identifier) type); 558 } else if (type instanceof HumanName) { 559 return displayHumanName((HumanName) type); 560 } else if (type instanceof Address) { 561 return displayAddress((Address) type); 562 } else if (type instanceof ContactPoint) { 563 return displayContactPoint((ContactPoint) type); 564 } else if (type instanceof Quantity) { 565 return displayQuantity((Quantity) type); 566 } else if (type instanceof Range) { 567 return displayRange((Range) type); 568 } else if (type instanceof Period) { 569 return displayPeriod((Period) type); 570 } else if (type instanceof Timing) { 571 return displayTiming((Timing) type); 572 } else if (type instanceof SampledData) { 573 return displaySampledData((SampledData) type); 574 } else if (type.isDateTime()) { 575 return displayDateTime((BaseDateTimeType) type); 576 } else if (type.isPrimitive()) { 577 return type.primitiveValue(); 578 } else { 579 return "No display for "+type.fhirType(); 580 } 581 } 582 583 private String displayDateTime(BaseDateTimeType type) { 584 if (!type.hasPrimitiveValue()) { 585 return ""; 586 } 587 588 // relevant inputs in rendering context: 589 // timeZone, dateTimeFormat, locale, mode 590 // timezone - application specified timezone to use. 591 // null = default to the time of the date/time itself 592 // dateTimeFormat - application specified format for date times 593 // null = default to ... depends on mode 594 // mode - if rendering mode is technical, format defaults to XML format 595 // locale - otherwise, format defaults to SHORT for the Locale (which defaults to default Locale) 596 if (isOnlyDate(type.getPrecision())) { 597 598 DateTimeFormatter fmt = getDateFormatForPrecision(type); 599 LocalDate date = LocalDate.of(type.getYear(), type.getMonth()+1, type.getDay()); 600 return fmt.format(date); 601 } 602 603 DateTimeFormatter fmt = context.getDateTimeFormat(); 604 if (fmt == null) { 605 if (context.isTechnicalMode()) { 606 fmt = DateTimeFormatter.ISO_OFFSET_DATE_TIME; 607 } else { 608 fmt = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).withLocale(context.getLocale()); 609 } 610 } 611 ZonedDateTime zdt = ZonedDateTime.parse(type.primitiveValue()); 612 ZoneId zone = context.getTimeZoneId(); 613 if (zone != null) { 614 zdt = zdt.withZoneSameInstant(zone); 615 } 616 return fmt.format(zdt); 617 } 618 619 private DateTimeFormatter getDateFormatForPrecision(BaseDateTimeType type) { 620 DateTimeFormatter fmt = getContextDateFormat(type); 621 if (fmt != null) { 622 return fmt; 623 } 624 if (context.isTechnicalMode()) { 625 switch (type.getPrecision()) { 626 case YEAR: 627 return new DateTimeFormatterBuilder().appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD).toFormatter(); 628 case MONTH: 629 return new DateTimeFormatterBuilder().appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD).appendLiteral('-').appendValue(MONTH_OF_YEAR, 2).toFormatter(); 630 default: 631 return DateTimeFormatter.ISO_DATE; 632 } 633 } else { 634 switch (type.getPrecision()) { 635 case YEAR: 636 return DateTimeFormatter.ofPattern("uuuu"); 637 case MONTH: 638 return DateTimeFormatter.ofPattern("MMM uuuu"); 639 default: 640 return DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(context.getLocale()); 641 } 642 } 643 } 644 645 private DateTimeFormatter getContextDateFormat(BaseDateTimeType type) { 646 switch (type.getPrecision()) { 647 case YEAR: 648 return context.getDateYearFormat(); 649 case MONTH: 650 return context.getDateYearMonthFormat(); 651 default: 652 return context.getDateFormat(); 653 } 654 } 655 656 private boolean isOnlyDate(TemporalPrecisionEnum temporalPrecisionEnum) { 657 return temporalPrecisionEnum == TemporalPrecisionEnum.YEAR || temporalPrecisionEnum == TemporalPrecisionEnum.MONTH || temporalPrecisionEnum == TemporalPrecisionEnum.DAY; 658 } 659 660 public String display(BaseWrapper type) { 661 return "to do"; 662 } 663 664 public void render(XhtmlNode x, BaseWrapper type) throws FHIRFormatError, DefinitionException, IOException { 665 Base base = null; 666 try { 667 base = type.getBase(); 668 } catch (FHIRException | IOException e) { 669 x.tx("Error: " + e.getMessage()); // this shouldn't happen - it's an error in the library itself 670 return; 671 } 672 if (base instanceof DataType) { 673 render(x, (DataType) base); 674 } else { 675 x.tx("to do: "+base.fhirType()); 676 } 677 } 678 679 public void renderBase(XhtmlNode x, Base b) throws FHIRFormatError, DefinitionException, IOException { 680 if (b instanceof DataType) { 681 render(x, (DataType) b); 682 } else { 683 x.tx("No display for "+b.fhirType()); 684 } 685 } 686 687 public void render(XhtmlNode x, DataType type) throws FHIRFormatError, DefinitionException, IOException { 688 if (type instanceof BaseDateTimeType) { 689 x.tx(displayDateTime((BaseDateTimeType) type)); 690 } else if (type instanceof UriType) { 691 renderUri(x, (UriType) type); 692 } else if (type instanceof Annotation) { 693 renderAnnotation(x, (Annotation) type); 694 } else if (type instanceof Coding) { 695 renderCodingWithDetails(x, (Coding) type); 696 } else if (type instanceof CodeableConcept) { 697 renderCodeableConcept(x, (CodeableConcept) type); 698 } else if (type instanceof Identifier) { 699 renderIdentifier(x, (Identifier) type); 700 } else if (type instanceof HumanName) { 701 renderHumanName(x, (HumanName) type); 702 } else if (type instanceof Address) { 703 renderAddress(x, (Address) type); 704 } else if (type instanceof Expression) { 705 renderExpression(x, (Expression) type); 706 } else if (type instanceof Money) { 707 renderMoney(x, (Money) type); 708 } else if (type instanceof ContactPoint) { 709 renderContactPoint(x, (ContactPoint) type); 710 } else if (type instanceof Quantity) { 711 renderQuantity(x, (Quantity) type); 712 } else if (type instanceof Range) { 713 renderRange(x, (Range) type); 714 } else if (type instanceof Period) { 715 renderPeriod(x, (Period) type); 716 } else if (type instanceof Timing) { 717 renderTiming(x, (Timing) type); 718 } else if (type instanceof SampledData) { 719 renderSampledData(x, (SampledData) type); 720 } else if (type instanceof Reference) { 721 renderReference(x, (Reference) type); 722 } else if (type instanceof CodeableReference) { 723 CodeableReference cr = (CodeableReference) type; 724 if (cr.hasConcept()) { 725 renderCodeableConcept(x, cr.getConcept()); 726 } else { 727 renderReference(x, cr.getReference()); 728 } 729 } else if (type instanceof MarkdownType) { 730 addMarkdown(x, ((MarkdownType) type).asStringValue()); 731 } else if (type instanceof Base64BinaryType) { 732 Base64BinaryType b64 = (Base64BinaryType) type; 733 x.tx("(base64 data - "+(b64.getValue() == null ? "0" : b64.getValue().length)+" bytes)"); 734 } else if (type.isPrimitive()) { 735 x.tx(type.primitiveValue()); 736 } else { 737 x.tx("No display for "+type.fhirType()); 738 } 739 } 740 741 private void renderReference(XhtmlNode x, Reference ref) { 742 if (ref.hasDisplay()) { 743 x.tx(ref.getDisplay()); 744 } else if (ref.hasReference()) { 745 x.tx(ref.getReference()); 746 } else { 747 x.tx("??"); 748 } 749 } 750 751 public void renderDateTime(XhtmlNode x, Base e) { 752 if (e.hasPrimitiveValue()) { 753 x.addText(displayDateTime((DateTimeType) e)); 754 } 755 } 756 757 public void renderDate(XhtmlNode x, Base e) { 758 if (e.hasPrimitiveValue()) { 759 x.addText(displayDateTime((DateType) e)); 760 } 761 } 762 763 public void renderDateTime(XhtmlNode x, String s) { 764 if (s != null) { 765 DateTimeType dt = new DateTimeType(s); 766 x.addText(displayDateTime(dt)); 767 } 768 } 769 770 protected void renderUri(XhtmlNode x, UriType uri) { 771 if (uri.getValue().startsWith("mailto:")) { 772 x.ah(uri.getValue()).addText(uri.getValue().substring(7)); 773 } else { 774 Resource r = context.getContext().fetchResource(Resource.class, uri.getValue()); 775 if (r != null && r.getWebPath() != null) { 776 if (r instanceof CanonicalResource) { 777 x.ah(r.getWebPath()).addText(((CanonicalResource) r).present()); 778 } else { 779 x.ah(r.getWebPath()).addText(uri.getValue()); 780 } 781 } else if (Utilities.isAbsoluteUrlLinkable(uri.getValue()) && !(uri instanceof IdType)) { 782 x.ah(uri.getValue()).addText(uri.getValue()); 783 } else { 784 x.addText(uri.getValue()); 785 } 786 } 787 } 788 789 protected void renderUri(XhtmlNode x, UriType uri, String path, String id, Resource src) { 790 if (isCanonical(path)) { 791 x.code().tx(uri.getValue()); 792 } else { 793 String url = uri.getValue(); 794 if (url == null) { 795 x.b().tx(uri.getValue()); 796 } else if (uri.getValue().startsWith("mailto:")) { 797 x.ah(uri.getValue()).addText(uri.getValue().substring(7)); 798 } else { 799 Resource target = context.getContext().fetchResource(Resource.class, uri.getValue(), src); 800 if (target != null && target.hasWebPath()) { 801 String title = target instanceof CanonicalResource ? ((CanonicalResource) target).present() : uri.getValue(); 802 x.ah(target.getWebPath()).addText(title); 803 } else if (uri.getValue().contains("|")) { 804 x.ah(uri.getValue().substring(0, uri.getValue().indexOf("|"))).addText(uri.getValue()); 805 } else if (url.startsWith("http:") || url.startsWith("https:") || url.startsWith("ftp:")) { 806 x.ah(uri.getValue()).addText(uri.getValue()); 807 } else { 808 x.code().addText(uri.getValue()); 809 } 810 } 811 } 812 } 813 814 protected void renderAnnotation(XhtmlNode x, Annotation annot) { 815 renderAnnotation(x, annot, false); 816 } 817 818 protected void renderAnnotation(XhtmlNode x, Annotation a, boolean showCodeDetails) throws FHIRException { 819 StringBuilder b = new StringBuilder(); 820 if (a.hasText()) { 821 b.append(a.getText()); 822 } 823 824 if (a.hasText() && (a.hasAuthor() || a.hasTimeElement())) { 825 b.append(" ("); 826 } 827 828 if (a.hasAuthor()) { 829 b.append("By "); 830 if (a.hasAuthorReference()) { 831 b.append(a.getAuthorReference().getReference()); 832 } else if (a.hasAuthorStringType()) { 833 b.append(a.getAuthorStringType().getValue()); 834 } 835 } 836 837 838 if (a.hasTimeElement()) { 839 if (b.length() > 0) { 840 b.append(" "); 841 } 842 b.append("@").append(a.getTimeElement().toHumanDisplay()); 843 } 844 if (a.hasText() && (a.hasAuthor() || a.hasTimeElement())) { 845 b.append(")"); 846 } 847 848 849 x.addText(b.toString()); 850 } 851 852 public String displayCoding(Coding c) { 853 String s = ""; 854 if (context.isTechnicalMode()) { 855 s = c.getDisplay(); 856 if (Utilities.noString(s)) { 857 s = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); 858 } 859 if (Utilities.noString(s)) { 860 s = displayCodeTriple(c.getSystem(), c.getVersion(), c.getCode()); 861 } else if (c.hasSystem()) { 862 s = s + " ("+displayCodeTriple(c.getSystem(), c.getVersion(), c.getCode())+")"; 863 } else if (c.hasCode()) { 864 s = s + " ("+c.getCode()+")"; 865 } 866 } else { 867 if (c.hasDisplayElement()) 868 return c.getDisplay(); 869 if (Utilities.noString(s)) 870 s = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); 871 if (Utilities.noString(s)) 872 s = c.getCode(); 873 } 874 return s; 875 } 876 877 private String displayCodeSource(String system, String version) { 878 String s = displaySystem(system); 879 if (version != null) { 880 s = s + "["+describeVersion(version)+"]"; 881 } 882 return s; 883 } 884 885 private String displayCodeTriple(String system, String version, String code) { 886 if (system == null) { 887 if (code == null) { 888 return ""; 889 } else { 890 return "#"+code; 891 } 892 } else { 893 String s = displayCodeSource(system, version); 894 if (code != null) { 895 s = s + "#"+code; 896 } 897 return s; 898 } 899 } 900 901 public String displayCoding(List<Coding> list) { 902 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 903 for (Coding c : list) { 904 b.append(displayCoding(c)); 905 } 906 return b.toString(); 907 } 908 909 protected void renderCoding(XhtmlNode x, Coding c) { 910 renderCoding(x, c, false); 911 } 912 913 protected void renderCoding(HierarchicalTableGenerator gen, List<Piece> pieces, Coding c) { 914 if (c.isEmpty()) { 915 return; 916 } 917 918 String url = getLinkForSystem(c.getSystem(), c.getVersion()); 919 String name = displayCodeSource(c.getSystem(), c.getVersion()); 920 if (!Utilities.noString(url)) { 921 pieces.add(gen.new Piece(url, name, c.getSystem()+(c.hasVersion() ? "#"+c.getVersion() : ""))); 922 } else { 923 pieces.add(gen.new Piece(null, name, c.getSystem()+(c.hasVersion() ? "#"+c.getVersion() : ""))); 924 } 925 pieces.add(gen.new Piece(null, "#"+c.getCode(), null)); 926 String s = c.getDisplay(); 927 if (Utilities.noString(s)) { 928 s = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); 929 } 930 if (!Utilities.noString(s)) { 931 pieces.add(gen.new Piece(null, " \""+s+"\"", null)); 932 } 933 } 934 935 private String getLinkForSystem(String system, String version) { 936 if ("http://snomed.info/sct".equals(system)) { 937 return "https://browser.ihtsdotools.org/"; 938 } else if ("http://loinc.org".equals(system)) { 939 return "https://loinc.org/"; 940 } else if ("http://unitsofmeasure.org".equals(system)) { 941 return "http://ucum.org"; 942 } else { 943 String url = system; 944 if (version != null) { 945 url = url + "|"+version; 946 } 947 CodeSystem cs = context.getWorker().fetchCodeSystem(url); 948 if (cs != null && cs.hasWebPath()) { 949 return cs.getWebPath(); 950 } 951 return null; 952 } 953 } 954 955 protected String getLinkForCode(String system, String version, String code) { 956 if ("http://snomed.info/sct".equals(system)) { 957 if (!Utilities.noString(code)) { 958 return "http://snomed.info/id/"+code; 959 } else { 960 return "https://browser.ihtsdotools.org/"; 961 } 962 } else if ("http://loinc.org".equals(system)) { 963 if (!Utilities.noString(code)) { 964 return "https://loinc.org/"+code; 965 } else { 966 return "https://loinc.org/"; 967 } 968 } else if ("http://www.nlm.nih.gov/research/umls/rxnorm".equals(system)) { 969 if (!Utilities.noString(code)) { 970 return "https://mor.nlm.nih.gov/RxNav/search?searchBy=RXCUI&searchTerm="+code; 971 } else { 972 return "https://www.nlm.nih.gov/research/umls/rxnorm/index.html"; 973 } 974 } else if ("urn:iso:std:iso:3166".equals(system)) { 975 if (!Utilities.noString(code)) { 976 return "https://en.wikipedia.org/wiki/ISO_3166-2:"+code; 977 } else { 978 return "https://en.wikipedia.org/wiki/ISO_3166-2"; 979 } 980 } else { 981 CodeSystem cs = context.getWorker().fetchCodeSystem(system, version); 982 if (cs != null && cs.hasWebPath()) { 983 if (!Utilities.noString(code)) { 984 return cs.getWebPath()+"#"+cs.getId()+"-"+Utilities.nmtokenize(code); 985 } else { 986 return cs.getWebPath(); 987 } 988 } 989 } 990 return null; 991 } 992 993 public CodeResolution resolveCode(String system, String code) { 994 return resolveCode(new Coding().setSystem(system).setCode(code)); 995 } 996 997 public CodeResolution resolveCode(Coding c) { 998 String systemName; 999 String systemLink; 1000 String link; 1001 String display = null; 1002 String hint; 1003 1004 if (c.hasDisplayElement()) 1005 display = c.getDisplay(); 1006 if (Utilities.noString(display)) 1007 display = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); 1008 if (Utilities.noString(display)) { 1009 display = c.getCode(); 1010 } 1011 1012 CodeSystem cs = context.getWorker().fetchCodeSystem(c.getSystem()); 1013 systemLink = cs != null ? cs.getWebPath() : null; 1014 systemName = cs != null ? cs.present() : describeSystem(c.getSystem()); 1015 link = getLinkForCode(c.getSystem(), c.getVersion(), c.getCode()); 1016 1017 hint = systemName+": "+display+(c.hasVersion() ? " (version = "+c.getVersion()+")" : ""); 1018 return new CodeResolution(systemName, systemLink, link, display, hint); 1019 } 1020 1021 public CodeResolution resolveCode(CodeableConcept code) { 1022 if (code.hasCoding()) { 1023 return resolveCode(code.getCodingFirstRep()); 1024 } else { 1025 return new CodeResolution(null, null, null, code.getText(), code.getText()); 1026 } 1027 } 1028 protected void renderCodingWithDetails(XhtmlNode x, Coding c) { 1029 String s = ""; 1030 if (c.hasDisplayElement()) 1031 s = c.getDisplay(); 1032 if (Utilities.noString(s)) 1033 s = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); 1034 1035 CodeSystem cs = context.getWorker().fetchCodeSystem(c.getSystem()); 1036 1037 String sn = cs != null ? cs.present() : describeSystem(c.getSystem()); 1038 String link = getLinkForCode(c.getSystem(), c.getVersion(), c.getCode()); 1039 if (link != null) { 1040 x.ah(link).tx(sn); 1041 } else { 1042 x.tx(sn); 1043 } 1044 1045 x.tx(" "); 1046 x.tx(c.getCode()); 1047 if (!Utilities.noString(s)) { 1048 x.tx(": "); 1049 x.tx(s); 1050 } 1051 if (c.hasVersion()) { 1052 x.tx(" (version = "+c.getVersion()+")"); 1053 } 1054 } 1055 1056 protected void renderCoding(XhtmlNode x, Coding c, boolean showCodeDetails) { 1057 String s = ""; 1058 if (c.hasDisplayElement()) 1059 s = c.getDisplay(); 1060 if (Utilities.noString(s)) 1061 s = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); 1062 1063 if (Utilities.noString(s)) 1064 s = c.getCode(); 1065 1066 if (showCodeDetails) { 1067 x.addText(s+" (Details: "+TerminologyRenderer.describeSystem(c.getSystem())+" code "+c.getCode()+" = '"+lookupCode(c.getSystem(), c.getVersion(), c.getCode())+"', stated as '"+c.getDisplay()+"')"); 1068 } else 1069 x.span(null, "{"+c.getSystem()+" "+c.getCode()+"}").addText(s); 1070 } 1071 1072 public String displayCodeableConcept(CodeableConcept cc) { 1073 String s = cc.getText(); 1074 if (Utilities.noString(s)) { 1075 for (Coding c : cc.getCoding()) { 1076 if (c.hasDisplayElement()) { 1077 s = c.getDisplay(); 1078 break; 1079 } 1080 } 1081 } 1082 if (Utilities.noString(s)) { 1083 // still? ok, let's try looking it up 1084 for (Coding c : cc.getCoding()) { 1085 if (c.hasCode() && c.hasSystem()) { 1086 s = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); 1087 if (!Utilities.noString(s)) 1088 break; 1089 } 1090 } 1091 } 1092 1093 if (Utilities.noString(s)) { 1094 if (cc.getCoding().isEmpty()) 1095 s = ""; 1096 else 1097 s = cc.getCoding().get(0).getCode(); 1098 } 1099 return s; 1100 } 1101 1102 protected void renderCodeableConcept(XhtmlNode x, CodeableConcept cc) throws FHIRFormatError, DefinitionException, IOException { 1103 renderCodeableConcept(x, cc, false); 1104 } 1105 1106 protected void renderCodeableReference(XhtmlNode x, CodeableReference e, boolean showCodeDetails) throws FHIRFormatError, DefinitionException, IOException { 1107 if (e.hasConcept()) { 1108 renderCodeableConcept(x, e.getConcept(), showCodeDetails); 1109 } 1110 if (e.hasReference()) { 1111 renderReference(x, e.getReference()); 1112 } 1113 } 1114 1115 protected void renderCodeableConcept(XhtmlNode x, CodeableConcept cc, boolean showCodeDetails) throws FHIRFormatError, DefinitionException, IOException { 1116 if (cc.isEmpty()) { 1117 return; 1118 } 1119 1120 String s = cc.getText(); 1121 if (Utilities.noString(s)) { 1122 for (Coding c : cc.getCoding()) { 1123 if (c.hasDisplayElement()) { 1124 s = c.getDisplay(); 1125 break; 1126 } 1127 } 1128 } 1129 if (Utilities.noString(s)) { 1130 // still? ok, let's try looking it up 1131 for (Coding c : cc.getCoding()) { 1132 if (c.hasCodeElement() && c.hasSystemElement()) { 1133 s = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); 1134 if (!Utilities.noString(s)) 1135 break; 1136 } 1137 } 1138 } 1139 1140 if (Utilities.noString(s)) { 1141 if (cc.getCoding().isEmpty()) 1142 s = ""; 1143 else 1144 s = cc.getCoding().get(0).getCode(); 1145 } 1146 1147 if (showCodeDetails) { 1148 x.addText(s+" "); 1149 XhtmlNode sp = x.span("background: LightGoldenRodYellow; margin: 4px; border: 1px solid khaki", null); 1150 sp.tx(" ("); 1151 boolean first = true; 1152 for (Coding c : cc.getCoding()) { 1153 if (first) { 1154 first = false; 1155 } else { 1156 sp.tx("; "); 1157 } 1158 String url = getLinkForSystem(c.getSystem(), c.getVersion()); 1159 if (url != null) { 1160 sp.ah(url).tx(displayCodeSource(c.getSystem(), c.getVersion())); 1161 } else { 1162 sp.tx(displayCodeSource(c.getSystem(), c.getVersion())); 1163 } 1164 if (c.hasCode()) { 1165 sp.tx("#"+c.getCode()); 1166 } 1167 if (c.hasDisplay() && !s.equals(c.getDisplay())) { 1168 sp.tx(" \""+c.getDisplay()+"\""); 1169 } 1170 } 1171 if (hasRenderableExtensions(cc)) { 1172 if (!first) { 1173 sp.tx("; "); 1174 } 1175 renderExtensionsInText(sp, cc, ";"); 1176 } 1177 sp.tx(")"); 1178 } else { 1179 1180 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1181 for (Coding c : cc.getCoding()) { 1182 if (c.hasCodeElement() && c.hasSystemElement()) { 1183 b.append("{"+c.getSystem()+" "+c.getCode()+"}"); 1184 } 1185 } 1186 1187 x.span(null, "Codes: "+b.toString()).addText(s); 1188 } 1189 } 1190 1191 private String displayIdentifier(Identifier ii) { 1192 String s = Utilities.noString(ii.getValue()) ? "?ngen-9?" : ii.getValue(); 1193 1194 if (ii.hasType()) { 1195 if (ii.getType().hasText()) 1196 s = ii.getType().getText()+":\u00A0"+s; 1197 else if (ii.getType().hasCoding() && ii.getType().getCoding().get(0).hasDisplay()) 1198 s = ii.getType().getCoding().get(0).getDisplay()+": "+s; 1199 else if (ii.getType().hasCoding() && ii.getType().getCoding().get(0).hasCode()) 1200 s = lookupCode(ii.getType().getCoding().get(0).getSystem(), ii.getType().getCoding().get(0).getVersion(), ii.getType().getCoding().get(0).getCode())+": "+s; 1201 } else { 1202 s = "id:\u00A0"+s; 1203 } 1204 1205 if (ii.hasUse() || ii.hasPeriod()) { 1206 s = s + "\u00A0("; 1207 if (ii.hasUse()) { 1208 s = s + "use:\u00A0"+ii.getUse().toString(); 1209 } 1210 if (ii.hasUse() && ii.hasPeriod()) { 1211 s = s + ",\u00A0"; 1212 } 1213 if (ii.hasPeriod()) { 1214 s = s + "period:\u00A0"+displayPeriod(ii.getPeriod()); 1215 } 1216 s = s + ")"; 1217 } 1218 return s; 1219 } 1220 1221 protected void renderIdentifier(XhtmlNode x, Identifier ii) { 1222 x.addText(displayIdentifier(ii)); 1223 } 1224 1225 public static String displayHumanName(HumanName name) { 1226 StringBuilder s = new StringBuilder(); 1227 if (name.hasText()) 1228 s.append(name.getText()); 1229 else { 1230 for (StringType p : name.getGiven()) { 1231 s.append(p.getValue()); 1232 s.append(" "); 1233 } 1234 if (name.hasFamily()) { 1235 s.append(name.getFamily()); 1236 s.append(" "); 1237 } 1238 } 1239 if (name.hasUse() && name.getUse() != NameUse.USUAL) 1240 s.append("("+name.getUse().toString()+")"); 1241 return s.toString(); 1242 } 1243 1244 1245 protected void renderHumanName(XhtmlNode x, HumanName name) { 1246 x.addText(displayHumanName(name)); 1247 } 1248 1249 private String displayAddress(Address address) { 1250 StringBuilder s = new StringBuilder(); 1251 if (address.hasText()) 1252 s.append(address.getText()); 1253 else { 1254 for (StringType p : address.getLine()) { 1255 s.append(p.getValue()); 1256 s.append(" "); 1257 } 1258 if (address.hasCity()) { 1259 s.append(address.getCity()); 1260 s.append(" "); 1261 } 1262 if (address.hasState()) { 1263 s.append(address.getState()); 1264 s.append(" "); 1265 } 1266 1267 if (address.hasPostalCode()) { 1268 s.append(address.getPostalCode()); 1269 s.append(" "); 1270 } 1271 1272 if (address.hasCountry()) { 1273 s.append(address.getCountry()); 1274 s.append(" "); 1275 } 1276 } 1277 if (address.hasUse()) 1278 s.append("("+address.getUse().toString()+")"); 1279 return s.toString(); 1280 } 1281 1282 protected void renderAddress(XhtmlNode x, Address address) { 1283 x.addText(displayAddress(address)); 1284 } 1285 1286 1287 public static String displayContactPoint(ContactPoint contact) { 1288 StringBuilder s = new StringBuilder(); 1289 s.append(describeSystem(contact.getSystem())); 1290 if (Utilities.noString(contact.getValue())) 1291 s.append("-unknown-"); 1292 else 1293 s.append(contact.getValue()); 1294 if (contact.hasUse()) 1295 s.append("("+contact.getUse().toString()+")"); 1296 return s.toString(); 1297 } 1298 1299 protected String getLocalizedBigDecimalValue(BigDecimal input, Currency c) { 1300 NumberFormat numberFormat = NumberFormat.getNumberInstance(context.getLocale()); 1301 numberFormat.setGroupingUsed(true); 1302 numberFormat.setMaximumFractionDigits(c.getDefaultFractionDigits()); 1303 numberFormat.setMinimumFractionDigits(c.getDefaultFractionDigits()); 1304 return numberFormat.format(input); 1305} 1306 1307 protected void renderMoney(XhtmlNode x, Money money) { 1308 if (x.getName().equals("blockquote")) { 1309 x = x.para(); 1310 } 1311 Currency c = Currency.getInstance(money.getCurrency()); 1312 if (c != null) { 1313 XhtmlNode s = x.span(null, c.getDisplayName()); 1314 s.tx(c.getSymbol(context.getLocale())); 1315 s.tx(getLocalizedBigDecimalValue(money.getValue(), c)); 1316 x.tx(" ("+c.getCurrencyCode()+")"); 1317 } else { 1318 x.tx(money.getCurrency()); 1319 x.tx(money.getValue().toPlainString()); 1320 } 1321 } 1322 1323 protected void renderExpression(XhtmlNode x, Expression expr) { 1324 // there's two parts: what the expression is, and how it's described. 1325 // we start with what it is, and then how it's described 1326 XhtmlNode p = x; 1327 if (p.getName().equals("blockquote")) { 1328 p = p.para(); 1329 } 1330 if (expr.hasExpression()) { 1331 if (expr.hasReference()) { 1332 p = x.ah(expr.getReference()); 1333 } 1334 XhtmlNode c = p; 1335 if (expr.hasLanguage()) { 1336 c = c.span(null, expr.getLanguage()); 1337 } 1338 c.code().tx(expr.getExpression()); 1339 } else if (expr.hasReference()) { 1340 p.ah(expr.getReference()).tx("source"); 1341 } 1342 if (expr.hasName() || expr.hasDescription()) { 1343 p.tx("("); 1344 if (expr.hasName()) { 1345 p.b().tx(expr.getName()); 1346 } 1347 if (expr.hasDescription()) { 1348 p.tx("\""); 1349 p.tx(expr.getDescription()); 1350 p.tx("\""); 1351 } 1352 p.tx(")"); 1353 } 1354 } 1355 1356 1357 protected void renderContactPoint(XhtmlNode x, ContactPoint contact) { 1358 if (contact != null) { 1359 if (!contact.hasSystem()) { 1360 x.addText(displayContactPoint(contact)); 1361 } else { 1362 switch (contact.getSystem()) { 1363 case EMAIL: 1364 x.ah("mailto:"+contact.getValue()).tx(contact.getValue()); 1365 break; 1366 case FAX: 1367 x.addText(displayContactPoint(contact)); 1368 break; 1369 case NULL: 1370 x.addText(displayContactPoint(contact)); 1371 break; 1372 case OTHER: 1373 x.addText(displayContactPoint(contact)); 1374 break; 1375 case PAGER: 1376 x.addText(displayContactPoint(contact)); 1377 break; 1378 case PHONE: 1379 if (contact.hasValue() && contact.getValue().startsWith("+")) { 1380 x.ah("tel:"+contact.getValue().replace(" ", "")).tx(contact.getValue()); 1381 } else { 1382 x.addText(displayContactPoint(contact)); 1383 } 1384 break; 1385 case SMS: 1386 x.addText(displayContactPoint(contact)); 1387 break; 1388 case URL: 1389 x.ah(contact.getValue()).tx(contact.getValue()); 1390 break; 1391 default: 1392 break; 1393 } 1394 } 1395 } 1396 } 1397 1398 protected void displayContactPoint(XhtmlNode p, ContactPoint c) { 1399 if (c != null) { 1400 if (c.getSystem() == ContactPointSystem.PHONE) { 1401 p.tx("Phone: "+c.getValue()); 1402 } else if (c.getSystem() == ContactPointSystem.FAX) { 1403 p.tx("Fax: "+c.getValue()); 1404 } else if (c.getSystem() == ContactPointSystem.EMAIL) { 1405 p.tx(c.getValue()); 1406 } else if (c.getSystem() == ContactPointSystem.URL) { 1407 if (c.getValue().length() > 30) { 1408 p.addText(c.getValue().substring(0, 30)+"..."); 1409 } else { 1410 p.addText(c.getValue()); 1411 } 1412 } 1413 } 1414 } 1415 1416 protected void addTelecom(XhtmlNode p, ContactPoint c) { 1417 if (c.getSystem() == ContactPointSystem.PHONE) { 1418 p.tx("Phone: "+c.getValue()); 1419 } else if (c.getSystem() == ContactPointSystem.FAX) { 1420 p.tx("Fax: "+c.getValue()); 1421 } else if (c.getSystem() == ContactPointSystem.EMAIL) { 1422 p.ah( "mailto:"+c.getValue()).addText(c.getValue()); 1423 } else if (c.getSystem() == ContactPointSystem.URL) { 1424 if (c.getValue().length() > 30) 1425 p.ah(c.getValue()).addText(c.getValue().substring(0, 30)+"..."); 1426 else 1427 p.ah(c.getValue()).addText(c.getValue()); 1428 } 1429 } 1430 private static String describeSystem(ContactPointSystem system) { 1431 if (system == null) 1432 return ""; 1433 switch (system) { 1434 case PHONE: return "ph: "; 1435 case FAX: return "fax: "; 1436 default: 1437 return ""; 1438 } 1439 } 1440 1441 protected String displayQuantity(Quantity q) { 1442 StringBuilder s = new StringBuilder(); 1443 1444 s.append(q.hasValue() ? q.getValue() : "?"); 1445 if (q.hasUnit()) 1446 s.append(" ").append(q.getUnit()); 1447 else if (q.hasCode()) 1448 s.append(" ").append(q.getCode()); 1449 1450 return s.toString(); 1451 } 1452 1453 protected void renderQuantity(XhtmlNode x, Quantity q) { 1454 renderQuantity(x, q, false); 1455 } 1456 1457 protected void renderQuantity(XhtmlNode x, Quantity q, boolean showCodeDetails) { 1458 if (q.hasComparator()) 1459 x.addText(q.getComparator().toCode()); 1460 if (q.hasValue()) { 1461 x.addText(q.getValue().toString()); 1462 } 1463 if (q.hasUnit()) 1464 x.tx(" "+q.getUnit()); 1465 else if (q.hasCode() && q.hasSystem()) { 1466 // 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 1467 if (q.hasSystem() && q.getSystem().equals("http://unitsofmeasure.org")) 1468 x.tx(" "+q.getCode()); 1469 else 1470 x.tx("(unit "+q.getCode()+" from "+q.getSystem()+")"); 1471 } 1472 if (showCodeDetails && q.hasCode()) { 1473 x.span("background: LightGoldenRodYellow", null).tx(" (Details: "+TerminologyRenderer.describeSystem(q.getSystem())+" code "+q.getCode()+" = '"+lookupCode(q.getSystem(), null, q.getCode())+"')"); 1474 } 1475 } 1476 1477 public String displayRange(Range q) { 1478 if (!q.hasLow() && !q.hasHigh()) 1479 return "?"; 1480 1481 StringBuilder b = new StringBuilder(); 1482 1483 boolean sameUnits = (q.getLow().hasUnit() && q.getHigh().hasUnit() && q.getLow().getUnit().equals(q.getHigh().getUnit())) 1484 || (q.getLow().hasCode() && q.getHigh().hasCode() && q.getLow().getCode().equals(q.getHigh().getCode())); 1485 String low = "?"; 1486 if (q.hasLow() && q.getLow().hasValue()) 1487 low = sameUnits ? q.getLow().getValue().toString() : displayQuantity(q.getLow()); 1488 String high = displayQuantity(q.getHigh()); 1489 if (high.isEmpty()) 1490 high = "?"; 1491 b.append(low).append("\u00A0to\u00A0").append(high); 1492 return b.toString(); 1493 } 1494 1495 protected void renderRange(XhtmlNode x, Range q) { 1496 if (q.hasLow()) 1497 x.addText(q.getLow().getValue().toString()); 1498 else 1499 x.tx("?"); 1500 x.tx("-"); 1501 if (q.hasHigh()) 1502 x.addText(q.getHigh().getValue().toString()); 1503 else 1504 x.tx("?"); 1505 if (q.getLow().hasUnit()) 1506 x.tx(" "+q.getLow().getUnit()); 1507 } 1508 1509 public String displayPeriod(Period p) { 1510 String s = !p.hasStart() ? "(?)" : displayDateTime(p.getStartElement()); 1511 s = s + " --> "; 1512 return s + (!p.hasEnd() ? "(ongoing)" : displayDateTime(p.getEndElement())); 1513 } 1514 1515 public void renderPeriod(XhtmlNode x, Period p) { 1516 x.addText(!p.hasStart() ? "??" : displayDateTime(p.getStartElement())); 1517 x.tx(" --> "); 1518 x.addText(!p.hasEnd() ? "(ongoing)" : displayDateTime(p.getEndElement())); 1519 } 1520 1521 public void renderUsageContext(XhtmlNode x, UsageContext u) throws FHIRFormatError, DefinitionException, IOException { 1522 renderCoding(x, u.getCode()); 1523 x.tx(": "); 1524 render(x, u.getValue()); 1525 } 1526 1527 public void renderDataRequirement(XhtmlNode x, DataRequirement dr) throws FHIRFormatError, DefinitionException, IOException { 1528 XhtmlNode tbl = x.table("grid"); 1529 XhtmlNode tr = tbl.tr(); 1530 XhtmlNode td = tr.td().colspan("2"); 1531 td.b().tx("Type"); 1532 td.tx(": "); 1533 StructureDefinition sd = context.getWorker().fetchTypeDefinition(dr.getType().toCode()); 1534 if (sd != null && sd.hasWebPath()) { 1535 td.ah(sd.getWebPath()).tx(dr.getType().toCode()); 1536 } else { 1537 td.tx(dr.getType().toCode()); 1538 } 1539 if (dr.hasProfile()) { 1540 td.tx(" ("); 1541 boolean first = true; 1542 for (CanonicalType p : dr.getProfile()) { 1543 if (first) first = false; else td.tx(" | "); 1544 sd = context.getWorker().fetchResource(StructureDefinition.class, p.getValue()); 1545 if (sd != null && sd.hasWebPath()) { 1546 td.ah(sd.getWebPath()).tx(sd.present()); 1547 } else { 1548 td.tx(p.asStringValue()); 1549 } 1550 } 1551 td.tx(")"); 1552 } 1553 if (dr.hasSubject()) { 1554 tr = tbl.tr(); 1555 td = tr.td().colspan("2"); 1556 td.b().tx("Subject"); 1557 if (dr.hasSubjectReference()) { 1558 renderReference(td, dr.getSubjectReference()); 1559 } else { 1560 renderCodeableConcept(td, dr.getSubjectCodeableConcept()); 1561 } 1562 } 1563 if (dr.hasCodeFilter() || dr.hasDateFilter()) { 1564 tr = tbl.tr().backgroundColor("#efefef"); 1565 tr.td().tx("Filter"); 1566 tr.td().tx("Value"); 1567 } 1568 for (DataRequirementCodeFilterComponent cf : dr.getCodeFilter()) { 1569 tr = tbl.tr(); 1570 if (cf.hasPath()) { 1571 tr.td().tx(cf.getPath()); 1572 } else { 1573 tr.td().tx("Search on " +cf.getSearchParam()); 1574 } 1575 if (cf.hasValueSet()) { 1576 td = tr.td(); 1577 td.tx("In ValueSet "); 1578 render(td, cf.getValueSetElement()); 1579 } else { 1580 boolean first = true; 1581 td = tr.td(); 1582 td.tx("One of these codes: "); 1583 for (Coding c : cf.getCode()) { 1584 if (first) first = false; else td.tx(", "); 1585 render(td, c); 1586 } 1587 } 1588 } 1589 for (DataRequirementDateFilterComponent cf : dr.getDateFilter()) { 1590 tr = tbl.tr(); 1591 if (cf.hasPath()) { 1592 tr.td().tx(cf.getPath()); 1593 } else { 1594 tr.td().tx("Search on " +cf.getSearchParam()); 1595 } 1596 render(tr.td(), cf.getValue()); 1597 } 1598 if (dr.hasSort() || dr.hasLimit()) { 1599 tr = tbl.tr(); 1600 td = tr.td().colspan("2"); 1601 if (dr.hasLimit()) { 1602 td.b().tx("Limit"); 1603 td.tx(": "); 1604 td.tx(dr.getLimit()); 1605 if (dr.hasSort()) { 1606 td.tx(", "); 1607 } 1608 } 1609 if (dr.hasSort()) { 1610 td.b().tx("Sort"); 1611 td.tx(": "); 1612 boolean first = true; 1613 for (DataRequirementSortComponent p : dr.getSort()) { 1614 if (first) first = false; else td.tx(" | "); 1615 td.tx(p.getDirection() == SortDirection.ASCENDING ? "+" : "-"); 1616 td.tx(p.getPath()); 1617 } 1618 } 1619 } 1620 } 1621 1622 1623 private String displayTiming(Timing s) throws FHIRException { 1624 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1625 if (s.hasCode()) 1626 b.append("Code: "+displayCodeableConcept(s.getCode())); 1627 1628 if (s.getEvent().size() > 0) { 1629 CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder(); 1630 for (DateTimeType p : s.getEvent()) { 1631 if (p.hasValue()) { 1632 c.append(displayDateTime(p)); 1633 } else if (!renderExpression(c, p)) { 1634 c.append("??"); 1635 } 1636 } 1637 b.append("Events: "+ c.toString()); 1638 } 1639 1640 if (s.hasRepeat()) { 1641 TimingRepeatComponent rep = s.getRepeat(); 1642 if (rep.hasBoundsPeriod() && rep.getBoundsPeriod().hasStart()) 1643 b.append("Starting "+displayDateTime(rep.getBoundsPeriod().getStartElement())); 1644 if (rep.hasCount()) 1645 b.append("Count "+Integer.toString(rep.getCount())+" times"); 1646 if (rep.hasDuration()) 1647 b.append("Duration "+rep.getDuration().toPlainString()+displayTimeUnits(rep.getPeriodUnit())); 1648 1649 if (rep.hasWhen()) { 1650 String st = ""; 1651 if (rep.hasOffset()) { 1652 st = Integer.toString(rep.getOffset())+"min "; 1653 } 1654 b.append(st); 1655 for (Enumeration<EventTiming> wh : rep.getWhen()) 1656 b.append(displayEventCode(wh.getValue())); 1657 } else { 1658 String st = ""; 1659 if (!rep.hasFrequency() || (!rep.hasFrequencyMax() && rep.getFrequency() == 1) ) 1660 st = "Once"; 1661 else { 1662 st = Integer.toString(rep.getFrequency()); 1663 if (rep.hasFrequencyMax()) 1664 st = st + "-"+Integer.toString(rep.getFrequency()); 1665 } 1666 if (rep.hasPeriod()) { 1667 st = st + " per "+rep.getPeriod().toPlainString(); 1668 if (rep.hasPeriodMax()) 1669 st = st + "-"+rep.getPeriodMax().toPlainString(); 1670 st = st + " "+displayTimeUnits(rep.getPeriodUnit()); 1671 } 1672 b.append(st); 1673 } 1674 if (rep.hasBoundsPeriod() && rep.getBoundsPeriod().hasEnd()) 1675 b.append("Until "+displayDateTime(rep.getBoundsPeriod().getEndElement())); 1676 } 1677 return b.toString(); 1678 } 1679 1680 private boolean renderExpression(CommaSeparatedStringBuilder c, PrimitiveType p) { 1681 Extension exp = p.getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/cqf-expression"); 1682 if (exp == null) { 1683 return false; 1684 } 1685 c.append(exp.getValueExpression().getExpression()); 1686 return true; 1687 } 1688 1689 private String displayEventCode(EventTiming when) { 1690 switch (when) { 1691 case C: return "at meals"; 1692 case CD: return "at lunch"; 1693 case CM: return "at breakfast"; 1694 case CV: return "at dinner"; 1695 case AC: return "before meals"; 1696 case ACD: return "before lunch"; 1697 case ACM: return "before breakfast"; 1698 case ACV: return "before dinner"; 1699 case HS: return "before sleeping"; 1700 case PC: return "after meals"; 1701 case PCD: return "after lunch"; 1702 case PCM: return "after breakfast"; 1703 case PCV: return "after dinner"; 1704 case WAKE: return "after waking"; 1705 default: return "?ngen-6?"; 1706 } 1707 } 1708 1709 private String displayTimeUnits(UnitsOfTime units) { 1710 if (units == null) 1711 return "?ngen-7?"; 1712 switch (units) { 1713 case A: return "years"; 1714 case D: return "days"; 1715 case H: return "hours"; 1716 case MIN: return "minutes"; 1717 case MO: return "months"; 1718 case S: return "seconds"; 1719 case WK: return "weeks"; 1720 default: return "?ngen-8?"; 1721 } 1722 } 1723 1724 protected void renderTiming(XhtmlNode x, Timing s) throws FHIRException { 1725 x.addText(displayTiming(s)); 1726 } 1727 1728 1729 private String displaySampledData(SampledData s) { 1730 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1731 if (s.hasOrigin()) 1732 b.append("Origin: "+displayQuantity(s.getOrigin())); 1733 1734 if (s.hasInterval()) { 1735 b.append("Interval: "+s.getInterval().toString()); 1736 1737 if (s.hasIntervalUnit()) 1738 b.append(s.getIntervalUnit().toString()); 1739 } 1740 1741 if (s.hasFactor()) 1742 b.append("Factor: "+s.getFactor().toString()); 1743 1744 if (s.hasLowerLimit()) 1745 b.append("Lower: "+s.getLowerLimit().toString()); 1746 1747 if (s.hasUpperLimit()) 1748 b.append("Upper: "+s.getUpperLimit().toString()); 1749 1750 if (s.hasDimensions()) 1751 b.append("Dimensions: "+s.getDimensions()); 1752 1753 if (s.hasData()) 1754 b.append("Data: "+s.getData()); 1755 1756 return b.toString(); 1757 } 1758 1759 protected void renderSampledData(XhtmlNode x, SampledData sampledData) { 1760 x.addText(displaySampledData(sampledData)); 1761 } 1762 1763 public RenderingContext getContext() { 1764 return context; 1765 } 1766 1767 1768 public XhtmlNode makeExceptionXhtml(Exception e, String function) { 1769 XhtmlNode xn; 1770 xn = new XhtmlNode(NodeType.Element, "div"); 1771 XhtmlNode p = xn.para(); 1772 p.b().tx("Exception "+function+": "+e.getMessage()); 1773 p.addComment(getStackTrace(e)); 1774 return xn; 1775 } 1776 1777 private String getStackTrace(Exception e) { 1778 StringBuilder b = new StringBuilder(); 1779 b.append("\r\n"); 1780 for (StackTraceElement t : e.getStackTrace()) { 1781 b.append(t.getClassName()+"."+t.getMethodName()+" ("+t.getFileName()+":"+t.getLineNumber()); 1782 b.append("\r\n"); 1783 } 1784 return b.toString(); 1785 } 1786 1787 protected String versionFromCanonical(String system) { 1788 if (system == null) { 1789 return null; 1790 } else if (system.contains("|")) { 1791 return system.substring(0, system.indexOf("|")); 1792 } else { 1793 return null; 1794 } 1795 } 1796 1797 protected String systemFromCanonical(String system) { 1798 if (system == null) { 1799 return null; 1800 } else if (system.contains("|")) { 1801 return system.substring(system.indexOf("|")+1); 1802 } else { 1803 return system; 1804 } 1805 } 1806 1807 1808}