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