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