001package org.hl7.fhir.r5.renderers; 002 003import static java.time.temporal.ChronoField.MONTH_OF_YEAR; 004import static java.time.temporal.ChronoField.YEAR; 005 006import java.io.IOException; 007import java.math.BigDecimal; 008import java.text.NumberFormat; 009import java.time.LocalDate; 010import java.time.ZoneId; 011import java.time.ZonedDateTime; 012import java.time.format.DateTimeFormatter; 013import java.time.format.DateTimeFormatterBuilder; 014import java.time.format.FormatStyle; 015import java.time.format.SignStyle; 016import java.util.Currency; 017import java.util.List; 018 019import org.hl7.fhir.exceptions.DefinitionException; 020import org.hl7.fhir.exceptions.FHIRException; 021import org.hl7.fhir.exceptions.FHIRFormatError; 022import org.hl7.fhir.r5.context.IWorkerContext; 023import org.hl7.fhir.r5.model.BackboneType; 024import org.hl7.fhir.r5.model.Base; 025import org.hl7.fhir.r5.model.BaseDateTimeType; 026import org.hl7.fhir.r5.model.CanonicalResource; 027import org.hl7.fhir.r5.model.CodeSystem; 028import org.hl7.fhir.r5.model.CodeableConcept; 029import org.hl7.fhir.r5.model.Coding; 030import org.hl7.fhir.r5.model.ContactPoint; 031import org.hl7.fhir.r5.model.ContactPoint.ContactPointSystem; 032import org.hl7.fhir.r5.model.DataType; 033import org.hl7.fhir.r5.model.DateTimeType; 034import org.hl7.fhir.r5.model.ElementDefinition; 035import org.hl7.fhir.r5.model.Extension; 036import org.hl7.fhir.r5.model.NamingSystem; 037import org.hl7.fhir.r5.model.PrimitiveType; 038import org.hl7.fhir.r5.model.Quantity; 039import org.hl7.fhir.r5.model.Resource; 040import org.hl7.fhir.r5.model.StructureDefinition; 041import org.hl7.fhir.r5.model.ValueSet; 042import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; 043import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceDesignationComponent; 044import org.hl7.fhir.r5.renderers.utils.RenderingContext; 045import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules; 046import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode; 047import org.hl7.fhir.r5.renderers.utils.ResourceWrapper; 048import org.hl7.fhir.r5.terminologies.JurisdictionUtilities; 049import org.hl7.fhir.r5.terminologies.utilities.ValidationResult; 050import org.hl7.fhir.r5.utils.ToolingExtensions; 051import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 052import org.hl7.fhir.utilities.Utilities; 053import org.hl7.fhir.utilities.VersionUtilities; 054import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 055import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece; 056import org.hl7.fhir.utilities.xhtml.NodeType; 057import org.hl7.fhir.utilities.xhtml.XhtmlNode; 058import org.hl7.fhir.utilities.xhtml.XhtmlParser; 059 060import ca.uhn.fhir.model.api.TemporalPrecisionEnum; 061 062public class DataRenderer extends Renderer implements CodeResolver { 063 064 // -- 1. context -------------------------------------------------------------- 065 066 public DataRenderer(RenderingContext context) { 067 super(context); 068 } 069 070 public DataRenderer(IWorkerContext worker) { 071 super(worker); 072 } 073 074 // -- 2. Markdown support ------------------------------------------------------- 075 076 public static String processRelativeUrls(String markdown, String path) { 077 if (markdown == null) { 078 return ""; 079 } 080 if (!Utilities.isAbsoluteUrl(path)) { 081 return markdown; 082 } 083 String basePath = path.contains("/") ? path.substring(0, path.lastIndexOf("/")+1) : path+"/"; 084 StringBuilder b = new StringBuilder(); 085 int i = 0; 086 while (i < markdown.length()) { 087 if (i < markdown.length()-3 && markdown.substring(i, i+2).equals("](")) { 088 int j = i + 2; 089 while (j < markdown.length() && markdown.charAt(j) != ')') 090 j++; 091 if (j < markdown.length()) { 092 String url = markdown.substring(i+2, j); 093 if (!Utilities.isAbsoluteUrl(url) && !url.startsWith("..")) { 094 // it's relative - so it's relative to the base URL 095 b.append("]("); 096 b.append(basePath); 097 } else { 098 b.append("]("); 099 } 100 i = i + 1; 101 } else 102 b.append(markdown.charAt(i)); 103 } else { 104 b.append(markdown.charAt(i)); 105 } 106 i++; 107 } 108 return b.toString(); 109 } 110 111 protected void addMarkdown(XhtmlNode x, String text, String path) throws FHIRFormatError, IOException, DefinitionException { 112 addMarkdown(x, processRelativeUrls(text, path)); 113 } 114 115 protected void addMarkdown(XhtmlNode x, String text) throws FHIRFormatError, IOException, DefinitionException { 116 if (text != null) { 117 // 1. custom FHIR extensions 118 while (text.contains("[[[")) { 119 String left = text.substring(0, text.indexOf("[[[")); 120 String link = text.substring(text.indexOf("[[[")+3, text.indexOf("]]]")); 121 String right = text.substring(text.indexOf("]]]")+3); 122 String path = null; 123 String url = link; 124 String[] parts = link.split("\\#"); 125 if (parts[0].contains(".")) { 126 path = parts[0]; 127 parts[0] = parts[0].substring(0, parts[0].indexOf(".")); 128 } 129 StructureDefinition p = getContext().getWorker().fetchResource(StructureDefinition.class, parts[0]); 130 if (p == null) { 131 p = getContext().getWorker().fetchTypeDefinition(parts[0]); 132 } 133 if (context.getTypeMap().containsKey(parts[0])) { 134 p = getContext().getWorker().fetchTypeDefinition(context.getTypeMap().get(parts[0])); 135 } 136 if (p == null) { 137 p = getContext().getWorker().fetchResource(StructureDefinition.class, link); 138 } 139 if (p != null) { 140 if ("Extension".equals(p.getType())) { 141 path = null; 142 } else if (p.hasSnapshot()) { 143 path = p.getSnapshot().getElementFirstRep().getPath(); 144 } else if (Utilities.isAbsoluteUrl(path)) { 145 path = null; 146 } 147 url = p.getWebPath(); 148 if (url == null) { 149 url = p.getUserString("filename"); 150 } 151 } else { 152 throw new DefinitionException(context.formatPhrase(RenderingContext.DATA_REND_MKDWN_LNK, link) + " "); 153 } 154 155 text = left+"["+link+"]("+url+(path == null ? "" : "#"+path)+")"+right; 156 } 157 158 // 2. markdown 159 String s = getContext().getMarkdown().process(text, "narrative generator"); 160 XhtmlParser p = new XhtmlParser(); 161 XhtmlNode m; 162 try { 163 m = p.parse("<div>"+s+"</div>", "div"); 164 } catch (org.hl7.fhir.exceptions.FHIRFormatError e) { 165 throw new FHIRFormatError(e.getMessage(), e); 166 } 167 x.addChildNodes(m.getChildNodes()); 168 } 169 } 170 171 protected void smartAddText(XhtmlNode p, String text) { 172 if (text == null) 173 return; 174 175 String[] lines = text.split("\\r\\n"); 176 for (int i = 0; i < lines.length; i++) { 177 if (i > 0) 178 p.br(); 179 p.addText(lines[i]); 180 } 181 } 182 183 // -- 3. General Purpose Terminology Support ----------------------------------------- 184 185 private static String snMonth(String m) { 186 switch (m) { 187 case "1" : return "Jan"; 188 case "2" : return "Feb"; 189 case "3" : return "Mar"; 190 case "4" : return "Apr"; 191 case "5" : return "May"; 192 case "6" : return "Jun"; 193 case "7" : return "Jul"; 194 case "8" : return "Aug"; 195 case "9" : return "Sep"; 196 case "10" : return "Oct"; 197 case "11" : return "Nov"; 198 case "12" : return "Dec"; 199 default: return null; 200 } 201 } 202 203 public static String describeVersion(String version) { 204 if (version.startsWith("http://snomed.info/sct")) { 205 String[] p = version.split("\\/"); 206 String ed = null; 207 String dt = ""; 208 209 if (p[p.length-2].equals("version")) { 210 ed = p[p.length-3]; 211 String y = p[p.length-3].substring(4, 8); 212 String m = p[p.length-3].substring(2, 4); 213 dt = " rel. "+snMonth(m)+" "+y; 214 } else { 215 ed = p[p.length-1]; 216 } 217 switch (ed) { 218 case "900000000000207008": return "Intl"+dt; 219 case "731000124108": return "US"+dt; 220 case "32506021000036107": return "AU"+dt; 221 case "449081005": return "ES"+dt; 222 case "554471000005108": return "DK"+dt; 223 case "11000146104": return "NL"+dt; 224 case "45991000052106": return "SE"+dt; 225 case "999000041000000102": return "UK"+dt; 226 case "20611000087101": return "CA"+dt; 227 case "11000172109": return "BE"+dt; 228 default: return "??"+dt; 229 } 230 } else { 231 return version; 232 } 233 } 234 235 public String displaySystem(String system) { 236 if (system == null) 237 return (context.formatPhrase(RenderingContext.DATA_REND_NOT_STAT)); 238 if (system.equals("http://loinc.org")) 239 return (context.formatPhrase(RenderingContext.DATA_REND_LOINC)); 240 if (system.startsWith("http://snomed.info")) 241 return (context.formatPhrase(RenderingContext.DATA_REND_SNOMED)); 242 if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm")) 243 return (context.formatPhrase(RenderingContext.DATA_REND_RXNORM)); 244 if (system.equals("http://hl7.org/fhir/sid/icd-9")) 245 return (context.formatPhrase(RenderingContext.DATA_REND_ICD)); 246 if (system.equals("http://dicom.nema.org/resources/ontology/DCM")) 247 return (context.formatPhrase(RenderingContext.DATA_REND_DICOM)); 248 if (system.equals("http://unitsofmeasure.org")) 249 return (context.formatPhrase(RenderingContext.GENERAL_UCUM)); 250 251 CodeSystem cs = context.getContext().fetchCodeSystem(system); 252 if (cs != null) { 253 return crPresent(cs); 254 } 255 return tails(system); 256 } 257 258 private String crPresent(CanonicalResource cr) { 259 if (cr.hasUserData("presentation")) { 260 return cr.getUserString("presentation"); 261 } 262 if (cr.hasTitle()) 263 return context.getTranslated(cr.getTitleElement()); 264 if (cr.hasName()) 265 return context.getTranslated(cr.getNameElement()); 266 return cr.toString(); 267 } 268 269 private String tails(String system) { 270 if (system.contains("/")) { 271 return system.substring(system.lastIndexOf("/")+1); 272 } else { 273 return (context.formatPhrase(RenderingContext.DATA_REND_UNKNWN)); 274 } 275 } 276 277 protected String makeAnchor(String codeSystem, String code) { 278 String s = codeSystem+'-'+code; 279 StringBuilder b = new StringBuilder(); 280 for (char c : s.toCharArray()) { 281 if (Utilities.isValidHtmlAnchorChar(c)) { 282 b.append(c); 283 } else { 284 b.append("|"+Integer.toHexString(c)); // not % to save double coding confusing users 285 } 286 } 287 return b.toString(); 288 } 289 290 private String lookupCode(String system, String version, String code) { 291 if (JurisdictionUtilities.isJurisdiction(system)) { 292 return JurisdictionUtilities.displayJurisdiction(system+"#"+code); 293 } 294 ValidationResult t = getContext().getWorker().validateCode(getContext().getTerminologyServiceOptions().withLanguage(context.getLocale().toString().replace("_", "-")).withVersionFlexible(true), system, version, code, null); 295 296 if (t != null && t.getDisplay() != null) 297 return t.getDisplay(); 298 else 299 return code; 300 } 301 302 protected String describeLang(String lang) { 303 // special cases: 304 if ("fr-CA".equals(lang)) { 305 return "French (Canadian)"; // this one was omitted from the value set 306 } 307 ValueSet v = getContext().getWorker().findTxResource(ValueSet.class, "http://hl7.org/fhir/ValueSet/languages"); 308 if (v != null) { 309 ConceptReferenceComponent l = null; 310 for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) { 311 if (cc.getCode().equals(lang)) 312 l = cc; 313 } 314 if (l == null) { 315 if (lang.contains("-")) { 316 lang = lang.substring(0, lang.indexOf("-")); 317 } 318 for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) { 319 if (cc.getCode().equals(lang)) { 320 l = cc; 321 break; 322 } 323 } 324 if (l == null) { 325 for (ConceptReferenceComponent cc : v.getCompose().getIncludeFirstRep().getConcept()) { 326 if (cc.getCode().startsWith(lang+"-")) { 327 l = cc; 328 break; 329 } 330 } 331 } 332 } 333 if (l != null) { 334 if (lang.contains("-")) 335 lang = lang.substring(0, lang.indexOf("-")); 336 String en = l.getDisplay(); 337 String nativelang = null; 338 for (ConceptReferenceDesignationComponent cd : l.getDesignation()) { 339 if (cd.getLanguage().equals(lang)) 340 nativelang = cd.getValue(); 341 } 342 if (nativelang == null) 343 return en+" ("+lang+")"; 344 else 345 return nativelang+" ("+en+", "+lang+")"; 346 } 347 } 348 return lang; 349 } 350 351 private boolean isCanonical(String path) { 352 if (!path.endsWith(".url")) 353 return false; 354 String t = path.substring(0, path.length()-4); 355 StructureDefinition sd = getContext().getWorker().fetchTypeDefinition(t); 356 if (sd == null) 357 return false; 358 if (VersionUtilities.getCanonicalResourceNames(getContext().getWorker().getVersion()).contains(t)) { 359 return true; 360 } 361 if (Utilities.existsInList(t, 362 "ActivityDefinition", "CapabilityStatement", "ChargeItemDefinition", "Citation", "CodeSystem", 363 "CompartmentDefinition", "ConceptMap", "ConditionDefinition", "EventDefinition", "Evidence", "EvidenceReport", "EvidenceVariable", 364 "ExampleScenario", "GraphDefinition", "ImplementationGuide", "Library", "Measure", "MessageDefinition", "NamingSystem", "PlanDefinition" 365 )) 366 return true; 367 return false; 368 } 369 370 // -- 4. Language support ------------------------------------------------------ 371 372 public String gt(@SuppressWarnings("rawtypes") PrimitiveType value) { 373 return context.getTranslated(value); 374 } 375 376 public String gt(ResourceWrapper value) { 377 return context.getTranslated(value); 378 } 379 380 // -- 6. General purpose extension rendering ---------------------------------------------- 381 382 public boolean hasRenderableExtensions(DataType element) { 383 for (Extension ext : element.getExtension()) { 384 if (canRender(ext)) { 385 return true; 386 } 387 } 388 return false; 389 } 390 391 public boolean hasRenderableExtensions(BackboneType element) { 392 for (Extension ext : element.getExtension()) { 393 if (canRender(ext)) { 394 return true; 395 } 396 } 397 return element.hasModifierExtension(); 398 } 399 400 public boolean hasRenderableExtensions(ResourceWrapper element) { 401 for (ResourceWrapper ext : element.extensions()) { 402 if (canRender(ext)) { 403 return true; 404 } 405 } 406 return false; 407 } 408 409 private String getExtensionLabel(Extension ext) { 410 StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, ext.getUrl()); 411 if (sd != null && ext.hasValue() && ext.getValue().isPrimitive() && sd.hasSnapshot()) { 412 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 413 if (Utilities.existsInList(ed.getPath(), "Extension", "Extension.value[x]") && ed.hasLabel()) { 414 return context.getTranslated(ed.getLabelElement()); 415 } 416 } 417 } 418 return null; 419 } 420 421 private String getExtensionLabel(ResourceWrapper ext) { 422 StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, ext.primitiveValue("url")); 423 if (sd != null && ext.has("value") && ext.child("value").isPrimitive() && sd.hasSnapshot()) { 424 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 425 if (Utilities.existsInList(ed.getPath(), "Extension", "Extension.value[x]") && ed.hasLabel()) { 426 return context.getTranslated(ed.getLabelElement()); 427 } 428 } 429 } 430 return null; 431 } 432 433 private boolean canRender(Extension ext) { 434 return getExtensionLabel(ext) != null; 435 } 436 437 438 private boolean canRender(ResourceWrapper ext) { 439 StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, ext.primitiveValue("url")); 440 if (sd != null && ext.has("value") && ext.isPrimitive("value") && sd.hasSnapshot()) { 441 for (ElementDefinition ed : sd.getSnapshot().getElement()) { 442 if (Utilities.existsInList(ed.getPath(), "Extension", "Extension.value[x]") && ed.hasLabel()) { 443 return true; 444 } 445 } 446 } 447 return false; 448 } 449 450 public void renderExtensionsInList(RenderingStatus status, XhtmlNode ul, ResourceWrapper element) throws FHIRFormatError, DefinitionException, IOException { 451 for (ResourceWrapper ext : element.extensions()) { 452 if (canRender(ext)) { 453 String lbl = getExtensionLabel(ext); 454 XhtmlNode li = ul.li(); 455 li.tx(lbl); 456 li.tx(": "); 457 renderDataType(status, li, ext.child("value")); 458 } 459 } 460 } 461 462 // public void renderExtensionsInList(XhtmlNode ul, BackboneType element) throws FHIRFormatError, DefinitionException, IOException { 463 // for (Extension ext : element.getModifierExtension()) { 464 // if (canRender(ext)) { 465 // String lbl = getExtensionLabel(ext); 466 // XhtmlNode li = ul.li(); 467 // li = li.b(); 468 // li.tx(lbl); 469 // li.tx(": "); 470 // render(li, ext.getValue()); 471 // } else { 472 // // somehow have to do better than this 473 // XhtmlNode li = ul.li(); 474 // li.b().tx(context.formatPhrase(RenderingContext.DATA_REND_UNRD_EX)); 475 // } 476 // } 477 // for (Extension ext : element.getExtension()) { 478 // if (canRender(ext)) { 479 // String lbl = getExtensionLabel(ext); 480 // XhtmlNode li = ul.li(); 481 // li.tx(lbl); 482 // li.tx(": "); 483 // render(li, ext.getValue()); 484 // } 485 // } 486 // } 487 // 488 public void renderExtensionsInText(RenderingStatus status, XhtmlNode div, ResourceWrapper element, String sep) throws FHIRFormatError, DefinitionException, IOException { 489 boolean first = true; 490 for (ResourceWrapper ext : element.extensions()) { 491 if (canRender(ext)) { 492 if (first) { 493 first = false; 494 } else { 495 div.tx(sep); 496 div.tx(" "); 497 } 498 499 String lbl = getExtensionLabel(ext); 500 div.tx(lbl); 501 div.tx(": "); 502 renderDataType(status, div, ext.child("value")); 503 } 504 } 505 } 506 507 // public void renderExtensionsInList(XhtmlNode div, BackboneType element, String sep) throws FHIRFormatError, DefinitionException, IOException { 508 // boolean first = true; 509 // for (Extension ext : element.getModifierExtension()) { 510 // if (first) { 511 // first = false; 512 // } else { 513 // div.tx(sep); 514 // div.tx(" "); 515 // } 516 // if (canRender(ext)) { 517 // String lbl = getExtensionLabel(ext); 518 // XhtmlNode b = div.b(); 519 // b.tx(lbl); 520 // b.tx(": "); 521 // render(div, ext.getValue()); 522 // } else { 523 // // somehow have to do better than this 524 // div.b().tx(context.formatPhrase(RenderingContext.DATA_REND_UNRD_EX)); 525 // } 526 // } 527 // for (Extension ext : element.getExtension()) { 528 // if (canRender(ext)) { 529 // if (first) { 530 // first = false; 531 // } else { 532 // div.tx(sep); 533 // div.tx(" "); 534 // } 535 // 536 // String lbl = getExtensionLabel(ext); 537 // div.tx(lbl); 538 // div.tx(": "); 539 // render(div, ext.getValue()); 540 // } 541 // } 542 // 543 // } 544 545 // -- 6. Data type Rendering ---------------------------------------------- 546 547 public static String display(IWorkerContext context, DataType type) { 548 return new DataRenderer(new RenderingContext(context, null, null, "http://hl7.org/fhir/R4", "", null, ResourceRendererMode.END_USER, GenerationRules.VALID_RESOURCE)).displayDataType(type); 549 } 550 551 public String displayBase(Base b) { 552 if (b instanceof DataType) { 553 return displayDataType((DataType) b); 554 } else { 555 return (context.formatPhrase(RenderingContext.DATA_REND_NO_DISP, b.fhirType()) + " "); 556 } 557 } 558 559 public String displayDataType(DataType type) { 560 if (type == null) { 561 return ""; 562 } 563 return displayDataType(wrapNC(type)); 564 } 565 566 public String displayDataType(ResourceWrapper type) { 567 if (type == null || type.isEmpty()) { 568 return ""; 569 } 570 571 switch (type.fhirType()) { 572 case "Coding": return displayCoding(type); 573 case "CodeableConcept": return displayCodeableConcept(type); 574 case "CodeableReference": return displayCodeableReference(type); 575 case "Identifier": return displayIdentifier(type); 576 case "HumanName": return displayHumanName(type); 577 case "Address": return displayAddress(type); 578 case "ContactPoint": return displayContactPoint(type); 579 case "Quantity": return displayQuantity(type); 580 case "Range": return displayRange(type); 581 case "Period": return displayPeriod(type); 582 case "Timing": return displayTiming(type); 583 case "SampledData": return displaySampledData(type); 584 case "ContactDetail": return displayContactDetail(type); 585 case "Annotation": return displayAnnotation(type); 586 case "Ratio": return displayRatio(type); 587 case "Reference" : return displayReference(type); 588 case "Money" : return displayMoney(type); 589 case "dateTime": 590 case "date" : 591 case "instant" : 592 return displayDateTime(type); 593 default: 594 if (type.isPrimitive()) { 595 return context.getTranslated(type); 596 } else if (Utilities.existsInList(type.fhirType(), "Meta", "Dosage", "Signature", "UsageContext", "RelatedArtifact", "ElementDefinition", "Base64BinaryType", "Attachment")) { 597 return ""; 598 } else if ("Extension".equals(type.fhirType())) { 599 return displayDataType(type.child("value")); 600 } else { 601 return (context.formatPhrase(RenderingContext.DATA_REND_NO_DISP, type.fhirType()) + " "); 602 } 603 } 604 } 605 606 private String displayMoney(ResourceWrapper type) { 607 String currency = type.primitiveValue("currency"); 608 String value = type.primitiveValue("value"); 609 return context.formatPhrase(RenderingContext.DATA_REND_CURRENCY, currency, value); 610 } 611 612 private String displayAnnotation(ResourceWrapper type) { 613 return type.primitiveValue("text"); 614 } 615 616 private String displayCodeableReference(ResourceWrapper type) { 617 if (type.has("reference")) { 618 return displayReference(type.child("reference")); 619 } else { 620 return displayCodeableConcept(type.child("concept")); 621 } 622 } 623 624 625 protected String displayReference(ResourceWrapper type) { 626 if (type.has("display")) { 627 return type.primitiveValue("display"); 628 } else if (type.has("reference")) { 629 // ResourceWithReference tr = resolveReference(res, r.getReference()); 630 // x.addText(tr == null ? r.getReference() : "?ngen-3"); // getDisplayForReference(tr.getReference())); 631 return "?ngen-3"; 632 } else { 633 return "?ngen-4?"; 634 } 635 } 636 637 private String displayRatio(ResourceWrapper type) { 638 return displayQuantity(type.child("numerator"))+" / "+displayQuantity(type.child("denominator")); 639 } 640 641 protected String displayDateTime(ResourceWrapper type) { 642 if (!type.hasPrimitiveValue()) { 643 return ""; 644 } 645 646 BaseDateTimeType t = new DateTimeType(type.primitiveValue()); 647 // relevant inputs in rendering context: 648 // timeZone, dateTimeFormat, locale, mode 649 // timezone - application specified timezone to use. 650 // null = default to the time of the date/time itself 651 // dateTimeFormat - application specified format for date times 652 // null = default to ... depends on mode 653 // mode - if rendering mode is technical, format defaults to XML format 654 // locale - otherwise, format defaults to SHORT for the Locale (which defaults to default Locale) 655 if (isOnlyDate(t.getPrecision())) { 656 657 DateTimeFormatter fmt = getDateFormatForPrecision(t); 658 LocalDate date = LocalDate.of(t.getYear(), t.getMonth()+1, t.getDay()); 659 return fmt.format(date); 660 } 661 662 DateTimeFormatter fmt = context.getDateTimeFormat(); 663 if (fmt == null) { 664 if (context.isTechnicalMode()) { 665 fmt = DateTimeFormatter.ISO_OFFSET_DATE_TIME; 666 } else { 667 fmt = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).withLocale(context.getLocale()); 668 } 669 } 670 ZonedDateTime zdt = ZonedDateTime.parse(t.primitiveValue()); 671 ZoneId zone = context.getTimeZoneId(); 672 if (zone != null) { 673 zdt = zdt.withZoneSameInstant(zone); 674 } 675 return fmt.format(zdt); 676 } 677 678 private DateTimeFormatter getDateFormatForPrecision(BaseDateTimeType type) { 679 DateTimeFormatter fmt = getContextDateFormat(type); 680 if (fmt != null) { 681 return fmt; 682 } 683 if (context.isTechnicalMode()) { 684 switch (type.getPrecision()) { 685 case YEAR: 686 return new DateTimeFormatterBuilder().appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD).toFormatter(); 687 case MONTH: 688 return new DateTimeFormatterBuilder().appendValue(YEAR, 4, 10, SignStyle.EXCEEDS_PAD).appendLiteral('-').appendValue(MONTH_OF_YEAR, 2).toFormatter(); 689 default: 690 return DateTimeFormatter.ISO_DATE; 691 } 692 } else { 693 switch (type.getPrecision()) { 694 case YEAR: 695 return DateTimeFormatter.ofPattern("uuuu"); 696 case MONTH: 697 return DateTimeFormatter.ofPattern("MMM uuuu"); 698 default: 699 return DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(context.getLocale()); 700 } 701 } 702 } 703 704 private DateTimeFormatter getContextDateFormat(BaseDateTimeType type) { 705 switch (type.getPrecision()) { 706 case YEAR: 707 return context.getDateYearFormat(); 708 case MONTH: 709 return context.getDateYearMonthFormat(); 710 default: 711 return context.getDateFormat(); 712 } 713 } 714 715 private boolean isOnlyDate(TemporalPrecisionEnum temporalPrecisionEnum) { 716 return temporalPrecisionEnum == TemporalPrecisionEnum.YEAR || temporalPrecisionEnum == TemporalPrecisionEnum.MONTH || temporalPrecisionEnum == TemporalPrecisionEnum.DAY; 717 } 718 719 720 // public void renderDataType(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException { 721 //// Base base = null; 722 //// try { 723 //// base = type.getBase(); 724 //// } catch (FHIRException | IOException e) { 725 //// x.tx(context.formatPhrase(RenderingContext.DATA_REND_ERROR, e.getMessage()) + " "); // this shouldn't happen - it's an error in the library itself 726 //// return; 727 //// } 728 //// if (base instanceof DataType) { 729 //// render(x, (DataType) base); 730 //// } else { 731 //// x.tx(context.formatPhrase(RenderingContext.DATA_REND_TO_DO, base.fhirType())); 732 //// } 733 // } 734 735 public void renderBase(RenderingStatus status, XhtmlNode x, Base b) throws FHIRFormatError, DefinitionException, IOException { 736 if (b instanceof DataType) { 737 renderDataType(status, x, wrapNC((DataType) b)); 738 } else { 739 x.tx(context.formatPhrase(RenderingContext.DATA_REND_NO_DISP, b.fhirType()) + " "); 740 } 741 } 742 743 public boolean canRenderDataType(String type) { 744 return context.getContextUtilities().isPrimitiveType(type) || Utilities.existsInList(type, "Annotation", "Coding", "CodeableConcept", "Identifier", "HumanName", "Address", 745 "Expression", "Money", "ContactPoint", "Quantity", "Range", "Period", "Timing", "SampledData", "Reference", "UsageContext", "ContactDetail", "Ratio", "Attachment", "CodeableReference"); 746 } 747 748 public boolean renderDataType(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException { 749 return renderDataType(status, null, x, type); 750 } 751 public boolean renderDataType(RenderingStatus status, XhtmlNode parent, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException { 752 if (type == null) { 753 return false; 754 } 755 switch (type.fhirType()) { 756 case "dateTime": 757 case "date" : 758 case "instant" : 759 renderDateTime(status, x, type); 760 break; 761 case "uri" : 762 case "url" : 763 renderUri(status, x, type); 764 break; 765 case "canonical" : 766 renderCanonical(status, x, type); 767 break; 768 case "Annotation": 769 renderAnnotation(status, x, type); 770 break; 771 case "Coding": 772 renderCodingWithDetails(status, x, type); 773 break; 774 case "CodeableConcept": 775 renderCodeableConcept(status, x, type); 776 break; 777 case "Identifier": 778 renderIdentifier(status, x, type); 779 break; 780 case "HumanName": 781 renderHumanName(status, x, type); 782 break; 783 case "Address": 784 renderAddress(status, x, type); 785 break; 786 case "Expression": 787 renderExpression(status, x, type); 788 break; 789 case "Money": 790 renderMoney(status, x, type); 791 break; 792 case "ContactPoint": 793 renderContactPoint(status, x, type); 794 break; 795 case "Quantity": 796 case "Age": 797 renderQuantity(status, x, type); 798 break; 799 case "Range": 800 renderRange(status, x, type); 801 break; 802 case "Period": 803 renderPeriod(status, x, type); 804 break; 805 case "Timing": 806 renderTiming(status, x, type); 807 break; 808 case "SampledData": 809 renderSampledData(status, x, type); 810 break; 811 case "Reference": 812 renderReference(status, x, type); 813 break; 814 case "UsageContext": 815 renderUsageContext(status, x, type); 816 break; 817 case "ContactDetail": 818 renderContactDetail(status, x, type); 819 break; 820 case "Ratio": 821 renderRatio(status, x, type); 822 break; 823 case "Attachment": 824 renderAttachment(status, x, type); 825 break; 826 case "CodeableReference": 827 if (type.has("concept")) { 828 renderCodeableConcept(status, x, type.child("concept")); 829 } else { 830 renderReference(status, x, type.child("reference")); 831 } 832 break; 833 case "code": 834 x.tx(getTranslatedCode(type)); 835 break; 836 case "markdown": 837 addMarkdown(parent == null ? x : parent, context.getTranslated(type)); // note parent not focus, because of paragraph issues and markdown 838 break; 839 case "base64Binary": 840 int length = type.primitiveValue().length(); 841 if (length >= context.getBase64Limit()) { 842 x.tx(context.formatPhrase(RenderingContext.DATA_REND_BASE64, length)); 843 } else { 844 x.code(type.primitiveValue()); 845 } 846 break; 847 default: 848 if (type.isPrimitive()) { 849 if (!renderPrimitiveWithNoValue(status, x, type)) { 850 x.tx(context.getTranslated(type)); 851 } 852 } else { 853 x.tx(context.formatPhrase(RenderingContext.DATA_REND_NO_DISP, type.fhirType()) + " "); 854 return false; 855 } 856 } 857 return true; 858 } 859 860 // overide in ResourceRenderer 861 protected void renderCanonical(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException { 862 renderUri(status, x, type); 863 } 864 865 private void renderRatio(RenderingStatus status, XhtmlNode x, ResourceWrapper type) { 866 renderQuantity(status, x, type.child("numerator")); 867 x.tx("/"); 868 renderQuantity(status, x, type.child("denominator")); 869 } 870 871 private void renderAttachment(RenderingStatus status, XhtmlNode x, ResourceWrapper att) { 872 String ct = att.primitiveValue("contentType"); 873 if (att.has("url")) { 874 x.tx(context.formatMessage(RenderingContext.DATA_REND_ATT_URL, ct, att.primitiveValue("url"))); 875 } else if (att.has("data")) { 876 x.tx(context.formatMessage(RenderingContext.DATA_REND_ATT_DATA, ct, displayDataType(att.child("data")))); 877 } 878 } 879 880 private void renderContactDetail(RenderingStatus status, XhtmlNode x, ResourceWrapper cd) { 881 if (cd.has("name")) { 882 x.tx(cd.primitiveValue("name")+": "); 883 } 884 boolean first = true; 885 for (ResourceWrapper c : cd.children("telecom")) { 886 if (first) first = false; else x.tx(","); 887 renderContactPoint(status, x, c); 888 } 889 } 890 891 private void renderDateTime(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException { 892 if (!renderPrimitiveWithNoValue(status, x, type)) { 893 x.tx(displayDateTime(type)); 894 } 895 } 896 897 /** 898 * this is overriden in ResourceRenderer where a better rendering is performed 899 * @param status 900 * @param x 901 * @param ref 902 * @throws IOException 903 * @throws DefinitionException 904 * @throws FHIRFormatError 905 */ 906 protected void renderReference(RenderingStatus status, XhtmlNode x, ResourceWrapper ref) throws FHIRFormatError, DefinitionException, IOException { 907 if (ref.has("display")) { 908 x.tx(context.getTranslated(ref.child("display"))); 909 } else if (ref.has("reference")) { 910 x.tx(ref.primitiveValue("reference")); 911 } else { 912 x.tx("??"); 913 } 914 } 915 // 916 // public void renderDateTime(RenderingStatus status, XhtmlNode x, Base e) { 917 // if (e.hasPrimitiveValue()) { 918 // x.addText(displayDateTime((DateTimeType) e)); 919 // } 920 // } 921 // 922 // public void renderDate(RenderingStatus status, XhtmlNode x, Base e) { 923 // if (e.hasPrimitiveValue()) { 924 // x.addText(displayDateTime((DateType) e)); 925 // } 926 // } 927 // 928 // public void renderDateTime(XhtmlNode x, String s) { 929 // if (s != null) { 930 // DateTimeType dt = new DateTimeType(s); 931 // x.addText(displayDateTime(dt)); 932 // } 933 // } 934 935 936 protected boolean renderPrimitiveWithNoValue(RenderingStatus status, XhtmlNode x, PrimitiveType<?> prim) throws FHIRFormatError, DefinitionException, IOException { 937 if (prim.hasPrimitiveValue()) { 938 return false; 939 } 940 boolean first = true; 941 for (Extension ext : prim.getExtension()) { 942 if (first) first = false; else x.tx(", "); 943 String url = ext.getUrl(); 944 if (url.equals(ToolingExtensions.EXT_DAR)) { 945 x.tx("Absent because : "); 946 displayCode(x, wrapNC(ext.getValue())); 947 } else if (url.equals(ToolingExtensions.EXT_NF)) { 948 x.tx("Null because: "); 949 displayCode(x, wrapNC(ext.getValue())); 950 } else if (url.equals(ToolingExtensions.EXT_OT)) { 951 x.code().tx("Text: "); 952 displayCode(x, wrapNC(ext.getValue())); 953 } else if (url.equals(ToolingExtensions.EXT_CQF_EXP)) { 954 x.code().tx("Value calculated by: "); 955 renderExpression(status, x, wrapNC(ext.getValue())); 956 } else { 957 StructureDefinition def = context.getContext().fetchResource(StructureDefinition.class, url); 958 if (def == null) { 959 x.code().tx(tail(url)+": "); 960 } else { 961 x.code().tx(def.present()+": "); 962 } 963 renderDataType(status, x, wrapNC(ext.getValue())); 964 } 965 } 966 status.setExtensions(true); 967 return true; 968 } 969 970 protected boolean renderPrimitiveWithNoValue(RenderingStatus status, XhtmlNode x, ResourceWrapper prim) throws FHIRFormatError, DefinitionException, IOException { 971 if (prim.hasPrimitiveValue()) { 972 return false; 973 } 974 boolean first = true; 975 for (ResourceWrapper ext : prim.extensions()) { 976 if (first) first = false; else x.tx(", "); 977 String url = ext.primitiveValue("url"); 978 if (url.equals(ToolingExtensions.EXT_DAR)) { 979 x.tx("Absent because : "); 980 displayCode(x, ext.child("value")); 981 } else if (url.equals(ToolingExtensions.EXT_NF)) { 982 x.tx("Null because: "); 983 displayCode(x, ext.child("value")); 984 } else if (url.equals(ToolingExtensions.EXT_OT)) { 985 x.code().tx("Text: "); 986 displayCode(x, ext.child("value")); 987 } else if (url.equals(ToolingExtensions.EXT_CQF_EXP)) { 988 x.code().tx("Value calculated by: "); 989 renderExpression(status, x, ext.child("value")); 990 } else { 991 StructureDefinition def = context.getContext().fetchResource(StructureDefinition.class, url); 992 if (def == null) { 993 x.code().tx(tail(url)+": "); 994 } else { 995 x.code().tx(def.present()+": "); 996 } 997 renderDataType(status, x, ext.child("value")); 998 } 999 } 1000 status.setExtensions(true); 1001 return true; 1002 } 1003 1004 private String tail(String url) { 1005 return url.contains("/") ? url.substring(url.lastIndexOf("/")+1) : url; 1006 } 1007 1008 private void displayCode(XhtmlNode x, ResourceWrapper code) { 1009 x.tx(code.primitiveValue()); 1010 } 1011 1012 protected void renderUri(RenderingStatus status, XhtmlNode x, ResourceWrapper uri) throws FHIRFormatError, DefinitionException, IOException { 1013 if (!renderPrimitiveWithNoValue(status, x, uri)) { 1014 String v = uri.primitiveValue(); 1015 1016 if (context.getContextUtilities().isResource(v)) { 1017 v = "http://hl7.org/fhir/"+v; 1018 } 1019 if (v.startsWith("mailto:")) { 1020 x.ah(v).addText(v.substring(7)); 1021 } else { 1022 Resource r = context.getContext().fetchResource(Resource.class, v); 1023 if (r != null && r.getWebPath() != null) { 1024 if (r instanceof CanonicalResource) { 1025 x.ah(context.prefixLocalHref(r.getWebPath())).addText(crPresent((CanonicalResource) r)); 1026 } else { 1027 x.ah(context.prefixLocalHref(r.getWebPath())).addText(v); 1028 } 1029 } else { 1030 String url = context.getResolver() != null ? context.getResolver().resolveUri(context, v) : null; 1031 if (url != null) { 1032 x.ah(context.prefixLocalHref(url)).addText(v); 1033 } else if (Utilities.isAbsoluteUrlLinkable(v) && !uri.fhirType().equals("id")) { 1034 x.ah(context.prefixLocalHref(v)).addText(v); 1035 } else { 1036 x.addText(v); 1037 } 1038 } 1039 } 1040 } 1041 } 1042 1043 protected void renderAnnotation(RenderingStatus status, XhtmlNode x, ResourceWrapper a) throws FHIRException { 1044 StringBuilder b = new StringBuilder(); 1045 if (a.has("text")) { 1046 b.append(context.getTranslated(a.child("text"))); 1047 } 1048 1049 if (a.has("text") && (a.has("author") || a.has("time"))) { 1050 b.append(" ("); 1051 } 1052 1053 if (a.has("author")) { 1054 b.append(context.formatPhrase(RenderingContext.DATA_REND_BY) + " "); 1055 ResourceWrapper auth = a.child("author"); 1056 if (auth.fhirType().equals("Reference")) { 1057 b.append(auth.primitiveValue("reference")); 1058 } else if (auth.fhirType().equals("string")) { 1059 b.append(context.getTranslated(auth)); 1060 } 1061 } 1062 1063 1064 if (a.has("time")) { 1065 if (b.length() > 0) { 1066 b.append(" "); 1067 } 1068 b.append("@").append(displayDateTime(a.child("time"))); 1069 } 1070 if (a.has("text") && (a.has("author") || a.has("time"))) { 1071 b.append(")"); 1072 } 1073 1074 1075 x.addText(b.toString()); 1076 } 1077 1078 public String displayCoding(ResourceWrapper c) { 1079 String s = ""; 1080 if (context.isTechnicalMode()) { 1081 s = context.getTranslated(c.child("display")); 1082 if (Utilities.noString(s)) { 1083 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1084 } 1085 if (Utilities.noString(s)) { 1086 s = displayCodeTriple(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1087 } else if (c.has("system")) { 1088 s = s + " ("+displayCodeTriple(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code"))+")"; 1089 } else if (c.has("code")) { 1090 s = s + " ("+c.primitiveValue("code")+")"; 1091 } 1092 } else { 1093 if (c.has("display")) 1094 return context.getTranslated(c.child("display")); 1095 if (Utilities.noString(s)) 1096 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1097 if (Utilities.noString(s)) 1098 s = c.primitiveValue("code"); 1099 } 1100 return s; 1101 } 1102 1103 private String displayCodeSource(String system, String version) { 1104 String s = displaySystem(system); 1105 if (version != null) { 1106 s = s + "["+describeVersion(version)+"]"; 1107 } 1108 return s; 1109 } 1110 1111 private String displayCodeTriple(String system, String version, String code) { 1112 if (system == null) { 1113 if (code == null) { 1114 return ""; 1115 } else { 1116 return "#"+code; 1117 } 1118 } else { 1119 String s = displayCodeSource(system, version); 1120 if (code != null) { 1121 s = s + "#"+code; 1122 } 1123 return s; 1124 } 1125 } 1126 1127 public String displayCoding(List<Coding> list) { 1128 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1129 for (Coding c : list) { 1130 b.append(displayCoding(wrapNC(c))); 1131 } 1132 return b.toString(); 1133 } 1134 1135 protected void renderCoding(HierarchicalTableGenerator gen, List<Piece> pieces, Coding c) { 1136 if (c.isEmpty()) { 1137 return; 1138 } 1139 1140 String url = getLinkForSystem(c.getSystem(), c.getVersion()); 1141 String name = displayCodeSource(c.getSystem(), c.getVersion()); 1142 if (!Utilities.noString(url)) { 1143 pieces.add(gen.new Piece(url, name, c.getSystem()+(c.hasVersion() ? "#"+c.getVersion() : ""))); 1144 } else { 1145 pieces.add(gen.new Piece(null, name, c.getSystem()+(c.hasVersion() ? "#"+c.getVersion() : ""))); 1146 } 1147 pieces.add(gen.new Piece(null, "#"+c.getCode(), null)); 1148 String s = context.getTranslated(c.getDisplayElement()); 1149 if (Utilities.noString(s)) { 1150 s = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); 1151 } 1152 if (!Utilities.noString(s)) { 1153 pieces.add(gen.new Piece(null, " \""+s+"\"", null)); 1154 } 1155 } 1156 1157 protected void renderCoding(HierarchicalTableGenerator gen, List<Piece> pieces, ResourceWrapper c) { 1158 if (c.isEmpty()) { 1159 return; 1160 } 1161 1162 String url = getLinkForSystem(c.primitiveValue("system"), c.primitiveValue("version")); 1163 String name = displayCodeSource(c.primitiveValue("system"), c.primitiveValue("version")); 1164 if (!Utilities.noString(url)) { 1165 pieces.add(gen.new Piece(url, name, c.primitiveValue("system")+(c.has("version") ? "#"+c.primitiveValue("version") : ""))); 1166 } else { 1167 pieces.add(gen.new Piece(null, name, c.primitiveValue("system")+(c.has("version") ? "#"+c.primitiveValue("version") : ""))); 1168 } 1169 pieces.add(gen.new Piece(null, "#"+c.primitiveValue("code"), null)); 1170 String s = context.getTranslated(c.child("display")); 1171 if (Utilities.noString(s)) { 1172 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1173 } 1174 if (!Utilities.noString(s)) { 1175 pieces.add(gen.new Piece(null, " \""+s+"\"", null)); 1176 } 1177 } 1178 1179 private String getLinkForSystem(String system, String version) { 1180 if ("http://snomed.info/sct".equals(system)) { 1181 return "https://browser.ihtsdotools.org/"; 1182 } else if ("http://loinc.org".equals(system)) { 1183 return "https://loinc.org/"; 1184 } else if ("http://unitsofmeasure.org".equals(system)) { 1185 return "http://ucum.org"; 1186 } else { 1187 String url = system; 1188 if (version != null) { 1189 url = url + "|"+version; 1190 } 1191 CodeSystem cs = context.getWorker().fetchCodeSystem(url); 1192 if (cs != null && cs.hasWebPath()) { 1193 return cs.getWebPath(); 1194 } 1195 return null; 1196 } 1197 } 1198 1199 protected String getLinkForCode(String system, String version, String code) { 1200 if ("http://snomed.info/sct".equals(system)) { 1201 if (!Utilities.noString(code)) { 1202 return "http://snomed.info/id/"+code; 1203 } else { 1204 return "https://browser.ihtsdotools.org/"; 1205 } 1206 } else if ("http://loinc.org".equals(system)) { 1207 if (!Utilities.noString(code)) { 1208 return "https://loinc.org/"+code; 1209 } else { 1210 return "https://loinc.org/"; 1211 } 1212 } else if ("http://www.nlm.nih.gov/research/umls/rxnorm".equals(system)) { 1213 if (!Utilities.noString(code)) { 1214 return "https://mor.nlm.nih.gov/RxNav/search?searchBy=RXCUI&searchTerm="+code; 1215 } else { 1216 return "https://www.nlm.nih.gov/research/umls/rxnorm/index.html"; 1217 } 1218 } else if ("urn:iso:std:iso:3166".equals(system)) { 1219 if (!Utilities.noString(code)) { 1220 return "https://en.wikipedia.org/wiki/ISO_3166-2:"+code; 1221 } else { 1222 return "https://en.wikipedia.org/wiki/ISO_3166-2"; 1223 } 1224 } else { 1225 CodeSystem cs = context.getWorker().fetchCodeSystem(system, version); 1226 if (cs != null && cs.hasWebPath()) { 1227 if (!Utilities.noString(code)) { 1228 return cs.getWebPath()+"#"+cs.getId()+"-"+Utilities.nmtokenize(code); 1229 } else { 1230 return cs.getWebPath(); 1231 } 1232 } 1233 } 1234 return null; 1235 } 1236 1237 public CodeResolution resolveCode(String system, String code) { 1238 return resolveCode(new Coding().setSystem(system).setCode(code)); 1239 } 1240 1241 public CodeResolution resolveCode(ResourceWrapper c) { 1242 String systemName; 1243 String systemLink; 1244 String link; 1245 String display = null; 1246 String hint; 1247 1248 if (c.has("display")) 1249 display = context.getTranslated(c.child("display")); 1250 if (Utilities.noString(display)) 1251 display = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1252 if (Utilities.noString(display)) { 1253 display = c.primitiveValue("code"); 1254 } 1255 1256 CodeSystem cs = context.getWorker().fetchCodeSystem(c.primitiveValue("system")); 1257 systemLink = cs != null ? cs.getWebPath() : null; 1258 systemName = cs != null ? crPresent(cs) : displaySystem(c.primitiveValue("system")); 1259 link = getLinkForCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1260 1261 hint = systemName+": "+display+(c.has("version") ? " "+ context.formatPhrase(RenderingContext.DATA_REND_VERSION, c.primitiveValue("version"), ")") : ""); 1262 return new CodeResolution(systemName, systemLink, link, display, hint); 1263 } 1264 1265 public CodeResolution resolveCode(Coding code) { 1266 return resolveCode(wrapNC(code)); 1267 } 1268 1269 public CodeResolution resolveCode(CodeableConcept code) { 1270 if (code.hasCoding()) { 1271 return resolveCode(code.getCodingFirstRep()); 1272 } else { 1273 return new CodeResolution(null, null, null, code.getText(), code.getText()); 1274 } 1275 } 1276 protected void renderCodingWithDetails(RenderingStatus status, XhtmlNode x, ResourceWrapper c) { 1277 String s = ""; 1278 if (c.has("display")) 1279 s = context.getTranslated(c.child("display")); 1280 if (Utilities.noString(s)) 1281 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1282 1283 CodeSystem cs = context.getWorker().fetchCodeSystem(c.primitiveValue("system")); 1284 1285 String sn = cs != null ? crPresent(cs) : displaySystem(c.primitiveValue("system")); 1286 String link = getLinkForCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1287 if (link != null) { 1288 x.ah(context.prefixLocalHref(link)).tx(sn); 1289 } else { 1290 x.tx(sn); 1291 } 1292 1293 x.tx(" "); 1294 x.tx(c.primitiveValue("code")); 1295 if (!Utilities.noString(s)) { 1296 x.tx(": "); 1297 x.tx(s); 1298 } 1299 if (c.has("version")) { 1300 x.tx(" "+context.formatPhrase(RenderingContext.DATA_REND_VERSION, c.primitiveValue("version"), ")")); 1301 } 1302 } 1303 1304 protected void renderCoding(RenderingStatus status, XhtmlNode x, ResourceWrapper c) { 1305 String s = ""; 1306 if (c.has("display")) 1307 s = context.getTranslated(c.child("display")); 1308 if (Utilities.noString(s)) 1309 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1310 1311 if (Utilities.noString(s)) 1312 s = c.primitiveValue("code"); 1313 1314 if (context.isTechnicalMode()) { 1315 x.addText(s+" "+context.formatPhrase(RenderingContext.DATA_REND_DETAILS_STATED, displaySystem(c.primitiveValue("system")), c.primitiveValue("code"), " = '", lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")), c.primitiveValue("display"), "')")); 1316 } else 1317 x.span(null, "{"+c.primitiveValue("system")+" "+c.primitiveValue("code")+"}").addText(s); 1318 } 1319 1320 public String displayCodeableConcept(ResourceWrapper cc) { 1321 String s = context.getTranslated(cc.child("Text")); 1322 if (Utilities.noString(s)) { 1323 for (ResourceWrapper c : cc.children("coding")) { 1324 if (c.has("display")) { 1325 s = context.getTranslated(c.child("display")); 1326 break; 1327 } 1328 } 1329 } 1330 if (Utilities.noString(s)) { 1331 // still? ok, let's try looking it up 1332 for (ResourceWrapper c : cc.children("coding")) { 1333 if (c.has("code") && c.has("system")) { 1334 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1335 if (!Utilities.noString(s)) 1336 break; 1337 } 1338 } 1339 } 1340 1341 if (Utilities.noString(s)) { 1342 if (!cc.has("coding")) 1343 s = ""; 1344 else 1345 s = cc.children("coding").get(0).primitiveValue("code"); 1346 } 1347 return s; 1348 } 1349 1350 1351 protected void renderCodeableReference(RenderingStatus status, XhtmlNode x, ResourceWrapper e) throws FHIRFormatError, DefinitionException, IOException { 1352 if (e.has("concept")) { 1353 renderCodeableConcept(status, x, e.child("concept")); 1354 } 1355 if (e.has("reference")) { 1356 renderReference(status, x, e.child("reference")); 1357 } 1358 } 1359 1360 protected void renderCodeableConcept(RenderingStatus status, XhtmlNode x, ResourceWrapper cc) throws FHIRFormatError, DefinitionException, IOException { 1361 if (cc.isEmpty()) { 1362 return; 1363 } 1364 1365 String s = context.getTranslated(cc.child("text")); 1366 if (Utilities.noString(s)) { 1367 for (ResourceWrapper c : cc.children("coding")) { 1368 if (c.has("display")) { 1369 s = context.getTranslated(c.child("display")); 1370 break; 1371 } 1372 } 1373 } 1374 if (Utilities.noString(s)) { 1375 // still? ok, let's try looking it up 1376 for (ResourceWrapper c : cc.children("coding")) { 1377 if (c.has("code") && c.has("system")) { 1378 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1379 if (!Utilities.noString(s)) 1380 break; 1381 } 1382 } 1383 } 1384 1385 if (Utilities.noString(s)) { 1386 if (!cc.has("coding")) 1387 s = ""; 1388 else 1389 s = cc.children("coding").get(0).primitiveValue("code"); 1390 } 1391 1392 if (status.isShowCodeDetails()) { 1393 x.addText(s+" "); 1394 XhtmlNode sp = x.span("background: LightGoldenRodYellow; margin: 4px; border: 1px solid khaki", null); 1395 sp.tx(" ("); 1396 boolean first = true; 1397 for (ResourceWrapper c : cc.children("coding")) { 1398 if (first) { 1399 first = false; 1400 } else { 1401 sp.tx("; "); 1402 } 1403 String url = getLinkForSystem(c.primitiveValue("system"), c.primitiveValue("version")); 1404 if (url != null) { 1405 sp.ah(context.prefixLocalHref(url)).tx(displayCodeSource(c.primitiveValue("system"), c.primitiveValue("version"))); 1406 } else { 1407 sp.tx(displayCodeSource(c.primitiveValue("system"), c.primitiveValue("version"))); 1408 } 1409 if (c.has("code")) { 1410 sp.tx("#"+c.primitiveValue("code")); 1411 } 1412 if (c.has("display") && !s.equals(c.primitiveValue("display"))) { 1413 sp.tx(" \""+context.getTranslated(c.child("display"))+"\""); 1414 } 1415 } 1416 if (hasRenderableExtensions(cc)) { 1417 if (!first) { 1418 sp.tx("; "); 1419 } 1420 renderExtensionsInText(status, sp, cc, ";"); 1421 } 1422 sp.tx(")"); 1423 } else { 1424 1425 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1426 for (ResourceWrapper c : cc.children("coding")) { 1427 if (c.has("code") && c.has("system")) { 1428 b.append("{"+c.primitiveValue("system")+" "+c.primitiveValue("code")+"}"); 1429 } 1430 } 1431 1432 x.span(null, context.formatPhrase(RenderingContext.DATA_REND_CODES) +b.toString()).addText(s); 1433 } 1434 } 1435 1436 protected String displayIdentifier(ResourceWrapper ii) { 1437 String s = Utilities.noString(ii.primitiveValue("value")) ? "?ngen-9?" : ii.primitiveValue("value"); 1438 if ("urn:ietf:rfc:3986".equals(ii.primitiveValue("system")) && s.startsWith("urn:oid:")) { 1439 s = "OID:"+s.substring(8); 1440 } else if ("urn:ietf:rfc:3986".equals(ii.primitiveValue("system")) && s.startsWith("urn:uuid:")) { 1441 s = "UUID:"+s.substring(9); 1442 } else { 1443 NamingSystem ns = context.getContext().getNSUrlMap().get(ii.primitiveValue("system")); 1444 if (ns != null) { 1445 s = crPresent(ns)+"#"+s; 1446 } 1447 if (ii.has("type")) { 1448 ResourceWrapper type = ii.child("type"); 1449 if (type.has("text")) 1450 s = context.getTranslated(type.child("text"))+":\u00A0"+s; 1451 else if (type.has("coding") && type.children("coding").get(0).has("display")) 1452 s = context.getTranslated(type.children("coding").get(0).child("display"))+": "+s; 1453 else if (type.has("coding") && type.children("coding").get(0).has("code")) 1454 s = lookupCode(type.children("coding").get(0).primitiveValue("system"), type.children("coding").get(0).primitiveValue("version"), type.children("coding").get(0).primitiveValue("code")); 1455 } else if (ii.has("system")) { 1456 s = ii.primitiveValue("system")+"#"+s; 1457 } 1458 } 1459 1460 if (ii.has("use") || ii.has("period")) { 1461 s = s + "\u00A0("; 1462 if (ii.has("use")) { 1463 s = s + "use:\u00A0"+ii.primitiveValue("use"); 1464 } 1465 if (ii.has("use") || ii.has("period")) { 1466 s = s + ",\u00A0"; 1467 } 1468 if (ii.has("period")) { 1469 s = s + "period:\u00A0"+displayPeriod(ii.child("period")); 1470 } 1471 s = s + ")"; 1472 } 1473 return s; 1474 } 1475 1476 protected void renderIdentifier(RenderingStatus status, XhtmlNode x, ResourceWrapper ii) { 1477 if (ii.has("type")) { 1478 ResourceWrapper type = ii.child("type"); 1479 if (type.has("text")) { 1480 x.tx(context.getTranslated(type.child("text"))); 1481 } else if (type.has("coding") && type.children("coding").get(0).has("display")) { 1482 x.tx(context.getTranslated(type.children("coding").get(0).child("display"))); 1483 } else if (type.has("coding") && type.children("coding").get(0).has("code")) { 1484 x.tx(lookupCode(type.children("coding").get(0).primitiveValue("system"), type.children("coding").get(0).primitiveValue("version"), type.children("coding").get(0).primitiveValue("code"))); 1485 } 1486 x.tx("/"); 1487 } else if (ii.has("system")) { 1488 NamingSystem ns = context.getContext().getNSUrlMap().get(ii.primitiveValue("system")); 1489 if (ns != null) { 1490 if (ns.hasWebPath()) { 1491 x.ah(context.prefixLocalHref(ns.getWebPath()), ns.getDescription()).tx(crPresent(ns)); 1492 } else { 1493 x.tx(crPresent(ns)); 1494 } 1495 } else { 1496 switch (ii.primitiveValue("system")) { 1497 case "urn:oid:2.51.1.3": 1498 x.ah("https://www.gs1.org/standards/id-keys/gln", context.formatPhrase(RenderingContext.DATA_REND_GLN)).tx("GLN"); 1499 break; 1500 default: 1501 x.code(ii.primitiveValue("system")); 1502 } 1503 } 1504 x.tx("/"); 1505 } 1506 x.tx(Utilities.noString(ii.primitiveValue("value")) ? "?ngen-9?" : ii.primitiveValue("value")); 1507 1508 if (ii.has("use") || ii.has("period")) { 1509 x.nbsp(); 1510 x.tx("("); 1511 if (ii.has("use")) { 1512 x.tx(context.formatPhrase(RenderingContext.DATA_REND_USE)); 1513 x.nbsp(); 1514 x.tx(ii.primitiveValue("use")); 1515 } 1516 if (ii.has("use") || ii.has("period")) { 1517 x.tx(","); 1518 x.nbsp(); 1519 } 1520 if (ii.has("period")) { 1521 x.tx(context.formatPhrase(RenderingContext.DATA_REND_PERIOD)); 1522 x.nbsp(); 1523 x.tx(displayPeriod(ii.child("period"))); 1524 } 1525 x.tx(")"); 1526 } 1527 } 1528 1529 public static String displayHumanName(ResourceWrapper name) { 1530 StringBuilder s = new StringBuilder(); 1531 if (name.has("text")) 1532 s.append(name.primitiveValue("text")); 1533 else { 1534 for (ResourceWrapper p : name.children("given")) { 1535 s.append(p.primitiveValue()); 1536 s.append(" "); 1537 } 1538 if (name.has("family")) { 1539 s.append(name.primitiveValue("family")); 1540 s.append(" "); 1541 } 1542 } 1543 if (name.has("use") && !name.primitiveValue("use").equals("usual")) 1544 s.append("("+name.primitiveValue("use")+")"); 1545 return s.toString(); 1546 } 1547 1548 1549 protected void renderHumanName(RenderingStatus status, XhtmlNode x, ResourceWrapper name) { 1550 StringBuilder s = new StringBuilder(); 1551 if (name.has("text")) 1552 s.append(context.getTranslated(name.child("text"))); 1553 else { 1554 for (ResourceWrapper p : name.children("given")) { 1555 s.append(context.getTranslated(p)); 1556 s.append(" "); 1557 } 1558 if (name.has("family")) { 1559 s.append(context.getTranslated(name.child("family"))); 1560 s.append(" "); 1561 } 1562 } 1563 if (name.has("use") && !name.primitiveValue("use").equals("usual")) { 1564 s.append("("+context.getTranslatedCode(name.primitiveValue("use"), "http://hl7.org/fhir/name-use")+")"); 1565 } 1566 x.addText(s.toString()); 1567 } 1568 1569 private String displayAddress(ResourceWrapper address) { 1570 StringBuilder s = new StringBuilder(); 1571 if (address.has("text")) 1572 s.append(context.getTranslated(address.child("text"))); 1573 else { 1574 for (ResourceWrapper p : address.children("line")) { 1575 s.append(context.getTranslated(p)); 1576 s.append(" "); 1577 } 1578 if (address.has("city")) { 1579 s.append(context.getTranslated(address.child("city"))); 1580 s.append(" "); 1581 } 1582 if (address.has("state")) { 1583 s.append(context.getTranslated(address.child("state"))); 1584 s.append(" "); 1585 } 1586 1587 if (address.has("postalCode")) { 1588 s.append(context.getTranslated(address.child("postalCode"))); 1589 s.append(" "); 1590 } 1591 1592 if (address.has("country")) { 1593 s.append(context.getTranslated(address.child("country"))); 1594 s.append(" "); 1595 } 1596 } 1597 if (address.has("use")) { 1598 s.append("("+address.primitiveValue("use")+")"); 1599 } 1600 return s.toString(); 1601 } 1602 1603 protected void renderAddress(RenderingStatus status, XhtmlNode x, ResourceWrapper address) { 1604 x.addText(displayAddress(address)); 1605 } 1606 1607 1608 public String displayContactPoint(ResourceWrapper contact) { 1609 StringBuilder s = new StringBuilder(); 1610 s.append(describeSystem(contact.primitiveValue("system"))); 1611 if (Utilities.noString(contact.primitiveValue("value"))) 1612 s.append("-unknown-"); 1613 else 1614 s.append(contact.primitiveValue("value")); 1615 if (contact.has("use")) 1616 s.append("("+getTranslatedCode(contact.child("use"))+")"); 1617 return s.toString(); 1618 } 1619 1620 public String displayContactDetail(ResourceWrapper contact) { 1621 CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); 1622 for (ResourceWrapper cp : contact.children("telecom")) { 1623 s.append(displayContactPoint(cp)); 1624 } 1625 return contact.primitiveValue("name")+(s.length() == 0 ? "" : " ("+s.toString()+")"); 1626 } 1627 1628 protected String getLocalizedBigDecimalValue(BigDecimal input, Currency c) { 1629 NumberFormat numberFormat = NumberFormat.getNumberInstance(context.getLocale()); 1630 numberFormat.setGroupingUsed(true); 1631 numberFormat.setMaximumFractionDigits(c.getDefaultFractionDigits()); 1632 numberFormat.setMinimumFractionDigits(c.getDefaultFractionDigits()); 1633 return numberFormat.format(input); 1634 } 1635 1636 protected void renderMoney(RenderingStatus status, XhtmlNode x, ResourceWrapper money) { 1637 if (x.getName().equals("blockquote")) { 1638 x = x.para(); 1639 } 1640 Currency c = money.has("currency") ? Currency.getInstance(money.primitiveValue("currency")) : null; 1641 if (c != null) { 1642 XhtmlNode s = x.span(null, c.getDisplayName()); 1643 s.tx(c.getSymbol(context.getLocale())); 1644 s.tx(getLocalizedBigDecimalValue(new BigDecimal(money.primitiveValue("value")), c)); 1645 x.tx(" ("+c.getCurrencyCode()+")"); 1646 } else { 1647 if (money.has("currency")) { 1648 x.tx(money.primitiveValue("currency")); 1649 } 1650 x.tx(money.primitiveValue("value")); 1651 } 1652 } 1653 1654 protected void renderExpression(RenderingStatus status, XhtmlNode x, ResourceWrapper expr) { 1655 // there's two parts: what the expression is, and how it's described. 1656 // we start with what it is, and then how it's described 1657 XhtmlNode p = x; 1658 if (p.getName().equals("blockquote")) { 1659 p = p.para(); 1660 } 1661 if (expr.has("expression")) { 1662 if (expr.has("reference")) { 1663 p = x.ah(context.prefixLocalHref(expr.primitiveValue("reference"))); 1664 } 1665 XhtmlNode c = p; 1666 if (expr.has("language")) { 1667 c = c.span(null, expr.primitiveValue("language")); 1668 } 1669 c.code().tx(expr.primitiveValue("expression")); 1670 } else if (expr.has("reference")) { 1671 p.ah(context.prefixLocalHref(expr.primitiveValue("reference"))).tx(context.formatPhrase(RenderingContext.DATA_REND_SOURCE)); 1672 } 1673 if (expr.has("name") || expr.has("description")) { 1674 p.tx("("); 1675 if (expr.has("name")) { 1676 p.b().tx(expr.primitiveValue("name")); 1677 } 1678 if (expr.has("description")) { 1679 p.tx("\""); 1680 p.tx(context.getTranslated(expr.child("description"))); 1681 p.tx("\""); 1682 } 1683 p.tx(")"); 1684 } 1685 } 1686 1687 1688 protected void renderContactPoint(RenderingStatus status, XhtmlNode x, ResourceWrapper contact) { 1689 if (contact != null) { 1690 if (!contact.has("system")) { 1691 x.addText(displayContactPoint(contact)); 1692 } else { 1693 String v = contact.primitiveValue("value"); 1694 switch (contact.primitiveValue("system")) { 1695 case "email": 1696 x.ah("mailto:"+v).tx(v); 1697 break; 1698 case "fax": 1699 x.addText(displayContactPoint(contact)); 1700 break; 1701 case "other": 1702 x.addText(displayContactPoint(contact)); 1703 break; 1704 case "pager": 1705 x.addText(displayContactPoint(contact)); 1706 break; 1707 case "phone": 1708 if (contact.has("value") && v != null && v.startsWith("+")) { 1709 x.ah("tel:"+v.replace(" ", "")).tx(v); 1710 } else { 1711 x.addText(displayContactPoint(contact)); 1712 } 1713 break; 1714 case "sms": 1715 x.addText(displayContactPoint(contact)); 1716 break; 1717 case "url": 1718 x.ah(context.prefixLocalHref(v)).tx(v); 1719 break; 1720 default: 1721 break; 1722 } 1723 } 1724 } 1725 } 1726 1727 protected void displayContactPoint(XhtmlNode p, ContactPoint c) { 1728 if (c != null) { 1729 if (c.getSystem() == ContactPointSystem.PHONE) { 1730 p.tx(context.formatPhrase(RenderingContext.DATA_REND_PHONE, c.getValue()) + " "); 1731 } else if (c.getSystem() == ContactPointSystem.FAX) { 1732 p.tx(context.formatPhrase(RenderingContext.DATA_REND_FAX, c.getValue()) + " "); 1733 } else if (c.getSystem() == ContactPointSystem.EMAIL) { 1734 p.tx(c.getValue()); 1735 } else if (c.getSystem() == ContactPointSystem.URL) { 1736 if (c.getValue().length() > 30) { 1737 p.addText(c.getValue().substring(0, 30)+"..."); 1738 } else { 1739 p.addText(c.getValue()); 1740 } 1741 } 1742 } 1743 } 1744 1745 protected void addTelecom(XhtmlNode p, ResourceWrapper c) { 1746 String sys = c.primitiveValue("system"); 1747 String value = c.primitiveValue("value"); 1748 if (sys.equals("phone")) { 1749 p.tx(context.formatPhrase(RenderingContext.DATA_REND_PHONE, value) + " "); 1750 } else if (sys.equals("fax")) { 1751 p.tx(context.formatPhrase(RenderingContext.DATA_REND_FAX, value) + " "); 1752 } else if (sys.equals("email")) { 1753 p.ah("mailto:"+value).addText(value); 1754 } else if (sys.equals("url")) { 1755 if (value.length() > 30) 1756 p.ah(context.prefixLocalHref(value)).addText(value.substring(0, 30)+"..."); 1757 else 1758 p.ah(context.prefixLocalHref(value)).addText(value); 1759 } 1760 } 1761 private static String describeSystem(String system) { 1762 if (system == null) 1763 return ""; 1764 switch (system) { 1765 case "phone": return "ph: "; 1766 case "fax": return "fax: "; 1767 default: 1768 return ""; 1769 } 1770 } 1771 1772 protected String displayQuantity(ResourceWrapper q) { 1773 if (q == null) { 1774 return ""; 1775 } 1776 StringBuilder s = new StringBuilder(); 1777 1778 s.append(q.has("value") ? q.primitiveValue("value") : "?"); 1779 if (q.has("unit")) 1780 s.append(" ").append(q.primitiveValue("unit")); 1781 else if (q.has("code")) 1782 s.append(" ").append(q.primitiveValue("code")); 1783 1784 return s.toString(); 1785 } 1786 1787 protected void renderQuantity(RenderingStatus status, XhtmlNode x, ResourceWrapper q) { 1788 if (q.has("comparator")) 1789 x.addText(q.primitiveValue("comparator")); 1790 if (q.has("value")) { 1791 x.addText(context.getTranslated(q.child("value"))); 1792 } 1793 if (q.has("unit")) 1794 x.tx(" "+context.getTranslated(q.child("unit"))); 1795 else if (q.has("code") && q.has("system")) { 1796 // 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 1797 if (q.has("system") && q.primitiveValue("system").equals("http://unitsofmeasure.org")) 1798 x.tx(" "+q.primitiveValue("code")); 1799 else 1800 x.tx("(unit "+q.primitiveValue("code")+" from "+q.primitiveValue("system")+")"); 1801 } 1802 if (context.isTechnicalMode() && q.has("code")) { 1803 x.span("background: LightGoldenRodYellow", null).tx(" "+ (context.formatPhrase(RenderingContext.DATA_REND_DETAILS, displaySystem(q.primitiveValue("system")))) +q.primitiveValue("code")+" = '"+lookupCode(q.primitiveValue("system"), null, q.primitiveValue("code"))+"')"); 1804 } 1805 } 1806 1807 1808 protected void renderQuantity(HierarchicalTableGenerator gen, List<Piece> pieces, ResourceWrapper q, boolean showCodeDetails) { 1809 pieces.add(gen.new Piece(null, displayQuantity(q), null)); 1810 } 1811 1812 protected void renderQuantity(HierarchicalTableGenerator gen, List<Piece> pieces, Quantity q, boolean showCodeDetails) { 1813 pieces.add(gen.new Piece(null, displayQuantity(wrapNC(q)), null)); 1814 } 1815 1816 public String displayRange(ResourceWrapper q) { 1817 if (!q.has("low") && !q.has("high")) 1818 return "?"; 1819 1820 StringBuilder b = new StringBuilder(); 1821 1822 ResourceWrapper lowC = q.child("low"); 1823 ResourceWrapper highC = q.child("high"); 1824 boolean sameUnits = (lowC != null && highC != null) && ((lowC.has("unit") && highC.has("unit") && lowC.child("unit").matches(highC.child("unit"))) 1825 || (lowC.has("code") && highC.has("code") && lowC.child("code").matches(highC.child("code")))); 1826 String low = "?"; 1827 if (q.has("low") && lowC.has("value")) 1828 low = sameUnits ? lowC.primitiveValue("value").toString() : displayQuantity(lowC); 1829 String high = displayQuantity(highC); 1830 if (high.isEmpty()) 1831 high = "?"; 1832 b.append(low).append("\u00A0to\u00A0").append(high); 1833 return b.toString(); 1834 } 1835 1836 protected void renderRange(RenderingStatus status, XhtmlNode x, ResourceWrapper q) { 1837 if (q.has("low")) 1838 x.addText(q.child("low").primitiveValue("value").toString()); 1839 else 1840 x.tx("?"); 1841 x.tx("-"); 1842 if (q.has("high")) 1843 x.addText(q.child("high").primitiveValue("value").toString()); 1844 else 1845 x.tx("?"); 1846 if (q.has("low") && q.child("low").has("unit")) 1847 x.tx(" "+q.child("low").primitiveValue("unit")); 1848 } 1849 1850 public String displayPeriod(ResourceWrapper p) { 1851 String s = !p.has("start") ? "(?)" : displayDateTime(p.child("start")); 1852 s = s + " --> "; 1853 return s + (!p.has("end") ? context.formatPhrase(RenderingContext.DATA_REND_ONGOING) : displayDateTime(p.child("end"))); 1854 } 1855 1856 public void renderPeriod(RenderingStatus status, XhtmlNode x, ResourceWrapper p) { 1857 x.addText(!p.has("start") ? "??" : displayDateTime(p.child("start"))); 1858 x.tx(" --> "); 1859 x.addText(!p.has("end") ? context.formatPhrase(RenderingContext.DATA_REND_ONGOING) : displayDateTime(p.child("end"))); 1860 } 1861 1862 public void renderUsageContext(RenderingStatus status, XhtmlNode x, ResourceWrapper u) throws FHIRFormatError, DefinitionException, IOException { 1863 renderCoding(status, x, u.child("code")); 1864 x.tx(": "); 1865 renderDataType(status, x, u.child("value")); 1866 } 1867 1868 1869 public void renderTriggerDefinition(RenderingStatus status, XhtmlNode x, ResourceWrapper td) throws FHIRFormatError, DefinitionException, IOException { 1870 if (x.isPara()) { 1871 x.b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 1872 x.tx(": "); 1873 x.tx(td.child("type").primitiveValue("display")); 1874 1875 if (td.has("name")) { 1876 x.tx(", "); 1877 x.b().tx(context.formatPhrase(RenderingContext.GENERAL_NAME)); 1878 x.tx(": "); 1879 x.tx(context.getTranslated(td.child("name"))); 1880 } 1881 if (td.has("code")) { 1882 x.tx(", "); 1883 x.b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 1884 x.tx(": "); 1885 renderCodeableConcept(status, x, td.child("code")); 1886 } 1887 if (td.has("timing")) { 1888 x.tx(", "); 1889 x.b().tx(context.formatPhrase(RenderingContext.DATA_REND_TIMING)); 1890 x.tx(": "); 1891 renderDataType(status, x, td.child("timing")); 1892 } 1893 if (td.has("condition")) { 1894 x.tx(", "); 1895 x.b().tx(context.formatPhrase(RenderingContext.DATA_REND_COND)); 1896 x.tx(": "); 1897 renderExpression(status, x, td.child("condition")); 1898 } 1899 } else { 1900 XhtmlNode tbl = x.table("grid"); 1901 1902 XhtmlNode tr = tbl.tr(); 1903 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 1904 tr.td().tx(td.child("type").primitiveValue("display")); 1905 1906 if (td.has("name")) { 1907 tr = tbl.tr(); 1908 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_NAME)); 1909 tr.td().tx(context.getTranslated(td.child("name"))); 1910 } 1911 if (td.has("code")) { 1912 tr = tbl.tr(); 1913 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 1914 renderCodeableConcept(status, tr.td(), td.child("code")); 1915 } 1916 if (td.has("timing")) { 1917 tr = tbl.tr(); 1918 tr.td().b().tx(context.formatPhrase(RenderingContext.DATA_REND_TIMING)); 1919 renderDataType(status, tr.td(), td.child("timing")); 1920 } 1921 if (td.has("condition")) { 1922 tr = tbl.tr(); 1923 tr.td().b().tx(context.formatPhrase(RenderingContext.DATA_REND_COND)); 1924 renderExpression(status, tr.td(), td.child("condition")); 1925 } 1926 } 1927 } 1928 1929 public void renderDataRequirement(RenderingStatus status, XhtmlNode x, ResourceWrapper dr) throws FHIRFormatError, DefinitionException, IOException { 1930 XhtmlNode tbl = x.table("grid"); 1931 XhtmlNode tr = tbl.tr(); 1932 XhtmlNode td = tr.td().colspan("2"); 1933 td.b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 1934 td.tx(": "); 1935 StructureDefinition sd = context.getWorker().fetchTypeDefinition(dr.primitiveValue("type")); 1936 if (sd != null && sd.hasWebPath()) { 1937 td.ah(context.prefixLocalHref(sd.getWebPath())).tx(dr.primitiveValue("type")); 1938 } else { 1939 td.tx(dr.primitiveValue("type")); 1940 } 1941 if (dr.has("profile")) { 1942 td.tx(" ("); 1943 boolean first = true; 1944 for (ResourceWrapper p : dr.children("profile")) { 1945 if (first) first = false; else td.tx(" | "); 1946 sd = context.getWorker().fetchResource(StructureDefinition.class, p.primitiveValue()); 1947 if (sd != null && sd.hasWebPath()) { 1948 td.ah(context.prefixLocalHref(sd.getWebPath())).tx(crPresent(sd)); 1949 } else { 1950 td.tx(p.primitiveValue()); 1951 } 1952 } 1953 td.tx(")"); 1954 } 1955 if (dr.has("subject")) { 1956 tr = tbl.tr(); 1957 td = tr.td().colspan("2"); 1958 td.b().tx(context.formatPhrase(RenderingContext.GENERAL_SUBJ)); 1959 ResourceWrapper subj = dr.child("subject"); 1960 if (subj.fhirType().equals("reference")) { 1961 renderReference(status, td, subj); 1962 } else { 1963 renderCodeableConcept(status, td, subj); 1964 } 1965 } 1966 if (dr.has("codeFilter") || dr.has("dateFilter")) { 1967 tr = tbl.tr().backgroundColor("#efefef"); 1968 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_FILTER)); 1969 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_VALUE)); 1970 } 1971 for (ResourceWrapper cf : dr.children("codeFilter")) { 1972 tr = tbl.tr(); 1973 if (cf.has("path")) { 1974 tr.td().tx(cf.primitiveValue("path")); 1975 } else { 1976 tr.td().tx(context.formatPhrase(RenderingContext.DATA_REND_SEARCH, cf.primitiveValue("searchParam")) + " "); 1977 } 1978 if (cf.has("valueSet")) { 1979 td = tr.td(); 1980 td.tx(context.formatPhrase(RenderingContext.DATA_REND_VALUESET) + " "); 1981 renderDataType(status, td, cf.child("valueSet")); 1982 } else { 1983 boolean first = true; 1984 td = tr.td(); 1985 td.tx(context.formatPhrase(RenderingContext.DATA_REND_THESE_CODES) + " "); 1986 for (ResourceWrapper c : cf.children("code")) { 1987 if (first) first = false; else td.tx(", "); 1988 renderDataType(status, td, c); 1989 } 1990 } 1991 } 1992 for (ResourceWrapper cf : dr.children("dateFilter")) { 1993 tr = tbl.tr(); 1994 if (cf.has("path")) { 1995 tr.td().tx(cf.primitiveValue("path")); 1996 } else { 1997 tr.td().tx(context.formatPhrase(RenderingContext.DATA_REND_SEARCH, cf.primitiveValue("searchParam")) + " "); 1998 } 1999 renderDataType(status, tr.td(), cf.child("value")); 2000 } 2001 if (dr.has("sort") || dr.has("limit")) { 2002 tr = tbl.tr(); 2003 td = tr.td().colspan("2"); 2004 if (dr.has("limit")) { 2005 td.b().tx(context.formatPhrase(RenderingContext.DATA_REND_LIMIT)); 2006 td.tx(": "); 2007 td.tx(dr.primitiveValue("limit")); 2008 if (dr.has("sort")) { 2009 td.tx(", "); 2010 } 2011 } 2012 if (dr.has("sort")) { 2013 td.b().tx(context.formatPhrase(RenderingContext.DATA_REND_SORT)); 2014 td.tx(": "); 2015 boolean first = true; 2016 for (ResourceWrapper p : dr.children("sort")) { 2017 if (first) first = false; else td.tx(" | "); 2018 td.tx(p.primitiveValue("direction").equals("ascending") ? "+" : "-"); 2019 td.tx(p.primitiveValue("path")); 2020 } 2021 } 2022 } 2023 } 2024 2025 2026 private String displayTiming(ResourceWrapper s) throws FHIRException { 2027 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 2028 if (s.has("code")) { 2029 b.append(context.formatPhrase(RenderingContext.GENERAL_CODE, displayCodeableConcept(s.child("code"))) + " "); 2030 } 2031 2032 if (s.has("event")) { 2033 CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder(); 2034 for (ResourceWrapper p : s.children("event")) { 2035 if (p.hasPrimitiveValue()) { 2036 c.append(displayDateTime(p)); 2037 } else if (!renderExpression(c, p)) { 2038 c.append("??"); 2039 } 2040 } 2041 b.append(context.formatPhrase(RenderingContext.DATA_REND_EVENTS, c.toString()) + " "); 2042 } 2043 2044 if (s.has("repeat")) { 2045 ResourceWrapper rep = s.child("repeat"); 2046 if (rep.has("boundsPeriod") && rep.child("boundsPeriod").has("start")) 2047 b.append(context.formatPhrase(RenderingContext.DATA_REND_STARTING, displayDateTime(rep.child("boundsPeriod").child("start"))) + " "); 2048 if (rep.has("count")) 2049 b.append(context.formatPhrase(RenderingContext.DATA_REND_COUNT, rep.primitiveValue("count")) + " " + " times"); 2050 if (rep.has("duration")) 2051 b.append(context.formatPhrase(RenderingContext.DATA_REND_DURATION, rep.primitiveValue("duration")+displayTimeUnits(rep.primitiveValue("periodUnit"))) + " "); 2052 2053 if (rep.has("when")) { 2054 String st = ""; 2055 if (rep.has("offset")) { 2056 st = rep.primitiveValue("offset")+"min "; 2057 } 2058 b.append(st); 2059 for (ResourceWrapper wh : rep.children("when")) { 2060 b.append(displayEventCode(wh.primitiveValue())); 2061 } 2062 } else { 2063 String st = ""; 2064 if (!rep.has("frequency") || (!rep.has("frequencyMax") && rep.primitiveValue("frequency").equals("1"))) { 2065 st = context.formatPhrase(RenderingContext.DATA_REND_ONCE); 2066 } else { 2067 st = rep.primitiveValue("frequency"); 2068 if (rep.has("frequencyMax")) 2069 st = st + "-"+rep.primitiveValue("frequencyMax"); 2070 } 2071 if (rep.has("period")) { 2072 st = st + " "+ (context.formatPhrase(RenderingContext.DATA_REND_PER))+rep.primitiveValue("period"); 2073 if (rep.has("periodMax")) 2074 st = st + "-"+rep.primitiveValue("periodMax"); 2075 st = st + " "+displayTimeUnits(rep.primitiveValue("periodUnit")); 2076 } 2077 b.append(st); 2078 } 2079 if (rep.has("boundsPeriod") && rep.child("boundsPeriod").has("end")) 2080 b.append(context.formatPhrase(RenderingContext.DATA_REND_UNTIL, displayDateTime(rep.child("boundsPeriod").child("end"))) + " "); 2081 } 2082 return b.toString(); 2083 } 2084 2085 private boolean renderExpression(CommaSeparatedStringBuilder c, ResourceWrapper p) { 2086 ResourceWrapper exp = p.extensionValue("http://hl7.org/fhir/StructureDefinition/cqf-expression"); 2087 if (exp == null || !exp.has("value")) { 2088 return false; 2089 } 2090 c.append(exp.child("value").primitiveValue("expression")); 2091 return true; 2092 } 2093 2094 private String displayEventCode(String when) { 2095 switch (when) { 2096 case "c": return (context.formatPhrase(RenderingContext.DATA_REND_MEALS)); 2097 case "cd": return (context.formatPhrase(RenderingContext.DATA_REND_ATLUNCH)); 2098 case "cm": return (context.formatPhrase(RenderingContext.DATA_REND_ATBKFST)); 2099 case "cv": return (context.formatPhrase(RenderingContext.DATA_REND_ATDINR)); 2100 case "ac": return (context.formatPhrase(RenderingContext.DATA_REND_BFMEALS)); 2101 case "acd": return (context.formatPhrase(RenderingContext.DATA_REND_BFLUNCH)); 2102 case "acm": return (context.formatPhrase(RenderingContext.DATA_REND_BFBKFST)); 2103 case "acv": return (context.formatPhrase(RenderingContext.DATA_REND_BFDINR)); 2104 case "hs": return (context.formatPhrase(RenderingContext.DATA_REND_BFSLEEP)); 2105 case "pc": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRMEALS)); 2106 case "pcd": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRLUNCH)); 2107 case "pcm": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRBKFST)); 2108 case "pcv": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRDINR)); 2109 case "wake": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRWKNG)); 2110 default: return "?ngen-6?"; 2111 } 2112 } 2113 2114 private String displayTimeUnits(String units) { 2115 if (units == null) 2116 return "?ngen-7?"; 2117 switch (units) { 2118 case "a": return "years"; 2119 case "d": return "days"; 2120 case "h": return "hours"; 2121 case "min": return "minutes"; 2122 case "mo": return "months"; 2123 case "s": return "seconds"; 2124 case "wk": return "weeks"; 2125 default: return "?ngen-8?"; 2126 } 2127 } 2128 2129 protected void renderTiming(RenderingStatus status, XhtmlNode x, ResourceWrapper s) throws FHIRException { 2130 x.addText(displayTiming(s)); 2131 } 2132 2133 2134 private String displaySampledData(ResourceWrapper s) { 2135 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 2136 if (s.has("origin")) 2137 b.append(context.formatPhrase(RenderingContext.DATA_REND_ORIGIN, displayQuantity(s.child("origin"))) + " "); 2138 2139 if (s.has("interval")) { 2140 b.append(context.formatPhrase(RenderingContext.DATA_REND_INT, s.primitiveValue("interval")) + " "); 2141 2142 if (s.has("intervalUnit")) 2143 b.append(s.primitiveValue("intervalUnit")); 2144 } 2145 2146 if (s.has("factor")) 2147 b.append(context.formatPhrase(RenderingContext.DATA_REND_FACT, s.primitiveValue("factor")) + " "); 2148 2149 if (s.has("lowerLimit")) 2150 b.append(context.formatPhrase(RenderingContext.DATA_REND_LOWER, s.primitiveValue("lowerLimit")) + " "); 2151 2152 if (s.has("upperLimit")) 2153 b.append(context.formatPhrase(RenderingContext.DATA_REND_UP, s.primitiveValue("upperLimit")) + " "); 2154 2155 if (s.has("dimensions")) 2156 b.append(context.formatPhrase(RenderingContext.DATA_REND_DIM, s.primitiveValue("dimensions")) + " "); 2157 2158 if (s.has("data")) 2159 b.append(context.formatPhrase(RenderingContext.DATA_REND_DATA, s.primitiveValue("data")) + " "); 2160 2161 return b.toString(); 2162 } 2163 2164 protected void renderSampledData(RenderingStatus status, XhtmlNode x, ResourceWrapper sampledData) { 2165 x.addText(displaySampledData(sampledData)); 2166 } 2167 2168 public RenderingContext getContext() { 2169 return context; 2170 } 2171 2172 2173 public XhtmlNode makeExceptionXhtml(Exception e, String function) { 2174 XhtmlNode xn; 2175 xn = new XhtmlNode(NodeType.Element, "div"); 2176 XhtmlNode p = xn.para(); 2177 p.b().tx((context.formatPhrase(RenderingContext.DATA_REND_EXCEPTION)) +function+": "+e.getMessage()); 2178 p.addComment(getStackTrace(e)); 2179 return xn; 2180 } 2181 2182 private String getStackTrace(Exception e) { 2183 StringBuilder b = new StringBuilder(); 2184 b.append("\r\n"); 2185 for (StackTraceElement t : e.getStackTrace()) { 2186 b.append(t.getClassName()+"."+t.getMethodName()+" ("+t.getFileName()+":"+t.getLineNumber()); 2187 b.append("\r\n"); 2188 } 2189 return b.toString(); 2190 } 2191 2192 protected String versionFromCanonical(String system) { 2193 if (system == null) { 2194 return null; 2195 } else if (system.contains("|")) { 2196 return system.substring(0, system.indexOf("|")); 2197 } else { 2198 return null; 2199 } 2200 } 2201 2202 protected String systemFromCanonical(String system) { 2203 if (system == null) { 2204 return null; 2205 } else if (system.contains("|")) { 2206 return system.substring(system.indexOf("|")+1); 2207 } else { 2208 return system; 2209 } 2210 } 2211 2212 2213}