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      String id = c.getScopedId();
240      if (!context.hasAnchor(id)) {
241        context.addAnchor(id);
242        x.an(context.prefixAnchor(id));
243      }
244      RendererFactory.factory(c, context.forContained()).buildNarrative(status, x, c);
245    }
246  }
247
248  private void addExtensions(RenderingStatus status, XhtmlNode tbl, ResourceWrapper r) throws UnsupportedEncodingException, FHIRException, IOException {
249    Map<String, List<ResourceWrapper>> extensions = new HashMap<>();
250    List<ResourceWrapper> pw = r.children("extension");
251    for (ResourceWrapper t : pw) {  
252      String url = t.primitiveValue("url");
253      if (!extensions.containsKey(url)) {
254        extensions.put(url, new ArrayList<>());
255      }
256      extensions.get(url).add(t);
257    }
258
259    for (String url : extensions.keySet()) {
260      StructureDefinition sd = findCanonical(StructureDefinition.class, url, r);
261      if (sd != null) {
262        List<ResourceWrapper> list = extensions.get(url);
263        boolean anyComplex = false;
264        for (ResourceWrapper ext : list) {
265          anyComplex = anyComplex || ext.has("extension");
266        }
267        if (!anyComplex) {
268          XhtmlNode tr = tbl.tr();
269          nameCell(tr, getContext().getTranslated(sd.getTitleElement()), sd.getDescription(), sd.getWebPath());
270          XhtmlNode td = tr.td();
271          td.colspan("3");
272          if (list.size() != 1) {
273            XhtmlNode ul = td.ul();
274            for (ResourceWrapper s : list) {
275              XhtmlNode li = ul.li();
276              renderDataType(status, li, s.child("value"));
277            }
278          } else {
279            renderDataType(status, td, list.get(0).child("value"));
280          }
281        } else {
282          for (ResourceWrapper ext : list) {
283            XhtmlNode tr = tbl.tr();
284            nameCell(tr, sd.getTitle()+":", sd.getDescription());
285            XhtmlNode td = tr.td();
286            td.colspan("3");
287            if (ext.has("extension")) {
288              XhtmlNode ul = td.ul();
289              for (ResourceWrapper s : ext.extensions()) {
290                XhtmlNode li = ul.li();
291                li.tx(s.primitiveValue("url")+": ");
292                if (s.has("extension")) {
293                  boolean first = true;
294                  for (ResourceWrapper t : s.extensions()) {
295                    if (first) first = false; else li.tx("; ");
296                    li.tx(t.primitiveValue("url")+"=");
297                    renderDataType(status, li, t.child("value"));
298                  }
299                } else {
300                  renderDataType(status, li, s.child("value"));
301                }
302              }
303            } else {
304              renderDataType(status, td, ext.child("value"));
305            }
306          }
307        }
308      }
309    }
310
311
312  }
313
314  private void addIdentifiers(RenderingStatus status, XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException {
315    List<ResourceWrapper> ids = r.children("identifier");
316    ResourceWrapper id = null;
317    for (ResourceWrapper i : ids) {
318      id = chooseId(id, i);
319    }
320    if (id != null) {
321      ids.remove(id);
322    };
323    if (ids.size() > 0) {
324      XhtmlNode tr = tbl.tr();
325      nameCell(tr, context.formatMessagePlural(ids.size(), RenderingContext.PAT_OTHER_ID),context.formatMessagePlural(ids.size(), RenderingContext.PAT_OTHER_ID_HINT));
326      XhtmlNode td = tr.td();
327      td.colspan("3");
328      if (ids.size() == 1) {
329        renderDataType(status, td, ids.get(0));
330      } else { 
331        XhtmlNode ul = td.ul();
332        for (ResourceWrapper i : ids) {
333          renderDataType(status, ul.li(), i);
334        }
335      }
336    }
337  }
338
339  private void addLangs(RenderingStatus status, XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException {
340    List<ResourceWrapper> langs = new ArrayList<ResourceWrapper>();
341    List<ResourceWrapper> comms = r.children("communication");
342    ResourceWrapper prefLang = null;
343    for (ResourceWrapper t : comms) {
344      ResourceWrapper lang = t.child("language");
345      if (lang != null) {
346        langs.add(lang);
347        ResourceWrapper l = t.child("preferred");
348        if (l != null && "true".equals(l.primitiveValue())) {
349          prefLang = lang;
350        }
351      }
352    }
353    if (langs.size() > 0) {
354      XhtmlNode tr = tbl.tr();
355      nameCell(tr, context.formatMessagePlural(langs.size(), RenderingContext.PAT_LANG), context.formatMessagePlural(langs.size(), RenderingContext.PAT_LANG_HINT));
356      XhtmlNode td = tr.td();
357      td.colspan("3");
358      if (langs.size() == 1) {
359        renderDataType(status, td, langs.get(0));
360        if (prefLang != null) {
361          td.tx(" "+context.formatPhrase(RenderingContext.PAT_LANG_PREFERRED));
362        }
363      } else if (langs.size() > 1) {
364        XhtmlNode ul = td.ul();
365        for (ResourceWrapper i : langs) {
366          XhtmlNode li = ul.li();
367          renderDataType(status, li, i);
368          if (i == prefLang) {
369            li.tx(" "+context.formatPhrase(RenderingContext.PAT_LANG_PREFERRED));;
370          }
371        }
372      }
373    }
374  }
375
376
377
378  public class NamedReferance {
379
380    private String name;
381    private ResourceWrapper reference;
382
383    public NamedReferance(String name, ResourceWrapper ref) {
384      this.name = name;
385      this.reference = ref;
386    }
387
388    public String getName() {
389      return name;
390    }
391
392    public ResourceWrapper getReference() {
393      return reference;
394    }
395
396  }
397
398
399  private void addLinks(RenderingStatus status, XhtmlNode tbl, ResourceWrapper r) throws UnsupportedEncodingException, FHIRException, IOException {
400    List<NamedReferance> refs = new ArrayList<>();
401    List<ResourceWrapper> pw = r.children("generalPractitioner");
402    for (ResourceWrapper t : pw) {
403      refs.add(new NamedReferance(context.formatPhrase(RenderingContext.PAT_GP), t));
404    }
405    pw = r.children("managingOrganization");
406    for (ResourceWrapper t : pw) {
407      refs.add(new NamedReferance(context.formatPhrase(RenderingContext.PAT_MO), t));
408    }
409    pw = r.children("link");
410    for (ResourceWrapper t : pw) {
411      ResourceWrapper o = t.firstChild("other");
412      ResourceWrapper l = t.firstChild("type");
413      if (l != null && o != null) {
414        refs.add(new NamedReferance(describeLinkedRecord(l.primitiveValue()), o));        
415      }
416    }
417
418    if (refs.size() > 0) {      
419      XhtmlNode tr = tbl.tr();
420      nameCell(tr, context.formatPhrase(RenderingContext.PAT_LINKS), context.formatPhrase(RenderingContext.PAT_LINKS_HINT));
421      XhtmlNode td = tr.td();
422      td.colspan("3");
423      XhtmlNode ul = td.ul();
424      for (NamedReferance ref : refs) {
425        XhtmlNode li = ul.li();
426        li.tx(ref.getName());
427        li.tx(": ");
428        renderReference(status, li, ref.getReference());        
429      }
430    }
431  }
432
433  private String describeLinkedRecord(String type) {
434    switch (type) {
435    case "replaced-by" : return context.formatPhrase(RenderingContext.PAT_LINK_REPLBY);
436    case "replaces": return context.formatPhrase(RenderingContext.PAT_LINK_REPL);
437    case "refer": return context.formatPhrase(RenderingContext.PAT_LINK_REFER);
438    case "seealso": return context.formatPhrase(RenderingContext.PAT_LINK_SEE);
439    }
440    return "Unknown";
441  }
442
443  private void addNOKs(RenderingStatus status, XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException {
444    for (ResourceWrapper t : r.children("contact")) {
445      addNOK(status, tbl, r,  t);
446    }
447  }
448
449  private void addNOK(RenderingStatus status, XhtmlNode tbl, ResourceWrapper r, ResourceWrapper bw) throws FHIRFormatError, DefinitionException, IOException {
450    List<ResourceWrapper> rels = bw.children("relationship");
451    ResourceWrapper name = bw.firstChild("name");
452    ResourceWrapper add = bw.firstChild("address");
453    String gender = context.getTranslatedCode(bw.primitiveValue("gender"), "http://hl7.org/fhir/administrative-gender");
454    ResourceWrapper period = bw.firstChild("period");
455    ResourceWrapper organization = bw.firstChild("organization");
456    List<ResourceWrapper> tels = bw.children("telecom");
457
458    if (rels.size() < 2 && name == null && add == null && gender == null && period == null && organization == null && tels.size() == 0) {
459      return; // nothing to render 
460    }
461    XhtmlNode tr = tbl.tr();
462    if (rels.size() == 1) {
463      nameCell(tr, displayDataType(rels.get(0))+":",  context.formatPhrase(RenderingContext.PAT_NOM_CONTACT)+" "+displayDataType(rels.get(0)));
464    } else {
465      nameCell(tr, context.formatPhrase(RenderingContext.GENERAL_CONTACT), context.formatPhrase(RenderingContext.PAT_NOK_CONTACT_HINT));
466    }
467    XhtmlNode td = tr.td();
468    td.colspan("3");
469    XhtmlNode ul = td.ul();
470    XhtmlNode li;
471    if (name != null) {
472      li = ul.li();
473      renderDataType(status, li, name);
474      if (gender != null) {
475        li.tx(" "+"("+gender+")");
476      }
477    } else if (gender != null) {
478      li = ul.li();
479      li.tx(context.formatPhrase(RenderingContext.PAT_GENDER, gender));      
480    }
481    if (rels.size() > 1) {
482      li = ul.li();
483      li.tx(context.formatPhrase(RenderingContext.PAT_RELN));
484      boolean first = true;
485      for (ResourceWrapper rel : rels) {
486        if (first) first = false; else li.tx(", ");
487        renderDataType(status, li, rel);
488      }      
489    }
490    if (add != null) {
491      renderDataType(status, ul.li(), add);
492    }
493    for (ResourceWrapper cp : tels) {
494      renderDataType(status, ul.li(), cp);
495    }
496    if (organization != null) {
497      li = ul.li();
498      li.tx(context.formatPhrase(RenderingContext.PAT_ORG));
499      renderDataType(status, li, organization);
500    }
501    if (period != null) {
502      li = ul.li();
503      li.tx(context.formatPhrase(RenderingContext.PAT_PERIOD));
504      renderDataType(status, li, period);
505    }
506  }
507
508  private void addNames(RenderingStatus status, XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException {
509    List<ResourceWrapper> names = r.children("name");
510    ResourceWrapper name = null;
511    for (ResourceWrapper n : names) {
512      name = chooseName(name, n);
513    }
514    if (name != null) {
515      names.remove(name);
516    };
517    if (names.size() == 1) {
518      XhtmlNode tr = tbl.tr();
519      nameCell(tr, context.formatPhrase(RenderingContext.PAT_ALT_NAME), context.formatPhrase(RenderingContext.PAT_ALT_NAME_HINT));
520      XhtmlNode td = tr.td();
521      td.colspan("3");
522      if (names.size() == 1) {
523        renderDataType(status, td, names.get(0));
524      } else {
525        XhtmlNode ul = td.ul();
526        for (ResourceWrapper n : names) {
527          renderDataType(status, ul.li(), n);
528        }
529      }
530    }
531  }
532
533  private void addComms(RenderingStatus status, XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException {
534    List<ResourceWrapper> tels = r.children("telecom");
535    List<ResourceWrapper> adds = r.children("address");
536    if (tels.size() + adds.size() > 0) {
537      XhtmlNode tr = tbl.tr();
538      nameCell(tr, context.formatPhrase(RenderingContext.PAT_CONTACT), context.formatPhrase(RenderingContext.PAT_CONTACT_HINT));
539      XhtmlNode td = tr.td();
540      td.colspan("3");
541      if (tels.size() + adds.size() == 1) {
542        if (adds.isEmpty()) {
543          renderDataType(status, td, tels.get(0));
544        } else {
545          renderDataType(status, td, adds.get(0));
546        }
547      } else {
548        XhtmlNode ul = td.ul();
549        for (ResourceWrapper n : tels) {
550          renderDataType(status, ul.li(), n);
551        }
552        for (ResourceWrapper n : adds) {
553          renderDataType(status, ul.li(), n);
554        }
555      }
556    }
557  }
558
559  private void addStatus(RenderingStatus status, XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, UnsupportedEncodingException, FHIRException, IOException {
560    // TODO Auto-generated method stub
561    int count = 0;
562    if (r.has("active")) {
563      count++;
564    }
565    if (r.has("deceased")) {
566      count++;
567    }
568    if (r.has("maritalStatus")) {
569      count++;
570    }
571    if (r.has("multipleBirth")) {
572      count++;
573    }
574    if (count > 0) {
575      XhtmlNode tr = tbl.tr();
576      int pos = 0;
577      if (r.has("active")) {
578        List<ResourceWrapper> a = r.children("active");
579        if (!a.isEmpty()) {
580          pos++;
581          nameCell(tr, context.formatPhrase(RenderingContext.PAT_ACTIVE), context.formatPhrase(RenderingContext.PAT_ACTIVE_HINT));
582          XhtmlNode td = tr.td();
583          if (pos == count) {
584            td.colspan("3");
585          }
586          renderDataType(status, td, a.get(0));
587        }
588      }      
589      if (r.has("deceased[x]")) {
590        List<ResourceWrapper> a = r.children("deceased[x]");
591        if (!a.isEmpty()) {
592          pos++;
593          nameCell(tr, context.formatPhrase(RenderingContext.PAT_DECEASED), context.formatPhrase(RenderingContext.PAT_DECEASED_HINT));
594          XhtmlNode td = tr.td();
595          if (pos == count) {
596            td.colspan("3");
597          }
598          renderDataType(status, td, a.get(0));
599        }
600      }      
601      if (r.has("maritalStatus")) {
602        List<ResourceWrapper> a = r.children("maritalStatus");
603        if (!a.isEmpty()) {
604          pos++;
605          if (pos == 3) {
606            tr = tbl.tr();          
607          }
608          nameCell(tr, context.formatPhrase(RenderingContext.PAT_MARITAL), context.formatPhrase(RenderingContext.PAT_MARITAL_HINT));
609          XhtmlNode td = tr.td();
610          if (pos == count) {
611            td.colspan("3");
612          }
613          renderDataType(status, td, a.get(0));
614        }
615      }      
616      if (r.has("multipleBirth[x]")) {
617        List<ResourceWrapper> a = r.children("multipleBirth[x]");
618        if (!a.isEmpty()) {
619          pos++;
620          if (pos == 3) {
621            tr = tbl.tr();          
622          }
623          nameCell(tr, context.formatPhrase(RenderingContext.PAT_MUL_BIRTH), context.formatPhrase(RenderingContext.PAT_MUL_BIRTH_HINT));
624          XhtmlNode td = tr.td();
625          if (pos == count) {
626            td.colspan("3");
627          }
628          renderDataType(status, td, a.get(0));
629        }
630      }      
631    }  
632  }
633
634  private void nameCell(XhtmlNode tr, String text, String title) {
635    XhtmlNode td = tr.td();
636    td.setAttribute("title", title);
637    td.tx(text);
638    td.style("background-color: #f3f5da");
639  }
640
641  private void nameCell(XhtmlNode tr, String text, String title, String link) {
642    XhtmlNode td = tr.td();
643    td.setAttribute("title", title);
644    if (link != null) {
645      td.ah(context.prefixLocalHref(link)).tx(text); 
646    } else {
647      td.tx(text);
648    }
649    td.style("background-color: #f3f5da");
650  }
651
652  private void renderPhoto(XhtmlNode td, ResourceWrapper r) throws UnsupportedEncodingException, FHIRException, IOException {
653    if (r.has("photo")) {
654      List<ResourceWrapper> a = r.children("photo");
655      for (ResourceWrapper att : a) {
656        String ct = att.primitiveValue("contentType");
657        byte[] cnt = att.has("data") ? Base64.getDecoder().decode(att.primitiveValue("data")) : null;
658        if (ct.startsWith("image/") &&
659            cnt != null && (!context.isInlineGraphics() || (cnt.length > 0 && cnt.length < MAX_IMAGE_LENGTH))) {
660          String ext = extensionForType(ct);
661          if (context.isInlineGraphics() || Utilities.noString(context.getDestDir()) || ext == null) {
662            td.img("data:"+ct+";base64,"+att.primitiveValue("data"), "patient photo");
663          } else {
664            String n = UUID.randomUUID().toString().toLowerCase()+ext;
665            TextFile.bytesToFile(cnt, ManagedFileAccess.file(Utilities.path(context.getDestDir(), n)));
666            context.registerFile(n);
667            td.img(n, context.formatPhrase(RenderingContext.PAT_PHOTO));            
668          }
669          return;
670        } 
671      }
672    }      
673    return;
674  }
675
676  private String extensionForType(String contentType) {
677    if (contentType.equals("image/gif")) {
678      return ".gif";
679    }
680    if (contentType.equals("image/png")) {
681      return ".png";
682    }
683    if (contentType.equals("image/jpeg")) {
684      return ".jpg";
685    }
686    return null;
687  }
688
689  private boolean hasRenderablePhoto(ResourceWrapper r) throws UnsupportedEncodingException, FHIRException, IOException {
690    if (r.has("photo")) {
691      List<ResourceWrapper> a = r.children("photo");
692      for (ResourceWrapper att : a) {
693        if (att.has("contentType") && att.primitiveValue("contentType").startsWith("image/") &&
694            att.has("data") && (!context.isInlineGraphics() || (att.primitiveValue("data").length() > 0 && 
695                att.primitiveValue("data").length() < MAX_IMAGE_LENGTH))) {
696          return true;
697        } 
698      }
699    }      
700    return false;
701  }
702
703  private XhtmlNode makeBanner(XhtmlNode para) {
704    para.style("border: 1px #661aff solid; background-color: #e6e6ff; padding: 10px;");
705    return para;
706  }
707}