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