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}