001package org.hl7.fhir.r4b.renderers;
002
003import java.io.IOException;
004import java.io.UnsupportedEncodingException;
005import java.util.ArrayList;
006import java.util.HashMap;
007import java.util.HashSet;
008import java.util.List;
009import java.util.Map;
010import java.util.Set;
011
012import org.apache.commons.codec.binary.Base64;
013import org.apache.commons.lang3.NotImplementedException;
014import org.hl7.fhir.exceptions.DefinitionException;
015import org.hl7.fhir.exceptions.FHIRException;
016import org.hl7.fhir.exceptions.FHIRFormatError;
017import org.hl7.fhir.r4b.formats.FormatUtilities;
018import org.hl7.fhir.r4b.model.Address;
019import org.hl7.fhir.r4b.model.Annotation;
020import org.hl7.fhir.r4b.model.Attachment;
021import org.hl7.fhir.r4b.model.Base;
022import org.hl7.fhir.r4b.model.Base64BinaryType;
023import org.hl7.fhir.r4b.model.BooleanType;
024import org.hl7.fhir.r4b.model.CodeType;
025import org.hl7.fhir.r4b.model.CodeableConcept;
026import org.hl7.fhir.r4b.model.CodeableReference;
027import org.hl7.fhir.r4b.model.Coding;
028import org.hl7.fhir.r4b.model.ContactDetail;
029import org.hl7.fhir.r4b.model.ContactPoint;
030import org.hl7.fhir.r4b.model.DataRequirement;
031import org.hl7.fhir.r4b.model.DateTimeType;
032import org.hl7.fhir.r4b.model.DomainResource;
033import org.hl7.fhir.r4b.model.Dosage;
034import org.hl7.fhir.r4b.model.ElementDefinition;
035import org.hl7.fhir.r4b.model.Enumeration;
036import org.hl7.fhir.r4b.model.Expression;
037import org.hl7.fhir.r4b.model.Extension;
038import org.hl7.fhir.r4b.model.HumanName;
039import org.hl7.fhir.r4b.model.IdType;
040import org.hl7.fhir.r4b.model.Identifier;
041import org.hl7.fhir.r4b.model.InstantType;
042import org.hl7.fhir.r4b.model.Meta;
043import org.hl7.fhir.r4b.model.Money;
044import org.hl7.fhir.r4b.model.Narrative;
045import org.hl7.fhir.r4b.model.Narrative.NarrativeStatus;
046import org.hl7.fhir.r4b.model.StructureDefinition.StructureDefinitionKind;
047import org.hl7.fhir.r4b.model.Period;
048import org.hl7.fhir.r4b.model.PrimitiveType;
049import org.hl7.fhir.r4b.model.Property;
050import org.hl7.fhir.r4b.model.Quantity;
051import org.hl7.fhir.r4b.model.Range;
052import org.hl7.fhir.r4b.model.Ratio;
053import org.hl7.fhir.r4b.model.Reference;
054import org.hl7.fhir.r4b.model.RelatedArtifact;
055import org.hl7.fhir.r4b.model.Resource;
056import org.hl7.fhir.r4b.model.SampledData;
057import org.hl7.fhir.r4b.model.Signature;
058import org.hl7.fhir.r4b.model.StringType;
059import org.hl7.fhir.r4b.model.StructureDefinition;
060import org.hl7.fhir.r4b.model.Timing;
061import org.hl7.fhir.r4b.model.UriType;
062import org.hl7.fhir.r4b.model.UsageContext;
063import org.hl7.fhir.r4b.renderers.utils.BaseWrappers.BaseWrapper;
064import org.hl7.fhir.r4b.renderers.utils.BaseWrappers.PropertyWrapper;
065import org.hl7.fhir.r4b.renderers.utils.BaseWrappers.ResourceWrapper;
066import org.hl7.fhir.r4b.renderers.utils.DOMWrappers.BaseWrapperElement;
067import org.hl7.fhir.r4b.renderers.utils.DOMWrappers.ResourceWrapperElement;
068import org.hl7.fhir.r4b.renderers.utils.DirectWrappers;
069import org.hl7.fhir.r4b.renderers.utils.DirectWrappers.BaseWrapperDirect;
070import org.hl7.fhir.r4b.renderers.utils.DirectWrappers.PropertyWrapperDirect;
071import org.hl7.fhir.r4b.renderers.utils.DirectWrappers.ResourceWrapperDirect;
072import org.hl7.fhir.r4b.renderers.utils.RenderingContext;
073import org.hl7.fhir.r4b.renderers.utils.Resolver.ResourceContext;
074import org.hl7.fhir.r4b.renderers.utils.Resolver.ResourceWithReference;
075import org.hl7.fhir.r4b.utils.EOperationOutcome;
076import org.hl7.fhir.r4b.utils.ToolingExtensions;
077import org.hl7.fhir.r4b.utils.XVerExtensionManager;
078import org.hl7.fhir.r4b.utils.XVerExtensionManager.XVerExtensionStatus;
079import org.hl7.fhir.utilities.Utilities;
080import org.hl7.fhir.utilities.xhtml.NodeType;
081import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
082import org.hl7.fhir.utilities.xhtml.XhtmlNode;
083import org.hl7.fhir.utilities.xml.XMLUtil;
084import org.w3c.dom.Element;
085
086public class ProfileDrivenRenderer extends ResourceRenderer {
087
088  private Set<String> containedIds = new HashSet<>();
089  private boolean hasExtensions;
090  
091  public ProfileDrivenRenderer(RenderingContext context, ResourceContext rcontext) {
092    super(context, rcontext);
093  }
094
095  public ProfileDrivenRenderer(RenderingContext context) {
096    super(context);
097  }
098
099  @Override
100  public boolean render(XhtmlNode x, Resource r) throws FHIRFormatError, DefinitionException, IOException {
101    return render(x, new DirectWrappers.ResourceWrapperDirect(context, r));
102  }
103
104  @Override
105  public boolean render(XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException {
106    if (context.isAddGeneratedNarrativeHeader()) {
107      x.para().b().tx("Generated Narrative");
108    }
109    if (context.isTechnicalMode()) {
110      renderResourceHeader(r, x);
111    }
112    try {
113      StructureDefinition sd = r.getDefinition();
114      if (sd == null) {
115        throw new FHIRException("Cannot find definition for "+r.fhirType());
116      } else {
117        ElementDefinition ed = sd.getSnapshot().getElement().get(0);
118        containedIds.clear();
119        hasExtensions = false;
120        generateByProfile(r, sd, r.root(), sd.getSnapshot().getElement(), ed, context.getProfileUtilities().getChildList(sd, ed), x, r.fhirType(), context.isTechnicalMode(), 0);
121      }
122    } catch (Exception e) {
123      System.out.println("Error Generating Narrative for "+r.fhirType()+"/"+r.getId()+": "+e.getMessage());
124      e.printStackTrace();
125      x.para().b().style("color: maroon").tx("Exception generating Narrative: "+e.getMessage());
126    }
127    return hasExtensions;
128  }
129
130
131  @Override
132  public String display(Resource r) throws UnsupportedEncodingException, IOException {
133    return "todo";
134  }
135  
136  @Override
137  public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException {
138    return "Not done yet";
139  }
140
141//
142//  public void inject(Element er, XhtmlNode x, NarrativeStatus status, boolean pretty) {
143//    if (!x.hasAttribute("xmlns"))
144//      x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
145//    Element le = XMLUtil.getNamedChild(er, "language");
146//    String l = le == null ? null : le.getAttribute("value");
147//    if (!Utilities.noString(l)) {
148//      // use both - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues
149//      x.setAttribute("lang", l);
150//      x.setAttribute("xml:lang", l);
151//    }
152//    Element txt = XMLUtil.getNamedChild(er, "text");
153//    if (txt == null) {
154//      txt = er.getOwnerDocument().createElementNS(FormatUtilities.FHIR_NS, "text");
155//      Element n = XMLUtil.getFirstChild(er);
156//      while (n != null && (n.getNodeName().equals("id") || n.getNodeName().equals("meta") || n.getNodeName().equals("implicitRules") || n.getNodeName().equals("language")))
157//        n = XMLUtil.getNextSibling(n);
158//      if (n == null)
159//        er.appendChild(txt);
160//      else
161//        er.insertBefore(txt, n);
162//    }
163//    Element st = XMLUtil.getNamedChild(txt, "status");
164//    if (st == null) {
165//      st = er.getOwnerDocument().createElementNS(FormatUtilities.FHIR_NS, "status");
166//      Element n = XMLUtil.getFirstChild(txt);
167//      if (n == null)
168//        txt.appendChild(st);
169//      else
170//        txt.insertBefore(st, n);
171//    }
172//    st.setAttribute("value", status.toCode());
173//    Element div = XMLUtil.getNamedChild(txt, "div");
174//    if (div == null) {
175//      div = er.getOwnerDocument().createElementNS(FormatUtilities.XHTML_NS, "div");
176//      div.setAttribute("xmlns", FormatUtilities.XHTML_NS);
177//      txt.appendChild(div);
178//    }
179//    if (div.hasChildNodes())
180//      div.appendChild(er.getOwnerDocument().createElementNS(FormatUtilities.XHTML_NS, "hr"));
181//    new XhtmlComposer(XhtmlComposer.XML, pretty).compose(div, x);
182//  }
183//
184//  public void inject(org.hl7.fhir.r4b.elementmodel.Element er, XhtmlNode x, NarrativeStatus status, boolean pretty) throws IOException, FHIRException {
185//    if (!x.hasAttribute("xmlns"))
186//      x.setAttribute("xmlns", "http://www.w3.org/1999/xhtml");
187//    String l = er.getChildValue("language");
188//    if (!Utilities.noString(l)) {
189//      // use both - see https://www.w3.org/TR/i18n-html-tech-lang/#langvalues
190//      x.setAttribute("lang", l);
191//      x.setAttribute("xml:lang", l);
192//    }
193//    org.hl7.fhir.r4b.elementmodel.Element txt = er.getNamedChild("text");
194//    if (txt == null) {
195//      txt = new org.hl7.fhir.r4b.elementmodel.Element("text", er.getProperty().getChild(null, "text"));
196//      int i = 0;
197//      while (i < er.getChildren().size() && (er.getChildren().get(i).getName().equals("id") || er.getChildren().get(i).getName().equals("meta") || er.getChildren().get(i).getName().equals("implicitRules") || er.getChildren().get(i).getName().equals("language")))
198//        i++;
199//      if (i >= er.getChildren().size())
200//        er.getChildren().add(txt);
201//      else
202//        er.getChildren().add(i, txt);
203//    }
204//    org.hl7.fhir.r4b.elementmodel.Element st = txt.getNamedChild("status");
205//    if (st == null) {
206//      st = new org.hl7.fhir.r4b.elementmodel.Element("status", txt.getProperty().getChild(null, "status"));
207//      txt.getChildren().add(0, st);
208//    }
209//    st.setValue(status.toCode());
210//    org.hl7.fhir.r4b.elementmodel.Element div = txt.getNamedChild("div");
211//    if (div == null) {
212//      div = new org.hl7.fhir.r4b.elementmodel.Element("div", txt.getProperty().getChild(null, "div"));
213//      txt.getChildren().add(div);
214//      div.setValue(new XhtmlComposer(XhtmlComposer.XML, pretty).compose(x));
215//    }
216//    div.setValue(x.toString());
217//    div.setXhtml(x);
218//  }
219//
220
221  
222  public void generateResourceSummary(XhtmlNode x, ResourceWrapper res, boolean textAlready, boolean showCodeDetails, boolean canLink) throws FHIRException, UnsupportedEncodingException, IOException {
223    if (!textAlready) {
224      XhtmlNode div = res.getNarrative();
225      if (div != null) {
226        if (div.allChildrenAreText())
227          x.getChildNodes().addAll(div.getChildNodes());
228        if (div.getChildNodes().size() == 1 && div.getChildNodes().get(0).allChildrenAreText())
229          x.getChildNodes().addAll(div.getChildNodes().get(0).getChildNodes());
230      }
231      x.tx("Generated Summary: ");
232    }
233    String path = res.fhirType();
234    StructureDefinition profile = getContext().getWorker().fetchResource(StructureDefinition.class, path);
235    if (profile == null)
236      x.tx("unknown resource " +path);
237    else {
238      boolean firstElement = true;
239      boolean last = false;
240      for (PropertyWrapper p : res.children()) {
241        if (!ignoreProperty(p) && !p.getElementDefinition().getBase().getPath().startsWith("Resource.")) {
242          ElementDefinition child = getElementDefinition(profile.getSnapshot().getElement(), path+"."+p.getName(), p);
243          if (p.getValues().size() > 0 && p.getValues().get(0) != null && child != null && isPrimitive(child) && includeInSummary(child, p.getValues())) {
244            if (firstElement)
245              firstElement = false;
246            else if (last)
247              x.tx("; ");
248            boolean first = true;
249            last = false;
250            for (BaseWrapper v : p.getValues()) {
251              if (first)
252                first = false;
253              else if (last)
254                x.tx(", ");
255              last = displayLeaf(res, v, child, x, p.getName(), showCodeDetails, canLink) || last;
256            }
257          }
258        }
259      }
260    }
261  }
262
263
264  private boolean ignoreProperty(PropertyWrapper p) {
265    return Utilities.existsInList(p.getName(), "contained");
266  }
267
268  private boolean includeInSummary(ElementDefinition child, List<BaseWrapper> list) throws UnsupportedEncodingException, FHIRException, IOException {
269    if (child.getName().endsWith("active") && list != null && list.size() > 0 && "true".equals(list.get(0).getBase().primitiveValue())) {
270      return false;
271    }
272    if (child.getIsModifier())
273      return true;
274    if (child.getMustSupport())
275      return true;
276    if (child.getType().size() == 1) {
277      String t = child.getType().get(0).getWorkingCode();
278      if (t.equals("Address") || t.equals("Contact") || t.equals("Reference") || t.equals("Uri") || t.equals("Url") || t.equals("Canonical"))
279        return false;
280    }
281    return true;
282  }
283  
284  private ElementDefinition getElementDefinition(List<ElementDefinition> elements, String path, PropertyWrapper p) {
285    for (ElementDefinition element : elements)
286      if (element.getPath().equals(path))
287        return element;
288    if (path.endsWith("\"]") && p.getStructure() != null)
289      return p.getStructure().getSnapshot().getElement().get(0);
290    return null;
291  }
292
293  private void renderLeaf(ResourceWrapper res, BaseWrapper ew, ElementDefinition defn, XhtmlNode parent, XhtmlNode x, boolean title, boolean showCodeDetails, Map<String, String> displayHints, String path, int indent) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome {
294    if (ew == null)
295      return;
296
297    Base e = ew.getBase();
298
299    if (e instanceof StringType)
300      x.addText(((StringType) e).getValue());
301    else if (e instanceof CodeType)
302      x.addText(((CodeType) e).getValue());
303    else if (e instanceof IdType)
304      x.addText(((IdType) e).getValue());
305    else if (e instanceof Extension)
306      return;
307    else if (e instanceof InstantType)
308      x.addText(((InstantType) e).toHumanDisplay());
309    else if (e instanceof DateTimeType) {
310      renderDateTime(x, e);
311    } else if (e instanceof Base64BinaryType)
312      x.addText(new Base64().encodeAsString(((Base64BinaryType) e).getValue()));
313    else if (e instanceof org.hl7.fhir.r4b.model.DateType) {
314      org.hl7.fhir.r4b.model.DateType dt = ((org.hl7.fhir.r4b.model.DateType) e);
315      if (((org.hl7.fhir.r4b.model.DateType) e).hasValue()) {
316        x.addText(((org.hl7.fhir.r4b.model.DateType) e).toHumanDisplay());
317      }
318    } else if (e instanceof Enumeration) {
319      Object ev = ((Enumeration<?>) e).getValue();
320      x.addText(ev == null ? "" : ev.toString()); // todo: look up a display name if there is one
321    } else if (e instanceof BooleanType) {
322      x.addText(((BooleanType) e).getValue().toString());
323    } else if (e instanceof CodeableConcept) {
324      renderCodeableConcept(x, (CodeableConcept) e, showCodeDetails);
325    } else if (e instanceof Coding) {
326      renderCoding(x, (Coding) e, showCodeDetails);
327    } else if (e instanceof CodeableReference) {
328      renderCodeableReference(x, (CodeableReference) e, showCodeDetails);
329    } else if (e instanceof Annotation) {
330      renderAnnotation(x, (Annotation) e);
331    } else if (e instanceof Identifier) {
332      renderIdentifier(x, (Identifier) e);
333    } else if (e instanceof org.hl7.fhir.r4b.model.IntegerType) {
334      if (((org.hl7.fhir.r4b.model.IntegerType) e).hasValue()) {
335        x.addText(Integer.toString(((org.hl7.fhir.r4b.model.IntegerType) e).getValue()));
336      } else {
337        x.addText("??");
338      }
339    } else if (e instanceof org.hl7.fhir.r4b.model.Integer64Type) {
340      if (((org.hl7.fhir.r4b.model.Integer64Type) e).hasValue()) {
341        x.addText(Long.toString(((org.hl7.fhir.r4b.model.Integer64Type) e).getValue()));
342      } else {
343        x.addText("??");
344      }
345    } else if (e instanceof org.hl7.fhir.r4b.model.DecimalType) {
346      x.addText(((org.hl7.fhir.r4b.model.DecimalType) e).getValue().toString());
347    } else if (e instanceof HumanName) {
348      renderHumanName(x, (HumanName) e);
349    } else if (e instanceof SampledData) {
350      renderSampledData(x, (SampledData) e);
351    } else if (e instanceof Address) {
352      renderAddress(x, (Address) e);
353    } else if (e instanceof ContactPoint) {
354      renderContactPoint(x, (ContactPoint) e);
355    } else if (e instanceof Expression) {
356      renderExpression(x, (Expression) e);
357    } else if (e instanceof Money) {
358      renderMoney(x, (Money) e);
359    } else if (e instanceof ContactDetail) {
360      ContactDetail cd = (ContactDetail) e;
361      if (cd.hasName()) {
362        x.tx(cd.getName()+": ");
363      }
364      boolean first = true;
365      for (ContactPoint c : cd.getTelecom()) {
366        if (first) first = false; else x.tx(",");
367        renderContactPoint(x, c);
368      }
369    } else if (e instanceof UriType) {
370      renderUri(x, (UriType) e, defn.getPath(), rcontext != null && rcontext.getResourceResource() != null ? rcontext.getResourceResource().getId() : null);
371    } else if (e instanceof Timing) {
372      renderTiming(x, (Timing) e);
373    } else if (e instanceof Range) {
374      renderRange(x, (Range) e);
375    } else if (e instanceof Quantity) {
376      renderQuantity(x, (Quantity) e, showCodeDetails);
377    } else if (e instanceof Ratio) {
378      renderQuantity(x, ((Ratio) e).getNumerator(), showCodeDetails);
379      x.tx("/");
380      renderQuantity(x, ((Ratio) e).getDenominator(), showCodeDetails);
381    } else if (e instanceof Period) {
382      Period p = (Period) e;
383      renderPeriod(x, p);
384    } else if (e instanceof Reference) {
385      Reference r = (Reference) e;
386      if (r.getReference() != null && r.getReference().contains("#")) {
387        if (containedIds.contains(r.getReference().substring(1))) {
388          x.ah(r.getReference()).tx("See "+r.getReference());
389        } else {
390          // in this case, we render the resource in line
391          ResourceWrapper rw = null;
392          for (ResourceWrapper t : res.getContained()) {
393            if (r.getReference().substring(1).equals(t.getId())) {
394              rw = t;
395            }
396          }
397          if (rw == null) {
398            renderReference(res, x, r);
399          } else {
400            x.an(rw.getId());
401            ResourceRenderer rr = RendererFactory.factory(rw, context.copy().setAddGeneratedNarrativeHeader(false));
402            rr.render(parent.blockquote(), rw);
403          }
404        }
405      } else {
406        renderReference(res, x, r);
407      }
408    } else if (e instanceof Resource) {
409      return;
410    } else if (e instanceof DataRequirement) {
411      DataRequirement p = (DataRequirement) e;
412      renderDataRequirement(x, p);
413    } else if (e instanceof PrimitiveType) {
414      x.tx(((PrimitiveType) e).primitiveValue());
415    } else if (e instanceof ElementDefinition) {
416      x.tx("todo-bundle");
417    } else if (e != null && !(e instanceof Attachment) && !(e instanceof Narrative) && !(e instanceof Meta)) {
418      throw new NotImplementedException("type "+e.getClass().getName()+" not handled - should not be here");
419    }
420  }
421
422  private boolean displayLeaf(ResourceWrapper res, BaseWrapper ew, ElementDefinition defn, XhtmlNode x, String name, boolean showCodeDetails) throws FHIRException, UnsupportedEncodingException, IOException {
423    return displayLeaf(res, ew, defn, x, name, showCodeDetails, true);
424  }
425  
426  private boolean displayLeaf(ResourceWrapper res, BaseWrapper ew, ElementDefinition defn, XhtmlNode x, String name, boolean showCodeDetails, boolean allowLinks) throws FHIRException, UnsupportedEncodingException, IOException {
427    if (ew == null)
428      return false;
429    Base e = ew.getBase();
430    if (e == null)
431      return false;
432
433    Map<String, String> displayHints = readDisplayHints(defn);
434
435    if (name.endsWith("[x]"))
436      name = name.substring(0, name.length() - 3);
437
438    if (!showCodeDetails && e instanceof PrimitiveType && isDefault(displayHints, ((PrimitiveType) e)))
439      return false;
440
441    if (e instanceof StringType) {
442      x.addText(name+": "+((StringType) e).getValue());
443      return true;
444    } else if (e instanceof CodeType) {
445      x.addText(name+": "+((CodeType) e).getValue());
446      return true;
447    } else if (e instanceof IdType) {
448      x.addText(name+": "+((IdType) e).getValue());
449      return true;
450    } else if (e instanceof UriType) {
451      if (Utilities.isAbsoluteUrlLinkable(((UriType) e).getValue()) && allowLinks) {
452        x.tx(name+": ");
453        x.ah(((UriType) e).getValue()).addText(((UriType) e).getValue());
454      } else {
455        x.addText(name+": "+((UriType) e).getValue());
456      }
457      return true;
458    } else if (e instanceof DateTimeType) {
459      x.addText(name+": "+((DateTimeType) e).toHumanDisplay());
460      return true;
461    } else if (e instanceof InstantType) {
462      x.addText(name+": "+((InstantType) e).toHumanDisplay());
463      return true;
464    } else if (e instanceof Extension) {
465      //      x.tx("Extensions: todo");
466      return false;
467    } else if (e instanceof org.hl7.fhir.r4b.model.DateType) {
468      x.addText(name+": "+((org.hl7.fhir.r4b.model.DateType) e).toHumanDisplay());
469      return true;
470    } else if (e instanceof Enumeration) {
471      x.addText(((Enumeration<?>) e).getValue().toString()); // todo: look up a display name if there is one
472      return true;
473    } else if (e instanceof BooleanType) {
474      if (((BooleanType) e).hasValue()) {
475        x.addText(name);
476        x.addText(": ");
477        x.addText(((BooleanType) e).getValueAsString());
478        return true;
479      }
480    } else if (e instanceof CodeableReference) {
481      if (((CodeableReference) e).hasReference()) { 
482        Reference r = ((CodeableReference) e).getReference();
483        renderReference(res, x, r, allowLinks);
484      } else {
485        renderCodeableConcept(x, ((CodeableReference) e).getConcept(), showCodeDetails);
486      }
487      return true;
488    } else if (e instanceof CodeableConcept) {
489      renderCodeableConcept(x, (CodeableConcept) e, showCodeDetails);
490      return true;
491    } else if (e instanceof Coding) {
492      renderCoding(x, (Coding) e, showCodeDetails);
493      return true;
494    } else if (e instanceof Annotation) {
495      renderAnnotation(x, (Annotation) e, showCodeDetails);
496      return true;
497    } else if (e instanceof org.hl7.fhir.r4b.model.IntegerType) {
498      x.addText(Integer.toString(((org.hl7.fhir.r4b.model.IntegerType) e).getValue()));
499      return true;
500    } else if (e instanceof org.hl7.fhir.r4b.model.DecimalType) {
501      x.addText(((org.hl7.fhir.r4b.model.DecimalType) e).getValue().toString());
502      return true;
503    } else if (e instanceof Identifier) {
504      renderIdentifier(x, (Identifier) e);
505      return true;
506    } else if (e instanceof HumanName) {
507      renderHumanName(x, (HumanName) e);
508      return true;
509    } else if (e instanceof SampledData) {
510      renderSampledData(x, (SampledData) e);
511      return true;
512    } else if (e instanceof Address) {
513      renderAddress(x, (Address) e);
514      return true;
515    } else if (e instanceof ContactPoint) {
516      if (allowLinks) {
517        renderContactPoint(x, (ContactPoint) e);
518      } else {
519        displayContactPoint(x, (ContactPoint) e);
520      }
521      return true;
522    } else if (e instanceof Timing) {
523      renderTiming(x, (Timing) e);
524      return true;
525    } else if (e instanceof Quantity) {
526      renderQuantity(x, (Quantity) e, showCodeDetails);
527      return true;
528    } else if (e instanceof Ratio) {
529      renderQuantity(x, ((Ratio) e).getNumerator(), showCodeDetails);
530      x.tx("/");
531      renderQuantity(x, ((Ratio) e).getDenominator(), showCodeDetails);
532      return true;
533    } else if (e instanceof Period) {
534      Period p = (Period) e;
535      x.addText(name+": ");
536      x.addText(!p.hasStart() ? "?ngen-2?" : p.getStartElement().toHumanDisplay());
537      x.tx(" --> ");
538      x.addText(!p.hasEnd() ? "(ongoing)" : p.getEndElement().toHumanDisplay());
539      return true;
540    } else if (e instanceof Reference) {
541      Reference r = (Reference) e;
542      if (r.hasDisplayElement())
543        x.addText(r.getDisplay());
544      else if (r.hasReferenceElement()) {
545        ResourceWithReference tr = resolveReference(res, r.getReference());
546        x.addText(tr == null ? r.getReference() : "?ngen-3"); // getDisplayForReference(tr.getReference()));
547      } else
548        x.tx("?ngen-4?");
549      return true;
550    } else if (e instanceof Narrative) {
551      return false;
552    } else if (e instanceof Resource) {
553      return false;
554    } else if (e instanceof ContactDetail) {
555      ContactDetail cd = (ContactDetail) e;
556      if (cd.hasName()) {
557        x.tx(cd.getName()+": ");
558      }
559      boolean first = true;
560      for (ContactPoint c : cd.getTelecom()) {
561        if (first) first = false; else x.tx(",");
562        if (allowLinks) {      
563          renderContactPoint(x, c);
564        } else {
565          displayContactPoint(x, c);
566        }
567      }
568      return true;
569    } else if (e instanceof Range) {
570      return false;
571    } else if (e instanceof Meta) {
572      return false;
573    } else if (e instanceof Dosage) {
574      return false;
575    } else if (e instanceof Signature) {
576      return false;
577    } else if (e instanceof UsageContext) {
578      return false;
579    } else if (e instanceof RelatedArtifact) {
580      return false;
581    } else if (e instanceof ElementDefinition) {
582      return false;
583    } else if (e instanceof Base64BinaryType) {
584      return false;
585    } else if (!(e instanceof Attachment))
586      throw new NotImplementedException("type "+e.getClass().getName()+" not handled yet");
587    return false;
588  }
589
590
591
592  private boolean isPrimitive(ElementDefinition e) {
593    //we can tell if e is a primitive because it has types
594    if (e.getType().isEmpty()) {
595      return false;
596    }
597    if (e.getType().size() == 1 && isBase(e.getType().get(0).getWorkingCode())) {
598      return false;
599    }
600    if (e.getType().size() > 1) {
601      return true;
602    }
603    StructureDefinition sd = context.getWorker().fetchTypeDefinition(e.getTypeFirstRep().getCode());
604    if (sd != null) {
605      if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) {
606        return true;
607      }
608      if (sd.getKind() == StructureDefinitionKind.COMPLEXTYPE) {
609        if (Utilities.existsInList(e.getTypeFirstRep().getCode(), "Extension", "CodeableConcept", "Coding", "Annotation", "Identifier", "HumanName", "SampledData", 
610            "Address", "ContactPoint", "ContactDetail", "Timing", "Range", "Quantity", "Ratio", "Period", "Reference")) {
611          return true;
612        }        
613      }
614    }
615    return false;
616  }
617
618  private boolean isBase(String code) {
619    return code.equals("Element") || code.equals("BackboneElement");
620  }
621  
622  private List<ElementDefinition> getChildrenForPath(List<ElementDefinition> elements, String path) throws DefinitionException {
623    // do we need to do a name reference substitution?
624    for (ElementDefinition e : elements) {
625      if (e.getPath().equals(path) && e.hasContentReference()) {
626        String ref = e.getContentReference();
627        ElementDefinition t = null;
628        // now, resolve the name
629        for (ElementDefinition e1 : elements) {
630          if (ref.equals("#"+e1.getId()))
631            t = e1;
632        }
633        if (t == null)
634          throw new DefinitionException("Unable to resolve content reference "+ref+" trying to resolve "+path);
635        path = t.getPath();
636        break;
637      }
638    }
639
640    List<ElementDefinition> results = new ArrayList<ElementDefinition>();
641    for (ElementDefinition e : elements) {
642      if (e.getPath().startsWith(path+".") && !e.getPath().substring(path.length()+1).contains("."))
643        results.add(e);
644    }
645    return results;
646  }
647
648
649  private boolean generateByProfile(StructureDefinition profile, boolean showCodeDetails) {
650    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
651    if(context.isAddGeneratedNarrativeHeader()) {
652      x.para().b().tx("Generated Narrative"+(showCodeDetails ? " with Details" : ""));
653    }
654    try {
655      generateByProfile(rcontext.getResourceResource(), profile, rcontext.getResourceResource(), profile.getSnapshot().getElement(), profile.getSnapshot().getElement().get(0), getChildrenForPath(profile.getSnapshot().getElement(), rcontext.getResourceResource().getResourceType().toString()), x, rcontext.getResourceResource().getResourceType().toString(), showCodeDetails);
656    } catch (Exception e) {
657      e.printStackTrace();
658      x.para().b().style("color: maroon").tx("Exception generating Narrative: "+e.getMessage());
659    }
660    inject(rcontext.getResourceResource(), x,  NarrativeStatus.GENERATED);
661    return true;
662  }
663
664  private void generateByProfile(Resource res, StructureDefinition profile, Base e, List<ElementDefinition> allElements, ElementDefinition defn, List<ElementDefinition> children,  XhtmlNode x, String path, boolean showCodeDetails) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome {
665    generateByProfile(new ResourceWrapperDirect(this.context, res), profile, new BaseWrapperDirect(this.context, e), allElements, defn, children, x, path, showCodeDetails, 0);
666  }
667
668  private void generateByProfile(ResourceWrapper res, StructureDefinition profile, BaseWrapper e, List<ElementDefinition> allElements, ElementDefinition defn, List<ElementDefinition> children,  XhtmlNode x, String path, boolean showCodeDetails, int indent) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome {
669    if (children.isEmpty()) {
670      renderLeaf(res, e, defn, x, x, false, showCodeDetails, readDisplayHints(defn), path, indent);
671    } else {
672      for (PropertyWrapper p : splitExtensions(profile, e.children())) {
673        if (p.hasValues()) {
674          ElementDefinition child = getElementDefinition(children, path+"."+p.getName(), p);
675          if (child == null) {
676            child = p.getElementDefinition();
677          }
678          if (child != null) {
679            if (!child.getBase().hasPath() || !child.getBase().getPath().startsWith("Resource.")) {
680              generateElementByProfile(res, profile, allElements, x, path, showCodeDetails, indent, p, child);
681            }
682          }
683        }
684      }
685    }
686  }
687
688  public void generateElementByProfile(ResourceWrapper res, StructureDefinition profile, List<ElementDefinition> allElements, XhtmlNode x, String path,
689      boolean showCodeDetails, int indent, PropertyWrapper p, ElementDefinition child) throws UnsupportedEncodingException, IOException, EOperationOutcome {
690    Map<String, String> displayHints = readDisplayHints(child);
691    if ("DomainResource.contained".equals(child.getBase().getPath())) {
692//              if (p.getValues().size() > 0 && child != null) {
693//                for (BaseWrapper v : p.getValues()) {
694//                  x.an(v.get("id").primitiveValue());
695//                }
696//              }
697    } else if (!exemptFromRendering(child)) {
698      if (isExtension(p)) {
699        hasExtensions = true;
700      }
701      List<ElementDefinition> grandChildren = getChildrenForPath(allElements, path+"."+p.getName());
702      filterGrandChildren(grandChildren, path+"."+p.getName(), p);
703      if (p.getValues().size() > 0) {
704         if (isPrimitive(child)) {
705           XhtmlNode para = x.isPara() ? para = x : x.para();
706           String name = p.getName();
707           if (name.endsWith("[x]"))
708             name = name.substring(0, name.length() - 3);
709           if (showCodeDetails || !isDefaultValue(displayHints, p.getValues())) {
710             para.b().addText(name);
711             para.tx(": ");
712             if (renderAsList(child) && p.getValues().size() > 1) {
713               XhtmlNode list = x.ul();
714               for (BaseWrapper v : p.getValues())
715                 renderLeaf(res, v, child, x, list.li(), false, showCodeDetails, displayHints, path, indent);
716             } else {
717               boolean first = true;
718               for (BaseWrapper v : p.getValues()) {
719                 if (first) {
720                   first = false;
721                 } else {
722                   para.tx(", ");
723                 }
724                 renderLeaf(res, v, child, x, para, false, showCodeDetails, displayHints, path, indent);
725               }
726             }
727           }
728        } else if (canDoTable(path, p, grandChildren, x)) {
729          XhtmlNode xn = new XhtmlNode(NodeType.Element, getHeader());
730          xn.addText(Utilities.capitalize(Utilities.camelCase(Utilities.pluralizeMe(p.getName()))));
731          XhtmlNode tbl = new XhtmlNode(NodeType.Element, "table"); 
732          tbl.setAttribute("class", "grid");
733          XhtmlNode tr = tbl.tr();
734          tr.td().tx("-"); // work around problem with empty table rows
735          boolean add = addColumnHeadings(tr, grandChildren);          
736          for (BaseWrapper v : p.getValues()) {
737            if (v != null) {
738              tr = tbl.tr();
739              tr.td().tx("*"); // work around problem with empty table rows
740              add = addColumnValues(res, tr, grandChildren, v, showCodeDetails, displayHints, path, indent) || add;
741            }
742          }
743          if (add) {
744            x.add(xn);
745            x.add(tbl);
746          }
747        } else if (isExtension(p)) {
748          for (BaseWrapper v : p.getValues()) {
749            if (v != null) {
750              PropertyWrapper vp = v.getChildByName("value");
751              PropertyWrapper ev = v.getChildByName("extension");
752              if (vp.hasValues()) {
753                BaseWrapper vv = vp.value();
754                XhtmlNode para = x.para();
755                para.b().addText(p.getStructure().present());
756                para.tx(": ");
757                renderLeaf(res, vv, child, x, para, false, showCodeDetails, displayHints, path, indent);
758              } else if (ev.hasValues()) {
759                XhtmlNode bq = x.addTag("blockquote");                
760                bq.para().b().addText(isExtension(p) ? p.getStructure().present() : p.getName());
761                for (BaseWrapper vv : ev.getValues()) {
762                  StructureDefinition ex = context.getWorker().fetchTypeDefinition("Extension");
763                  List<ElementDefinition> children = getChildrenForPath(ex.getSnapshot().getElement(), "Extension");
764                  generateByProfile(res, ex, vv, allElements, child, children, bq, "Extension", showCodeDetails, indent+1);
765                }
766              }
767            }
768          }          
769        } else {
770          for (BaseWrapper v : p.getValues()) {
771            if (v != null) {
772              XhtmlNode bq = x.addTag("blockquote");
773              bq.para().b().addText(isExtension(p) ? p.getStructure().present() : p.getName());
774              generateByProfile(res, profile, v, allElements, child, grandChildren, bq, path+"."+p.getName(), showCodeDetails, indent+1);
775            }
776          }
777        }
778      }
779    }
780  }
781
782
783  private String getHeader() {
784    int i = 3;
785    while (i <= context.getHeaderLevelContext())
786      i++;
787    if (i > 6)
788      i = 6;
789    return "h"+Integer.toString(i);
790  }
791
792  private List<PropertyWrapper> getValues(String path, PropertyWrapper p, ElementDefinition e) {
793    List<PropertyWrapper> res = new ArrayList<PropertyWrapper>();
794    for (BaseWrapper v : p.getValues()) {
795      for (PropertyWrapper g : v.children()) {
796        if ((path+"."+p.getName()+"."+g.getName()).equals(e.getPath()))
797          res.add(p);
798      }
799    }
800    return res;
801  }
802  
803  private boolean canDoTable(String path, PropertyWrapper p, List<ElementDefinition> grandChildren, XhtmlNode x) {
804    if (isExtension(p)) {
805      return false;
806    }
807    if (x.getName().equals("p")) {
808      return false;
809    }
810
811    for (ElementDefinition e : grandChildren) {
812      List<PropertyWrapper> values = getValues(path, p, e);
813      if (values.size() > 1 || !isPrimitive(e) || !canCollapse(e))
814        return false;
815    }
816    return true;
817  }
818
819  public boolean isExtension(PropertyWrapper p) {
820    return p.getName().contains("extension[");
821  }
822
823
824  private boolean canCollapse(ElementDefinition e) {
825    // we can collapse any data type
826    return !e.getType().isEmpty();
827  }
828  private boolean exemptFromRendering(ElementDefinition child) {
829    if (child == null)
830      return false;
831    if ("Composition.subject".equals(child.getPath()))
832      return true;
833    if ("Composition.section".equals(child.getPath()))
834      return true;
835    return false;
836  }
837
838  private boolean renderAsList(ElementDefinition child) {
839    if (child.getType().size() == 1) {
840      String t = child.getType().get(0).getWorkingCode();
841      if (t.equals("Address") || t.equals("Reference"))
842        return true;
843    }
844    return false;
845  }
846
847  private boolean addColumnHeadings(XhtmlNode tr, List<ElementDefinition> grandChildren) {
848    boolean b = false;
849    for (ElementDefinition e : grandChildren) {
850      b = true;
851      tr.td().b().addText(Utilities.capitalize(tail(e.getPath())));
852    }
853    return b;
854  }
855
856  private boolean addColumnValues(ResourceWrapper res, XhtmlNode tr, List<ElementDefinition> grandChildren, BaseWrapper v, boolean showCodeDetails, Map<String, String> displayHints, String path, int indent) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome {
857    boolean b = false;
858    for (ElementDefinition e : grandChildren) {
859      PropertyWrapper p = v.getChildByName(e.getPath().substring(e.getPath().lastIndexOf(".")+1));
860      XhtmlNode td = tr.td();
861      if (p == null || p.getValues().size() == 0 || p.getValues().get(0) == null) {
862        b = true;
863        td.tx(" ");
864      } else {
865        for (BaseWrapper vv : p.getValues()) {
866          b = true;
867          td.sep(", ");
868          renderLeaf(res, vv, e, td, td, false, showCodeDetails, displayHints, path, indent);
869        }
870      }
871    }
872    return b;
873  }
874
875  private void filterGrandChildren(List<ElementDefinition> grandChildren,  String string, PropertyWrapper prop) {
876    List<ElementDefinition> toRemove = new ArrayList<ElementDefinition>();
877    toRemove.addAll(grandChildren);
878    for (BaseWrapper b : prop.getValues()) {
879      List<ElementDefinition> list = new ArrayList<ElementDefinition>();
880      for (ElementDefinition ed : toRemove) {
881        PropertyWrapper p = b.getChildByName(tail(ed.getPath()));
882        if (p != null && p.hasValues())
883          list.add(ed);
884      }
885      toRemove.removeAll(list);
886    }
887    grandChildren.removeAll(toRemove);
888  }
889
890  private List<PropertyWrapper> splitExtensions(StructureDefinition profile, List<PropertyWrapper> children) throws UnsupportedEncodingException, IOException, FHIRException {
891    List<PropertyWrapper> results = new ArrayList<PropertyWrapper>();
892    Map<String, PropertyWrapper> map = new HashMap<String, PropertyWrapper>();
893    for (PropertyWrapper p : children)
894      if (p.getName().equals("extension") || p.getName().equals("modifierExtension")) {
895        // we're going to split these up, and create a property for each url
896        if (p.hasValues()) {
897          for (BaseWrapper v : p.getValues()) {
898            Extension ex  = (Extension) v.getBase();
899            String url = ex.getUrl();
900            StructureDefinition ed = getContext().getWorker().fetchResource(StructureDefinition.class, url);
901            if (ed == null) {
902              if (xverManager == null) {
903                xverManager = new XVerExtensionManager(context.getWorker());
904              }
905              if (xverManager.matchingUrl(url) && xverManager.status(url) == XVerExtensionStatus.Valid) {
906                ed = xverManager.makeDefinition(url);
907                getContext().getWorker().generateSnapshot(ed);
908                getContext().getWorker().cacheResource(ed);
909              }
910            }
911            if (p.getName().equals("modifierExtension") && ed == null) {
912              throw new DefinitionException("Unknown modifier extension "+url);
913            }
914            PropertyWrapper pe = map.get(p.getName()+"["+url+"]");
915            if (pe == null) {
916              if (ed == null) {
917                if (url.startsWith("http://hl7.org/fhir") && !url.startsWith("http://hl7.org/fhir/us")) {
918                  throw new DefinitionException("unknown extension "+url);
919                }
920                // System.out.println("unknown extension "+url);
921                pe = new PropertyWrapperDirect(this.context, new Property(p.getName()+"["+url+"]", p.getTypeCode(), p.getDefinition(), p.getMinCardinality(), p.getMaxCardinality(), ex), null);
922              } else {
923                ElementDefinition def = ed.getSnapshot().getElement().get(0);
924                pe = new PropertyWrapperDirect(this.context, new Property(p.getName()+"["+url+"]", "Extension", def.getDefinition(), def.getMin(), def.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(def.getMax()), ex), ed.getSnapshot().getElementFirstRep());
925                ((PropertyWrapperDirect) pe).getWrapped().setStructure(ed);
926              }
927              results.add(pe);
928            } else
929              pe.getValues().add(v);
930          }
931        }
932      } else
933        results.add(p);
934    return results;
935  }
936
937
938  private Map<String, String> readDisplayHints(ElementDefinition defn) throws DefinitionException {
939    Map<String, String> hints = new HashMap<String, String>();
940    if (defn != null) {
941      String displayHint = ToolingExtensions.getDisplayHint(defn);
942      if (!Utilities.noString(displayHint)) {
943        String[] list = displayHint.split(";");
944        for (String item : list) {
945          String[] parts = item.split(":");
946          if (parts.length == 1) {
947            hints.put("value", parts[0].trim());            
948          } else {
949            if (parts.length != 2) {
950              throw new DefinitionException("error reading display hint: '"+displayHint+"'");
951            }
952            hints.put(parts[0].trim(), parts[1].trim());
953          }
954        }
955      }
956    }
957    return hints;
958  }
959
960  @SuppressWarnings("rawtypes")
961  private boolean isDefaultValue(Map<String, String> displayHints, List<BaseWrapper> list) throws UnsupportedEncodingException, IOException, FHIRException {
962    if (list.size() != 1)
963      return false;
964    if (list.get(0).getBase() instanceof PrimitiveType)
965      return isDefault(displayHints, (PrimitiveType) list.get(0).getBase());
966    else
967      return false;
968  }
969
970  private boolean isDefault(Map<String, String> displayHints, PrimitiveType primitiveType) {
971    String v = primitiveType.asStringValue();
972    if (!Utilities.noString(v) && displayHints.containsKey("default") && v.equals(displayHints.get("default")))
973      return true;
974    return false;
975  }
976
977
978  protected String tail(String path) {
979    return path.substring(path.lastIndexOf(".")+1);
980  }
981
982  public boolean canRender(Resource resource) {
983    return context.getWorker().getResourceNames().contains(resource.fhirType());
984  }
985
986}