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.hl7.fhir.exceptions.DefinitionException;
013import org.hl7.fhir.exceptions.FHIRException;
014import org.hl7.fhir.exceptions.FHIRFormatError;
015import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.SourcedChildDefinitions;
016import org.hl7.fhir.r5.context.ContextUtilities;
017import org.hl7.fhir.r5.model.ElementDefinition;
018import org.hl7.fhir.r5.model.Resource;
019import org.hl7.fhir.r5.model.StructureDefinition;
020import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
021import org.hl7.fhir.r5.renderers.utils.RenderingContext;
022import org.hl7.fhir.r5.renderers.utils.ResourceWrapper;
023import org.hl7.fhir.r5.renderers.utils.ResourceWrapper.NamedResourceWrapperList;
024import org.hl7.fhir.r5.utils.EOperationOutcome;
025import org.hl7.fhir.r5.utils.ToolingExtensions;
026import org.hl7.fhir.r5.utils.XVerExtensionManager;
027import org.hl7.fhir.r5.utils.XVerExtensionManager.XVerExtensionStatus;
028import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
029import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
030import org.hl7.fhir.utilities.Utilities;
031import org.hl7.fhir.utilities.xhtml.NodeType;
032import org.hl7.fhir.utilities.xhtml.XhtmlNode;
033
034@MarkedToMoveToAdjunctPackage
035public class ProfileDrivenRenderer extends ResourceRenderer {
036
037  private Set<String> containedIds = new HashSet<>();
038  
039  public ProfileDrivenRenderer(RenderingContext context) {
040    super(context);
041  }
042
043  @Override
044  public void buildNarrative(RenderingStatus status, XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException {
045    renderResourceTechDetails(r, x);
046    try {
047      StructureDefinition sd = context.getContext().fetchTypeDefinition(r.fhirType());
048      if (sd == null) {
049        throw new FHIRException(context.formatPhrase(RenderingContext.PROF_DRIV_FEXCP, r.fhirType())+" ");
050      } else {
051        ElementDefinition ed = sd.getSnapshot().getElement().get(0);
052        containedIds.clear();
053        generateByProfile(status, r, sd, r, ed, context.getProfileUtilities().getChildList(sd, ed), x, r.fhirType(), context.isTechnicalMode(), 0);
054      }
055    } catch (Exception e) {
056      if (DEBUG) {
057        System.out.println(context.formatPhrase(RenderingContext.PROF_DRIV_ERR_GEN_NARR) +r.fhirType()+"/"+r.getId()+": "+e.getMessage());
058        e.printStackTrace();
059      }
060      x.para().b().style("color: maroon").tx(context.formatPhrase(RenderingContext.PROF_DRIV_EXCP, e.getMessage())+" ");
061    }
062  }
063
064  
065  @Override
066  public String buildSummary(ResourceWrapper res) throws UnsupportedEncodingException, IOException {
067    StructureDefinition profile = getContext().getWorker().fetchTypeDefinition(res.fhirType());
068    if (profile == null)
069      return "unknown resource type " +res.fhirType();
070    else {
071      List<ResourceWrapper> children = res.children();
072      ContextUtilities cu = res.getContextUtilities();
073      for (ResourceWrapper p : children) {
074        if (p.name().equals("title") && cu.isDatatype(p.fhirType()) && !p.isEmpty()) {
075          return res.fhirType()+" "+ displayDataType(p);
076        }
077      }
078      for (ResourceWrapper p : children) {
079        if (p.name().equals("name") && cu.isDatatype(p.fhirType()) && !p.isEmpty()) {
080          return res.fhirType()+" "+ displayDataType(p);
081        }
082      }
083      for (ResourceWrapper p : children) {
084        if (p.name().equals("code") && cu.isDatatype(p.fhirType()) && !p.isEmpty()) {
085          return res.fhirType()+" "+ displayDataType(p);
086        }
087      }
088      switch (res.fhirType()) {
089      case "Binary" : return res.fhirType()+": "+res.primitiveValue("contentType")+" ("+res.primitiveValue("data").length()+" bytes base64)";
090      }
091      return generateResourceSummary(res, profile, profile.getSnapshot().getElementFirstRep(), false, false);
092    }
093  }
094
095  public String generateResourceSummary(ResourceWrapper res, StructureDefinition sd, ElementDefinition ed, boolean showCodeDetails, boolean canLink) throws FHIRException, UnsupportedEncodingException, IOException {
096    if (sd == null)
097      return "unknown resource " +res.fhirType();
098    else {
099      SourcedChildDefinitions childDefs = context.getProfileUtilities().getChildMap(sd, ed, true);
100      CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder("; ");
101      for (NamedResourceWrapperList p : res.childrenInGroups()) {
102        ElementDefinition pDefn = getElementDefinition(childDefs, p); 
103        if (pDefn != null && !ignoreProperty(p) && !pDefn.getBase().getPath().startsWith("Resource.")) {
104          if (p.getValues().size() > 0 && p.getValues().get(0) != null && pDefn != null && isSimple(pDefn) && includeInSummary(pDefn, p)) {
105            CommaSeparatedStringBuilder b2 = new CommaSeparatedStringBuilder(",");
106            for (ResourceWrapper v : p.getValues()) {
107              b2.append(displayDataType(v));
108            }
109            b.append(formatPhrase(RenderingContext.PROF_DRIV_SUMM_PROP, labelForElement(pDefn), b2.toString()));
110          }
111        }
112      }
113      if (b.length() == 0) {
114        return formatPhrase(RenderingContext.PROF_DRIV_SUMM_NONE, res.fhirType());        
115      } else {
116        return formatPhrase(RenderingContext.PROF_DRIV_SUMM, res.fhirType(), b.toString());
117      }
118    }
119  }
120  
121  private String labelForElement(ElementDefinition pDefn) {
122    return pDefn.getName();
123  }
124
125  private ElementDefinition getElementDefinition(SourcedChildDefinitions childDefs, NamedResourceWrapperList p) {
126    for (ElementDefinition ed : childDefs.getList()) {
127      if (ed.getName().equals(p.getName())) {
128        return ed;
129      }
130    }
131    return null;
132  }
133
134  private boolean ignoreProperty(NamedResourceWrapperList p) {
135    return Utilities.existsInList(p.getName(), "contained");
136  }
137
138  private boolean includeInSummary(ElementDefinition child, NamedResourceWrapperList list) throws UnsupportedEncodingException, FHIRException, IOException {
139    if (child.getName().endsWith("active") && list != null && list.getValues().size() > 0 && "true".equals(list.getValues().get(0).primitiveValue())) {
140      return false;
141    }
142    if (child.getIsModifier())
143      return true;
144    if (child.getMustSupport())
145      return true;
146    if (child.getType().size() == 1) {
147      String t = child.getType().get(0).getWorkingCode();
148      if (t.equals("Address") || t.equals("Contact") || t.equals("Reference") || t.equals("Uri") || t.equals("Url") || t.equals("Canonical"))
149        return false;
150    }
151    return true;
152  }
153  
154  private ElementDefinition getElementDefinition(List<ElementDefinition> elements, String path) {
155    for (ElementDefinition element : elements)
156      if (element.getPath().equals(path))
157        return element;
158    return null;
159  }
160
161  private void renderLeaf(RenderingStatus status, ResourceWrapper res, ResourceWrapper ew, StructureDefinition sd, ElementDefinition defn, XhtmlNode parent, XhtmlNode x, boolean title, boolean showCodeDetails, Map<String, String> displayHints, int indent) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome {
162    if (ew == null)
163      return;
164
165    if (context.isShowComments()) {
166      x = renderCommentsSpan(x, ew);
167    }
168    if (Utilities.existsInList(ew.fhirType(), "Extension") || ew.isResource()) {
169      return;
170    } else if (ew.fhirType().equals("ElementDefinition")) {
171      x.tx("todo-bundle");
172    } else if (!renderDataType(status, parent, x, ew)) {
173      // well, we have a cell (x) to render this thing, whatever it is
174      // it's not a data type for which we have a built rendering, so we're going to get a list of it's renderable datatype properties, and render them in a list
175      // SourcedChildDefinitions childDefs = context.getProfileUtilities().getChildMap(sd, defn);
176      boolean first = true;
177      x.tx(" (");
178      for (ResourceWrapper child : ew.children()) {
179//        ElementDefinition childDefn = getElementDefinition(childDefs.getList(), child.name());
180        if (child != null && !"Extension".equals(child.fhirType()) && canRenderDataType(child.fhirType())) {
181          if (first) {
182            first = false;
183          } else {
184            x.tx("; ");
185          }
186          x.tx(context.formatMessage(RenderingContext.GENERAL_DATA_DISPLAY_PROPERTY, child.name(), displayDataType(child)));          
187        }
188      }
189      x.tx(")");
190    }    
191  }
192
193  private XhtmlNode renderCommentsSpan(XhtmlNode x, ResourceWrapper e) {
194    if (e.hasFormatComment()) {      
195      return x.span(null, CommaSeparatedStringBuilder.join("&#10;", e.getFormatCommentsPre()));
196    } else {
197      return x;
198    }
199  }
200  
201//  private boolean displayLeaf(ResourceWrapper res, ResourceWrapper ew, ElementDefinition defn, XhtmlNode x, String name, boolean showCodeDetails, boolean allowLinks) throws FHIRException, UnsupportedEncodingException, IOException {
202//    if (ew == null)
203//      return false;
204//
205//    Map<String, String> displayHints = readDisplayHints(defn);
206//
207//    if (name.endsWith("[x]"))
208//      name = name.substring(0, name.length() - 3);
209//
210//    if (!showCodeDetails && ew.isPrimitive() && isDefault(displayHints, ew)) {
211//      return false;
212//    } else if (Utilities.existsInList(ew.fhirType(), "Extension")) {
213//      return false;
214//    } else {
215//      x.addText(name+": "+ displayDataType(ew));
216//      return true;
217//    }
218//  }
219
220
221
222  private boolean isSimple(ElementDefinition e) {
223    //we can tell if e is a primitive because it has types
224    if (e.getType().isEmpty()) {
225      return false;
226    }
227    if (e.getType().size() == 1 && isBase(e.getType().get(0).getWorkingCode())) {
228      return false;
229    }
230    if (e.getType().size() > 1) {
231      return true;
232    }
233    StructureDefinition sd = context.getWorker().fetchTypeDefinition(e.getTypeFirstRep().getCode());
234    if (sd != null) {
235      if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) {
236        return true;
237      }
238      if (sd.getKind() == StructureDefinitionKind.COMPLEXTYPE) {
239        if (Utilities.existsInList(e.getTypeFirstRep().getCode(), "Extension", "CodeableConcept", "Coding", "Annotation", "Identifier", "HumanName", "SampledData", 
240            "Address", "ContactPoint", "ContactDetail", "Timing", "Range", "Quantity", "Ratio", "Period", "Reference")) {
241          return true;
242        }        
243      }
244    }
245    return false;
246  }
247
248  private boolean isBase(String code) {
249    return code != null && (code.equals("Element") || code.equals("BackboneElement"));
250  }
251  
252  private SourcedChildDefinitions getChildrenForPath(StructureDefinition profile, String path) throws DefinitionException {
253    var elements = profile.getSnapshot().getElement();
254    // do we need to do a name reference substitution?
255    for (ElementDefinition e : elements) {
256      if (e.getPath().equals(path) && e.hasContentReference()) {
257        String ref = e.getContentReference();
258        ElementDefinition t = null;
259        // now, resolve the name
260        for (ElementDefinition e1 : elements) {
261          if (ref.equals("#"+e1.getId()))
262            t = e1;
263        }
264        if (t == null)
265          throw new DefinitionException("Unable to resolve content reference "+ref+" trying to resolve "+path);
266        path = t.getPath();
267        break;
268      }
269    }
270
271    ElementDefinition t = null;
272    List<ElementDefinition> results = new ArrayList<ElementDefinition>();
273    for (ElementDefinition e : elements) {
274      if (e.getPath().equals(path)) {
275        t = e; 
276      }
277      if (e.getPath().startsWith(path+".") && !e.getPath().substring(path.length()+1).contains("."))
278        results.add(e);
279    }
280    if (results.isEmpty() && t != null && t.getType().size() == 1) {
281       StructureDefinition tsd = context.getWorker().fetchTypeDefinition(t.getTypeFirstRep().getWorkingCode());
282       return getChildrenForPath(tsd, tsd.getType());
283    }
284    return new SourcedChildDefinitions(profile, results, path);
285  }
286
287  private void generateByProfile(RenderingStatus status, ResourceWrapper res, StructureDefinition profile, ResourceWrapper e, ElementDefinition defn, List<ElementDefinition> children,  XhtmlNode x, String path, boolean showCodeDetails, int indent) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome {
288    if (children.isEmpty()) {
289      StructureDefinition sdt = context.getWorker().fetchTypeDefinition(e.fhirType());
290      if (sdt != null && (sdt.getKind() == StructureDefinitionKind.COMPLEXTYPE || sdt.getKind() == StructureDefinitionKind.PRIMITIVETYPE)) {
291        renderLeaf(status, res, e, profile, defn, x, x, false, showCodeDetails, readDisplayHints(defn), indent);
292      } else {
293        // we don't have anything to render?
294      }
295    } else {
296      List<NamedResourceWrapperList> pl = splitExtensions(profile, e.childrenInGroups());
297      for (NamedResourceWrapperList p : pl) {
298        generateForProperty(status, res, profile, children, x, path, showCodeDetails, indent, false, p);
299      }
300      for (NamedResourceWrapperList p : pl) {
301        generateForProperty(status, res, profile, children, x, path, showCodeDetails, indent, true, p);
302      }
303    }
304  }
305
306  private void generateForProperty(RenderingStatus status, ResourceWrapper res, StructureDefinition profile,
307      List<ElementDefinition> children, XhtmlNode x, String path,
308      boolean showCodeDetails, int indent, boolean round2, NamedResourceWrapperList p)
309      throws UnsupportedEncodingException, IOException, EOperationOutcome {
310    if (!p.getValues().isEmpty()) {
311      ElementDefinition child = getElementDefinition(children, path+"."+p.getName());
312      if (child != null) {
313        if (!child.getBase().hasPath() || !child.getBase().getPath().startsWith("Resource.")) {
314          generateElementByProfile(status, res, profile, x, path, showCodeDetails, indent, p, child, round2);
315        }
316      }
317    }
318  }
319
320  public void generateElementByProfile(RenderingStatus status, ResourceWrapper res, StructureDefinition profile, XhtmlNode x, String path,
321      boolean showCodeDetails, int indent, NamedResourceWrapperList p, ElementDefinition child, boolean round2) throws UnsupportedEncodingException, IOException, EOperationOutcome {
322    Map<String, String> displayHints = readDisplayHints(child);
323    if ("DomainResource.contained".equals(child.getBase().getPath())) {
324      if (round2) {
325        for (ResourceWrapper v : p.getValues()) {
326          RenderingContext ctxt = context.forContained();
327          if (v.getResourceWrapper() != null && !RendererFactory.hasSpecificRenderer(v.fhirType())) {
328            x.hr();
329            ResourceRenderer rnd = RendererFactory.factory(v.fhirType(), ctxt);
330            rnd.buildNarrative(status, x.blockquote(), v);
331          }
332        }
333      }
334    } else if (!round2 && !exemptFromRendering(child)) {
335      boolean isExt = isExtension(p);
336      if (isExt) {
337        status.setExtensions(true);
338      }
339      SourcedChildDefinitions grandChildren = getChildrenForPath(profile, path+"."+p.getName());
340      filterGrandChildren(grandChildren.getList(), path+"."+p.getName(), p);
341      if (p.getValues().size() > 0) {
342         if (isSimple(child) && !isExt) {
343           XhtmlNode para = x.isPara() ? para = x : x.para();
344           String name = p.getName();
345           if (name.endsWith("[x]"))
346             name = name.substring(0, name.length() - 3);
347           if (showCodeDetails || !isDefaultValue(displayHints, p.getValues())) {
348
349             para.b().addText(name);
350             para.tx(": ");
351             if (renderAsList(child) && p.getValues().size() > 1) {
352               XhtmlNode list = x.ul();
353               for (ResourceWrapper v : p.getValues())
354                 renderLeaf(status, res, v, profile, child, x, list.li(), false, showCodeDetails, displayHints, indent);
355             } else {
356               boolean first = true;
357               for (ResourceWrapper v : p.getValues()) {
358                 if (first) {
359                   first = false;
360                 } else {
361                   para.tx(", ");
362                 }
363                 renderLeaf(status, res, v, profile, child, x, para, false, showCodeDetails, displayHints, indent);
364               }
365             }
366           }
367        } else if (canDoTable(path, p, grandChildren.getList(), x)) {
368          XhtmlNode xn = new XhtmlNode(NodeType.Element, getHeader());
369          xn.addText(Utilities.capitalize(Utilities.camelCase(Utilities.pluralizeMe(p.getName()))));
370          XhtmlNode tbl = new XhtmlNode(NodeType.Element, "table"); 
371          tbl.setAttribute("class", "grid");
372          XhtmlNode tr = tbl.tr();
373          tr.td().style("display: none").tx("-"); // work around problem with empty table rows
374          boolean add = addColumnHeadings(tr, grandChildren.getList());          
375          for (ResourceWrapper v : p.getValues()) {
376            if (v != null) {
377              tr = tbl.tr();
378              tr.td().style("display: none").tx("*"); // work around problem with empty table rows
379              add = addColumnValues(status, res, tr, profile, grandChildren.getList(), v, showCodeDetails, displayHints, indent) || add;
380            }
381          }
382          if (add) {
383            x.add(xn);
384            x.add(tbl);
385          }
386        } else if (isExtension(p)) {
387          StructureDefinition sd = context.getContext().fetchResource(StructureDefinition.class, p.getUrl());          
388          for (ResourceWrapper v : p.getValues()) {
389            if (v != null) {
390              ResourceWrapper vp = v.child("value");
391              List<ResourceWrapper> ev = v.children("extension");
392              if (vp != null) {
393                XhtmlNode para = x.para();
394                para.b().addText(labelforExtension(sd, p.getUrl()));
395                para.tx(": ");
396                renderLeaf(status, res, vp, profile, child, x, para, false, showCodeDetails, displayHints, indent);
397              } else if (!ev.isEmpty()) {
398                XhtmlNode bq = x.addTag("blockquote");  
399                bq.para().b().addText(labelforExtension(sd, p.getUrl()));
400                // what happens now depends. If all the children are simple extensions, they'll be rendered as properties 
401                boolean allSimple = true;
402                for (ResourceWrapper vv : ev) {
403                  if (!vv.has("value")) {
404                    allSimple = false;
405                  }
406                }
407                if (allSimple) {
408                  XhtmlNode ul = bq.ul();
409                  for (ResourceWrapper vv : ev) {
410                    XhtmlNode li = ul.li();
411                    li.tx(labelForSubExtension(vv.primitiveValue("url"), sd));
412                    li.tx(": ");
413                    renderLeaf(status, res, vv.child("value"), sd, child, x, li, isExt, showCodeDetails, displayHints, indent);
414                  }
415                } else {
416                  for (ResourceWrapper vv : ev) {
417                    StructureDefinition ex = context.getWorker().fetchTypeDefinition("Extension");
418                    SourcedChildDefinitions children = getChildrenForPath(ex, "Extension");
419                    generateByProfile(status, res, ex, vv, child, children.getList(), bq, "Extension", showCodeDetails, indent+1);
420                  }
421                }
422              }
423            }
424          }          
425        } else {
426          for (ResourceWrapper v : p.getValues()) {
427            if (v != null) {
428              XhtmlNode bq = x.addTag("blockquote");
429              bq.para().b().addText(p.getName());
430              generateByProfile(status, res, grandChildren.getSource(), v, child, grandChildren.getList(), bq, grandChildren.getPath(), showCodeDetails, indent+1);
431            }
432          }
433        }
434      }
435    }
436  }
437
438//
439//  private String getGrandChildBase(List<ElementDefinition> grandChildren) {
440//    if (grandChildren == null || grandChildren.isEmpty()) {
441//      return null;
442//    }
443//    String[] path = grandChildren.get(0).getPath().split("\\.");
444//    for (int i = 1; i < grandChildren.size(); i++) {
445//      path = getSharedString(path, grandChildren.get(1).getPath().split("\\."));
446//    }
447//    return CommaSeparatedStringBuilder.join(".", path);
448//  }
449//
450//  private String[] getSharedString(String[] path, String[] path2) {
451//    int m = -1;
452//    for (int i = 0; i < Integer.min(path.length, path2.length); i++) {
453//      if (path[i].equals(path2[i])) {
454//        m = i;
455//      } else {
456//        break;
457//      }
458//    }
459//    return m == -1 ? new String[0] : Arrays.copyOfRange(path, 0, m+1);
460//  }
461
462  private String labelForSubExtension(String url, StructureDefinition sd) {  
463    return url;
464  }
465
466  private String labelforExtension(StructureDefinition sd, String url) {
467    if (sd == null) {
468      return tail(url);
469    } else {
470      return sd.present();
471    }
472  }
473
474  private String getHeader() {
475    int i = 3;
476    while (i <= context.getHeaderLevelContext())
477      i++;
478    if (i > 6)
479      i = 6;
480    return "h"+Integer.toString(i);
481  }
482
483  private List<ResourceWrapper> getValues(String path, NamedResourceWrapperList p, ElementDefinition e) {
484    List<ResourceWrapper> res = new ArrayList<ResourceWrapper>();
485    for (ResourceWrapper v : p.getValues()) {
486      for (ResourceWrapper g : v.children()) {
487        if ((path+"."+p.getName()+"."+g.name()).equals(e.getPath()))
488          res.add(v);
489      }
490    }
491    return res;
492  }
493  
494  private boolean canDoTable(String path, NamedResourceWrapperList p, List<ElementDefinition> grandChildren, XhtmlNode x) {
495    if (isExtension(p)) {
496      return false;
497    }
498    if (x.getName().equals("p")) {
499      return false;
500    }
501    
502    if (grandChildren.size() == 0) {
503      return false;
504    }
505
506    for (ElementDefinition e : grandChildren) {
507      List<ResourceWrapper> values = getValues(path, p, e);
508      if (values.size() > 1 || !isSimple(e) || !canCollapse(e))
509        return false;
510    }
511    return true;
512  }
513
514  public boolean isExtension(NamedResourceWrapperList p) {
515    return p.getUrl() != null;
516  }
517
518
519  private boolean canCollapse(ElementDefinition e) {
520    // we can collapse any data type
521    return !e.getType().isEmpty();
522  }
523  private boolean exemptFromRendering(ElementDefinition child) {
524    if (child == null)
525      return false;
526    if ("DomainResource.text".equals(child.getBase().getPath())) {
527      return true;
528    }
529    if ("Composition.subject".equals(child.getPath())) {
530      return true;
531    }
532    if ("Composition.section".equals(child.getPath())) {
533      return true;
534    }
535    return false;
536  }
537
538  private boolean renderAsList(ElementDefinition child) {
539    if (child.getType().size() == 1) {
540      String t = child.getType().get(0).getWorkingCode();
541      if (t.equals("Address") || t.equals("Reference"))
542        return true;
543    }
544    return false;
545  }
546
547  private boolean addColumnHeadings(XhtmlNode tr, List<ElementDefinition> grandChildren) {
548    boolean b = false;
549    for (ElementDefinition e : grandChildren) {
550      b = true;
551      tr.td().b().addText(Utilities.capitalize(tail(e.getPath())));
552    }
553    return b;
554  }
555
556  private boolean addColumnValues(RenderingStatus status, ResourceWrapper res, XhtmlNode tr, StructureDefinition profile, List<ElementDefinition> grandChildren, ResourceWrapper v, boolean showCodeDetails, Map<String, String> displayHints, int indent) throws FHIRException, UnsupportedEncodingException, IOException, EOperationOutcome {
557    boolean b = false;
558    for (ElementDefinition e : grandChildren) {
559      List<ResourceWrapper> p = v.children(e.getPath().substring(e.getPath().lastIndexOf(".")+1));
560      XhtmlNode td = tr.td();
561      if (p == null || p.size() == 0) {
562        b = true;
563        td.tx(" ");
564      } else {
565        for (ResourceWrapper vv : p) {
566          b = true;
567          td.sep(", ");
568          renderLeaf(status, res, vv, profile, e, td, td, false, showCodeDetails, displayHints, indent);
569        }
570      }
571    }
572    return b;
573  }
574
575  private void filterGrandChildren(List<ElementDefinition> grandChildren,  String string, NamedResourceWrapperList prop) {
576    List<ElementDefinition> toRemove = new ArrayList<ElementDefinition>();
577    toRemove.addAll(grandChildren);
578    for (ResourceWrapper b : prop.getValues()) {
579      List<ElementDefinition> list = new ArrayList<ElementDefinition>();
580      for (ElementDefinition ed : toRemove) {
581        List<ResourceWrapper> p = b.children(tail(ed.getPath()));
582        if (p != null && !p.isEmpty())
583          list.add(ed);
584      }
585      toRemove.removeAll(list);
586    }
587    grandChildren.removeAll(toRemove);
588  }
589
590  private List<NamedResourceWrapperList> splitExtensions(StructureDefinition profile, List<NamedResourceWrapperList> children) throws UnsupportedEncodingException, IOException, FHIRException {
591    List<NamedResourceWrapperList> results = new ArrayList<NamedResourceWrapperList>();
592    for (NamedResourceWrapperList p : children) {
593      if (p.getName().equals("extension") || p.getName().equals("modifierExtension")) {
594        // we're going to split these up, and create a property for each url
595        for (ResourceWrapper v : p.getValues()) {
596          String url = v.primitiveValue("url");
597          if (url != null) {
598            // 1. check extension is valid
599            StructureDefinition ed = getContext().getWorker().fetchResource(StructureDefinition.class, url);
600            if (ed == null) {
601              if (xverManager == null) {
602                xverManager = new XVerExtensionManager(context.getWorker());
603              }
604              if (xverManager.matchingUrl(url) && xverManager.status(url) == XVerExtensionStatus.Valid) {
605                ed = xverManager.makeDefinition(url);
606                new ContextUtilities(getContext().getWorker()).generateSnapshot(ed);
607                getContext().getWorker().cacheResource(ed);
608              } 
609            }
610            if (p.getName().equals("modifierExtension") && ed == null) {
611              throw new DefinitionException("Unknown modifier extension "+url);
612            } else {
613              // nothing
614            }
615
616            // 2. Park it
617            NamedResourceWrapperList nl = null;
618            for (NamedResourceWrapperList t : results) {
619              if (t.getUrl() != null && t.getUrl().equals(url)) {
620                nl = t;
621              }
622            }
623            if (nl == null) {
624              nl = new NamedResourceWrapperList(p.getName(), url);
625              results.add(nl);
626            }
627            nl.getValues().add(v);
628          }
629        }          
630      } else {
631        results.add(p);
632      }
633    }
634    return results;
635  }
636
637
638  private Map<String, String> readDisplayHints(ElementDefinition defn) throws DefinitionException {
639    Map<String, String> hints = new HashMap<String, String>();
640    if (defn != null) {
641      String displayHint = ToolingExtensions.getDisplayHint(defn);
642      if (!Utilities.noString(displayHint)) {
643        String[] list = displayHint.split(";");
644        for (String item : list) {
645          String[] parts = item.split(":");
646          if (parts.length == 1) {
647            hints.put("value", parts[0].trim());            
648          } else {
649            if (parts.length != 2) {
650              throw new DefinitionException("error reading display hint: '"+displayHint+"'");
651            }
652            hints.put(parts[0].trim(), parts[1].trim());
653          }
654        }
655      }
656    }
657    return hints;
658  }
659
660  @SuppressWarnings("rawtypes")
661  private boolean isDefaultValue(Map<String, String> displayHints, List<ResourceWrapper> list) throws UnsupportedEncodingException, IOException, FHIRException {
662    if (list.size() != 1)
663      return false;
664    if (list.get(0).isPrimitive())
665      return isDefault(displayHints, list.get(0));
666    else
667      return false;
668  }
669
670  private boolean isDefault(Map<String, String> displayHints, ResourceWrapper primitiveType) {
671    String v = primitiveType.primitiveValue();
672    if (!Utilities.noString(v) && displayHints.containsKey("default") && v.equals(displayHints.get("default")))
673      return true;
674    return false;
675  }
676
677
678  protected String tail(String path) {
679    return path.substring(path.lastIndexOf(".")+1);
680  }
681
682  protected String utail(String path) {
683    return path.contains("/") ? path.substring(path.lastIndexOf("/")+1) : path;
684  }
685
686  public boolean canRender(Resource resource) {
687    return context.getWorker().getResourceNames().contains(resource.fhirType());
688  }
689
690  public RendererType getRendererType() {
691    return RendererType.PROFILE;
692  }
693
694}