001package org.hl7.fhir.r5.renderers; 002 003import java.io.IOException; 004import java.io.UnsupportedEncodingException; 005import java.util.ArrayList; 006import java.util.Base64; 007import java.util.HashMap; 008import java.util.List; 009import java.util.Map; 010import java.util.UUID; 011 012import org.hl7.fhir.exceptions.DefinitionException; 013import org.hl7.fhir.exceptions.FHIRException; 014import org.hl7.fhir.exceptions.FHIRFormatError; 015import org.hl7.fhir.r5.model.Attachment; 016import org.hl7.fhir.r5.model.StructureDefinition; 017import org.hl7.fhir.r5.renderers.utils.RenderingContext; 018import org.hl7.fhir.r5.renderers.utils.ResourceWrapper; 019import org.hl7.fhir.r5.utils.EOperationOutcome; 020import org.hl7.fhir.utilities.TextFile; 021import org.hl7.fhir.utilities.Utilities; 022import org.hl7.fhir.utilities.filesystem.ManagedFileAccess; 023import org.hl7.fhir.utilities.xhtml.XhtmlNode; 024 025public class PatientRenderer extends ResourceRenderer { 026 027 028 public PatientRenderer(RenderingContext context) { 029 super(context); 030 } 031 032 033 @Override 034 public String buildSummary(ResourceWrapper pat) throws UnsupportedEncodingException, IOException { 035 ResourceWrapper id = null; 036 List<ResourceWrapper> list = pat.children("identifier"); 037 for (ResourceWrapper t : list) { 038 id = chooseId(id, t); 039 } 040 list = pat.children("name"); 041 ResourceWrapper n = null; 042 for (ResourceWrapper t : list) { 043 n = chooseName(n, t); 044 } 045 String gender = null; 046 ResourceWrapper item = pat.child("gender"); 047 if (item != null) { 048 gender = context.getTranslatedCode(item.primitiveValue(), "http://hl7.org/fhir/administrative-gender"); 049 } 050 ResourceWrapper dt = pat.child("birthDate"); 051 052 StringBuilder b = new StringBuilder(); 053 if (n != null) { 054 b.append(displayHumanName(n)); 055 } else { 056 b.append(context.formatPhrase(RenderingContext.PAT_NO_NAME)); 057 } 058 b.append(" "); 059 if (item == null) { 060 b.append(context.formatPhrase(RenderingContext.PAT_NO_GENDER)); 061 } else { 062 b.append(gender); 063 } 064 b.append(", "); 065 if (dt == null) { 066 b.append(context.formatPhrase(RenderingContext.PAT_NO_DOB)); 067 } else { 068 b.append(context.formatPhrase(RenderingContext.PAT_DOB, displayDateTime(dt))); 069 } 070 if (id != null) { 071 b.append(" ( "); 072 b.append(displayIdentifier(id)); 073 b.append(")"); 074 } 075 return b.toString(); 076 } 077 078 079 // // name gender DoB (MRN) 080 // public String display(Resource dr) { 081 // Patient pat = (Patient) dr; 082 // Identifier id = null; 083 // for (Identifier t : pat.getIdentifier()) { 084 // id = chooseId(id, t); 085 // } 086 // HumanName n = null; 087 // for (HumanName t : pat.getName()) { 088 // n = chooseName(n, t); 089 // } 090 // return display(n, pat.hasGender() ? context.getTranslatedCode(pat.getGenderElement(), "http://hl7.org/fhir/administrative-gender") : null, pat.getBirthDateElement(), id); 091 // } 092 093 094 private static final int MAX_IMAGE_LENGTH = 2*1024*1024; 095 private static final boolean SHORT = false; 096 097 098 @Override 099 public void buildNarrative(RenderingStatus status, XhtmlNode x, ResourceWrapper pat) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { 100 renderResourceTechDetails(pat, x); 101 if (context.isShortPatientForm()) { 102 ResourceWrapper id = null; 103 List<ResourceWrapper> list = pat.children("identifier"); 104 for (ResourceWrapper t : list) { 105 id = chooseId(id, t); 106 } 107 list = pat.children("name"); 108 ResourceWrapper n = null; 109 for (ResourceWrapper t : list) { 110 n = chooseName(n, t); 111 } 112 String gender = null; 113 ResourceWrapper item = pat.child("gender"); 114 if (item != null) { 115 gender = getTranslatedCode(item); 116 } 117 ResourceWrapper dt = pat.child("birthDate"); 118 119 if (n == null) { 120 x.b().tx(context.formatPhrase(RenderingContext.PAT_NO_NAME)); // todo: is this appropriate? 121 } else { 122 renderDataType(status, x.b(), n); 123 } 124 x.tx(" "); 125 if (gender == null) { 126 x.tx(context.formatPhrase(RenderingContext.PAT_NO_GENDER)); 127 } else { 128 x.tx(gender); 129 } 130 x.tx(", "); 131 if (dt == null) { 132 x.tx(context.formatPhrase(RenderingContext.PAT_NO_DOB)); 133 } else { 134 x.tx(context.formatPhrase(RenderingContext.PAT_DOB, displayDateTime(dt))); 135 } 136 if (id != null) { 137 x.tx(" ( "); 138 renderDataType(status, x, id); 139 x.tx(")"); 140 } 141 } else { 142 // banner 143 makeBanner(x.para()).tx(buildSummary(pat)); 144 x.hr(); 145 XhtmlNode tbl; 146 if (hasRenderablePhoto(pat)) { 147 tbl = x.table("none"); 148 XhtmlNode tr = tbl.tr(); 149 tbl = tr.td().table("grid"); 150 renderPhoto(tr.td(), pat); 151 } else { 152 tbl = x.table("grid"); 153 } 154 155 // the table has 4 columns 156 addStatus(status, tbl, pat); 157 addIdentifiers(status, tbl, pat); 158 addNames(status, tbl, pat); 159 addComms(status, tbl, pat); 160 addLangs(status, tbl, pat); 161 addNOKs(status, tbl, pat); 162 addLinks(status, tbl, pat); 163 addExtensions(status, tbl, pat); 164 if (tbl.isEmpty()) { 165 x.remove(tbl); 166 } 167 if (pat.has("contained") && context.isTechnicalMode()) { 168 x.hr(); 169 x.para().b().tx(context.formatMessagePlural(pat.children("contained").size(), RenderingContext.PAT_CONTAINED)); 170 addContained(status, x, pat.children("contained")); 171 } 172 } 173 } 174 175 private ResourceWrapper chooseId(ResourceWrapper oldId, ResourceWrapper newId) { 176 if (oldId == null) { 177 return newId; 178 } 179 if (newId == null) { 180 return oldId; 181 } 182 return isPreferredId(newId.primitiveValue("use"), oldId.primitiveValue("use")) ? newId : oldId; 183 } 184 185 private boolean isPreferredId(String newUse, String oldUse) { 186 if (newUse == null && oldUse == null || newUse == oldUse) { 187 return false; 188 } 189 if (newUse == null) { 190 return true; 191 } 192 switch (newUse) { 193 case "official": return !Utilities.existsInList(oldUse, "usual"); 194 case "old": return !Utilities.existsInList(oldUse, "official", "secondary", "usual"); 195 case "secondary": return !Utilities.existsInList(oldUse, "official", "usual"); 196 case "temp": return !Utilities.existsInList(oldUse, "official", "secondary", "usual"); 197 case "usual": return true; 198 default: return false; 199 } 200 } 201 202 private ResourceWrapper chooseName(ResourceWrapper oldName, ResourceWrapper newName) { 203 if (oldName == null) { 204 return newName; 205 } 206 if (newName == null) { 207 return oldName; 208 } 209 return isPreferredName(newName.primitiveValue("use"), oldName.primitiveValue("use")) ? newName : oldName; 210 } 211 212 213 private boolean isPreferredName(String newUse, String oldUse) { 214 if (newUse == null && oldUse == null || newUse == oldUse) { 215 return false; 216 } 217 if (newUse == null) { 218 return true; 219 } 220 if (oldUse == null) { 221 return Utilities.existsInList(newUse, "official", "usual"); 222 } 223 switch (oldUse) { 224 case "anonymous": return Utilities.existsInList(newUse, "official", "usual"); 225 case "maiden": return Utilities.existsInList(newUse, "official", "usual"); 226 case "nickname": return Utilities.existsInList(newUse, "official", "usual"); 227 case "official": return Utilities.existsInList(newUse, "usual"); 228 case "old": return Utilities.existsInList(newUse, "official", "usual"); 229 case "temp": return Utilities.existsInList(newUse, "official", "usual"); 230 case "usual": return false; 231 } 232 return false; 233 } 234 235 236 private void addContained(RenderingStatus status, XhtmlNode x, List<ResourceWrapper> list) throws FHIRFormatError, DefinitionException, FHIRException, IOException, EOperationOutcome { 237 for (ResourceWrapper c : list) { 238 x.hr(); 239 x.an(context.prefixAnchor(c.getId())); 240 RendererFactory.factory(c, context.forContained()).buildNarrative(status, x, c); 241 } 242 } 243 244 private void addExtensions(RenderingStatus status, XhtmlNode tbl, ResourceWrapper r) throws UnsupportedEncodingException, FHIRException, IOException { 245 Map<String, List<ResourceWrapper>> extensions = new HashMap<>(); 246 List<ResourceWrapper> pw = r.children("extension"); 247 for (ResourceWrapper t : pw) { 248 String url = t.primitiveValue("url"); 249 if (!extensions.containsKey(url)) { 250 extensions.put(url, new ArrayList<>()); 251 } 252 extensions.get(url).add(t); 253 } 254 255 for (String url : extensions.keySet()) { 256 StructureDefinition sd = findCanonical(StructureDefinition.class, url, r); 257 if (sd != null) { 258 List<ResourceWrapper> list = extensions.get(url); 259 boolean anyComplex = false; 260 for (ResourceWrapper ext : list) { 261 anyComplex = anyComplex || ext.has("extension"); 262 } 263 if (!anyComplex) { 264 XhtmlNode tr = tbl.tr(); 265 nameCell(tr, getContext().getTranslated(sd.getTitleElement()), sd.getDescription(), sd.getWebPath()); 266 XhtmlNode td = tr.td(); 267 td.colspan("3"); 268 if (list.size() != 1) { 269 XhtmlNode ul = td.ul(); 270 for (ResourceWrapper s : list) { 271 XhtmlNode li = ul.li(); 272 renderDataType(status, li, s.child("value")); 273 } 274 } else { 275 renderDataType(status, td, list.get(0).child("value")); 276 } 277 } else { 278 for (ResourceWrapper ext : list) { 279 XhtmlNode tr = tbl.tr(); 280 nameCell(tr, sd.getTitle()+":", sd.getDescription()); 281 XhtmlNode td = tr.td(); 282 td.colspan("3"); 283 if (ext.has("extension")) { 284 XhtmlNode ul = td.ul(); 285 for (ResourceWrapper s : ext.extensions()) { 286 XhtmlNode li = ul.li(); 287 li.tx(s.primitiveValue("url")+": "); 288 if (s.has("extension")) { 289 boolean first = true; 290 for (ResourceWrapper t : s.extensions()) { 291 if (first) first = false; else li.tx("; "); 292 li.tx(t.primitiveValue("url")+"="); 293 renderDataType(status, li, t.child("value")); 294 } 295 } else { 296 renderDataType(status, li, s.child("value")); 297 } 298 } 299 } else { 300 renderDataType(status, td, ext.child("value")); 301 } 302 } 303 } 304 } 305 } 306 307 308 } 309 310 private void addIdentifiers(RenderingStatus status, XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException { 311 List<ResourceWrapper> ids = r.children("identifier"); 312 ResourceWrapper id = null; 313 for (ResourceWrapper i : ids) { 314 id = chooseId(id, i); 315 } 316 if (id != null) { 317 ids.remove(id); 318 }; 319 if (ids.size() > 0) { 320 XhtmlNode tr = tbl.tr(); 321 nameCell(tr, context.formatMessagePlural(ids.size(), RenderingContext.PAT_OTHER_ID),context.formatMessagePlural(ids.size(), RenderingContext.PAT_OTHER_ID_HINT)); 322 XhtmlNode td = tr.td(); 323 td.colspan("3"); 324 if (ids.size() == 1) { 325 renderDataType(status, td, ids.get(0)); 326 } else { 327 XhtmlNode ul = td.ul(); 328 for (ResourceWrapper i : ids) { 329 renderDataType(status, ul.li(), i); 330 } 331 } 332 } 333 } 334 335 private void addLangs(RenderingStatus status, XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException { 336 List<ResourceWrapper> langs = new ArrayList<ResourceWrapper>(); 337 List<ResourceWrapper> comms = r.children("communication"); 338 ResourceWrapper prefLang = null; 339 for (ResourceWrapper t : comms) { 340 ResourceWrapper lang = t.child("language"); 341 if (lang != null) { 342 langs.add(lang); 343 ResourceWrapper l = t.child("preferred"); 344 if (l != null && "true".equals(l.primitiveValue())) { 345 prefLang = lang; 346 } 347 } 348 } 349 if (langs.size() > 0) { 350 XhtmlNode tr = tbl.tr(); 351 nameCell(tr, context.formatMessagePlural(langs.size(), RenderingContext.PAT_LANG), context.formatMessagePlural(langs.size(), RenderingContext.PAT_LANG_HINT)); 352 XhtmlNode td = tr.td(); 353 td.colspan("3"); 354 if (langs.size() == 1) { 355 renderDataType(status, td, langs.get(0)); 356 if (prefLang != null) { 357 td.tx(" "+context.formatPhrase(RenderingContext.PAT_LANG_PREFERRED)); 358 } 359 } else if (langs.size() > 1) { 360 XhtmlNode ul = td.ul(); 361 for (ResourceWrapper i : langs) { 362 XhtmlNode li = ul.li(); 363 renderDataType(status, li, i); 364 if (i == prefLang) { 365 li.tx(" "+context.formatPhrase(RenderingContext.PAT_LANG_PREFERRED));; 366 } 367 } 368 } 369 } 370 } 371 372 373 374 public class NamedReferance { 375 376 private String name; 377 private ResourceWrapper reference; 378 379 public NamedReferance(String name, ResourceWrapper ref) { 380 this.name = name; 381 this.reference = ref; 382 } 383 384 public String getName() { 385 return name; 386 } 387 388 public ResourceWrapper getReference() { 389 return reference; 390 } 391 392 } 393 394 395 private void addLinks(RenderingStatus status, XhtmlNode tbl, ResourceWrapper r) throws UnsupportedEncodingException, FHIRException, IOException { 396 List<NamedReferance> refs = new ArrayList<>(); 397 List<ResourceWrapper> pw = r.children("generalPractitioner"); 398 for (ResourceWrapper t : pw) { 399 refs.add(new NamedReferance(context.formatPhrase(RenderingContext.PAT_GP), t)); 400 } 401 pw = r.children("managingOrganization"); 402 for (ResourceWrapper t : pw) { 403 refs.add(new NamedReferance(context.formatPhrase(RenderingContext.PAT_MO), t)); 404 } 405 pw = r.children("link"); 406 for (ResourceWrapper t : pw) { 407 ResourceWrapper o = t.firstChild("other"); 408 ResourceWrapper l = t.firstChild("type"); 409 if (l != null && o != null) { 410 refs.add(new NamedReferance(describeLinkedRecord(l.primitiveValue()), o)); 411 } 412 } 413 414 if (refs.size() > 0) { 415 XhtmlNode tr = tbl.tr(); 416 nameCell(tr, context.formatPhrase(RenderingContext.PAT_LINKS), context.formatPhrase(RenderingContext.PAT_LINKS_HINT)); 417 XhtmlNode td = tr.td(); 418 td.colspan("3"); 419 XhtmlNode ul = td.ul(); 420 for (NamedReferance ref : refs) { 421 XhtmlNode li = ul.li(); 422 li.tx(ref.getName()); 423 li.tx(": "); 424 renderReference(status, li, ref.getReference()); 425 } 426 } 427 } 428 429 private String describeLinkedRecord(String type) { 430 switch (type) { 431 case "replaced-by" : return context.formatPhrase(RenderingContext.PAT_LINK_REPLBY); 432 case "replaces": return context.formatPhrase(RenderingContext.PAT_LINK_REPL); 433 case "refer": return context.formatPhrase(RenderingContext.PAT_LINK_REFER); 434 case "seealso": return context.formatPhrase(RenderingContext.PAT_LINK_SEE); 435 } 436 return "Unknown"; 437 } 438 439 private void addNOKs(RenderingStatus status, XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException { 440 for (ResourceWrapper t : r.children("contact")) { 441 addNOK(status, tbl, r, t); 442 } 443 } 444 445 private void addNOK(RenderingStatus status, XhtmlNode tbl, ResourceWrapper r, ResourceWrapper bw) throws FHIRFormatError, DefinitionException, IOException { 446 List<ResourceWrapper> rels = bw.children("relationship"); 447 ResourceWrapper name = bw.firstChild("name"); 448 ResourceWrapper add = bw.firstChild("address"); 449 String gender = context.getTranslatedCode(bw.primitiveValue("gender"), "http://hl7.org/fhir/administrative-gender"); 450 ResourceWrapper period = bw.firstChild("period"); 451 ResourceWrapper organization = bw.firstChild("organization"); 452 List<ResourceWrapper> tels = bw.children("telecom"); 453 454 if (rels.size() < 2 && name == null && add == null && gender == null && period == null && organization == null && tels.size() == 0) { 455 return; // nothing to render 456 } 457 XhtmlNode tr = tbl.tr(); 458 if (rels.size() == 1) { 459 nameCell(tr, displayDataType(rels.get(0))+":", context.formatPhrase(RenderingContext.PAT_NOM_CONTACT)+" "+displayDataType(rels.get(0))); 460 } else { 461 nameCell(tr, context.formatPhrase(RenderingContext.GENERAL_CONTACT), context.formatPhrase(RenderingContext.PAT_NOK_CONTACT_HINT)); 462 } 463 XhtmlNode td = tr.td(); 464 td.colspan("3"); 465 XhtmlNode ul = td.ul(); 466 XhtmlNode li; 467 if (name != null) { 468 li = ul.li(); 469 renderDataType(status, li, name); 470 if (gender != null) { 471 li.tx(" "+"("+gender+")"); 472 } 473 } else if (gender != null) { 474 li = ul.li(); 475 li.tx(context.formatPhrase(RenderingContext.PAT_GENDER, gender)); 476 } 477 if (rels.size() > 1) { 478 li = ul.li(); 479 li.tx(context.formatPhrase(RenderingContext.PAT_RELN)); 480 boolean first = true; 481 for (ResourceWrapper rel : rels) { 482 if (first) first = false; else li.tx(", "); 483 renderDataType(status, li, rel); 484 } 485 } 486 if (add != null) { 487 renderDataType(status, ul.li(), add); 488 } 489 for (ResourceWrapper cp : tels) { 490 renderDataType(status, ul.li(), cp); 491 } 492 if (organization != null) { 493 li = ul.li(); 494 li.tx(context.formatPhrase(RenderingContext.PAT_ORG)); 495 renderDataType(status, li, organization); 496 } 497 if (period != null) { 498 li = ul.li(); 499 li.tx(context.formatPhrase(RenderingContext.PAT_PERIOD)); 500 renderDataType(status, li, period); 501 } 502 } 503 504 private void addNames(RenderingStatus status, XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException { 505 List<ResourceWrapper> names = r.children("name"); 506 ResourceWrapper name = null; 507 for (ResourceWrapper n : names) { 508 name = chooseName(name, n); 509 } 510 if (name != null) { 511 names.remove(name); 512 }; 513 if (names.size() == 1) { 514 XhtmlNode tr = tbl.tr(); 515 nameCell(tr, context.formatPhrase(RenderingContext.PAT_ALT_NAME), context.formatPhrase(RenderingContext.PAT_ALT_NAME_HINT)); 516 XhtmlNode td = tr.td(); 517 td.colspan("3"); 518 if (names.size() == 1) { 519 renderDataType(status, td, names.get(0)); 520 } else { 521 XhtmlNode ul = td.ul(); 522 for (ResourceWrapper n : names) { 523 renderDataType(status, ul.li(), n); 524 } 525 } 526 } 527 } 528 529 private void addComms(RenderingStatus status, XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException { 530 List<ResourceWrapper> tels = r.children("telecom"); 531 List<ResourceWrapper> adds = r.children("address"); 532 if (tels.size() + adds.size() > 0) { 533 XhtmlNode tr = tbl.tr(); 534 nameCell(tr, context.formatPhrase(RenderingContext.PAT_CONTACT), context.formatPhrase(RenderingContext.PAT_CONTACT_HINT)); 535 XhtmlNode td = tr.td(); 536 td.colspan("3"); 537 if (tels.size() + adds.size() == 1) { 538 if (adds.isEmpty()) { 539 renderDataType(status, td, tels.get(0)); 540 } else { 541 renderDataType(status, td, adds.get(0)); 542 } 543 } else { 544 XhtmlNode ul = td.ul(); 545 for (ResourceWrapper n : tels) { 546 renderDataType(status, ul.li(), n); 547 } 548 for (ResourceWrapper n : adds) { 549 renderDataType(status, ul.li(), n); 550 } 551 } 552 } 553 } 554 555 private void addStatus(RenderingStatus status, XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, UnsupportedEncodingException, FHIRException, IOException { 556 // TODO Auto-generated method stub 557 int count = 0; 558 if (r.has("active")) { 559 count++; 560 } 561 if (r.has("deceased")) { 562 count++; 563 } 564 if (r.has("maritalStatus")) { 565 count++; 566 } 567 if (r.has("multipleBirth")) { 568 count++; 569 } 570 if (count > 0) { 571 XhtmlNode tr = tbl.tr(); 572 int pos = 0; 573 if (r.has("active")) { 574 List<ResourceWrapper> a = r.children("active"); 575 if (!a.isEmpty()) { 576 pos++; 577 nameCell(tr, context.formatPhrase(RenderingContext.PAT_ACTIVE), context.formatPhrase(RenderingContext.PAT_ACTIVE_HINT)); 578 XhtmlNode td = tr.td(); 579 if (pos == count) { 580 td.colspan("3"); 581 } 582 renderDataType(status, td, a.get(0)); 583 } 584 } 585 if (r.has("deceased[x]")) { 586 List<ResourceWrapper> a = r.children("deceased[x]"); 587 if (!a.isEmpty()) { 588 pos++; 589 nameCell(tr, context.formatPhrase(RenderingContext.PAT_DECEASED), context.formatPhrase(RenderingContext.PAT_DECEASED_HINT)); 590 XhtmlNode td = tr.td(); 591 if (pos == count) { 592 td.colspan("3"); 593 } 594 renderDataType(status, td, a.get(0)); 595 } 596 } 597 if (r.has("maritalStatus")) { 598 List<ResourceWrapper> a = r.children("maritalStatus"); 599 if (!a.isEmpty()) { 600 pos++; 601 if (pos == 3) { 602 tr = tbl.tr(); 603 } 604 nameCell(tr, context.formatPhrase(RenderingContext.PAT_MARITAL), context.formatPhrase(RenderingContext.PAT_MARITAL_HINT)); 605 XhtmlNode td = tr.td(); 606 if (pos == count) { 607 td.colspan("3"); 608 } 609 renderDataType(status, td, a.get(0)); 610 } 611 } 612 if (r.has("multipleBirth[x]")) { 613 List<ResourceWrapper> a = r.children("multipleBirth[x]"); 614 if (!a.isEmpty()) { 615 pos++; 616 if (pos == 3) { 617 tr = tbl.tr(); 618 } 619 nameCell(tr, context.formatPhrase(RenderingContext.PAT_MUL_BIRTH), context.formatPhrase(RenderingContext.PAT_MUL_BIRTH_HINT)); 620 XhtmlNode td = tr.td(); 621 if (pos == count) { 622 td.colspan("3"); 623 } 624 renderDataType(status, td, a.get(0)); 625 } 626 } 627 } 628 } 629 630 private void nameCell(XhtmlNode tr, String text, String title) { 631 XhtmlNode td = tr.td(); 632 td.setAttribute("title", title); 633 td.tx(text); 634 td.style("background-color: #f3f5da"); 635 } 636 637 private void nameCell(XhtmlNode tr, String text, String title, String link) { 638 XhtmlNode td = tr.td(); 639 td.setAttribute("title", title); 640 if (link != null) { 641 td.ah(context.prefixLocalHref(link)).tx(text); 642 } else { 643 td.tx(text); 644 } 645 td.style("background-color: #f3f5da"); 646 } 647 648 private void renderPhoto(XhtmlNode td, ResourceWrapper r) throws UnsupportedEncodingException, FHIRException, IOException { 649 if (r.has("photo")) { 650 List<ResourceWrapper> a = r.children("photo"); 651 for (ResourceWrapper att : a) { 652 String ct = att.primitiveValue("contentType"); 653 byte[] cnt = att.has("data") ? Base64.getDecoder().decode(att.primitiveValue("data")) : null; 654 if (ct.startsWith("image/") && 655 cnt != null && (!context.isInlineGraphics() || (cnt.length > 0 && cnt.length < MAX_IMAGE_LENGTH))) { 656 String ext = extensionForType(ct); 657 if (context.isInlineGraphics() || Utilities.noString(context.getDestDir()) || ext == null) { 658 td.img("data:"+ct+";base64,"+att.primitiveValue("data"), "patient photo"); 659 } else { 660 String n = UUID.randomUUID().toString().toLowerCase()+ext; 661 TextFile.bytesToFile(cnt, ManagedFileAccess.file(Utilities.path(context.getDestDir(), n))); 662 context.registerFile(n); 663 td.img(n, context.formatPhrase(RenderingContext.PAT_PHOTO)); 664 } 665 return; 666 } 667 } 668 } 669 return; 670 } 671 672 private String extensionForType(String contentType) { 673 if (contentType.equals("image/gif")) { 674 return ".gif"; 675 } 676 if (contentType.equals("image/png")) { 677 return ".png"; 678 } 679 if (contentType.equals("image/jpeg")) { 680 return ".jpg"; 681 } 682 return null; 683 } 684 685 private boolean hasRenderablePhoto(ResourceWrapper r) throws UnsupportedEncodingException, FHIRException, IOException { 686 if (r.has("photo")) { 687 List<ResourceWrapper> a = r.children("photo"); 688 for (ResourceWrapper att : a) { 689 if (att.has("contentType") && att.primitiveValue("contentType").startsWith("image/") && 690 att.has("data") && (!context.isInlineGraphics() || (att.primitiveValue("data").length() > 0 && 691 att.primitiveValue("data").length() < MAX_IMAGE_LENGTH))) { 692 return true; 693 } 694 } 695 } 696 return false; 697 } 698 699 private XhtmlNode makeBanner(XhtmlNode para) { 700 para.style("border: 1px #661aff solid; background-color: #e6e6ff; padding: 10px;"); 701 return para; 702 } 703}