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