001package org.hl7.fhir.r5.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.r5.context.IWorkerContext; 022import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult; 023import org.hl7.fhir.r5.model.Address; 024import org.hl7.fhir.r5.model.Annotation; 025import org.hl7.fhir.r5.model.Base; 026import org.hl7.fhir.r5.model.BaseDateTimeType; 027import org.hl7.fhir.r5.model.CanonicalResource; 028import org.hl7.fhir.r5.model.CanonicalType; 029import org.hl7.fhir.r5.model.CodeSystem; 030import org.hl7.fhir.r5.model.CodeableConcept; 031import org.hl7.fhir.r5.model.CodeableReference; 032import org.hl7.fhir.r5.model.Coding; 033import org.hl7.fhir.r5.model.ContactPoint; 034import org.hl7.fhir.r5.model.DataRequirement; 035import org.hl7.fhir.r5.model.DataRequirement.DataRequirementCodeFilterComponent; 036import org.hl7.fhir.r5.model.DataRequirement.DataRequirementDateFilterComponent; 037import org.hl7.fhir.r5.model.DataRequirement.DataRequirementSortComponent; 038import org.hl7.fhir.r5.model.DataRequirement.SortDirection; 039import org.hl7.fhir.r5.model.ContactPoint.ContactPointSystem; 040import org.hl7.fhir.r5.model.DataType; 041import org.hl7.fhir.r5.model.DateTimeType; 042import org.hl7.fhir.r5.model.Enumeration; 043import org.hl7.fhir.r5.model.Expression; 044import org.hl7.fhir.r5.model.Extension; 045import org.hl7.fhir.r5.model.HumanName; 046import org.hl7.fhir.r5.model.HumanName.NameUse; 047import org.hl7.fhir.r5.model.IdType; 048import org.hl7.fhir.r5.model.Identifier; 049import org.hl7.fhir.r5.model.MarkdownType; 050import org.hl7.fhir.r5.model.Money; 051import org.hl7.fhir.r5.model.Period; 052import org.hl7.fhir.r5.model.PrimitiveType; 053import org.hl7.fhir.r5.model.Quantity; 054import org.hl7.fhir.r5.model.Range; 055import org.hl7.fhir.r5.model.Reference; 056import org.hl7.fhir.r5.model.Resource; 057import org.hl7.fhir.r5.model.SampledData; 058import org.hl7.fhir.r5.model.StringType; 059import org.hl7.fhir.r5.model.StructureDefinition; 060import org.hl7.fhir.r5.model.Timing; 061import org.hl7.fhir.r5.model.Timing.EventTiming; 062import org.hl7.fhir.r5.model.Timing.TimingRepeatComponent; 063import org.hl7.fhir.r5.model.Timing.UnitsOfTime; 064import org.hl7.fhir.r5.model.UriType; 065import org.hl7.fhir.r5.model.ValueSet; 066import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; 067import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceDesignationComponent; 068import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper; 069import org.hl7.fhir.r5.renderers.utils.RenderingContext; 070import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; 071import org.hl7.fhir.r5.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(q.hasValue() ? q.getValue() : "?"); 1143 if (q.hasUnit()) 1144 s.append(" ").append(q.getUnit()); 1145 else if (q.hasCode()) 1146 s.append(" ").append(q.getCode()); 1147 1148 return s.toString(); 1149 } 1150 1151 protected void renderQuantity(XhtmlNode x, Quantity q) { 1152 renderQuantity(x, q, false); 1153 } 1154 1155 protected void renderQuantity(XhtmlNode x, Quantity q, boolean showCodeDetails) { 1156 if (q.hasComparator()) 1157 x.addText(q.getComparator().toCode()); 1158 if (q.hasValue()) { 1159 x.addText(q.getValue().toString()); 1160 } 1161 if (q.hasUnit()) 1162 x.tx(" "+q.getUnit()); 1163 else if (q.hasCode() && q.hasSystem()) { 1164 // 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 1165 if (q.hasSystem() && q.getSystem().equals("http://unitsofmeasure.org")) 1166 x.tx(" "+q.getCode()); 1167 else 1168 x.tx("(unit "+q.getCode()+" from "+q.getSystem()+")"); 1169 } 1170 if (showCodeDetails && q.hasCode()) { 1171 x.span("background: LightGoldenRodYellow", null).tx(" (Details: "+TerminologyRenderer.describeSystem(q.getSystem())+" code "+q.getCode()+" = '"+lookupCode(q.getSystem(), null, q.getCode())+"')"); 1172 } 1173 } 1174 1175 public String displayRange(Range q) { 1176 if (!q.hasLow() && !q.hasHigh()) 1177 return "?"; 1178 1179 StringBuilder b = new StringBuilder(); 1180 1181 boolean sameUnits = (q.getLow().hasUnit() && q.getHigh().hasUnit() && q.getLow().getUnit().equals(q.getHigh().getUnit())) 1182 || (q.getLow().hasCode() && q.getHigh().hasCode() && q.getLow().getCode().equals(q.getHigh().getCode())); 1183 String low = "?"; 1184 if (q.hasLow() && q.getLow().hasValue()) 1185 low = sameUnits ? q.getLow().getValue().toString() : displayQuantity(q.getLow()); 1186 String high = displayQuantity(q.getHigh()); 1187 if (high.isEmpty()) 1188 high = "?"; 1189 b.append(low).append("\u00A0to\u00A0").append(high); 1190 return b.toString(); 1191 } 1192 1193 protected void renderRange(XhtmlNode x, Range q) { 1194 if (q.hasLow()) 1195 x.addText(q.getLow().getValue().toString()); 1196 else 1197 x.tx("?"); 1198 x.tx("-"); 1199 if (q.hasHigh()) 1200 x.addText(q.getHigh().getValue().toString()); 1201 else 1202 x.tx("?"); 1203 if (q.getLow().hasUnit()) 1204 x.tx(" "+q.getLow().getUnit()); 1205 } 1206 1207 public String displayPeriod(Period p) { 1208 String s = !p.hasStart() ? "(?)" : displayDateTime(p.getStartElement()); 1209 s = s + " --> "; 1210 return s + (!p.hasEnd() ? "(ongoing)" : displayDateTime(p.getEndElement())); 1211 } 1212 1213 public void renderPeriod(XhtmlNode x, Period p) { 1214 x.addText(!p.hasStart() ? "??" : displayDateTime(p.getStartElement())); 1215 x.tx(" --> "); 1216 x.addText(!p.hasEnd() ? "(ongoing)" : displayDateTime(p.getEndElement())); 1217 } 1218 1219 public void renderDataRequirement(XhtmlNode x, DataRequirement dr) throws FHIRFormatError, DefinitionException, IOException { 1220 XhtmlNode tbl = x.table("grid"); 1221 XhtmlNode tr = tbl.tr(); 1222 XhtmlNode td = tr.td().colspan("2"); 1223 td.b().tx("Type"); 1224 td.tx(": "); 1225 StructureDefinition sd = context.getWorker().fetchTypeDefinition(dr.getType().toCode()); 1226 if (sd != null && sd.hasUserData("path")) { 1227 td.ah(sd.getUserString("path")).tx(dr.getType().toCode()); 1228 } else { 1229 td.tx(dr.getType().toCode()); 1230 } 1231 if (dr.hasProfile()) { 1232 td.tx(" ("); 1233 boolean first = true; 1234 for (CanonicalType p : dr.getProfile()) { 1235 if (first) first = false; else td.tx(" | "); 1236 sd = context.getWorker().fetchResource(StructureDefinition.class, p.getValue()); 1237 if (sd != null && sd.hasUserData("path")) { 1238 td.ah(sd.getUserString("path")).tx(sd.present()); 1239 } else { 1240 td.tx(p.asStringValue()); 1241 } 1242 } 1243 td.tx(")"); 1244 } 1245 if (dr.hasSubject()) { 1246 tr = tbl.tr(); 1247 td = tr.td().colspan("2"); 1248 td.b().tx("Subject"); 1249 if (dr.hasSubjectReference()) { 1250 renderReference(td, dr.getSubjectReference()); 1251 } else { 1252 renderCodeableConcept(td, dr.getSubjectCodeableConcept()); 1253 } 1254 } 1255 if (dr.hasCodeFilter() || dr.hasDateFilter()) { 1256 tr = tbl.tr().backgroundColor("#efefef"); 1257 tr.td().tx("Filter"); 1258 tr.td().tx("Value"); 1259 } 1260 for (DataRequirementCodeFilterComponent cf : dr.getCodeFilter()) { 1261 tr = tbl.tr(); 1262 if (cf.hasPath()) { 1263 tr.td().tx(cf.getPath()); 1264 } else { 1265 tr.td().tx("Search on " +cf.getSearchParam()); 1266 } 1267 if (cf.hasValueSet()) { 1268 td = tr.td(); 1269 td.tx("In ValueSet "); 1270 render(td, cf.getValueSetElement()); 1271 } else { 1272 boolean first = true; 1273 td = tr.td(); 1274 td.tx("One of these codes: "); 1275 for (Coding c : cf.getCode()) { 1276 if (first) first = false; else td.tx(", "); 1277 render(td, c); 1278 } 1279 } 1280 } 1281 for (DataRequirementDateFilterComponent cf : dr.getDateFilter()) { 1282 tr = tbl.tr(); 1283 if (cf.hasPath()) { 1284 tr.td().tx(cf.getPath()); 1285 } else { 1286 tr.td().tx("Search on " +cf.getSearchParam()); 1287 } 1288 render(tr.td(), cf.getValue()); 1289 } 1290 if (dr.hasSort() || dr.hasLimit()) { 1291 tr = tbl.tr(); 1292 td = tr.td().colspan("2"); 1293 if (dr.hasLimit()) { 1294 td.b().tx("Limit"); 1295 td.tx(": "); 1296 td.tx(dr.getLimit()); 1297 if (dr.hasSort()) { 1298 td.tx(", "); 1299 } 1300 } 1301 if (dr.hasSort()) { 1302 td.b().tx("Sort"); 1303 td.tx(": "); 1304 boolean first = true; 1305 for (DataRequirementSortComponent p : dr.getSort()) { 1306 if (first) first = false; else td.tx(" | "); 1307 td.tx(p.getDirection() == SortDirection.ASCENDING ? "+" : "-"); 1308 td.tx(p.getPath()); 1309 } 1310 } 1311 } 1312 } 1313 1314 1315 private String displayTiming(Timing s) throws FHIRException { 1316 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1317 if (s.hasCode()) 1318 b.append("Code: "+displayCodeableConcept(s.getCode())); 1319 1320 if (s.getEvent().size() > 0) { 1321 CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder(); 1322 for (DateTimeType p : s.getEvent()) { 1323 if (p.hasValue()) { 1324 c.append(displayDateTime(p)); 1325 } else if (!renderExpression(c, p)) { 1326 c.append("??"); 1327 } 1328 } 1329 b.append("Events: "+ c.toString()); 1330 } 1331 1332 if (s.hasRepeat()) { 1333 TimingRepeatComponent rep = s.getRepeat(); 1334 if (rep.hasBoundsPeriod() && rep.getBoundsPeriod().hasStart()) 1335 b.append("Starting "+displayDateTime(rep.getBoundsPeriod().getStartElement())); 1336 if (rep.hasCount()) 1337 b.append("Count "+Integer.toString(rep.getCount())+" times"); 1338 if (rep.hasDuration()) 1339 b.append("Duration "+rep.getDuration().toPlainString()+displayTimeUnits(rep.getPeriodUnit())); 1340 1341 if (rep.hasWhen()) { 1342 String st = ""; 1343 if (rep.hasOffset()) { 1344 st = Integer.toString(rep.getOffset())+"min "; 1345 } 1346 b.append("Do "+st); 1347 for (Enumeration<EventTiming> wh : rep.getWhen()) 1348 b.append(displayEventCode(wh.getValue())); 1349 } else { 1350 String st = ""; 1351 if (!rep.hasFrequency() || (!rep.hasFrequencyMax() && rep.getFrequency() == 1) ) 1352 st = "Once"; 1353 else { 1354 st = Integer.toString(rep.getFrequency()); 1355 if (rep.hasFrequencyMax()) 1356 st = st + "-"+Integer.toString(rep.getFrequency()); 1357 } 1358 if (rep.hasPeriod()) { 1359 st = st + " per "+rep.getPeriod().toPlainString(); 1360 if (rep.hasPeriodMax()) 1361 st = st + "-"+rep.getPeriodMax().toPlainString(); 1362 st = st + " "+displayTimeUnits(rep.getPeriodUnit()); 1363 } 1364 b.append("Do "+st); 1365 } 1366 if (rep.hasBoundsPeriod() && rep.getBoundsPeriod().hasEnd()) 1367 b.append("Until "+displayDateTime(rep.getBoundsPeriod().getEndElement())); 1368 } 1369 return b.toString(); 1370 } 1371 1372 private boolean renderExpression(CommaSeparatedStringBuilder c, PrimitiveType p) { 1373 Extension exp = p.getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/cqf-expression"); 1374 if (exp == null) { 1375 return false; 1376 } 1377 c.append(exp.getValueExpression().getExpression()); 1378 return true; 1379 } 1380 1381 private String displayEventCode(EventTiming when) { 1382 switch (when) { 1383 case C: return "at meals"; 1384 case CD: return "at lunch"; 1385 case CM: return "at breakfast"; 1386 case CV: return "at dinner"; 1387 case AC: return "before meals"; 1388 case ACD: return "before lunch"; 1389 case ACM: return "before breakfast"; 1390 case ACV: return "before dinner"; 1391 case HS: return "before sleeping"; 1392 case PC: return "after meals"; 1393 case PCD: return "after lunch"; 1394 case PCM: return "after breakfast"; 1395 case PCV: return "after dinner"; 1396 case WAKE: return "after waking"; 1397 default: return "?ngen-6?"; 1398 } 1399 } 1400 1401 private String displayTimeUnits(UnitsOfTime units) { 1402 if (units == null) 1403 return "?ngen-7?"; 1404 switch (units) { 1405 case A: return "years"; 1406 case D: return "days"; 1407 case H: return "hours"; 1408 case MIN: return "minutes"; 1409 case MO: return "months"; 1410 case S: return "seconds"; 1411 case WK: return "weeks"; 1412 default: return "?ngen-8?"; 1413 } 1414 } 1415 1416 protected void renderTiming(XhtmlNode x, Timing s) throws FHIRException { 1417 x.addText(displayTiming(s)); 1418 } 1419 1420 1421 private String displaySampledData(SampledData s) { 1422 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1423 if (s.hasOrigin()) 1424 b.append("Origin: "+displayQuantity(s.getOrigin())); 1425 1426 if (s.hasPeriod()) 1427 b.append("Period: "+s.getPeriod().toString()); 1428 1429 if (s.hasFactor()) 1430 b.append("Factor: "+s.getFactor().toString()); 1431 1432 if (s.hasLowerLimit()) 1433 b.append("Lower: "+s.getLowerLimit().toString()); 1434 1435 if (s.hasUpperLimit()) 1436 b.append("Upper: "+s.getUpperLimit().toString()); 1437 1438 if (s.hasDimensions()) 1439 b.append("Dimensions: "+s.getDimensions()); 1440 1441 if (s.hasData()) 1442 b.append("Data: "+s.getData()); 1443 1444 return b.toString(); 1445 } 1446 1447 protected void renderSampledData(XhtmlNode x, SampledData sampledData) { 1448 x.addText(displaySampledData(sampledData)); 1449 } 1450 1451 public RenderingContext getContext() { 1452 return context; 1453 } 1454 1455 1456 public XhtmlNode makeExceptionXhtml(Exception e, String function) { 1457 XhtmlNode xn; 1458 xn = new XhtmlNode(NodeType.Element, "div"); 1459 XhtmlNode p = xn.para(); 1460 p.b().tx("Exception "+function+": "+e.getMessage()); 1461 p.addComment(getStackTrace(e)); 1462 return xn; 1463 } 1464 1465 private String getStackTrace(Exception e) { 1466 StringBuilder b = new StringBuilder(); 1467 b.append("\r\n"); 1468 for (StackTraceElement t : e.getStackTrace()) { 1469 b.append(t.getClassName()+"."+t.getMethodName()+" ("+t.getFileName()+":"+t.getLineNumber()); 1470 b.append("\r\n"); 1471 } 1472 return b.toString(); 1473 } 1474 1475 protected String versionFromCanonical(String system) { 1476 if (system == null) { 1477 return null; 1478 } else if (system.contains("|")) { 1479 return system.substring(0, system.indexOf("|")); 1480 } else { 1481 return null; 1482 } 1483 } 1484 1485 protected String systemFromCanonical(String system) { 1486 if (system == null) { 1487 return null; 1488 } else if (system.contains("|")) { 1489 return system.substring(system.indexOf("|")+1); 1490 } else { 1491 return system; 1492 } 1493 } 1494 1495 1496}