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