001package org.hl7.fhir.r5.renderers;
002
003import java.io.File;
004import java.io.IOException;
005import java.io.UnsupportedEncodingException;
006import java.util.ArrayList;
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.Address;
016import org.hl7.fhir.r5.model.Attachment;
017import org.hl7.fhir.r5.model.CodeableConcept;
018import org.hl7.fhir.r5.model.ContactPoint;
019import org.hl7.fhir.r5.model.DataType;
020import org.hl7.fhir.r5.model.DateType;
021import org.hl7.fhir.r5.model.Extension;
022import org.hl7.fhir.r5.model.HumanName;
023import org.hl7.fhir.r5.model.HumanName.NameUse;
024import org.hl7.fhir.r5.model.Identifier;
025import org.hl7.fhir.r5.model.Identifier.IdentifierUse;
026import org.hl7.fhir.r5.model.Patient;
027import org.hl7.fhir.r5.model.Period;
028import org.hl7.fhir.r5.model.Reference;
029import org.hl7.fhir.r5.model.Resource;
030import org.hl7.fhir.r5.model.StructureDefinition;
031import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper;
032import org.hl7.fhir.r5.renderers.utils.BaseWrappers.PropertyWrapper;
033import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper;
034import org.hl7.fhir.r5.renderers.utils.RenderingContext;
035import org.hl7.fhir.utilities.TextFile;
036import org.hl7.fhir.utilities.Utilities;
037import org.hl7.fhir.utilities.xhtml.XhtmlNode;
038
039public class PatientRenderer extends ResourceRenderer {
040
041  public class NamedReferance {
042
043    private String name;
044    private Reference reference;
045    private BaseWrapper wrapper;
046
047    public NamedReferance(String name, Reference ref, BaseWrapper refw) {
048      this.name = name;
049      this.reference = ref;
050      this.wrapper = refw;
051    }
052
053    public String getName() {
054      return name;
055    }
056
057    public Reference getReference() {
058      return reference;
059    }
060
061    public BaseWrapper getWrapper() {
062      return wrapper;
063    }
064
065  }
066
067  private static final int MAX_IMAGE_LENGTH = 2*1024*1024;
068
069  public PatientRenderer(RenderingContext context) {
070    super(context);
071  }
072
073  public boolean render(XhtmlNode x, Resource dr) throws UnsupportedEncodingException, IOException {
074    describe(x, dr);
075    return false;
076  }
077
078  // name gender DoB (MRN)
079  public String display(Resource dr) {
080    Patient pat = (Patient) dr;
081    Identifier id = null;
082    for (Identifier t : pat.getIdentifier()) {
083      id = chooseId(id, t);
084    }
085    HumanName n = null;
086    for (HumanName t : pat.getName()) {
087      n = chooseName(n, t);
088    }
089    return display(n, pat.hasGender() ? pat.getGender().getDisplay() : null, pat.getBirthDateElement(), id);
090  }
091
092  private Identifier chooseId(Identifier oldId, Identifier newId) {
093    if (oldId == null) {
094      return newId;
095    }
096    if (newId == null) {
097      return oldId;
098    }
099    return isPreferred(newId.getUse(), oldId.getUse()) ? newId : oldId;
100  }
101
102  private boolean isPreferred(IdentifierUse newUse, IdentifierUse oldUse) {
103    if (newUse == null && oldUse == null || newUse == oldUse) {
104      return false;
105    }
106    if (newUse == null) {
107      return true;
108    }
109    switch (newUse) {
110    case NULL: return !existsInList(oldUse, IdentifierUse.OFFICIAL, IdentifierUse.USUAL);
111    case OFFICIAL: return !existsInList(oldUse, IdentifierUse.USUAL);
112    case OLD: return !existsInList(oldUse, IdentifierUse.OFFICIAL, IdentifierUse.SECONDARY, IdentifierUse.USUAL);
113    case SECONDARY: return !existsInList(oldUse, IdentifierUse.OFFICIAL, IdentifierUse.USUAL);
114    case TEMP: return !existsInList(oldUse, IdentifierUse.OFFICIAL, IdentifierUse.SECONDARY, IdentifierUse.USUAL);
115    case USUAL: return true;
116    default: return false;
117    }
118  }
119
120  private boolean existsInList(IdentifierUse oldUse, IdentifierUse... values) {
121    for (IdentifierUse value : values) {
122      if (value == oldUse) {
123        return true;
124      }
125    }
126    return false;
127  }
128
129  private HumanName chooseName(HumanName oldName, HumanName newName) {
130    if (oldName == null) {
131      return newName;
132    }
133    if (newName == null) {
134      return oldName;
135    }
136    return isPreferred(newName.getUse(), oldName.getUse()) ? newName : oldName;
137  }
138
139
140  private boolean isPreferred(NameUse newUse, NameUse oldUse) {
141    if (newUse == null && oldUse == null || newUse == oldUse) {
142      return false;
143    }
144    if (newUse == null) {
145      return true;
146    }
147    if (oldUse == null) {
148      return existsInList(newUse, NameUse.OFFICIAL, NameUse.USUAL);
149    }
150    switch (oldUse) {
151      case ANONYMOUS: return existsInList(newUse, NameUse.OFFICIAL, NameUse.USUAL);
152      case MAIDEN: return existsInList(newUse, NameUse.OFFICIAL, NameUse.USUAL);
153      case NICKNAME: return existsInList(newUse, NameUse.OFFICIAL, NameUse.USUAL);
154      case NULL: return existsInList(newUse, NameUse.OFFICIAL, NameUse.USUAL);
155      case OFFICIAL: return existsInList(newUse, NameUse.USUAL);
156      case OLD: return existsInList(newUse, NameUse.OFFICIAL, NameUse.USUAL);
157      case TEMP: return existsInList(newUse, NameUse.OFFICIAL, NameUse.USUAL);
158      case USUAL: return false; 
159    }
160    return false;
161  }
162
163  private boolean existsInList(NameUse oldUse, NameUse... values) {
164    for (NameUse value : values) {
165      if (value == oldUse) {
166        return true;
167      }
168    }
169    return false;
170  }
171
172  @Override
173  public String display(ResourceWrapper pat) throws UnsupportedEncodingException, IOException {
174    Identifier id = null;
175    PropertyWrapper pw = getProperty(pat, "identifier");
176    for (BaseWrapper t : pw.getValues()) {
177      id = chooseId(id, (Identifier) t.getBase());
178    }
179    pw = getProperty(pat, "name");
180    HumanName n = null;
181    for (BaseWrapper t : pw.getValues()) {
182      n = chooseName(n, (HumanName) t);
183    }
184    String gender = null;
185    pw = getProperty(pat, "gender");
186    if (valued(pw)) {
187      pw.value().getBase().primitiveValue();
188    }
189    DateType dt = null; 
190    pw = getProperty(pat, "birthDate");
191    if (valued(pw)) {
192      dt = (DateType) pw.value().getBase();
193    }
194    return display(n, gender, dt, id);  
195  }
196
197  public void describe(XhtmlNode x, ResourceWrapper pat) throws UnsupportedEncodingException, IOException {
198    Identifier id = null;
199    PropertyWrapper pw = getProperty(pat, "identifier");
200    for (BaseWrapper t : pw.getValues()) {
201      id = chooseId(id, (Identifier) t.getBase());
202    }
203    pw = getProperty(pat, "name");
204    HumanName n = null;
205    for (BaseWrapper t : pw.getValues()) {
206      n = chooseName(n, (HumanName) t.getBase());
207    }
208    String gender = null;
209    pw = getProperty(pat, "gender");
210    if (valued(pw)) {
211      gender = pw.value().getBase().primitiveValue();
212    }
213    DateType dt = null; 
214    pw = getProperty(pat, "birthDate");
215    if (valued(pw)) {
216      dt = (DateType) pw.value().getBase();
217    }
218    describe(x, n, gender, dt, id);
219  }
220
221
222  private String display(HumanName name, String gender, DateType dob, Identifier id) {
223    StringBuilder b = new StringBuilder();
224    b.append(display(name));
225    b.append(" ");
226    if (dob == null) {
227      b.append("(no stated gender)");
228    } else {
229      b.append(gender);
230    }
231    b.append(", ");
232    if (dob == null) {
233      b.append("DoB Unknown");
234    } else {
235      b.append("DoB: "+display(dob));      
236    }
237    if (id != null) {
238      b.append(" ( ");      
239      b.append(display(id));
240      b.append(")");      
241    }
242    return b.toString();
243  }
244  
245  public void describe(XhtmlNode x, HumanName name, String gender, DateType dob, Identifier id) throws UnsupportedEncodingException, IOException {
246    if (name == null) {
247      x.b().tx("Anonymous Patient"); // todo: is this appropriate?  
248    } else {
249      render(x.b(), name);
250    }
251    x.tx(" ");
252    if (gender == null) {
253      x.tx("(no stated gender)");
254    } else {
255      x.tx(gender);
256    }
257    x.tx(", ");
258    if (dob == null) {
259      x.tx("DoB Unknown");
260    } else {
261      x.tx("DoB: ");
262      render(x, dob);
263    }
264    if (id != null) {
265      x.tx(" ( ");      
266      render(x, id);
267      x.tx(")");      
268    }
269  }
270
271  @Override
272  public boolean render(XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException {
273    // banner
274    describe(makeBanner(x.para()), r);
275    x.hr();
276    XhtmlNode tbl;
277    if (hasRenderablePhoto(r)) {
278      tbl = x.table("none");
279      XhtmlNode tr = tbl.tr();
280      tbl = tr.td().table("grid");
281      renderPhoto(tr.td(), r);
282    } else {
283      tbl = x.table("grid");
284    }
285    
286    // the table has 4 columns
287    addStatus(tbl, r);
288    addIdentifiers(tbl, r);
289    addNames(tbl, r);
290    addComms(tbl, r);
291    addLangs(tbl, r);
292    addNOKs(tbl, r);
293    addLinks(tbl, r);
294    addExtensions(tbl, r);
295    if (tbl.isEmpty()) {
296      x.remove(tbl);
297    }
298    return false;
299  }
300
301  private void addExtensions(XhtmlNode tbl, ResourceWrapper r) throws UnsupportedEncodingException, FHIRException, IOException {
302    Map<String, List<Extension>> extensions = new HashMap<>();
303    PropertyWrapper pw = getProperty(r, "extension");
304    for (BaseWrapper t : pw.getValues()) {      
305      Extension ext = (Extension) t.getBase();
306      if (!extensions.containsKey(ext.getUrl())) {
307        extensions.put(ext.getUrl(), new ArrayList<>());
308      }
309      extensions.get(ext.getUrl()).add(ext);
310    }
311    for (String url : extensions.keySet()) {
312      StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, url);
313      if (sd != null) {
314        List<Extension> list = extensions.get(url);
315        boolean anyComplex = false;
316        for (Extension ext : list) {
317          anyComplex = anyComplex || ext.hasExtension();
318        }
319        if (!anyComplex) {
320          XhtmlNode tr = tbl.tr();
321          nameCell(tr, sd.getTitle()+":", sd.getDescription(), sd.getWebPath());
322          XhtmlNode td = tr.td();
323          td.colspan("3");
324          if (list.size() == 1) {
325            XhtmlNode ul = td.ul();
326            for (Extension s : list) {
327              XhtmlNode li = ul.li();
328              render(r, li, s.getValue());
329            }
330          } else {
331            render(r, td, list.get(0).getValue());
332          }
333        } else {
334          for (Extension ext : list) {
335            XhtmlNode tr = tbl.tr();
336            nameCell(tr, sd.getTitle()+":", sd.getDescription());
337            XhtmlNode td = tr.td();
338            td.colspan("3");
339            if (ext.hasExtension()) {
340              XhtmlNode ul = td.ul();
341              for (Extension s : ext.getExtension()) {
342                XhtmlNode li = ul.li();
343                li.tx(s.getUrl()+": ");
344                if (s.hasExtension()) {
345                  boolean first = true;
346                  for (Extension t : s.getExtension()) {
347                    if (first) first = false; else li.tx("; ");
348                    li.tx(t.getUrl()+"=");
349                    render(r, li, t.getValue());
350                  }
351                } else {
352                  render(r, li, s.getValue());
353                }
354              }
355            } else {
356              render(r, td, ext.getValue());
357            }
358          }
359        }
360      }
361    }
362    
363    
364  }
365
366  private void addIdentifiers(XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException {
367    List<Identifier> ids = new ArrayList<>();
368    PropertyWrapper pw = getProperty(r, "identifier");
369    for (BaseWrapper t : pw.getValues()) {
370      ids.add((Identifier) t.getBase());
371    }
372    Identifier id = null;
373    for (Identifier i : ids) {
374      id = chooseId(id, i);
375    }
376    if (id != null) {
377      ids.remove(id);
378    };
379    if (ids.size() == 1) {
380      XhtmlNode tr = tbl.tr();
381      nameCell(tr, "Other Id:", "Other Ids (see the one above)");
382      XhtmlNode td = tr.td();
383      td.colspan("3");
384      render(r, td, ids.get(0));
385    } else if (ids.size() > 1) {
386      XhtmlNode tr = tbl.tr();
387      nameCell(tr, "Other Ids:", "Other Ids (see the one above)");
388      XhtmlNode td = tr.td();
389      td.colspan("3");
390      XhtmlNode ul = td.ul();
391      for (Identifier i : ids) {
392        render(r, ul.li(), i);
393      }
394    }
395  }
396
397  private void addLangs(XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException {
398    List<CodeableConcept> langs = new ArrayList<>();
399    PropertyWrapper pw = getProperty(r, "communication");
400    CodeableConcept prefLang = null;
401    for (BaseWrapper t : pw.getValues()) {
402      PropertyWrapper l = getProperty(t, "language");
403      if (l != null && l.hasValues()) {
404        CodeableConcept lang = (CodeableConcept) l.getValues().get(0).getBase();
405        langs.add(lang);
406        l = getProperty(t, "preferred");
407        if (l != null && l.hasValues() && "true".equals(l.getValues().get(0).getBase().primitiveValue())) {
408          prefLang = lang;
409        }
410      }
411    }
412    if (langs.size() == 1) {
413      XhtmlNode tr = tbl.tr();
414      nameCell(tr, "Language:", "Languages spoken");
415      XhtmlNode td = tr.td();
416      td.colspan("3");
417      render(r, td, langs.get(0));
418      if (prefLang != null) {
419        td.tx(" (preferred)");
420      }
421    } else if (langs.size() > 1) {
422      XhtmlNode tr = tbl.tr();
423      nameCell(tr, "Languages:", "Languages spoken");
424      XhtmlNode td = tr.td();
425      td.colspan("3");
426      XhtmlNode ul = td.ul();
427      for (CodeableConcept i : langs) {
428        XhtmlNode li = ul.li();
429        render(r, li, i);
430        if (i == prefLang) {
431          li.tx(" (preferred)");
432        }
433      }
434    }
435  }
436
437
438  private void addLinks(XhtmlNode tbl, ResourceWrapper r) throws UnsupportedEncodingException, FHIRException, IOException {
439    List<NamedReferance> refs = new ArrayList<>();
440    PropertyWrapper pw = getProperty(r, "generalPractitioner");
441    if (pw != null) {
442      for (BaseWrapper t : pw.getValues()) {
443        refs.add(new NamedReferance("General Practitioner", (Reference) t.getBase(), t));
444      }
445    }
446    pw = getProperty(r, "managingOrganization");
447    if (pw != null) {
448      for (BaseWrapper t : pw.getValues()) {
449        refs.add(new NamedReferance("Managing Organization", (Reference) t.getBase(), t));
450      }
451    }
452    pw = getProperty(r, "link");
453    for (BaseWrapper t : pw.getValues()) {
454      PropertyWrapper l = getProperty(t, "other");
455      Reference ref = null;
456      BaseWrapper refw = null;
457      for (BaseWrapper v : l.getValues()) {
458        ref = (Reference) v.getBase();
459        refw = v;
460      }
461      String type = null;
462      l = getProperty(t, "type");
463      for (BaseWrapper v : l.getValues()) {
464        type = v.getBase().primitiveValue();
465      }
466      if (type != null && ref != null) {
467        refs.add(new NamedReferance(describeLinkedRecord(type), ref, refw));        
468      }
469    }
470    
471    if (refs.size() > 0) {      
472      XhtmlNode tr = tbl.tr();
473      nameCell(tr, "Links:", "Patient Links");
474      XhtmlNode td = tr.td();
475      td.colspan("3");
476      XhtmlNode ul = td.ul();
477      for (NamedReferance ref : refs) {
478        XhtmlNode li = ul.li();
479        li.tx(ref.getName());
480        li.tx(": ");
481        renderReference(r, li, ref.getReference());        
482      }
483    }
484  }
485
486  private String describeLinkedRecord(String type) {
487    switch (type) {
488    case "replaced-by" : return "This record replaced by";
489    case "replaces": return "This record replaces";
490    case "refer": return "Please refer to";
491    case "seealso": return "Also see";
492    }
493    return "Unknown";
494  }
495
496  private void addNOKs(XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException {
497    PropertyWrapper pw = getProperty(r, "contact");
498    for (BaseWrapper t : pw.getValues()) {
499      addNOK(tbl,r,  t);
500    }
501  }
502  
503  private void addNOK(XhtmlNode tbl, ResourceWrapper r, BaseWrapper bw) throws FHIRFormatError, DefinitionException, IOException {
504    List<CodeableConcept> rels = new ArrayList<>();
505    HumanName name = null;
506    Address add = null;
507    String gender = null;
508    Period period = null;
509    Reference organization = null;
510    List<ContactPoint> tels = new ArrayList<>();
511
512    PropertyWrapper pw = getProperty(bw, "relationship");
513    for (BaseWrapper t : pw.getValues()) {
514      CodeableConcept v = (CodeableConcept) t.getBase();
515      rels.add(v);
516    }
517        
518    pw = getProperty(bw, "name");
519    if (pw.hasValues()) {
520      name = (HumanName) pw.getValues().get(0).getBase();
521    }
522    pw = getProperty(bw, "telecom");
523    for (BaseWrapper t : pw.getValues()) {
524      ContactPoint v = (ContactPoint) t.getBase();
525      tels.add(v);
526    }
527        
528    pw = getProperty(bw, "address");
529    if (pw.hasValues()) {
530      add = (Address) pw.getValues().get(0).getBase();
531    }
532    
533    pw = getProperty(bw, "gender");
534    if (pw.hasValues()) {
535      gender = pw.getValues().get(0).getBase().primitiveValue();
536    }
537
538    pw = getProperty(bw, "organization");
539    if (pw.hasValues()) {
540      organization = (Reference) pw.getValues().get(0).getBase();
541    }
542    
543    pw = getProperty(bw, "period");
544    if (pw.hasValues()) {
545      period = (Period) pw.getValues().get(0).getBase();
546    }
547    
548    if (rels.size() < 2 && name == null && add == null && gender == null && period == null && organization == null && tels.size() == 0) {
549      return; // nothing to render 
550    }
551    XhtmlNode tr = tbl.tr();
552    if (rels.size() == 1) {
553      nameCell(tr, (rels.get(0).getCodingFirstRep().hasDisplay() ? rels.get(0).getCodingFirstRep().getDisplay() : display(rels.get(0)))+":", "Nominated Contact: "+display(rels.get(0)));
554    } else {
555      nameCell(tr, "Contact", "Patient contact");
556    }
557    XhtmlNode td = tr.td();
558    td.colspan("3");
559    XhtmlNode ul = td.ul();
560    XhtmlNode li;
561    if (name != null) {
562      li = ul.li();
563      render(r, li, name);
564      if (gender != null) {
565        li.tx(" ("+gender+")");
566      }
567    } else if (gender != null) {
568      li = ul.li();
569      li.tx("Gender: "+gender);      
570    }
571    if (rels.size() > 1) {
572      li = ul.li();
573      li.tx("Relationships: ");
574      boolean first = true;
575      for (CodeableConcept rel : rels) {
576        if (first) first = false; else li.tx(", ");
577        render(r, li, rel);
578      }      
579    }
580    if (add != null) {
581      render(r, ul.li(), add);
582    }
583    for (ContactPoint cp : tels) {
584      render(r, ul.li(), cp);
585    }
586    if (organization != null) {
587      li = ul.li();
588      li.tx("Organization: ");
589      render(r, li, organization);
590    }
591    if (period != null) {
592      li = ul.li();
593      li.tx("Valid Period: ");
594      render(r, li, period);
595    }
596  }
597
598  private void addNames(XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException {
599    List<HumanName> names = new ArrayList<>();
600    PropertyWrapper pw = getProperty(r, "name");
601    for (BaseWrapper t : pw.getValues()) {
602      names.add((HumanName) t.getBase());
603    }
604    HumanName name = null;
605    for (HumanName n : names) {
606      name = chooseName(name, n);
607    }
608    if (name != null) {
609      names.remove(name);
610    };
611    if (names.size() == 1) {
612      XhtmlNode tr = tbl.tr();
613      nameCell(tr, "Alt. Name:", "Alternate names (see the one above)");
614      XhtmlNode td = tr.td();
615      td.colspan("3");
616      render(r, td, names.get(0));
617    } else if (names.size() > 1) {
618      XhtmlNode tr = tbl.tr();
619      nameCell(tr, "Alt Names:", "Alternate names (see the one above)");
620      XhtmlNode td = tr.td();
621      td.colspan("3");
622      XhtmlNode ul = td.ul();
623      for (HumanName n : names) {
624        render(r, ul.li(), n);
625      }
626    }
627  }
628  
629  private void addComms(XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException {
630    List<ContactPoint> tels = new ArrayList<>();
631    PropertyWrapper pw = getProperty(r, "telecom");
632    for (BaseWrapper t : pw.getValues()) {
633      tels.add((ContactPoint) t.getBase());
634    }
635    List<Address> adds = new ArrayList<>();
636    pw = getProperty(r, "address");
637    for (BaseWrapper t : pw.getValues()) {
638      adds.add((Address) t.getBase());
639    }
640    if (tels.size() + adds.size() == 1) {
641      XhtmlNode tr = tbl.tr();
642      nameCell(tr, "Contact Detail:", "Ways to contact the Patient");
643      XhtmlNode td = tr.td();
644      td.colspan("3");
645      if (adds.isEmpty()) {
646        render(r, td, tels.get(0));
647      } else {
648        render(r, td, adds.get(0));
649      }
650    } else if (tels.size() + adds.size() > 1) {
651      XhtmlNode tr = tbl.tr();
652      nameCell(tr, "Contact Details:", "Ways to contact the Patient");
653      XhtmlNode td = tr.td();
654      td.colspan("3");
655      XhtmlNode ul = td.ul();
656      for (ContactPoint n : tels) {
657        render(r, ul.li(), n);
658      }
659      for (Address n : adds) {
660        render(r, ul.li(), n);
661      }
662    }
663  }
664  
665  private void addStatus(XhtmlNode tbl, ResourceWrapper r) throws FHIRFormatError, DefinitionException, UnsupportedEncodingException, FHIRException, IOException {
666    // TODO Auto-generated method stub
667    int count = 0;
668    if (r.has("active")) {
669      count++;
670    }
671    if (r.has("deceased")) {
672      count++;
673    }
674    if (r.has("maritalStatus")) {
675      count++;
676    }
677    if (r.has("multipleBirth")) {
678      count++;
679    }
680    if (count > 0) {
681      XhtmlNode tr = tbl.tr();
682      int pos = 0;
683      if (r.has("active")) {
684        PropertyWrapper a = r.getChildByName("active");
685        if (a.hasValues()) {
686          pos++;
687          nameCell(tr, "Active:", "Record is active");
688          XhtmlNode td = tr.td();
689          if (pos == count) {
690            td.colspan("3");
691          }
692          render(r, td, (DataType) a.value().getBase());
693        }
694      }      
695      if (r.has("deceased[x]")) {
696        PropertyWrapper a = r.getChildByName("deceased[x]");
697        if (a.hasValues()) {
698          pos++;
699          nameCell(tr, "Deceased:", "Known status of Patient");
700          XhtmlNode td = tr.td();
701          if (pos == count) {
702            td.colspan("3");
703          }
704          render(r, td, (DataType) a.value().getBase());
705        }
706      }      
707      if (r.has("maritalStatus")) {
708        PropertyWrapper a = r.getChildByName("maritalStatus");
709        if (a.hasValues()) {
710          pos++;
711          if (pos == 3) {
712            tr = tbl.tr();          
713          }
714          nameCell(tr, "Marital Status:", "Known Marital status of Patient");
715          XhtmlNode td = tr.td();
716          if (pos == count) {
717            td.colspan("3");
718          }
719          render(r, td, (DataType) a.value().getBase());
720        }
721      }      
722      if (r.has("multipleBirth[x]")) {
723        PropertyWrapper a = r.getChildByName("multipleBirth[x]");
724        if (a.hasValues()) {
725          pos++;
726          if (pos == 3) {
727            tr = tbl.tr();          
728          }
729          nameCell(tr, "Multiple Birth:", "Known multipleBirth status of Patient");
730          XhtmlNode td = tr.td();
731          if (pos == count) {
732            td.colspan("3");
733          }
734          render(r, td, (DataType) a.value().getBase());
735        }
736      }      
737    }  
738  }
739
740  private void nameCell(XhtmlNode tr, String text, String title) {
741    XhtmlNode td = tr.td();
742    td.setAttribute("title", title);
743    td.tx(text);
744    td.style("background-color: #f3f5da");
745  }
746
747  private void nameCell(XhtmlNode tr, String text, String title, String link) {
748    XhtmlNode td = tr.td();
749    td.setAttribute("title", title);
750    if (link != null) {
751      td.ah(link).tx(text); 
752    } else {
753      td.tx(text);
754    }
755    td.style("background-color: #f3f5da");
756  }
757
758  private void renderPhoto(XhtmlNode td, ResourceWrapper r) throws UnsupportedEncodingException, FHIRException, IOException {
759    if (r.has("photo")) {
760      PropertyWrapper a = r.getChildByName("photo");
761      for (BaseWrapper v : a.getValues()) {
762        Attachment att = (Attachment) v.getBase();
763        if (att.getContentType().startsWith("image/") &&
764            att.getData() != null && (!context.isInlineGraphics() || (att.getData().length > 0 && att.getData().length < MAX_IMAGE_LENGTH))) {
765          String ext = extensionForType(att.getContentType());
766          if (context.isInlineGraphics() || Utilities.noString(context.getDestDir()) || ext == null) {
767            td.img("data:"+att.getContentType()+";base64,"+att.getDataElement().asStringValue(), "patient photo");
768          } else {
769            String n = UUID.randomUUID().toString().toLowerCase()+ext;
770            TextFile.bytesToFile(att.getData(), new File(Utilities.path(context.getDestDir(), n)));
771            td.img(n, "patient photo");            
772          }
773          return;
774        } 
775      }
776    }      
777    return;
778  }
779
780  private String extensionForType(String contentType) {
781    if (contentType.equals("image/gif")) {
782      return ".gif";
783    }
784    if (contentType.equals("image/png")) {
785      return ".png";
786    }
787    if (contentType.equals("image/jpeg")) {
788      return ".jpg";
789    }
790    return null;
791  }
792
793  private boolean hasRenderablePhoto(ResourceWrapper r) throws UnsupportedEncodingException, FHIRException, IOException {
794    if (r.has("photo")) {
795      PropertyWrapper a = r.getChildByName("photo");
796      for (BaseWrapper v : a.getValues()) {
797        Attachment att = (Attachment) v.getBase();
798        if (att.hasContentType() && att.getContentType().startsWith("image/") &&
799            att.getData() != null && (!context.isInlineGraphics() || (att.getData().length > 0 && att.getData().length < MAX_IMAGE_LENGTH))) {
800          return true;
801        } 
802      }
803    }      
804    return false;
805  }
806
807  private XhtmlNode makeBanner(XhtmlNode para) {
808    para.style("border: 1px #661aff solid; background-color: #e6e6ff; padding: 10px;");
809    return para;
810  }
811}