
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-FR"+dt; 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", "", null, 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, null, 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, 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 x, ResourceWrapper a) throws FHIRException { 1098 StringBuilder b = new StringBuilder(); 1099 if (a.has("text")) { 1100 b.append(context.getTranslated(a.child("text"))); 1101 } 1102 1103 if (a.has("text") && (a.has("author") || a.has("time"))) { 1104 b.append(" ("); 1105 } 1106 1107 if (a.has("author")) { 1108 b.append(context.formatPhrase(RenderingContext.DATA_REND_BY) + " "); 1109 ResourceWrapper auth = a.child("author"); 1110 if (auth.fhirType().equals("Reference")) { 1111 b.append(auth.primitiveValue("reference")); 1112 } else if (auth.fhirType().equals("string")) { 1113 b.append(context.getTranslated(auth)); 1114 } 1115 } 1116 1117 1118 if (a.has("time")) { 1119 if (b.length() > 0) { 1120 b.append(" "); 1121 } 1122 b.append("@").append(displayDateTime(a.child("time"))); 1123 } 1124 if (a.has("text") && (a.has("author") || a.has("time"))) { 1125 b.append(")"); 1126 } 1127 1128 1129 x.addText(b.toString()); 1130 } 1131 1132 public String displayCoding(ResourceWrapper c) { 1133 String s = ""; 1134 if (context.isTechnicalMode()) { 1135 s = context.getTranslated(c.child("display")); 1136 if (Utilities.noString(s)) { 1137 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1138 } 1139 if (Utilities.noString(s)) { 1140 s = displayCodeTriple(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1141 } else if (c.has("system")) { 1142 s = s + " ("+displayCodeTriple(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code"))+")"; 1143 } else if (c.has("code")) { 1144 s = s + " ("+c.primitiveValue("code")+")"; 1145 } 1146 } else { 1147 if (c.has("display")) 1148 return context.getTranslated(c.child("display")); 1149 if (Utilities.noString(s)) 1150 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1151 if (Utilities.noString(s)) 1152 s = c.primitiveValue("code"); 1153 } 1154 return s; 1155 } 1156 1157 public String displayCodeSource(String system, String version) { 1158 String s = displaySystem(system); 1159 if (version != null) { 1160 s = s + "["+describeVersion(version)+"]"; 1161 } 1162 return s; 1163 } 1164 1165 private String displayCodeTriple(String system, String version, String code) { 1166 if (system == null) { 1167 if (code == null) { 1168 return ""; 1169 } else { 1170 return "#"+code; 1171 } 1172 } else { 1173 String s = displayCodeSource(system, version); 1174 if (code != null) { 1175 s = s + "#"+code; 1176 } 1177 return s; 1178 } 1179 } 1180 1181 public String displayCoding(List<Coding> list) { 1182 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1183 for (Coding c : list) { 1184 b.append(displayCoding(wrapNC(c))); 1185 } 1186 return b.toString(); 1187 } 1188 1189 protected void renderCoding(HierarchicalTableGenerator gen, List<Piece> pieces, Coding c) { 1190 if (c.isEmpty()) { 1191 return; 1192 } 1193 1194 String url = getLinkForSystem(c.getSystem(), c.getVersion()); 1195 String name = displayCodeSource(c.getSystem(), c.getVersion()); 1196 if (!Utilities.noString(url)) { 1197 pieces.add(gen.new Piece(url, name, c.getSystem()+(c.hasVersion() ? "#"+c.getVersion() : ""))); 1198 } else { 1199 pieces.add(gen.new Piece(null, name, c.getSystem()+(c.hasVersion() ? "#"+c.getVersion() : ""))); 1200 } 1201 pieces.add(gen.new Piece(null, "#"+c.getCode(), null)); 1202 String s = context.getTranslated(c.getDisplayElement()); 1203 if (Utilities.noString(s)) { 1204 s = lookupCode(c.getSystem(), c.getVersion(), c.getCode()); 1205 } 1206 if (!Utilities.noString(s)) { 1207 pieces.add(gen.new Piece(null, " \""+s+"\"", null)); 1208 } 1209 } 1210 1211 protected void renderCoding(HierarchicalTableGenerator gen, List<Piece> pieces, ResourceWrapper c) { 1212 if (c.isEmpty()) { 1213 return; 1214 } 1215 1216 String url = getLinkForSystem(c.primitiveValue("system"), c.primitiveValue("version")); 1217 String name = displayCodeSource(c.primitiveValue("system"), c.primitiveValue("version")); 1218 if (!Utilities.noString(url)) { 1219 pieces.add(gen.new Piece(url, name, c.primitiveValue("system")+(c.has("version") ? "#"+c.primitiveValue("version") : ""))); 1220 } else { 1221 pieces.add(gen.new Piece(null, name, c.primitiveValue("system")+(c.has("version") ? "#"+c.primitiveValue("version") : ""))); 1222 } 1223 pieces.add(gen.new Piece(null, "#"+c.primitiveValue("code"), null)); 1224 String s = context.getTranslated(c.child("display")); 1225 if (Utilities.noString(s)) { 1226 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1227 } 1228 if (!Utilities.noString(s)) { 1229 pieces.add(gen.new Piece(null, " \""+s+"\"", null)); 1230 } 1231 } 1232 1233 public String getLinkForSystem(String system, String version) { 1234 if ("http://snomed.info/sct".equals(system)) { 1235 return "https://browser.ihtsdotools.org/"; 1236 } else if ("http://loinc.org".equals(system)) { 1237 return "https://loinc.org/"; 1238 } else if ("http://unitsofmeasure.org".equals(system)) { 1239 return "http://ucum.org"; 1240 } else if ("http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl".equals(system)) { 1241 return "https://ncit.nci.nih.gov/ncitbrowser/pages/home.jsf"; 1242 } else { 1243 String url = system; 1244 if (version != null) { 1245 url = url + "|"+version; 1246 } 1247 CodeSystem cs = context.getWorker().fetchCodeSystem(url); 1248 if (cs != null && cs.hasWebPath()) { 1249 return cs.getWebPath(); 1250 } 1251 return null; 1252 } 1253 } 1254 1255 public String getLinkForCode(String system, String version, String code) { 1256 if ("http://snomed.info/sct".equals(system)) { 1257 return SnomedUtilities.getSctLink(version, code, context.getContext().getExpansionParameters()); 1258 } else if ("http://loinc.org".equals(system)) { 1259 if (!Utilities.noString(code)) { 1260 return "https://loinc.org/"+code; 1261 } else { 1262 return "https://loinc.org/"; 1263 } 1264 } else if ("http://www.nlm.nih.gov/research/umls/rxnorm".equals(system)) { 1265 if (!Utilities.noString(code)) { 1266 return "https://mor.nlm.nih.gov/RxNav/search?searchBy=RXCUI&searchTerm="+code; 1267 } else { 1268 return "https://www.nlm.nih.gov/research/umls/rxnorm/index.html"; 1269 } 1270 } else if ("http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl".equals(system)) { 1271 if (!Utilities.noString(code)) { 1272 return "https://ncit.nci.nih.gov/ncitbrowser/ConceptReport.jsp?code="+code; 1273 } else { 1274 return "https://ncit.nci.nih.gov/ncitbrowser/pages/home.jsf"; 1275 } 1276 } else if ("urn:iso:std:iso:3166".equals(system)) { 1277 if (!Utilities.noString(code)) { 1278 return "https://en.wikipedia.org/wiki/ISO_3166-2:"+code; 1279 } else { 1280 return "https://en.wikipedia.org/wiki/ISO_3166-2"; 1281 } 1282 } else { 1283 CodeSystem cs = context.getWorker().fetchCodeSystem(system, version); 1284 if (cs != null && cs.hasWebPath()) { 1285 if (!Utilities.noString(code)) { 1286 return cs.getWebPath()+"#"+cs.getId()+"-"+Utilities.nmtokenize(code); 1287 } else { 1288 return cs.getWebPath(); 1289 } 1290 } 1291 } 1292 return null; 1293 } 1294 1295 public CodeResolution resolveCode(String system, String code) { 1296 return resolveCode(new Coding().setSystem(system).setCode(code)); 1297 } 1298 1299 public CodeResolution resolveCode(ResourceWrapper c) { 1300 String systemName; 1301 String systemLink; 1302 String link; 1303 String display = null; 1304 String hint; 1305 1306 if (c.has("display")) 1307 display = context.getTranslated(c.child("display")); 1308 if (Utilities.noString(display)) 1309 display = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1310 if (Utilities.noString(display)) { 1311 display = c.primitiveValue("code"); 1312 } 1313 1314 CodeSystem cs = context.getWorker().fetchCodeSystem(c.primitiveValue("system")); 1315 systemLink = cs != null ? cs.getWebPath() : null; 1316 systemName = cs != null ? crPresent(cs) : displaySystem(c.primitiveValue("system")); 1317 link = getLinkForCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1318 1319 hint = systemName+": "+display+(c.has("version") ? " "+ context.formatPhrase(RenderingContext.DATA_REND_VERSION, c.primitiveValue("version"), ")") : ""); 1320 return new CodeResolution(systemName, systemLink, link, display, hint); 1321 } 1322 1323 public CodeResolution resolveCode(Coding code) { 1324 return resolveCode(wrapNC(code)); 1325 } 1326 1327 public CodeResolution resolveCode(CodeableConcept code) { 1328 if (code.hasCoding()) { 1329 return resolveCode(code.getCodingFirstRep()); 1330 } else { 1331 return new CodeResolution(null, null, null, code.getText(), code.getText()); 1332 } 1333 } 1334 protected void renderCodingWithDetails(RenderingStatus status, XhtmlNode x, ResourceWrapper c) throws FHIRFormatError, DefinitionException, IOException { 1335 String s = ""; 1336 if (c.has("display")) 1337 s = context.getTranslated(c.child("display")); 1338 if (Utilities.noString(s)) 1339 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1340 1341 String sn = displaySystem(c.primitiveValue("system")); 1342 String link = getLinkForCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1343 XhtmlNode xi = link != null ? x.ah(context.prefixLocalHref(link)) : x; 1344 xi.tx(sn); 1345 xi.tx(" "); 1346 1347 xi.tx(c.primitiveValue("code")); 1348 1349 if (!Utilities.noString(s)) { 1350 x.tx(": "); 1351 x.tx(s); 1352 } 1353 if (c.has("version")) { 1354 x.tx(" "+context.formatPhrase(RenderingContext.DATA_REND_VERSION, c.primitiveValue("version"), ")")); 1355 } 1356 checkRenderExtensions(status, x, c); 1357 } 1358 1359 protected void renderCoding(RenderingStatus status, XhtmlNode x, ResourceWrapper c) { 1360 renderCoding(status, x, c, true); 1361 } 1362 1363 protected void renderCoding(RenderingStatus status, XhtmlNode x, ResourceWrapper c, boolean details) { 1364 String s = ""; 1365 if (c.has("display")) 1366 s = context.getTranslated(c.child("display")); 1367 if (Utilities.noString(s)) 1368 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1369 1370 if (Utilities.noString(s)) 1371 s = c.primitiveValue("code"); 1372 1373 if (context.isTechnicalMode() && details) { 1374 String d = c.primitiveValue("display") == null ? lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")): c.primitiveValue("display"); 1375 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); 1376 x.addText(s+" "+d); 1377 } else { 1378 x.span(null, "{"+c.primitiveValue("system")+" "+c.primitiveValue("code")+"}").addText(s); 1379 } 1380 } 1381 1382 public String displayCodeableConcept(ResourceWrapper cc) { 1383 String s = context.getTranslated(cc.child("Text")); 1384 if (Utilities.noString(s)) { 1385 for (ResourceWrapper c : cc.children("coding")) { 1386 if (c.has("display")) { 1387 s = context.getTranslated(c.child("display")); 1388 break; 1389 } 1390 } 1391 } 1392 if (Utilities.noString(s)) { 1393 // still? ok, let's try looking it up 1394 for (ResourceWrapper c : cc.children("coding")) { 1395 if (c.has("code") && c.has("system")) { 1396 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1397 if (!Utilities.noString(s)) 1398 break; 1399 } 1400 } 1401 } 1402 1403 if (Utilities.noString(s)) { 1404 if (!cc.has("coding")) 1405 s = ""; 1406 else 1407 s = cc.children("coding").get(0).primitiveValue("code"); 1408 } 1409 return s; 1410 } 1411 1412 1413 protected void renderCodeableReference(RenderingStatus status, XhtmlNode x, ResourceWrapper e) throws FHIRFormatError, DefinitionException, IOException { 1414 if (e.has("concept")) { 1415 renderCodeableConcept(status, x, e.child("concept")); 1416 } 1417 if (e.has("reference")) { 1418 renderReference(status, x, e.child("reference")); 1419 } 1420 } 1421 1422 protected void renderCodeableConcept(RenderingStatus status, XhtmlNode x, ResourceWrapper cc) throws FHIRFormatError, DefinitionException, IOException { 1423 if (cc.isEmpty()) { 1424 return; 1425 } 1426 1427 String s = context.getTranslated(cc.child("text")); 1428 if (Utilities.noString(s)) { 1429 for (ResourceWrapper c : cc.children("coding")) { 1430 if (c.has("display")) { 1431 s = context.getTranslated(c.child("display")); 1432 break; 1433 } 1434 } 1435 } 1436 if (Utilities.noString(s)) { 1437 // still? ok, let's try looking it up 1438 for (ResourceWrapper c : cc.children("coding")) { 1439 if (c.has("code") && c.has("system")) { 1440 s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code")); 1441 if (!Utilities.noString(s)) 1442 break; 1443 } 1444 } 1445 } 1446 1447 if (Utilities.noString(s)) { 1448 if (!cc.has("coding")) 1449 s = ""; 1450 else 1451 s = cc.children("coding").get(0).primitiveValue("code"); 1452 } 1453 1454 if (status.isShowCodeDetails()) { 1455 x.addText(s+" "); 1456 XhtmlNode sp = x.span("background: LightGoldenRodYellow; margin: 4px; border: 1px solid khaki", null); 1457 sp.tx(" ("); 1458 boolean first = true; 1459 for (ResourceWrapper c : cc.children("coding")) { 1460 if (first) { 1461 first = false; 1462 } else { 1463 sp.tx("; "); 1464 } 1465 String url = getLinkForSystem(c.primitiveValue("system"), c.primitiveValue("version")); 1466 if (url != null) { 1467 sp.ah(context.prefixLocalHref(url)).tx(displayCodeSource(c.primitiveValue("system"), c.primitiveValue("version"))); 1468 } else { 1469 sp.tx(displayCodeSource(c.primitiveValue("system"), c.primitiveValue("version"))); 1470 } 1471 if (c.has("code")) { 1472 sp.tx("#"+c.primitiveValue("code")); 1473 } 1474 if (c.has("display") && !s.equals(c.primitiveValue("display"))) { 1475 sp.tx(" \""+context.getTranslated(c.child("display"))+"\""); 1476 } 1477 } 1478 if (hasRenderableExtensions(cc)) { 1479 if (!first) { 1480 sp.tx("; "); 1481 } 1482 renderExtensionsInText(status, sp, cc, ";"); 1483 } 1484 sp.tx(")"); 1485 } else { 1486 1487 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 1488 for (ResourceWrapper c : cc.children("coding")) { 1489 if (c.has("code") && c.has("system")) { 1490 b.append("{"+c.primitiveValue("system")+" "+c.primitiveValue("code")+"}"); 1491 } 1492 } 1493 1494 x.span(null, context.formatPhrase(RenderingContext.DATA_REND_CODES) +b.toString()).addText(s); 1495 } 1496 checkRenderExtensions(status, x, cc); 1497 } 1498 1499 protected String displayIdentifier(ResourceWrapper ii) { 1500 String s = Utilities.noString(ii.primitiveValue("value")) ? "?ngen-9?" : ii.primitiveValue("value"); 1501 if ("urn:ietf:rfc:3986".equals(ii.primitiveValue("system")) && s.startsWith("urn:oid:")) { 1502 s = "OID:"+s.substring(8); 1503 } else if ("urn:ietf:rfc:3986".equals(ii.primitiveValue("system")) && s.startsWith("urn:uuid:")) { 1504 s = "UUID:"+s.substring(9); 1505 } else { 1506 NamingSystem ns = context.getContext().getNSUrlMap().get(ii.primitiveValue("system")); 1507 if (ns != null) { 1508 s = crPresent(ns)+"#"+s; 1509 } 1510 if (ii.has("type")) { 1511 ResourceWrapper type = ii.child("type"); 1512 if (type.has("text")) 1513 s = context.getTranslated(type.child("text"))+":\u00A0"+s; 1514 else if (type.has("coding") && type.children("coding").get(0).has("display")) 1515 s = context.getTranslated(type.children("coding").get(0).child("display"))+": "+s; 1516 else if (type.has("coding") && type.children("coding").get(0).has("code")) 1517 s = lookupCode(type.children("coding").get(0).primitiveValue("system"), type.children("coding").get(0).primitiveValue("version"), type.children("coding").get(0).primitiveValue("code")); 1518 } else if (ii.has("system")) { 1519 s = ii.primitiveValue("system")+"#"+s; 1520 } 1521 } 1522 1523 if (ii.has("use") || ii.has("period")) { 1524 s = s + "\u00A0("; 1525 if (ii.has("use")) { 1526 s = s + "use:\u00A0"+ii.primitiveValue("use"); 1527 } 1528 if (ii.has("use") || ii.has("period")) { 1529 s = s + ",\u00A0"; 1530 } 1531 if (ii.has("period")) { 1532 s = s + "period:\u00A0"+displayPeriod(ii.child("period")); 1533 } 1534 s = s + ")"; 1535 } 1536 return s; 1537 } 1538 1539 protected void renderIdentifier(RenderingStatus status, XhtmlNode x, ResourceWrapper ii) throws FHIRFormatError, DefinitionException, IOException { 1540 if (ii.has("type")) { 1541 ResourceWrapper type = ii.child("type"); 1542 if (type.has("text")) { 1543 x.tx(context.getTranslated(type.child("text"))); 1544 } else if (type.has("coding") && type.children("coding").get(0).has("display")) { 1545 x.tx(context.getTranslated(type.children("coding").get(0).child("display"))); 1546 } else if (type.has("coding") && type.children("coding").get(0).has("code")) { 1547 x.tx(lookupCode(type.children("coding").get(0).primitiveValue("system"), type.children("coding").get(0).primitiveValue("version"), type.children("coding").get(0).primitiveValue("code"))); 1548 } 1549 x.tx("/"); 1550 } else if (ii.has("system")) { 1551 NamingSystem ns = context.getContext().getNSUrlMap().get(ii.primitiveValue("system")); 1552 if (ns != null) { 1553 if (ns.hasWebPath()) { 1554 x.ah(context.prefixLocalHref(ns.getWebPath()), ns.getDescription()).tx(crPresent(ns)); 1555 } else { 1556 x.tx(crPresent(ns)); 1557 } 1558 } else { 1559 switch (ii.primitiveValue("system")) { 1560 case "urn:oid:2.51.1.3": 1561 x.ah("https://www.gs1.org/standards/id-keys/gln", context.formatPhrase(RenderingContext.DATA_REND_GLN)).tx("GLN"); 1562 break; 1563 default: 1564 x.code(ii.primitiveValue("system")); 1565 } 1566 } 1567 x.tx("/"); 1568 } 1569 x.tx(Utilities.noString(ii.primitiveValue("value")) ? "?ngen-9?" : ii.primitiveValue("value")); 1570 1571 if (ii.has("use") || ii.has("period")) { 1572 x.nbsp(); 1573 x.tx("("); 1574 if (ii.has("use")) { 1575 x.tx(context.formatPhrase(RenderingContext.DATA_REND_USE)); 1576 x.nbsp(); 1577 x.tx(ii.primitiveValue("use")); 1578 } 1579 if (ii.has("use") || ii.has("period")) { 1580 x.tx(","); 1581 x.nbsp(); 1582 } 1583 if (ii.has("period")) { 1584 x.tx(context.formatPhrase(RenderingContext.DATA_REND_PERIOD)); 1585 x.nbsp(); 1586 x.tx(displayPeriod(ii.child("period"))); 1587 } 1588 x.tx(")"); 1589 } 1590 checkRenderExtensions(status, x, ii); 1591 } 1592 1593 public static String displayHumanName(ResourceWrapper name) { 1594 StringBuilder s = new StringBuilder(); 1595 if (name.has("text")) 1596 s.append(name.primitiveValue("text")); 1597 else { 1598 for (ResourceWrapper p : name.children("given")) { 1599 s.append(p.primitiveValue()); 1600 s.append(" "); 1601 } 1602 if (name.has("family")) { 1603 s.append(name.primitiveValue("family")); 1604 s.append(" "); 1605 } 1606 } 1607 if (name.has("use") && !"usual".equals(name.primitiveValue("use"))) 1608 s.append("("+name.primitiveValue("use")+")"); 1609 return s.toString(); 1610 } 1611 1612 1613 protected void renderHumanName(RenderingStatus status, XhtmlNode x, ResourceWrapper name) throws FHIRFormatError, DefinitionException, IOException { 1614 StringBuilder s = new StringBuilder(); 1615 if (name.has("text")) 1616 s.append(context.getTranslated(name.child("text"))); 1617 else { 1618 for (ResourceWrapper p : name.children("given")) { 1619 s.append(context.getTranslated(p)); 1620 s.append(" "); 1621 } 1622 if (name.has("family")) { 1623 s.append(context.getTranslated(name.child("family"))); 1624 s.append(" "); 1625 } 1626 } 1627 if (name.has("use") && !"usual".equals(name.primitiveValue("use"))) { 1628 s.append("("+context.getTranslatedCode(name.primitiveValue("use"), "http://hl7.org/fhir/name-use")+")"); 1629 } 1630 x.addText(s.toString()); 1631 checkRenderExtensions(status, x, name); 1632 } 1633 1634 private String displayAddress(ResourceWrapper address) { 1635 StringBuilder s = new StringBuilder(); 1636 if (address.has("text")) 1637 s.append(context.getTranslated(address.child("text"))); 1638 else { 1639 for (ResourceWrapper p : address.children("line")) { 1640 s.append(context.getTranslated(p)); 1641 s.append(" "); 1642 } 1643 if (address.has("city")) { 1644 s.append(context.getTranslated(address.child("city"))); 1645 s.append(" "); 1646 } 1647 if (address.has("state")) { 1648 s.append(context.getTranslated(address.child("state"))); 1649 s.append(" "); 1650 } 1651 1652 if (address.has("postalCode")) { 1653 s.append(context.getTranslated(address.child("postalCode"))); 1654 s.append(" "); 1655 } 1656 1657 if (address.has("country")) { 1658 s.append(context.getTranslated(address.child("country"))); 1659 s.append(" "); 1660 } 1661 } 1662 if (address.has("use")) { 1663 s.append("("+address.primitiveValue("use")+")"); 1664 } 1665 return s.toString(); 1666 } 1667 1668 protected void renderAddress(RenderingStatus status, XhtmlNode x, ResourceWrapper address) throws FHIRFormatError, DefinitionException, IOException { 1669 x.addText(displayAddress(address)); 1670 checkRenderExtensions(status, x, address); 1671 } 1672 1673 1674 public String displayContactPoint(ResourceWrapper contact) { 1675 StringBuilder s = new StringBuilder(); 1676 s.append(describeSystem(contact.primitiveValue("system"))); 1677 if (Utilities.noString(contact.primitiveValue("value"))) 1678 s.append("-unknown-"); 1679 else 1680 s.append(contact.primitiveValue("value")); 1681 if (contact.has("use")) 1682 s.append("("+getTranslatedCode(contact.child("use"))+")"); 1683 return s.toString(); 1684 } 1685 1686 public String displayContactDetail(ResourceWrapper contact) { 1687 CommaSeparatedStringBuilder s = new CommaSeparatedStringBuilder(); 1688 for (ResourceWrapper cp : contact.children("telecom")) { 1689 s.append(displayContactPoint(cp)); 1690 } 1691 return contact.primitiveValue("name")+(s.length() == 0 ? "" : " ("+s.toString()+")"); 1692 } 1693 1694 protected String getLocalizedBigDecimalValue(BigDecimal input, Currency c) { 1695 NumberFormat numberFormat = NumberFormat.getNumberInstance(context.getLocale()); 1696 numberFormat.setGroupingUsed(true); 1697 numberFormat.setMaximumFractionDigits(c.getDefaultFractionDigits()); 1698 numberFormat.setMinimumFractionDigits(c.getDefaultFractionDigits()); 1699 return numberFormat.format(input); 1700 } 1701 1702 protected void renderMoney(RenderingStatus status, XhtmlNode x, ResourceWrapper money) { 1703 if (x.getName().equals("blockquote")) { 1704 x = x.para(); 1705 } 1706 Currency c = money.has("currency") ? Currency.getInstance(money.primitiveValue("currency")) : null; 1707 if (c != null) { 1708 XhtmlNode s = x.span(null, c.getDisplayName()); 1709 s.tx(c.getSymbol(context.getLocale())); 1710 s.tx(getLocalizedBigDecimalValue(new BigDecimal(money.primitiveValue("value")), c)); 1711 x.tx(" ("+c.getCurrencyCode()+")"); 1712 } else { 1713 if (money.has("currency")) { 1714 x.tx(money.primitiveValue("currency")); 1715 } 1716 x.tx(money.primitiveValue("value")); 1717 } 1718 } 1719 1720 protected void renderExpression(RenderingStatus status, XhtmlNode x, ResourceWrapper expr) { 1721 // there's two parts: what the expression is, and how it's described. 1722 // we start with what it is, and then how it's described 1723 XhtmlNode p = x; 1724 if (p.getName().equals("blockquote")) { 1725 p = p.para(); 1726 } 1727 if (expr.has("expression")) { 1728 if (expr.has("reference")) { 1729 p = x.ah(context.prefixLocalHref(expr.primitiveValue("reference"))); 1730 } 1731 XhtmlNode c = p; 1732 if (expr.has("language")) { 1733 c = c.span(null, expr.primitiveValue("language")); 1734 } 1735 c.code().tx(expr.primitiveValue("expression")); 1736 } else if (expr.has("reference")) { 1737 p.ah(context.prefixLocalHref(expr.primitiveValue("reference"))).tx(context.formatPhrase(RenderingContext.DATA_REND_SOURCE)); 1738 } 1739 if (expr.has("name") || expr.has("description")) { 1740 p.tx("("); 1741 if (expr.has("name")) { 1742 p.b().tx(expr.primitiveValue("name")); 1743 } 1744 if (expr.has("description")) { 1745 p.tx("\""); 1746 p.tx(context.getTranslated(expr.child("description"))); 1747 p.tx("\""); 1748 } 1749 p.tx(")"); 1750 } 1751 } 1752 1753 1754 protected void renderContactPoint(RenderingStatus status, XhtmlNode x, ResourceWrapper contact) { 1755 if (contact != null) { 1756 if (!contact.has("system")) { 1757 x.addText(displayContactPoint(contact)); 1758 } else { 1759 String v = contact.primitiveValue("value"); 1760 switch (contact.primitiveValue("system")) { 1761 case "email": 1762 x.ah("mailto:"+v).tx(v); 1763 break; 1764 case "fax": 1765 x.addText(displayContactPoint(contact)); 1766 break; 1767 case "other": 1768 x.addText(displayContactPoint(contact)); 1769 break; 1770 case "pager": 1771 x.addText(displayContactPoint(contact)); 1772 break; 1773 case "phone": 1774 if (contact.has("value") && v != null && v.startsWith("+")) { 1775 x.ah("tel:"+v.replace(" ", "")).tx(v); 1776 } else { 1777 x.addText(displayContactPoint(contact)); 1778 } 1779 break; 1780 case "sms": 1781 x.addText(displayContactPoint(contact)); 1782 break; 1783 case "url": 1784 x.ah(context.prefixLocalHref(v)).tx(v); 1785 break; 1786 default: 1787 break; 1788 } 1789 } 1790 } 1791 } 1792 1793 protected void displayContactPoint(XhtmlNode p, ContactPoint c) { 1794 if (c != null) { 1795 if (c.getSystem() == ContactPointSystem.PHONE) { 1796 p.tx(context.formatPhrase(RenderingContext.DATA_REND_PHONE, c.getValue()) + " "); 1797 } else if (c.getSystem() == ContactPointSystem.FAX) { 1798 p.tx(context.formatPhrase(RenderingContext.DATA_REND_FAX, c.getValue()) + " "); 1799 } else if (c.getSystem() == ContactPointSystem.EMAIL) { 1800 p.tx(c.getValue()); 1801 } else if (c.getSystem() == ContactPointSystem.URL) { 1802 if (c.getValue().length() > 30) { 1803 p.addText(c.getValue().substring(0, 30)+"..."); 1804 } else { 1805 p.addText(c.getValue()); 1806 } 1807 } 1808 } 1809 } 1810 1811 protected void addTelecom(XhtmlNode p, ResourceWrapper c) { 1812 String sys = c.primitiveValue("system"); 1813 String value = c.primitiveValue("value"); 1814 if (sys.equals("phone")) { 1815 p.tx(context.formatPhrase(RenderingContext.DATA_REND_PHONE, value) + " "); 1816 } else if (sys.equals("fax")) { 1817 p.tx(context.formatPhrase(RenderingContext.DATA_REND_FAX, value) + " "); 1818 } else if (sys.equals("email")) { 1819 p.ah("mailto:"+value).addText(value); 1820 } else if (sys.equals("url")) { 1821 if (value.length() > 30) 1822 p.ah(context.prefixLocalHref(value)).addText(value.substring(0, 30)+"..."); 1823 else 1824 p.ah(context.prefixLocalHref(value)).addText(value); 1825 } 1826 } 1827 private static String describeSystem(String system) { 1828 if (system == null) 1829 return ""; 1830 switch (system) { 1831 case "phone": return "ph: "; 1832 case "fax": return "fax: "; 1833 default: 1834 return ""; 1835 } 1836 } 1837 1838 protected String displayQuantity(ResourceWrapper q) { 1839 if (q == null) { 1840 return ""; 1841 } 1842 StringBuilder s = new StringBuilder(); 1843 1844 s.append(q.has("value") ? q.primitiveValue("value") : "?"); 1845 if (q.has("unit")) 1846 s.append(" ").append(q.primitiveValue("unit")); 1847 else if (q.has("code")) 1848 s.append(" ").append(q.primitiveValue("code")); 1849 1850 return s.toString(); 1851 } 1852 1853 protected void renderQuantity(RenderingStatus status, XhtmlNode x, ResourceWrapper q) throws FHIRFormatError, DefinitionException, IOException { 1854 if (q.has("comparator")) 1855 x.addText(q.primitiveValue("comparator")); 1856 if (q.has("value")) { 1857 x.addText(context.getTranslated(q.child("value"))); 1858 } 1859 if (q.has("unit")) 1860 x.tx(" "+context.getTranslated(q.child("unit"))); 1861 else if (q.has("code") && q.has("system")) { 1862 // 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 1863 if (q.has("system") && q.primitiveValue("system").equals("http://unitsofmeasure.org")) 1864 x.tx(" "+q.primitiveValue("code")); 1865 else 1866 x.tx("(unit "+q.primitiveValue("code")+" from "+q.primitiveValue("system")+")"); 1867 } 1868 if (context.isTechnicalMode() && q.has("code")) { 1869 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"))+"')"); 1870 } 1871 checkRenderExtensions(status, x, q); 1872 } 1873 1874 1875 protected void renderQuantity(HierarchicalTableGenerator gen, List<Piece> pieces, ResourceWrapper q, boolean showCodeDetails) { 1876 pieces.add(gen.new Piece(null, displayQuantity(q), null)); 1877 } 1878 1879 protected void renderQuantity(HierarchicalTableGenerator gen, List<Piece> pieces, Quantity q, boolean showCodeDetails) { 1880 pieces.add(gen.new Piece(null, displayQuantity(wrapNC(q)), null)); 1881 } 1882 1883 public String displayRange(ResourceWrapper q) { 1884 if (!q.has("low") && !q.has("high")) 1885 return "?"; 1886 1887 StringBuilder b = new StringBuilder(); 1888 1889 ResourceWrapper lowC = q.child("low"); 1890 ResourceWrapper highC = q.child("high"); 1891 boolean sameUnits = (lowC != null && highC != null) && ((lowC.has("unit") && highC.has("unit") && lowC.child("unit").matches(highC.child("unit"))) 1892 || (lowC.has("code") && highC.has("code") && lowC.child("code").matches(highC.child("code")))); 1893 String low = "?"; 1894 if (q.has("low") && lowC.has("value")) 1895 low = sameUnits ? lowC.primitiveValue("value").toString() : displayQuantity(lowC); 1896 String high = displayQuantity(highC); 1897 if (high.isEmpty()) 1898 high = "?"; 1899 b.append(low).append("\u00A0to\u00A0").append(high); 1900 return b.toString(); 1901 } 1902 1903 protected void renderRange(RenderingStatus status, XhtmlNode x, ResourceWrapper q) { 1904 if (q.has("low")) 1905 x.addText(q.child("low").primitiveValue("value").toString()); 1906 else 1907 x.tx("?"); 1908 x.tx("-"); 1909 if (q.has("high")) 1910 x.addText(q.child("high").primitiveValue("value").toString()); 1911 else 1912 x.tx("?"); 1913 if (q.has("low") && q.child("low").has("unit")) 1914 x.tx(" "+q.child("low").primitiveValue("unit")); 1915 } 1916 1917 public String displayPeriod(ResourceWrapper p) { 1918 String s = !p.has("start") ? "(?)" : displayDateTime(p.child("start")); 1919 s = s + " --> "; 1920 return s + (!p.has("end") ? context.formatPhrase(RenderingContext.DATA_REND_ONGOING) : displayDateTime(p.child("end"))); 1921 } 1922 1923 public void renderPeriod(RenderingStatus status, XhtmlNode x, ResourceWrapper p) { 1924 x.addText(!p.has("start") ? "??" : displayDateTime(p.child("start"))); 1925 x.tx(" --> "); 1926 x.addText(!p.has("end") ? context.formatPhrase(RenderingContext.DATA_REND_ONGOING) : displayDateTime(p.child("end"))); 1927 } 1928 1929 public void renderUsageContext(RenderingStatus status, XhtmlNode x, ResourceWrapper u) throws FHIRFormatError, DefinitionException, IOException { 1930 renderCoding(status, x, u.child("code"), false); 1931 x.tx(" = "); 1932 renderDataType(status, x, u.child("value")); 1933 } 1934 1935 1936 public void renderTriggerDefinition(RenderingStatus status, XhtmlNode x, ResourceWrapper td) throws FHIRFormatError, DefinitionException, IOException { 1937 if (x.isPara()) { 1938 x.b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 1939 x.tx(": "); 1940 x.tx(td.child("type").primitiveValue("display")); 1941 1942 if (td.has("name")) { 1943 x.tx(", "); 1944 x.b().tx(context.formatPhrase(RenderingContext.GENERAL_NAME)); 1945 x.tx(": "); 1946 x.tx(context.getTranslated(td.child("name"))); 1947 } 1948 if (td.has("code")) { 1949 x.tx(", "); 1950 x.b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 1951 x.tx(": "); 1952 renderCodeableConcept(status, x, td.child("code")); 1953 } 1954 if (td.has("timing")) { 1955 x.tx(", "); 1956 x.b().tx(context.formatPhrase(RenderingContext.DATA_REND_TIMING)); 1957 x.tx(": "); 1958 renderDataType(status, x, td.child("timing")); 1959 } 1960 if (td.has("condition")) { 1961 x.tx(", "); 1962 x.b().tx(context.formatPhrase(RenderingContext.DATA_REND_COND)); 1963 x.tx(": "); 1964 renderExpression(status, x, td.child("condition")); 1965 } 1966 } else { 1967 XhtmlNode tbl = x.table("grid", false); 1968 1969 XhtmlNode tr = tbl.tr(); 1970 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 1971 tr.td().tx(td.child("type").primitiveValue("display")); 1972 1973 if (td.has("name")) { 1974 tr = tbl.tr(); 1975 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_NAME)); 1976 tr.td().tx(context.getTranslated(td.child("name"))); 1977 } 1978 if (td.has("code")) { 1979 tr = tbl.tr(); 1980 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 1981 renderCodeableConcept(status, tr.td(), td.child("code")); 1982 } 1983 if (td.has("timing")) { 1984 tr = tbl.tr(); 1985 tr.td().b().tx(context.formatPhrase(RenderingContext.DATA_REND_TIMING)); 1986 renderDataType(status, tr.td(), td.child("timing")); 1987 } 1988 if (td.has("condition")) { 1989 tr = tbl.tr(); 1990 tr.td().b().tx(context.formatPhrase(RenderingContext.DATA_REND_COND)); 1991 renderExpression(status, tr.td(), td.child("condition")); 1992 } 1993 } 1994 } 1995 1996 public void renderDataRequirement(RenderingStatus status, XhtmlNode x, ResourceWrapper dr) throws FHIRFormatError, DefinitionException, IOException { 1997 XhtmlNode tbl = x.table("grid", false); 1998 XhtmlNode tr = tbl.tr(); 1999 XhtmlNode td = tr.td().colspan("2"); 2000 td.b().tx(context.formatPhrase(RenderingContext.GENERAL_TYPE)); 2001 td.tx(": "); 2002 StructureDefinition sd = context.getWorker().fetchTypeDefinition(dr.primitiveValue("type")); 2003 if (sd != null && sd.hasWebPath()) { 2004 td.ah(context.prefixLocalHref(sd.getWebPath())).tx(dr.primitiveValue("type")); 2005 } else { 2006 td.tx(dr.primitiveValue("type")); 2007 } 2008 if (dr.has("profile")) { 2009 td.tx(" ("); 2010 boolean first = true; 2011 for (ResourceWrapper p : dr.children("profile")) { 2012 if (first) first = false; else td.tx(" | "); 2013 sd = context.getWorker().fetchResource(StructureDefinition.class, p.primitiveValue()); 2014 if (sd != null && sd.hasWebPath()) { 2015 td.ah(context.prefixLocalHref(sd.getWebPath())).tx(crPresent(sd)); 2016 } else { 2017 td.tx(p.primitiveValue()); 2018 } 2019 } 2020 td.tx(")"); 2021 } 2022 if (dr.has("subject")) { 2023 tr = tbl.tr(); 2024 td = tr.td().colspan("2"); 2025 td.b().tx(context.formatPhrase(RenderingContext.GENERAL_SUBJ)); 2026 ResourceWrapper subj = dr.child("subject"); 2027 if (subj.fhirType().equals("reference")) { 2028 renderReference(status, td, subj); 2029 } else { 2030 renderCodeableConcept(status, td, subj); 2031 } 2032 } 2033 if (dr.has("codeFilter") || dr.has("dateFilter")) { 2034 tr = tbl.tr().backgroundColor("#efefef"); 2035 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_FILTER)); 2036 tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_VALUE)); 2037 } 2038 for (ResourceWrapper cf : dr.children("codeFilter")) { 2039 tr = tbl.tr(); 2040 if (cf.has("path")) { 2041 tr.td().tx(cf.primitiveValue("path")); 2042 } else { 2043 tr.td().tx(context.formatPhrase(RenderingContext.DATA_REND_SEARCH, cf.primitiveValue("searchParam")) + " "); 2044 } 2045 if (cf.has("valueSet")) { 2046 td = tr.td(); 2047 td.tx(context.formatPhrase(RenderingContext.DATA_REND_VALUESET) + " "); 2048 renderDataType(status, td, cf.child("valueSet")); 2049 } else { 2050 boolean first = true; 2051 td = tr.td(); 2052 td.tx(context.formatPhrase(RenderingContext.DATA_REND_THESE_CODES) + " "); 2053 for (ResourceWrapper c : cf.children("code")) { 2054 if (first) first = false; else td.tx(", "); 2055 renderDataType(status, td, c); 2056 } 2057 } 2058 } 2059 for (ResourceWrapper cf : dr.children("dateFilter")) { 2060 tr = tbl.tr(); 2061 if (cf.has("path")) { 2062 tr.td().tx(cf.primitiveValue("path")); 2063 } else { 2064 tr.td().tx(context.formatPhrase(RenderingContext.DATA_REND_SEARCH, cf.primitiveValue("searchParam")) + " "); 2065 } 2066 renderDataType(status, tr.td(), cf.child("value")); 2067 } 2068 if (dr.has("sort") || dr.has("limit")) { 2069 tr = tbl.tr(); 2070 td = tr.td().colspan("2"); 2071 if (dr.has("limit")) { 2072 td.b().tx(context.formatPhrase(RenderingContext.DATA_REND_LIMIT)); 2073 td.tx(": "); 2074 td.tx(dr.primitiveValue("limit")); 2075 if (dr.has("sort")) { 2076 td.tx(", "); 2077 } 2078 } 2079 if (dr.has("sort")) { 2080 td.b().tx(context.formatPhrase(RenderingContext.DATA_REND_SORT)); 2081 td.tx(": "); 2082 boolean first = true; 2083 for (ResourceWrapper p : dr.children("sort")) { 2084 if (first) first = false; else td.tx(" | "); 2085 td.tx(p.primitiveValue("direction").equals("ascending") ? "+" : "-"); 2086 td.tx(p.primitiveValue("path")); 2087 } 2088 } 2089 } 2090 } 2091 2092 2093 private String displayTiming(ResourceWrapper s) throws FHIRException { 2094 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 2095 if (s.has("code")) { 2096 b.append(context.formatPhrase(RenderingContext.GENERAL_CODE, displayCodeableConcept(s.child("code"))) + " "); 2097 } 2098 2099 if (s.has("event")) { 2100 CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder(); 2101 for (ResourceWrapper p : s.children("event")) { 2102 if (p.hasPrimitiveValue()) { 2103 c.append(displayDateTime(p)); 2104 } else if (!renderExpression(c, p)) { 2105 c.append("??"); 2106 } 2107 } 2108 b.append(context.formatPhrase(RenderingContext.DATA_REND_EVENTS, c.toString()) + " "); 2109 } 2110 2111 if (s.has("repeat")) { 2112 ResourceWrapper rep = s.child("repeat"); 2113 if (rep.has("boundsPeriod") && rep.child("boundsPeriod").has("start")) 2114 b.append(context.formatPhrase(RenderingContext.DATA_REND_STARTING, displayDateTime(rep.child("boundsPeriod").child("start"))) + " "); 2115 if (rep.has("count")) 2116 b.append(context.formatPhrase(RenderingContext.DATA_REND_COUNT, rep.primitiveValue("count")) + " " + " times"); 2117 if (rep.has("duration")) 2118 b.append(context.formatPhrase(RenderingContext.DATA_REND_DURATION, rep.primitiveValue("duration")+displayTimeUnits(rep.primitiveValue("periodUnit"), "1".equals(rep.primitiveValue("duration")))) + " "); 2119 2120 String st = ""; 2121 if (rep.has("offset")) { 2122 st = rep.primitiveValue("offset")+"min "; 2123 } 2124 if (!Utilities.noString(st)) { 2125 b.append(st); 2126 } 2127 for (ResourceWrapper wh : rep.children("when")) { 2128 b.append(displayEventCode(wh.primitiveValue())); 2129 } 2130 st = ""; 2131 if (!rep.has("frequency") || (!rep.has("frequencyMax") && rep.primitiveValue("frequency").equals("1"))) { 2132 st = context.formatPhrase(RenderingContext.DATA_REND_ONCE); 2133 } else { 2134 st = rep.primitiveValue("frequency"); 2135 if (rep.has("frequencyMax")) 2136 st = st + "-"+rep.primitiveValue("frequencyMax"); 2137 } 2138 if (rep.has("period")) { 2139 st = st + " "+ (context.formatPhrase(RenderingContext.DATA_REND_PER))+" "+rep.primitiveValue("period"); 2140 if (rep.has("periodMax")) { 2141 st = st + "-"+rep.primitiveValue("periodMax"); 2142 } 2143 st = st + " "+displayTimeUnits(rep.primitiveValue("periodUnit"), "1".equals(rep.primitiveValue("period"))); 2144 } 2145 if (!Utilities.noString(st)) { 2146 b.append(st); 2147 } 2148 if (rep.has("boundsPeriod") && rep.child("boundsPeriod").has("end")) { 2149 b.append(context.formatPhrase(RenderingContext.DATA_REND_UNTIL, displayDateTime(rep.child("boundsPeriod").child("end"))) + " "); 2150 } 2151 } 2152 return b.toString(); 2153 } 2154 2155 private boolean renderExpression(CommaSeparatedStringBuilder c, ResourceWrapper p) { 2156 ResourceWrapper exp = p.extensionValue("http://hl7.org/fhir/StructureDefinition/cqf-expression"); 2157 if (exp == null || !exp.has("value")) { 2158 return false; 2159 } 2160 c.append(exp.child("value").primitiveValue("expression")); 2161 return true; 2162 } 2163 2164 private String displayEventCode(String when) { 2165 if (when == null) 2166 return "??"; 2167 switch (when.toLowerCase()) { 2168 case "c": return (context.formatPhrase(RenderingContext.DATA_REND_MEALS)); 2169 case "cd": return (context.formatPhrase(RenderingContext.DATA_REND_ATLUNCH)); 2170 case "cm": return (context.formatPhrase(RenderingContext.DATA_REND_ATBKFST)); 2171 case "cv": return (context.formatPhrase(RenderingContext.DATA_REND_ATDINR)); 2172 case "ac": return (context.formatPhrase(RenderingContext.DATA_REND_BFMEALS)); 2173 case "acd": return (context.formatPhrase(RenderingContext.DATA_REND_BFLUNCH)); 2174 case "acm": return (context.formatPhrase(RenderingContext.DATA_REND_BFBKFST)); 2175 case "acv": return (context.formatPhrase(RenderingContext.DATA_REND_BFDINR)); 2176 case "hs": return (context.formatPhrase(RenderingContext.DATA_REND_BFSLEEP)); 2177 case "pc": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRMEALS)); 2178 case "pcd": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRLUNCH)); 2179 case "pcm": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRBKFST)); 2180 case "pcv": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRDINR)); 2181 case "wake": return (context.formatPhrase(RenderingContext.DATA_REND_AFTRWKNG)); 2182 case "morn": return (context.formatPhrase(RenderingContext.DATA_REND_MORNING)); 2183 case "morn.early": return (context.formatPhrase(RenderingContext.DATA_REND_MORNING_EARLY)); 2184 case "morn.late": return (context.formatPhrase(RenderingContext.DATA_REND_MORNING_LATE)); 2185 case "noon": return (context.formatPhrase(RenderingContext.DATA_REND_NOON)); 2186 case "aft": return (context.formatPhrase(RenderingContext.DATA_REND_AFTERNOON)); 2187 case "aft.early": return (context.formatPhrase(RenderingContext.DATA_REND_AFTERNOON_EARLY)); 2188 case "aft.late": return (context.formatPhrase(RenderingContext.DATA_REND_AFTERNOON_LATE)); 2189 case "eve": return (context.formatPhrase(RenderingContext.DATA_REND_EVENING)); 2190 case "eve.early": return (context.formatPhrase(RenderingContext.DATA_REND_EVENING_EARLY)); 2191 case "eve.late": return (context.formatPhrase(RenderingContext.DATA_REND_EVENING_LATE)); 2192 case "night": return (context.formatPhrase(RenderingContext.DATA_REND_NIGHT)); 2193 case "phs": return (context.formatPhrase(RenderingContext.DATA_REND_AFTER_SLEEP)); 2194 case "imd": return (context.formatPhrase(RenderingContext.DATA_REND_IMMEDIATE)); 2195 2196 default: return "?"+when+"?"; 2197 } 2198 } 2199 2200 private String displayTimeUnits(String units, boolean singular) { 2201 if (units == null) 2202 return "??"; 2203 switch (units) { 2204 case "a": return singular ? "year" : "years"; 2205 case "d": return singular ? "day" : "days"; 2206 case "h": return singular ? "hour" : "hours"; 2207 case "min": return singular ? "minute" : "minutes"; 2208 case "mo": return singular ? "month" : "months"; 2209 case "s": return singular ? "second" : "seconds"; 2210 case "wk": return singular ? "week" : "weeks"; 2211 default: return "?"+units+"?"; 2212 } 2213 } 2214 2215 protected void renderTiming(RenderingStatus status, XhtmlNode x, ResourceWrapper s) throws FHIRException { 2216 x.addText(displayTiming(s)); 2217 } 2218 2219 2220 private String displaySampledData(ResourceWrapper s) { 2221 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 2222 if (s.has("origin")) 2223 b.append(context.formatPhrase(RenderingContext.DATA_REND_ORIGIN, displayQuantity(s.child("origin"))) + " "); 2224 2225 if (s.has("interval")) { 2226 b.append(context.formatPhrase(RenderingContext.DATA_REND_INT, s.primitiveValue("interval")) + " "); 2227 2228 if (s.has("intervalUnit")) 2229 b.append(s.primitiveValue("intervalUnit")); 2230 } 2231 2232 if (s.has("factor")) 2233 b.append(context.formatPhrase(RenderingContext.DATA_REND_FACT, s.primitiveValue("factor")) + " "); 2234 2235 if (s.has("lowerLimit")) 2236 b.append(context.formatPhrase(RenderingContext.DATA_REND_LOWER, s.primitiveValue("lowerLimit")) + " "); 2237 2238 if (s.has("upperLimit")) 2239 b.append(context.formatPhrase(RenderingContext.DATA_REND_UP, s.primitiveValue("upperLimit")) + " "); 2240 2241 if (s.has("dimensions")) 2242 b.append(context.formatPhrase(RenderingContext.DATA_REND_DIM, s.primitiveValue("dimensions")) + " "); 2243 2244 if (s.has("data")) 2245 b.append(context.formatPhrase(RenderingContext.DATA_REND_DATA, s.primitiveValue("data")) + " "); 2246 2247 return b.toString(); 2248 } 2249 2250 protected void renderSampledData(RenderingStatus status, XhtmlNode x, ResourceWrapper sampledData) { 2251 x.addText(displaySampledData(sampledData)); 2252 } 2253 2254 public RenderingContext getContext() { 2255 return context; 2256 } 2257 2258 2259 public XhtmlNode makeExceptionXhtml(Exception e, String function) { 2260 XhtmlNode xn; 2261 xn = new XhtmlNode(NodeType.Element, "div"); 2262 XhtmlNode p = xn.para(); 2263 p.b().tx((context.formatPhrase(RenderingContext.DATA_REND_EXCEPTION)) +function+": "+e.getMessage()); 2264 p.addComment(getStackTrace(e)); 2265 return xn; 2266 } 2267 2268 private String getStackTrace(Exception e) { 2269 StringBuilder b = new StringBuilder(); 2270 b.append("\r\n"); 2271 for (StackTraceElement t : e.getStackTrace()) { 2272 b.append(t.getClassName()+"."+t.getMethodName()+" ("+t.getFileName()+":"+t.getLineNumber()); 2273 b.append("\r\n"); 2274 } 2275 return b.toString(); 2276 } 2277 2278 protected String systemFromCanonical(String system) { 2279 if (system == null) { 2280 return null; 2281 } else if (system.contains("|")) { 2282 return system.substring(0, system.indexOf("|")); 2283 } else { 2284 return null; 2285 } 2286 } 2287 2288 protected String versionFromCanonical(String system) { 2289 if (system == null) { 2290 return null; 2291 } else if (system.contains("|")) { 2292 return system.substring(system.indexOf("|")+1); 2293 } else { 2294 return system; 2295 } 2296 } 2297 2298 2299}