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