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