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