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