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