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