001package org.hl7.fhir.r5.renderers;
002
003import java.io.IOException;
004import java.io.UnsupportedEncodingException;
005import java.util.ArrayList;
006import java.util.HashSet;
007import java.util.List;
008import java.util.Set;
009
010import org.apache.commons.lang3.StringUtils;
011import org.hl7.fhir.exceptions.DefinitionException;
012import org.hl7.fhir.exceptions.FHIRException;
013import org.hl7.fhir.exceptions.FHIRFormatError;
014import org.hl7.fhir.r5.conformance.AdditionalBindingsRenderer;
015import org.hl7.fhir.r5.conformance.profile.BindingResolution;
016import org.hl7.fhir.r5.conformance.profile.ProfileKnowledgeProvider;
017import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
018import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.ElementChoiceGroup;
019import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.ExtensionContext;
020import org.hl7.fhir.r5.context.IWorkerContext;
021import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult;
022import org.hl7.fhir.r5.formats.IParser;
023import org.hl7.fhir.r5.formats.JsonParser;
024import org.hl7.fhir.r5.model.ActorDefinition;
025import org.hl7.fhir.r5.model.Base;
026import org.hl7.fhir.r5.model.CanonicalType;
027import org.hl7.fhir.r5.model.CodeType;
028import org.hl7.fhir.r5.model.CodeableConcept;
029import org.hl7.fhir.r5.model.Coding;
030import org.hl7.fhir.r5.model.DataType;
031import org.hl7.fhir.r5.model.Element;
032import org.hl7.fhir.r5.model.ElementDefinition;
033import org.hl7.fhir.r5.model.ElementDefinition.AdditionalBindingPurposeVS;
034import org.hl7.fhir.r5.model.ElementDefinition.AggregationMode;
035import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType;
036import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingAdditionalComponent;
037import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
038import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent;
039import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionExampleComponent;
040import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent;
041import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionObligationComponent;
042import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent;
043import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent;
044import org.hl7.fhir.r5.model.ElementDefinition.PropertyRepresentation;
045import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules;
046import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
047import org.hl7.fhir.r5.model.Enumeration;
048import org.hl7.fhir.r5.model.Extension;
049import org.hl7.fhir.r5.model.IdType;
050import org.hl7.fhir.r5.model.IntegerType;
051import org.hl7.fhir.r5.model.PrimitiveType;
052import org.hl7.fhir.r5.model.Quantity;
053import org.hl7.fhir.r5.model.Resource;
054import org.hl7.fhir.r5.model.StringType;
055import org.hl7.fhir.r5.model.StructureDefinition;
056import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
057import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionMappingComponent;
058import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
059import org.hl7.fhir.r5.model.UriType;
060import org.hl7.fhir.r5.model.UsageContext;
061import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper;
062import org.hl7.fhir.r5.renderers.CodeResolver.CodeResolution;
063import org.hl7.fhir.r5.renderers.utils.RenderingContext;
064import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules;
065import org.hl7.fhir.r5.renderers.utils.RenderingContext.KnownLinkType;
066import org.hl7.fhir.r5.renderers.utils.RenderingContext.StructureDefinitionRendererMode;
067import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext;
068import org.hl7.fhir.r5.utils.PublicationHacker;
069import org.hl7.fhir.r5.utils.ToolingExtensions;
070import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
071import org.hl7.fhir.utilities.MarkDownProcessor;
072import org.hl7.fhir.utilities.StandardsStatus;
073import org.hl7.fhir.utilities.Utilities;
074import org.hl7.fhir.utilities.VersionUtilities;
075import org.hl7.fhir.utilities.i18n.I18nConstants;
076import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
077import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
078import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
079import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
080import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
081import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Title;
082import org.hl7.fhir.utilities.xhtml.NodeType;
083import org.hl7.fhir.utilities.xhtml.XhtmlNode;
084import org.hl7.fhir.utilities.xhtml.XhtmlNodeList;
085
086public class StructureDefinitionRenderer extends ResourceRenderer {
087
088  private List<String> keyRows = new ArrayList<>();
089
090  public StructureDefinitionRenderer(RenderingContext context) {
091    super(context);
092  }
093
094  public StructureDefinitionRenderer(RenderingContext context, ResourceContext rcontext) {
095    super(context, rcontext);
096  }
097  
098  public boolean render(XhtmlNode x, Resource dr) throws FHIRFormatError, DefinitionException, IOException {
099    return render(x, (StructureDefinition) dr);
100  }
101
102  public boolean render(XhtmlNode x, StructureDefinition sd) throws FHIRFormatError, DefinitionException, IOException {
103    x.getChildNodes().add(generateTable(context.getDefinitionsTarget(), sd, true, context.getDestDir(), false, sd.getId(), false, 
104        context.getLink(KnownLinkType.SPEC), "", sd.getKind() == StructureDefinitionKind.LOGICAL, false, null, false, context, ""));
105    return true;
106  }
107
108  public void describe(XhtmlNode x, StructureDefinition sd) {
109    x.tx(display(sd));
110  }
111
112  public String display(StructureDefinition sd) {
113    return sd.present();
114  }
115
116  @Override
117  public String display(Resource r) throws UnsupportedEncodingException, IOException {
118    return ((StructureDefinition) r).present();
119  }
120
121  public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException {
122    if (r.has("title")) {
123      return r.children("title").get(0).getBase().primitiveValue();
124    }
125    if (r.has("name")) {
126      return r.children("name").get(0).getBase().primitiveValue();
127    }
128    return "??";
129  }
130
131
132//  private static final int AGG_NONE = 0;
133//  private static final int AGG_IND = 1;
134//  private static final int AGG_GR = 2;
135//  private static final boolean TABLE_FORMAT_FOR_FIXED_VALUES = false;
136  public static final String CONSTRAINT_CHAR = "C";
137  public static final String CONSTRAINT_STYLE = "padding-left: 3px; padding-right: 3px; border: 1px maroon solid; font-weight: bold; color: #301212; background-color: #fdf4f4;";
138  private final boolean ADD_REFERENCE_TO_TABLE = true;
139
140  private boolean useTableForFixedValues = true;
141
142  public static class UnusedTracker {
143    private boolean used;
144  }
145
146  private class SpanEntry {
147    private List<SpanEntry> children = new ArrayList<SpanEntry>();
148    private boolean profile;
149    private String id;
150    private String name;
151    private String resType;
152    private String cardinality;
153    private String description;
154    private String profileLink;
155    private String resLink;
156    private String type;
157    
158    public String getName() {
159      return name;
160    }
161    public void setName(String name) {
162      this.name = name;
163    }
164    public String getResType() {
165      return resType;
166    }
167    public void setResType(String resType) {
168      this.resType = resType;
169    }
170    public String getCardinality() {
171      return cardinality;
172    }
173    public void setCardinality(String cardinality) {
174      this.cardinality = cardinality;
175    }
176    public String getDescription() {
177      return description;
178    }
179    public void setDescription(String description) {
180      this.description = description;
181    }
182    public String getProfileLink() {
183      return profileLink;
184    }
185    public void setProfileLink(String profileLink) {
186      this.profileLink = profileLink;
187    }
188    public String getResLink() {
189      return resLink;
190    }
191    public void setResLink(String resLink) {
192      this.resLink = resLink;
193    }
194    public String getId() {
195      return id;
196    }
197    public void setId(String id) {
198      this.id = id;
199    }
200    public boolean isProfile() {
201      return profile;
202    }
203    public void setProfile(boolean profile) {
204      this.profile = profile;
205    }
206    public List<SpanEntry> getChildren() {
207      return children;
208    }
209    public String getType() {
210      return type;
211    }
212    public void setType(String type) {
213      this.type = type;
214    }
215    
216  }
217
218  private class ElementInStructure {
219
220    private StructureDefinition source;
221    private ElementDefinition element;
222
223    public ElementInStructure(StructureDefinition source, ElementDefinition ed) {
224      this.source = source;
225      this.element = ed;
226    }
227
228    public StructureDefinition getSource() {
229      return source;
230    }
231
232    public ElementDefinition getElement() {
233      return element;
234    }
235    
236  }
237  private ElementInStructure getElementByName(List<ElementDefinition> elements, String contentReference, StructureDefinition source) {
238    if (contentReference.contains("#")) {
239      String url = contentReference.substring(0, contentReference.indexOf("#"));
240      contentReference = contentReference.substring(contentReference.indexOf("#"));
241      if (Utilities.noString(url)) {
242        url = source.getUrl();
243      }
244      if (!url.equals(source.getUrl())) {
245        source = context.getWorker().fetchResource(StructureDefinition.class, url, source);
246        if (source == null) {
247          throw new FHIRException("Unable to resolve StructureDefinition "+url+" resolving content reference "+contentReference);
248        }
249        elements = source.getSnapshot().getElement();
250      }
251    } 
252    for (ElementDefinition ed : elements) {
253      if (("#"+ed.getPath()).equals(contentReference)) {
254        return new ElementInStructure(source, ed);
255      }
256      if (("#"+ed.getId()).equals(contentReference)) {
257        return new ElementInStructure(source, ed);
258      }
259    }
260    throw new Error("getElementByName: can't find "+contentReference+" in "+elements.toString()+" from "+source.getUrl());
261//    return null;
262  }
263
264  public XhtmlNode generateGrid(String defFile, StructureDefinition profile, String imageFolder, boolean inlineGraphics, String profileBaseFileName, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException {
265    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true);
266    gen.setTranslator(getTranslator());
267    TableModel model = gen.initGridTable(corePath, profile.getId());
268    List<ElementDefinition> list = profile.getSnapshot().getElement();
269    List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
270    profiles.add(profile);
271    genGridElement(defFile == null ? null : defFile+"#", gen, model.getRows(), list.get(0), list, profiles, true, profileBaseFileName, null, corePath, imagePath, true, profile.getDerivation() == TypeDerivationRule.CONSTRAINT && usesMustSupport(list));
272    try {
273      return gen.generate(model, imagePath, 1, outputTracker);
274    } catch (org.hl7.fhir.exceptions.FHIRException e) {
275      throw new FHIRException(e.getMessage(), e);
276    }
277  }
278
279
280  private static class Column {
281    String id;
282    String title;
283    String hint;
284    private String link;
285
286    protected Column(String id, String title, String hint) {
287      super();
288      this.id = id;
289      this.title = title;
290      this.hint = hint;
291    }
292    protected Column(String id, String title, String hint, String link) {
293      super();
294      this.id = id;
295      this.title = title;
296      this.hint = hint;
297      this.link = link;
298    }
299    
300  }
301  public XhtmlNode generateTable(String defFile, StructureDefinition profile, boolean diff, String imageFolder, boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, String imagePath,
302      boolean logicalModel, boolean allInvariants, Set<String> outputTracker, boolean mustSupport, RenderingContext rc, String anchorPrefix) throws IOException, FHIRException {
303    assert(diff != snapshot);// check it's ok to get rid of one of these
304    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true);
305    gen.setTranslator(getTranslator());
306
307    List<ElementDefinition> list;
308    if (diff)
309      list = supplementMissingDiffElements(profile);
310    else {
311      list = new ArrayList<>();
312      list.addAll(profile.getSnapshot().getElement());
313    }
314    
315    List<Column> columns = new ArrayList<>();
316    TableModel model;
317    switch (context.getStructureMode()) {
318    case BINDINGS:
319      scanBindings(columns, list);
320      model = initCustomTable(gen, corePath, false, true, profile.getId()+(diff ? "d" : "s"), rc.getRules() == GenerationRules.IG_PUBLISHER, columns);    
321      break;
322    case OBLIGATIONS:
323      scanObligations(columns, list);
324      model = initCustomTable(gen, corePath, false, true, profile.getId()+(diff ? "d" : "s"), rc.getRules() == GenerationRules.IG_PUBLISHER, columns);    
325      break;
326    case SUMMARY:
327      model = gen.initNormalTable(corePath, false, true, profile.getId()+(diff ? "d" : "s"), rc.getRules() == GenerationRules.IG_PUBLISHER);
328      break;
329    default:
330      throw new Error("Unknown structure mode");
331    }
332
333    List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
334    profiles.add(profile);
335    keyRows.clear();
336
337    genElement(defFile == null ? null : defFile+"#", gen, model.getRows(), list.get(0), list, profiles, diff, profileBaseFileName, null, snapshot, corePath, imagePath, true, logicalModel, profile.getDerivation() == TypeDerivationRule.CONSTRAINT && usesMustSupport(list), allInvariants, null, mustSupport, rc, anchorPrefix, profile, columns);
338    try {
339      return gen.generate(model, imagePath, 0, outputTracker);
340    } catch (org.hl7.fhir.exceptions.FHIRException e) {
341      throw new FHIRException(context.getWorker().formatMessage(I18nConstants.ERROR_GENERATING_TABLE_FOR_PROFILE__, profile.getUrl(), e.getMessage()), e);
342    }
343  }
344
345  private void scanBindings(List<Column> columns, List<ElementDefinition> list) {
346    Set<String> cols = new HashSet<>();
347    scanBindings(cols, list, list.get(0));
348    if (cols.contains("required")) {
349      columns.add(new Column("required", "Required", "Concepts must come from this value set"));
350    }
351    if (cols.contains("extensible")) {
352      columns.add(new Column("extensible", "Extensible", "Concepts must come from this value set if an appropriate concept is in the value set "));
353    }
354    if (cols.contains("maximum")) {
355      columns.add(new Column("maximum", "Maximum", "A required binding for additional codes, for use when the binding strength is 'extensible' or 'preferred'"));
356    }
357    if (cols.contains("minimum")) {
358      columns.add(new Column("minimum", "Minimum", "The minimum allowable value set - any conformant system SHALL support all these codes"));
359    }
360    if (cols.contains("candidate")) {
361      columns.add(new Column("candidate", "Candidate", "This value set is a candidate to substitute for the overall conformance value set in some situations; usually these are defined in the documentation"));
362    }
363    if (cols.contains("current")) {
364      columns.add(new Column("current", "Current", "New records are required to use this value set, but legacy records may use other codes. The definition of 'new record' is difficult, since systems often create new records based on pre-existing data. Usually 'current' bindings are mandated by an external authority that makes clear rules around this"));
365    }
366    if (cols.contains("preferred")) {
367      columns.add(new Column("preferred", "Preferred", "This is the value set that is preferred in a given context (documentation should explain why)"));
368    }
369    if (cols.contains("ui")) {
370      columns.add(new Column("ui", "UI", "This value set is provided for user look up in a given context. Typically, these valuesets only include a subset of codes relevant for input in a context"));
371    }
372    if (cols.contains("starter")) {
373      columns.add(new Column("starter", "Starter", "This value set is a good set of codes to start with when designing your system"));
374    }
375    if (cols.contains("component")) {
376      columns.add(new Column("component", "Component", "This value set is a component of the base value set. Usually this is called out so that documentation can be written about a portion of the value set"));
377    }
378    if (cols.contains("example")) {
379      columns.add(new Column("example", "Example", "Instances are not expected or even encouraged to draw from the specified value set. The value set merely provides examples of the types of concepts intended to be included."));
380    }
381  }
382  
383  public void scanBindings(Set<String> cols, List<ElementDefinition> list, ElementDefinition ed) {
384    if (ed.hasBinding()) {
385      if (ed.getBinding().hasValueSet() && ed.getBinding().hasStrength()) {
386        switch (ed.getBinding().getStrength()) {
387        case EXAMPLE:
388          cols.add("example");
389          break;
390        case EXTENSIBLE:
391          cols.add("extensible");
392          break;
393        case PREFERRED:
394          cols.add("preferred");
395          break;
396        case REQUIRED:
397          cols.add("required");
398          break;
399        default:
400          break;
401        }
402      }
403      for (ElementDefinitionBindingAdditionalComponent ab : ed.getBinding().getAdditional()) {
404        cols.add(ab.getPurpose().toCode());
405      }
406      for (Extension ext : ed.getBinding().getExtensionsByUrl(ToolingExtensions.EXT_BINDING_ADDITIONAL)) {
407        cols.add(ext.getExtensionString("purpose"));        
408      }
409    }
410    
411    List<ElementDefinition> children = getChildren(list, ed);
412    for (ElementDefinition element : children) {
413      scanBindings(cols, list, element);
414    }
415  }
416  
417  private void scanObligations(List<Column> columns, List<ElementDefinition> list) {
418    Set<String> cols = new HashSet<>();
419    scanObligations(cols, list, list.get(0));
420
421    if (cols.contains("$all")) {
422      columns.add(new Column("$all", "All Actors", "Obligations that apply to all actors"));
423    }
424    for (String col : cols) {
425      if (!"$all".equals(col)) {
426        ActorDefinition actor = context.getWorker().fetchResource(ActorDefinition.class, col);
427        if (actor == null) {
428          columns.add(new Column(col, tail(col), "Obligations that apply to the undefined actor "+col, col));          
429        } else {
430          columns.add(new Column(col, actor.getName(), "Obligations that apply to the actor "+actor.present(), actor.getUserString("path")));                    
431        }
432      }
433    }
434  }
435
436  private void scanObligations(Set<String> cols, List<ElementDefinition> list, ElementDefinition ed) {
437
438    for (ElementDefinitionObligationComponent ob : ed.getObligation()) {
439      if (ob.hasActor()) {
440        for (CanonicalType a : ob.getActor()) {
441          cols.add(a.getValue());
442        }
443      } else 
444        cols.add("$all");
445    }
446
447    List<ElementDefinition> children = getChildren(list, ed);
448    for (ElementDefinition element : children) {
449      scanObligations(cols, list, element);
450    }
451  }
452
453  public TableModel initCustomTable(HierarchicalTableGenerator gen, String prefix, boolean isLogical, boolean alternating, String id, boolean isActive, List<Column> columns) {
454    TableModel model = gen.new TableModel(id, isActive);
455    
456    model.setAlternating(alternating);
457    model.setDocoImg(Utilities.pathURL(prefix, "help16.png"));
458    model.setDocoRef(Utilities.pathURL("https://build.fhir.org/ig/FHIR/ig-guidance", "readingIgs.html#table-views"));
459    model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", "Name"), translate("sd.hint", "The logical name of the element"), null, 0));
460    for (Column col : columns) {
461      model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", col.title), translate("sd.hint", col.hint), null, 0));      
462    }
463    return model;
464  }
465  
466  private Row genElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element, List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName, Boolean extensions, 
467      boolean snapshot, String corePath, String imagePath, boolean root, boolean logicalModel, boolean isConstraintMode, boolean allInvariants, Row slicingRow, boolean mustSupport, RenderingContext rc, String anchorPrefix, Resource srcSD, List<Column> columns) throws IOException, FHIRException {
468    Row originalRow = slicingRow;
469    StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1);
470    Row typesRow = null;
471    
472    List<ElementDefinition> children = getChildren(all, element);
473//    if (!snapshot && isExtension && extensions != null && extensions != isExtension)
474//      return;
475
476    if (!onlyInformationIsMapping(all, element)) {
477      Row row = gen.new Row();
478      row.setAnchor(element.getPath());
479      row.setColor(context.getProfileUtilities().getRowColor(element, isConstraintMode));
480      if (element.hasSlicing())
481        row.setLineColor(1);
482      else if (element.hasSliceName())
483        row.setLineColor(2);
484      else
485        row.setLineColor(0);
486      boolean hasDef = element != null;
487      boolean ext = false;
488      if (tail(element.getPath()).equals("extension")) {
489        if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue()))
490          row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
491        else
492          row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
493        ext = true;
494      } else if (tail(element.getPath()).equals("modifierExtension")) {
495        if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue()))
496          row.setIcon("icon_modifier_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
497        else
498          row.setIcon("icon_modifier_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
499      } else if (!hasDef || element.getType().size() == 0) {
500        if (root && profile != null && context.getWorker().getResourceNames().contains(profile.getType())) {
501          row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
502        } else if (hasDef && element.hasExtension(ToolingExtensions.EXT_JSON_PROP_KEY)) {
503          row.setIcon("icon-object-box.png", HierarchicalTableGenerator.TEXT_ICON_OBJECT_BOX);
504          keyRows.add(element.getId()+"."+ToolingExtensions.readStringExtension(element, ToolingExtensions.EXT_JSON_PROP_KEY));
505        } else {
506          row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
507        }
508      } else if (hasDef && element.getType().size() > 1) {
509        if (allAreReference(element.getType())) {
510          row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
511        } else if (element.hasExtension(ToolingExtensions.EXT_JSON_PRIMITIVE_CHOICE)) {
512          row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE);
513        } else {
514          row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE);
515          typesRow = row;
516        }
517      } else if (hasDef && element.getType().get(0).getWorkingCode() != null && element.getType().get(0).getWorkingCode().startsWith("@")) {
518        row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE);
519      } else if (hasDef && isPrimitive(element.getType().get(0).getWorkingCode())) {
520        if (keyRows.contains(element.getId())) {
521          row.setIcon("icon-key.png", HierarchicalTableGenerator.TEXT_ICON_KEY);
522        } else {
523          row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
524        }
525      } else if (hasDef && element.getType().get(0).hasTarget()) {
526        row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
527      } else if (hasDef && isDataType(element.getType().get(0).getWorkingCode())) {
528        row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
529      } else if (hasDef && element.hasExtension(ToolingExtensions.EXT_JSON_PROP_KEY)) {
530        row.setIcon("icon-object-box.png", HierarchicalTableGenerator.TEXT_ICON_OBJECT_BOX);
531        keyRows.add(element.getId()+"."+ToolingExtensions.readStringExtension(element, ToolingExtensions.EXT_JSON_PROP_KEY));
532      } else if (hasDef && Utilities.existsInList(element.getType().get(0).getWorkingCode(), "Base", "Element", "BackboneElement")) {
533        row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
534      } else {
535        row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
536      }
537      if (element.hasUserData("render.opaque")) {
538        row.setOpacity("0.5");
539      }
540      UnusedTracker used = new UnusedTracker();
541      String ref = defPath == null ? null : defPath + anchorPrefix + element.getId();
542      String sName = tail(element.getPath());
543      if (element.hasSliceName())
544        sName = sName +":"+element.getSliceName();
545      used.used = true;
546      if (logicalModel && element.hasRepresentation(PropertyRepresentation.XMLATTR))
547        sName = "@"+sName;
548      Cell nc = genElementNameCell(gen, element, profileBaseFileName, snapshot, corePath, imagePath, root, logicalModel, allInvariants, profile, typesRow, row, hasDef, ext, used, ref, sName, all);
549      switch (context.getStructureMode()) {
550      case BINDINGS:
551        genElementBindings(gen, element, columns, row, profile, corePath);
552        break;
553      case OBLIGATIONS:
554        genElementObligations(gen, element, columns, row);
555        break;
556      case SUMMARY:
557        genElementCells(gen, element, profileBaseFileName, snapshot, corePath, imagePath, root, logicalModel, allInvariants, profile, typesRow, row, hasDef, ext, used, ref, sName, nc, mustSupport, true, rc);
558        break;
559
560      }
561      if (element.hasSlicing()) {
562        if (standardExtensionSlicing(element)) {
563          used.used = true; // doesn't matter whether we have a type, we're used if we're setting up slicing ... element.hasType() && element.getType().get(0).hasProfile();
564          showMissing = false; //?
565        } else {
566          row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE);
567          slicingRow = row;
568          for (Cell cell : row.getCells())
569            for (Piece p : cell.getPieces()) {
570              p.addStyle("font-style: italic");
571            }
572        }
573      } else if (element.hasSliceName()) {
574        row.setIcon("icon_slice_item.png", HierarchicalTableGenerator.TEXT_ICON_SLICE_ITEM);
575      }
576      if (used.used || showMissing)
577        rows.add(row);
578      if (!used.used && !element.hasSlicing()) {
579        for (Cell cell : row.getCells())
580          for (Piece p : cell.getPieces()) {
581            p.setStyle("text-decoration:line-through");
582            p.setReference(null);
583          }
584      } else {
585        if (slicingRow != originalRow && !children.isEmpty()) {
586          // we've entered a slice; we're going to create a holder row for the slice children
587          Row hrow = gen.new Row();
588          hrow.setAnchor(element.getPath());
589          hrow.setColor(context.getProfileUtilities().getRowColor(element, isConstraintMode));
590          hrow.setLineColor(1);
591          hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
592          hrow.getCells().add(gen.new Cell(null, null, sName+":All Slices", "", null));
593          switch (context.getStructureMode()) {
594          case BINDINGS:
595          case OBLIGATIONS:
596            for (Column col : columns) {
597              hrow.getCells().add(gen.new Cell());              
598            }
599            break;
600          case SUMMARY:
601            hrow.getCells().add(gen.new Cell());
602            hrow.getCells().add(gen.new Cell());
603            hrow.getCells().add(gen.new Cell());
604            hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all slices", "", null));
605            break;            
606          }
607          row.getSubRows().add(hrow);
608          row = hrow;
609        }
610        if (typesRow != null && !children.isEmpty()) {
611          // we've entered a typing slice; we're going to create a holder row for the all types children
612          Row hrow = gen.new Row();
613          hrow.setAnchor(element.getPath());
614          hrow.setColor(context.getProfileUtilities().getRowColor(element, isConstraintMode));
615          hrow.setLineColor(1);
616          hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
617          hrow.getCells().add(gen.new Cell(null, null, sName+":All Types", "", null));
618          switch (context.getStructureMode()) {
619          case BINDINGS:
620          case OBLIGATIONS:
621            for (Column col : columns) {
622              hrow.getCells().add(gen.new Cell());              
623            }
624            break;
625          case SUMMARY:
626            hrow.getCells().add(gen.new Cell());
627            hrow.getCells().add(gen.new Cell());
628            hrow.getCells().add(gen.new Cell());
629            hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all Types", "", null));
630          }
631          row.getSubRows().add(hrow);
632          row = hrow;
633        }
634          
635        Row currRow = row;
636        List<ElementChoiceGroup> groups = readChoices(element, children);
637        boolean isExtension = Utilities.existsInList(tail(element.getPath()), "extension", "modifierExtension");
638        if (!element.prohibited()) {
639          for (ElementDefinition child : children) {
640            if (!child.hasSliceName()) {
641              currRow = row; 
642            }
643            Row childRow = chooseChildRowByGroup(gen, currRow, groups, child, element, isConstraintMode);
644
645            if (logicalModel || !child.getPath().endsWith(".id") || (child.getPath().endsWith(".id") && (profile != null) && (profile.getDerivation() == TypeDerivationRule.CONSTRAINT))) {  
646              currRow = genElement(defPath, gen, childRow.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, snapshot, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants, currRow, mustSupport, rc, anchorPrefix, srcSD, columns);
647            }
648          }
649        }
650//        if (!snapshot && (extensions == null || !extensions))
651//          for (ElementDefinition child : children)
652//            if (child.getPath().endsWith(".extension") || child.getPath().endsWith(".modifierExtension"))
653//              genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, true, false, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants);
654      }
655      if (typesRow != null && !element.prohibited() && context.getStructureMode() == StructureDefinitionRendererMode.SUMMARY) {
656        makeChoiceRows(typesRow.getSubRows(), element, gen, corePath, profileBaseFileName, mustSupport, srcSD);
657      }
658    }
659    return slicingRow;
660  }
661
662  private void genElementObligations(HierarchicalTableGenerator gen, ElementDefinition element, List<Column> columns, Row row) throws IOException {
663    for (Column col : columns) { 
664      Cell gc = gen.new Cell();
665      row.getCells().add(gc);
666      List<ElementDefinitionObligationComponent> obligations = collectObligations(element, col.id);
667      if (obligations.size() > 0) {
668        Piece p = gen.new Piece(null);
669        gc.addPiece(p);
670        if (obligations.size() == 1) {
671          renderObligation(p.getChildren(), obligations.get(0));
672        } else {
673          XhtmlNode ul = p.getChildren().ul();
674          for (ElementDefinitionObligationComponent ob : obligations) {
675            renderObligation(ul.li().getChildNodes(), ob);
676          }
677        }
678      }
679      
680    }
681  }
682
683  private List<ElementDefinitionObligationComponent> collectObligations(ElementDefinition element, String id) {
684    List<ElementDefinitionObligationComponent>  res = new ArrayList<>();
685    for (ElementDefinitionObligationComponent ob : element.getObligation()) {
686      if (("$all".equals(id) && !ob.hasActor()) || (ob.hasActor(id))) {
687        res.add(ob);
688      }
689    }
690    return res;
691  }
692
693  private void renderObligation(XhtmlNodeList children, ElementDefinitionObligationComponent ob) throws IOException {
694    if ("http://hl7.org/fhir/tools/CodeSystem/obligation".equals(ob.getCode().getSystem())) {
695      boolean first = true;
696      String[] codes = ob.getCode().getCode().split("\\+");
697      for (String code : codes) {
698        if (first) first = false; else children.tx(" & ");
699        int i = code.indexOf(":");
700        if (i > -1) {
701          String c = code.substring(0, i);
702          code = code.substring(i+1);
703          children.b().tx(c.toUpperCase());
704          children.tx(":");
705        }
706        CodeResolution cr = resolveCode("http://hl7.org/fhir/tools/CodeSystem/obligation", code);
707        code = code.replace("will-", "").replace("can-", "");
708        if (cr.getLink() != null) {
709          children.ah(cr.getLink(), cr.getHint()).tx(code);          
710        } else {
711          children.span(null, cr.getHint()).tx(code);
712        }
713      }
714      
715    } else {
716      CodeResolution cr = resolveCode(ob.getCode());
717      if (cr.getLink() != null) {
718        children.ah(cr.getLink(), cr.getHint()).tx(cr.getDisplay());        
719      } else {
720        children.span(null, cr.getHint()).tx(cr.getDisplay());
721      }
722    }
723    if (ob.hasFilter() || ob.hasUsage()) {
724      children.tx(" (");
725      boolean ffirst = !ob.hasFilter();
726      if (ob.hasFilter()) {
727        children.span(null, ob.getFilterDocumentation()).code().tx(ob.getFilter());
728      }
729      for (UsageContext uc : ob.getUsage()) {
730        if (ffirst) ffirst = false; else children.tx(",");
731        if (!uc.getCode().is("http://terminology.hl7.org/CodeSystem/usage-context-type", "jurisdiction")) {
732          children.tx(displayForUsage(uc.getCode()));
733          children.tx("=");
734        }
735        CodeResolution ccr = resolveCode(uc.getValueCodeableConcept());
736        children.ah(ccr.getLink(), ccr.getHint()).tx(ccr.getDisplay());
737      }
738      children.tx(")");
739    }
740    // usage
741    // filter
742    // process 
743  }
744
745
746  private String displayForUsage(Coding c) {
747    if (c.hasDisplay()) {
748      return c.getDisplay();
749    }
750    if ("http://terminology.hl7.org/CodeSystem/usage-context-type".equals(c.getSystem())) {
751      return c.getCode();
752    }
753    return c.getCode();
754  }
755
756  private void genElementBindings(HierarchicalTableGenerator gen, ElementDefinition element, List<Column> columns, Row row, StructureDefinition profile, String corepath) {
757    for (Column col : columns) { 
758      Cell gc = gen.new Cell();
759      row.getCells().add(gc);
760      List<ElementDefinitionBindingAdditionalComponent> bindings = collectBindings(element, col.id);
761      if (bindings.size() > 0) {
762        Piece p = gen.new Piece(null);
763        gc.addPiece(p);
764        new AdditionalBindingsRenderer(context.getPkp(), corepath, profile, element.getPath(), context, null, this).render(p.getChildren(), bindings);
765      }
766    }
767  }
768
769  private List<ElementDefinitionBindingAdditionalComponent> collectBindings(ElementDefinition element, String type) {
770    List<ElementDefinitionBindingAdditionalComponent> res = new ArrayList<>();
771    if (element.hasBinding()) {
772      ElementDefinitionBindingComponent b = element.getBinding();
773      if (b.hasStrength() && type.equals(b.getStrength().toCode())) {
774        ElementDefinitionBindingAdditionalComponent ab = new ElementDefinitionBindingAdditionalComponent();
775        res.add(ab.setAny(false).setDocumentation(b.getDescription()).setValueSet(b.getValueSet()));
776      }
777      if ("maximum".equals(type) && b.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) {
778        ElementDefinitionBindingAdditionalComponent ab = new ElementDefinitionBindingAdditionalComponent();
779        res.add(ab.setAny(false).setValueSet(ToolingExtensions.readStringExtension(b, ToolingExtensions.EXT_MAX_VALUESET)));
780      }
781      if ("minimum".equals(type) && b.hasExtension(ToolingExtensions.EXT_MIN_VALUESET)) {
782        ElementDefinitionBindingAdditionalComponent ab = new ElementDefinitionBindingAdditionalComponent();
783        res.add(ab.setAny(false).setValueSet(ToolingExtensions.readStringExtension(b, ToolingExtensions.EXT_MIN_VALUESET)));
784      }
785      for (ElementDefinitionBindingAdditionalComponent t : b.getAdditional()) {
786        if (type.equals(t.getPurpose().toCode())) {
787          res.add(t);
788        }
789      }
790      for (Extension ext : b.getExtensionsByUrl(ToolingExtensions.EXT_BINDING_ADDITIONAL)) {
791        if (type.equals(ext.getExtensionString("purpose"))) {
792          ElementDefinitionBindingAdditionalComponent ab = new ElementDefinitionBindingAdditionalComponent();
793          if (ext.hasExtension("any")) {
794            ab.setAny(ToolingExtensions.readBooleanExtension(ext, "any"));
795          }
796          if (ext.hasExtension("purpose")) {
797            ab.setPurpose(AdditionalBindingPurposeVS.fromCode(ToolingExtensions.readStringExtension(ext, "purpose")));
798          }
799          if (ext.hasExtension("documentation")) {
800            ab.setDocumentation(ToolingExtensions.readStringExtension(ext, "documentation"));
801          }
802          if (ext.hasExtension("shortDoco")) {
803            ab.setShortDoco(ToolingExtensions.readStringExtension(ext, "shortDoco"));
804          }
805          if (ToolingExtensions.hasExtension(ext, "usage")) {
806            ab.addUsage(ext.getExtensionByUrl("usage").getValueUsageContext());
807          }
808          if (ext.hasExtension("valueSet")) {
809            ab.setValueSet(ToolingExtensions.readStringExtension(ext, "valueSet"));
810          }
811          res.add(ab);        
812        }
813      }
814    }
815    return res;
816  }
817
818  public Cell genElementNameCell(HierarchicalTableGenerator gen, ElementDefinition element, String profileBaseFileName, boolean snapshot, String corePath,
819      String imagePath, boolean root, boolean logicalModel, boolean allInvariants, StructureDefinition profile, Row typesRow, Row row, boolean hasDef,
820      boolean ext, UnusedTracker used, String ref, String sName, List<ElementDefinition> elements) throws IOException {
821    String hint = "";
822    hint = checkAdd(hint, (element.hasSliceName() ? translate("sd.table", "Slice")+" "+element.getSliceName() : ""));
823    if (hasDef && element.hasDefinition()) {
824      hint = checkAdd(hint, (hasDef && element.hasSliceName() ? ": " : ""));
825      hint = checkAdd(hint, !hasDef ? null : gt(element.getDefinitionElement()));
826    }
827    if (element.hasSlicing() && slicesExist(elements, element)) { // some elements set up slicing but don't actually slice, so we don't augment the name 
828      sName = "Slices for "+sName; 
829    }
830    Cell left = gen.new Cell(null, ref, sName, hint, null);
831    row.getCells().add(left);
832    return left;
833  }
834
835  public List<Cell> genElementCells(HierarchicalTableGenerator gen, ElementDefinition element, String profileBaseFileName, boolean snapshot, String corePath,
836      String imagePath, boolean root, boolean logicalModel, boolean allInvariants, StructureDefinition profile, Row typesRow, Row row, boolean hasDef,
837      boolean ext, UnusedTracker used, String ref, String sName, Cell nameCell, boolean mustSupport, boolean allowSubRows, RenderingContext rc) throws IOException {
838    List<Cell> res = new ArrayList<>();
839    Cell gc = gen.new Cell();
840    row.getCells().add(gc);
841    res.add(gc);
842    if (element != null && element.getIsModifier()) {
843      checkForNoChange(element.getIsModifierElement(), gc.addStyledText(translate("sd.table", "This element is a modifier element"), "?!", null, null, null, false));
844    }
845    if (element != null && element.getMustSupport()) {
846      checkForNoChange(element.getMustSupportElement(), gc.addStyledText(translate("sd.table", "This element must be supported"), "S", "white", "red", null, false));
847    }
848    if (element != null && element.getIsSummary()) {
849      checkForNoChange(element.getIsSummaryElement(), gc.addStyledText(translate("sd.table", "This element is included in summaries"), "\u03A3", null, null, null, false));
850    }
851    if (element != null && (hasNonBaseConstraints(element.getConstraint()) || hasNonBaseConditions(element.getCondition()))) {
852      Piece p = gc.addText(CONSTRAINT_CHAR);
853      p.setHint(translate("sd.table", "This element has or is affected by constraints ("+listConstraintsAndConditions(element)+")"));
854      p.addStyle(CONSTRAINT_STYLE);
855      p.setReference(Utilities.pathURL(VersionUtilities.getSpecUrl(context.getWorker().getVersion()), "conformance-rules.html#constraints"));
856    }
857    if (element != null && element.hasExtension(ToolingExtensions.EXT_STANDARDS_STATUS)) {
858      StandardsStatus ss = StandardsStatus.fromCode(element.getExtensionString(ToolingExtensions.EXT_STANDARDS_STATUS));
859      gc.addStyledText("Standards Status = "+ss.toDisplay(), ss.getAbbrev(), "black", ss.getColor(), context.getWorker().getSpecUrl()+"versions.html#std-process", true);
860    }
861
862    ExtensionContext extDefn = null;
863    if (ext) {
864      if (element != null) {
865        if (element.getType().size() == 1 && element.getType().get(0).hasProfile()) {
866          String eurl = element.getType().get(0).getProfile().get(0).getValue();
867          extDefn = locateExtension(StructureDefinition.class, eurl);
868          if (extDefn == null) {
869            res.add(genCardinality(gen, element, row, hasDef, used, null));
870            res.add(addCell(row, gen.new Cell(null, null, "?gen-e1? "+element.getType().get(0).getProfile(), null, null)));
871            res.add(generateDescription(gen, row, element, (ElementDefinition) element.getUserData(ProfileUtilities.UD_DERIVATION_POINTER), used.used, profile == null ? "" : profile.getUrl(), eurl, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot, mustSupport, allowSubRows, rc));
872          } else {
873            String name = element.hasSliceName() ? element.getSliceName() : urltail(eurl);
874            nameCell.getPieces().get(0).setText(name);
875            // left.getPieces().get(0).setReference((String) extDefn.getExtensionStructure().getTag("filename"));
876            nameCell.getPieces().get(0).setHint(translate("sd.table", "Extension URL")+" = "+extDefn.getUrl());
877            res.add(genCardinality(gen, element, row, hasDef, used, extDefn.getElement()));
878            ElementDefinition valueDefn = extDefn.getExtensionValueDefinition();
879            if (valueDefn != null && !"0".equals(valueDefn.getMax()))
880              res.add(genTypes(gen, row, valueDefn, profileBaseFileName, profile, corePath, imagePath, root, mustSupport));
881            else // if it's complex, we just call it nothing
882              // genTypes(gen, row, extDefn.getSnapshot().getElement().get(0), profileBaseFileName, profile);
883              res.add(addCell(row, gen.new Cell(null, null, "("+translate("sd.table", "Complex")+")", null, null)));
884            res.add(generateDescription(gen, row, element, extDefn.getElement(), used.used, null, extDefn.getUrl(), profile, corePath, imagePath, root, logicalModel, allInvariants, valueDefn, snapshot, mustSupport, allowSubRows, rc));
885          }
886        } else {
887          res.add(genCardinality(gen, element, row, hasDef, used, null));
888          if ("0".equals(element.getMax()))
889            res.add(addCell(row, gen.new Cell()));            
890          else
891            res.add(genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath, root, mustSupport));
892          res.add(generateDescription(gen, row, element, (ElementDefinition) element.getUserData(ProfileUtilities.UD_DERIVATION_POINTER), used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot, mustSupport, allowSubRows, rc));
893        }
894      }
895    } else if (element != null) {
896      res.add(genCardinality(gen, element, row, hasDef, used, null));
897      if (hasDef && !"0".equals(element.getMax()) && typesRow == null)
898        res.add(genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath, root, mustSupport));
899      else
900        res.add(addCell(row, gen.new Cell()));
901      res.add(generateDescription(gen, row, element, (ElementDefinition) element.getUserData(ProfileUtilities.UD_DERIVATION_POINTER), used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot, mustSupport, allowSubRows, rc));
902    }
903    return res;
904  }
905
906  private Cell genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef, UnusedTracker tracker, ElementDefinition fallback) {
907    IntegerType min = !hasDef ? new IntegerType() : definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
908    StringType max = !hasDef ? new StringType() : definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
909    if (min.isEmpty() && definition.getUserData(ProfileUtilities.UD_DERIVATION_POINTER) != null) {
910      ElementDefinition base = (ElementDefinition) definition.getUserData(ProfileUtilities.UD_DERIVATION_POINTER);
911      if (base.hasMinElement()) {
912        min = base.getMinElement().copy();
913        min.setUserData(ProfileUtilities.UD_DERIVATION_EQUALS, true);
914      }
915    }
916    if (max.isEmpty() && definition.getUserData(ProfileUtilities.UD_DERIVATION_POINTER) != null) {
917      ElementDefinition base = (ElementDefinition) definition.getUserData(ProfileUtilities.UD_DERIVATION_POINTER);
918      if (base.hasMaxElement()) {
919        max = base.getMaxElement().copy();
920        max.setUserData(ProfileUtilities.UD_DERIVATION_EQUALS, true);
921      }
922    }
923    if (min.isEmpty() && fallback != null)
924      min = fallback.getMinElement();
925    if (max.isEmpty() && fallback != null)
926      max = fallback.getMaxElement();
927
928    if (!max.isEmpty())
929      tracker.used = !max.getValue().equals("0");
930
931    String hint = null;
932    if (max.hasValue() && min.hasValue() && "*".equals(max.getValue()) && 0 == min.getValue()) {
933      if (definition.hasExtension(ToolingExtensions.EXT_JSON_EMPTY)) {
934        String code = ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_JSON_EMPTY);
935        if ("present".equals(code)) {
936          hint = "This element is present as a JSON Array even when there are no items in the instance";
937        } else {
938          hint = "This element may be present as a JSON Array even when there are no items in the instance";          
939        }
940      }
941    }
942    Cell cell = gen.new Cell(null, null, null, null, null);
943    row.getCells().add(cell);
944    if (!min.isEmpty() || !max.isEmpty()) {
945      cell.addPiece(checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), hint)));
946      cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", hint)));
947      cell.addPiece(checkForNoChange(max, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), hint)));
948    }
949    return cell;
950  }
951
952  public List<ElementDefinition> supplementMissingDiffElements(StructureDefinition profile) {
953    List<ElementDefinition> list = new ArrayList<>();
954    list.addAll(profile.getDifferential().getElement());
955    if (list.isEmpty()) {
956      ElementDefinition root = new ElementDefinition().setPath(profile.getTypeName());
957      root.setId(profile.getTypeName());
958      list.add(root);
959    } else {
960      if (list.get(0).getPath().contains(".")) {
961        ElementDefinition root = new ElementDefinition().setPath(profile.getTypeName());
962        root.setId(profile.getTypeName());
963        list.add(0, root);
964      }
965    }
966    insertMissingSparseElements(list);
967    return list;
968  }
969
970  private boolean usesMustSupport(List<ElementDefinition> list) {
971    for (ElementDefinition ed : list)
972      if (ed.hasMustSupport() && ed.getMustSupport())
973        return true;
974    return false;
975  }
976
977
978
979  private Row chooseChildRowByGroup(HierarchicalTableGenerator gen, Row row, List<ElementChoiceGroup> groups, ElementDefinition element, ElementDefinition parent, boolean isConstraintMode) {
980    String name = tail(element.getPath());
981    for (ElementChoiceGroup grp : groups) {
982      if (grp.getElements().contains(name)) {
983        if (grp.getRow() == null) {
984          grp.setRow(makeChoiceElementRow(gen, row, grp, parent, isConstraintMode));
985        }
986        return grp.getRow();
987      }
988    }
989    return row;
990  }
991
992  private Row makeChoiceElementRow(HierarchicalTableGenerator gen, Row prow, ElementChoiceGroup grp, ElementDefinition parent, boolean isConstraintMode) {
993    if (context.getStructureMode() != StructureDefinitionRendererMode.SUMMARY) {
994      return prow;
995    }
996    Row row = gen.new Row();
997    row.setAnchor(parent.getPath()+"-"+grp.getName());
998    row.setColor(context.getProfileUtilities().getRowColor(parent, isConstraintMode));
999    row.setLineColor(1);
1000    row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE);
1001    row.getCells().add(gen.new Cell(null, null, "(Choice of one)", "", null));
1002    row.getCells().add(gen.new Cell());
1003    row.getCells().add(gen.new Cell(null, null, (grp.isMandatory() ? "1" : "0")+"..1", "", null));
1004    row.getCells().add(gen.new Cell());
1005    row.getCells().add(gen.new Cell());
1006    prow.getSubRows().add(row);
1007    return row;
1008  }
1009  
1010
1011  private void insertMissingSparseElements(List<ElementDefinition> list) {
1012    int i = 1;
1013    while (i < list.size()) {
1014      String[] pathCurrent = list.get(i).getPath().split("\\.");
1015      String[] pathLast = list.get(i-1).getPath().split("\\.");
1016      int firstDiff = 0; // the first entry must be a match
1017      while (firstDiff < pathCurrent.length && firstDiff < pathLast.length && pathCurrent[firstDiff].equals(pathLast[firstDiff])) {
1018        firstDiff++;
1019      }
1020      if (!(isSibling(pathCurrent, pathLast, firstDiff) || isChild(pathCurrent, pathLast, firstDiff))) {
1021        // now work backwards down to lastMatch inserting missing path nodes
1022        ElementDefinition parent = findParent(list, i, list.get(i).getPath());
1023        int parentDepth = Utilities.charCount(parent.getPath(), '.')+1;
1024        int childDepth =  Utilities.charCount(list.get(i).getPath(), '.')+1;
1025        if (childDepth > parentDepth + 1) {
1026          String basePath = parent.getPath();
1027          String baseId = parent.getId();
1028          for (int index = parentDepth; index >= firstDiff; index--) {
1029            String mtail = makeTail(pathCurrent, parentDepth, index);
1030            ElementDefinition root = new ElementDefinition().setPath(basePath+"."+mtail);
1031            root.setId(baseId+"."+mtail);
1032            list.add(i, root);
1033          }
1034        }
1035      } 
1036      i++;
1037    }
1038  }
1039  
1040
1041  private String urltail(String path) {
1042    if (path.contains("#"))
1043      return path.substring(path.lastIndexOf('#')+1);
1044    if (path.contains("/"))
1045      return path.substring(path.lastIndexOf('/')+1);
1046    else
1047      return path;
1048
1049  }
1050
1051  private boolean standardExtensionSlicing(ElementDefinition element) {
1052    String t = tail(element.getPath());
1053    return (t.equals("extension") || t.equals("modifierExtension"))
1054          && element.getSlicing().getRules() != SlicingRules.CLOSED && element.getSlicing().getDiscriminator().size() == 1 && element.getSlicing().getDiscriminator().get(0).getPath().equals("url") && element.getSlicing().getDiscriminator().get(0).getType().equals(DiscriminatorType.VALUE);
1055  }
1056
1057  public Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, boolean snapshot, boolean mustSupportOnly, boolean allowSubRows, RenderingContext rc) throws IOException, FHIRException {
1058    return generateDescription(gen, row, definition, fallback, used, baseURL, url, profile, corePath, imagePath, root, logicalModel, allInvariants, null, snapshot, mustSupportOnly, allowSubRows, rc);
1059  }
1060  
1061  public Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, ElementDefinition valueDefn, boolean snapshot, boolean mustSupportOnly, boolean allowSubRows, RenderingContext rc) throws IOException, FHIRException {
1062    Cell c = gen.new Cell();
1063    row.getCells().add(c);
1064
1065    if (used) {
1066      if (logicalModel && ToolingExtensions.hasExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) {
1067        if (root) {
1068          c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold"));
1069          c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null));        
1070        } else if (!root && ToolingExtensions.hasExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace") && 
1071            !ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace").equals(ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))) {
1072          c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold"));
1073          c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null));        
1074        }
1075      }
1076      if (root) {
1077        if (profile != null && profile.getAbstract()) {
1078          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1079          c.addPiece(gen.new Piece(null, "This is an abstract "+(profile.getDerivation() == TypeDerivationRule.CONSTRAINT ? "profile" : "type")+". ", null));
1080          
1081          List<StructureDefinition> children = new ArrayList<>();
1082          for (StructureDefinition sd : context.getWorker().fetchResourcesByType(StructureDefinition.class)) {
1083            if (sd.hasBaseDefinition() && sd.getBaseDefinition().equals(profile.getUrl())) {
1084              children.add(sd);
1085            }
1086          }
1087          if (!children.isEmpty()) {
1088            c.addPiece(gen.new Piece(null, "Child "+(profile.getDerivation() == TypeDerivationRule.CONSTRAINT ? "profiles" : "types")+": ", null));
1089            boolean first = true;
1090            for (StructureDefinition sd : children) {
1091              if (first) first = false; else c.addPiece(gen.new Piece(null, ", ", null));
1092              c.addPiece(gen.new Piece(sd.getUserString("path"), sd.getTypeName(), null));
1093            }
1094          }
1095        }
1096      }
1097      if (definition.getPath().endsWith("url") && definition.hasFixed()) {
1098        c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen")));
1099      } else {
1100        if (definition != null && definition.hasShort()) {
1101          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1102          c.addPiece(checkForNoChange(definition.getShortElement(), gen.new Piece(null, gt(definition.getShortElement()), null)));
1103        } else if (fallback != null && fallback.hasShort()) {
1104          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1105          c.addPiece(gen.new Piece(null, gt(fallback.getShortElement()), null).addStyle("opacity: 0.5"));
1106        }
1107        if (url != null) {
1108          if (!c.getPieces().isEmpty()) 
1109            c.addPiece(gen.new Piece("br"));
1110          String fullUrl = url.startsWith("#") ? baseURL+url : url;
1111          StructureDefinition ed = context.getWorker().fetchResource(StructureDefinition.class, url, profile);
1112          String ref = null;
1113          String ref2 = null;
1114          String fixedUrl = null;
1115          if (ed != null) {
1116            String p = ed.getUserString("path");
1117            if (p != null) {
1118              ref = p.startsWith("http:") || context.getRules() == GenerationRules.IG_PUBLISHER ? p : Utilities.pathURL(corePath, p);
1119            }             
1120            fixedUrl = getFixedUrl(ed);
1121            if (fixedUrl != null) {// if its null, we guess that it's not a profiled extension?
1122              if (fixedUrl.equals(url))
1123                fixedUrl = null;
1124              else {
1125                StructureDefinition ed2 = context.getWorker().fetchResource(StructureDefinition.class, fixedUrl);
1126                if (ed2 != null) {
1127                  String p2 = ed2.getUserString("path");
1128                  if (p2 != null) {
1129                    ref2 = p2.startsWith("http:") || context.getRules() == GenerationRules.IG_PUBLISHER ? p2 : Utilities.pathURL(corePath, p2);
1130                  }                              
1131                }
1132              }
1133            }
1134          }
1135          if (fixedUrl == null) {
1136            if (!Utilities.noString(fullUrl)) {
1137              c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold"));
1138              c.getPieces().add(gen.new Piece(ref, fullUrl, null));
1139            }
1140          } else { 
1141            // reference to a profile take on the extension show the base URL
1142            c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold"));
1143            c.getPieces().add(gen.new Piece(ref2, fixedUrl, null));
1144            c.getPieces().add(gen.new Piece(null, translate("sd.table", " profiled by ")+" ", null).addStyle("font-weight:bold"));
1145            c.getPieces().add(gen.new Piece(ref, fullUrl, null));
1146          
1147          }
1148        }
1149
1150        if (definition.hasSlicing()) {
1151          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1152          c.getPieces().add(gen.new Piece(null, translate("sd.table", "Slice")+": ", null).addStyle("font-weight:bold"));
1153          c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null));
1154        }
1155        if (!definition.getPath().contains(".") && ToolingExtensions.hasExtension(profile, ToolingExtensions.EXT_BINDING_STYLE)) {
1156          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1157          c.getPieces().add(gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold"));
1158          c.getPieces().add(gen.new Piece(null, "This type can be bound to a value set using the ", null));
1159          c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(profile, ToolingExtensions.EXT_BINDING_STYLE), null));
1160          c.getPieces().add(gen.new Piece(null, " binding style", null));            
1161          
1162        }
1163        if (definition.hasExtension(ToolingExtensions.EXT_IMPLIED_PREFIX)) {
1164          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1165          c.getPieces().add(gen.new Piece(null, "When this element is read ", null));          
1166          Piece piece = gen.new Piece("code");
1167          piece.addHtml(new XhtmlNode(NodeType.Text).setContent(ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_IMPLIED_PREFIX)));
1168          c.getPieces().add(piece);          
1169          c.getPieces().add(gen.new Piece(null, " is prefixed to the value before validation", null));          
1170        }
1171
1172        if (definition.hasExtension(ToolingExtensions.EXT_EXTENSION_STYLE)) {
1173          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1174          String es = definition.getExtensionString(ToolingExtensions.EXT_EXTENSION_STYLE);
1175          if ("named-elements".equals(es)) {
1176            if (rc.hasLink(KnownLinkType.JSON_NAMES)) {
1177              c.getPieces().add(gen.new Piece(rc.getLink(KnownLinkType.JSON_NAMES), "This element can be extended by named JSON elements", null));                        
1178            } else {
1179              c.getPieces().add(gen.new Piece(ToolingExtensions.WEB_EXTENSION_STYLE, "This element can be extended by named JSON elements", null));                        
1180            }
1181          }
1182        }
1183        if (definition.hasExtension(ToolingExtensions.EXT_ID_EXPECTATION)) {
1184          String ide = ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_ID_EXPECTATION);
1185          if (ide.equals("optional")) {
1186            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1187            c.getPieces().add(gen.new Piece(null, "Id may or not be present (this is the default for elements but not resources)", null));     
1188          } else if (ide.equals("required")) {
1189            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1190            c.getPieces().add(gen.new Piece(null, "Id is required to be present (this is the default for resources but not elements)", null));     
1191          } else if (ide.equals("required")) {
1192            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1193            c.getPieces().add(gen.new Piece(null, "An ID is not allowed in this context", null));     
1194          }
1195        }
1196        if (definition.hasExtension(ToolingExtensions.EXT_XML_NAME)) {
1197          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1198          if (definition.hasExtension(ToolingExtensions.EXT_XML_NAMESPACE)) {
1199            c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML")+": ", null).addStyle("font-weight:bold"));
1200            c.getPieces().add(gen.new Piece(null, definition.getExtensionString(ToolingExtensions.EXT_XML_NAME), null));
1201            c.getPieces().add(gen.new Piece(null, " (", null));
1202            c.getPieces().add(gen.new Piece(null, definition.getExtensionString(ToolingExtensions.EXT_XML_NAMESPACE), null));
1203            c.getPieces().add(gen.new Piece(null, ")", null));            
1204          } else {
1205            c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Element Name")+": ", null).addStyle("font-weight:bold"));
1206            c.getPieces().add(gen.new Piece(null, definition.getExtensionString(ToolingExtensions.EXT_XML_NAME), null));
1207          }            
1208        } else if (definition.hasExtension(ToolingExtensions.EXT_XML_NAMESPACE)) {
1209          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1210          c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold"));
1211          c.getPieces().add(gen.new Piece(null, definition.getExtensionString(ToolingExtensions.EXT_XML_NAMESPACE), null));          
1212        }
1213        if (definition.hasExtension(ToolingExtensions.EXT_JSON_EMPTY)) {
1214          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1215          String code = ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_JSON_EMPTY);
1216          if ("present".equals(code)) {
1217            c.getPieces().add(gen.new Piece(null, "JSON: This element is present as a JSON Array even when there are no items in the instance", null));     
1218          } else {
1219            c.getPieces().add(gen.new Piece(null, "JSON: This element may be present as a JSON Array even when there are no items in the instance", null));     
1220          }
1221        }
1222        String jn = ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_JSON_NAME);
1223        if (!Utilities.noString(jn)) {
1224          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1225          if (definition.getPath().contains(".")) {
1226            c.getPieces().add(gen.new Piece(null, translate("sd.table", "JSON Property Name")+": ", null).addStyle("font-weight:bold"));
1227            c.getPieces().add(gen.new Piece(null, jn, null));
1228          } else {
1229            c.getPieces().add(gen.new Piece(null, translate("sd.table", "JSON Property Name for Type")+": ", null).addStyle("font-weight:bold"));
1230            Piece piece = gen.new Piece("code");
1231            piece.addHtml(new XhtmlNode(NodeType.Text).setContent(jn));
1232            c.getPieces().add(piece);            
1233          }
1234        }
1235        
1236        if (ToolingExtensions.readBoolExtension(definition, ToolingExtensions.EXT_JSON_PRIMITIVE_CHOICE)) {
1237          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1238          c.getPieces().add(gen.new Piece(null, "JSON: The type of this element is inferred from the JSON type in the instance", null));     
1239        }
1240        if (ToolingExtensions.readBoolExtension(definition, ToolingExtensions.EXT_JSON_NULLABLE)) {
1241          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1242          c.getPieces().add(gen.new Piece(null, "JSON: This object can be represented as null in the JSON structure (which counts as 'present' for cardinality purposes)", null));     
1243        }
1244        if (definition.hasExtension(ToolingExtensions.EXT_JSON_PROP_KEY)) {
1245          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1246          String code = ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_JSON_EMPTY);
1247          c.getPieces().add(gen.new Piece(null, "JSON: Represented as a single JSON Object with named properties using the value of the "+code+" child as the key", null));     
1248        }      
1249        if (definition.hasExtension(ToolingExtensions.EXT_TYPE_SPEC)) {
1250          for (Extension e : definition.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_SPEC)) {
1251            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1252            String cond = ToolingExtensions.readStringExtension(e, "condition");
1253            String type = ToolingExtensions.readStringExtension(e, "type");
1254            c.getPieces().add(gen.new Piece(null, "JSON: If ", null));          
1255            Piece piece = gen.new Piece("code");
1256            piece.addHtml(new XhtmlNode(NodeType.Text).setContent(cond));
1257            c.getPieces().add(piece);          
1258            c.getPieces().add(gen.new Piece(null, "then the type is ", null));          
1259            StructureDefinition sd = context.getWorker().fetchTypeDefinition(type);
1260            if (sd == null) {
1261              c.getPieces().add(gen.new Piece("<code>"));          
1262              c.getPieces().add(gen.new Piece(null, type, null));          
1263              c.getPieces().add(gen.new Piece("</code>"));          
1264            } else {
1265              c.getPieces().add(gen.new Piece(sd.getUserString("path"), sd.getTypeName(), null));          
1266            }
1267          }
1268        }
1269        if (definition != null) {
1270          ElementDefinitionBindingComponent binding = null;
1271          if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty())
1272            binding = makeUnifiedBinding(valueDefn.getBinding(), valueDefn);
1273          else if (definition.hasBinding())
1274            binding = makeUnifiedBinding(definition.getBinding(), definition);
1275          if (binding!=null && !binding.isEmpty()) {
1276            if (!c.getPieces().isEmpty()) 
1277              c.addPiece(gen.new Piece("br"));
1278            BindingResolution br = context.getPkp() == null ? makeNullBr(binding) : context.getPkp().resolveBinding(profile, binding, definition.getPath());
1279            c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold")));
1280              c.getPieces().add(checkForNoChange(binding.getValueSetElement(), gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !context.getPkp().prependLinks() ? br.url : corePath+br.url, br.display, null)));
1281            if (binding.hasStrength()) {
1282              c.getPieces().add(checkForNoChange(binding.getStrengthElement(), gen.new Piece(null, " (", null)));
1283              c.getPieces().add(checkForNoChange(binding.getStrengthElement(), gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), egt(binding.getStrengthElement()), binding.getStrength().getDefinition())));                            
1284              c.getPieces().add(checkForNoChange(binding.getStrengthElement(), gen.new Piece(null, ")", null)));
1285            }
1286            if (binding.hasDescription() && MarkDownProcessor.isSimpleMarkdown(binding.getDescription())) {
1287              c.getPieces().add(gen.new Piece(null, ": ", null));
1288              c.addMarkdownNoPara(PublicationHacker.fixBindingDescriptions(context.getWorker(), binding.getDescriptionElement()).asStringValue(), checkForNoChange(PublicationHacker.fixBindingDescriptions(context.getWorker(), binding.getDescriptionElement())));
1289            } 
1290
1291            AdditionalBindingsRenderer abr = new AdditionalBindingsRenderer(context.getPkp(), corePath, profile, definition.getPath(), rc, null, this);
1292            if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) {
1293              abr.seeMaxBinding(ToolingExtensions.getExtension(binding, ToolingExtensions.EXT_MAX_VALUESET));
1294            }
1295            if (binding.hasExtension(ToolingExtensions.EXT_MIN_VALUESET)) {
1296              abr.seeMinBinding(ToolingExtensions.getExtension(binding, ToolingExtensions.EXT_MIN_VALUESET));
1297            }
1298            if (binding.hasExtension(ToolingExtensions.EXT_BINDING_ADDITIONAL)) {
1299              abr.seeAdditionalBindings(binding.getExtensionsByUrl(ToolingExtensions.EXT_BINDING_ADDITIONAL));
1300            }
1301            abr.render(gen, c);
1302          }
1303          for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
1304            if (!inv.hasSource() || profile == null || inv.getSource().equals(profile.getUrl()) || allInvariants) {
1305              if (!c.getPieces().isEmpty()) 
1306                c.addPiece(gen.new Piece("br"));
1307              c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold")));
1308              c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, gt(inv.getHumanElement()), null)));
1309            }
1310          }
1311          if ((definition.hasBase() && "*".equals(definition.getBase().getMax())) || (definition.hasMax() && "*".equals(definition.getMax()))) {
1312            if (c.getPieces().size() > 0)
1313              c.addPiece(gen.new Piece("br"));
1314            if (definition.hasOrderMeaning()) {
1315              c.getPieces().add(gen.new Piece(null, "This repeating element order: "+definition.getOrderMeaning(), null));
1316            } else {
1317              // don't show this, this it's important: c.getPieces().add(gen.new Piece(null, "This repeating element has no defined order", null));
1318            }           
1319          }
1320          if (definition.hasFixed()) {
1321            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1322            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, translate("sd.table", "Fixed Value")+": ", null).addStyle("font-weight:bold")));
1323            if (!useTableForFixedValues || !allowSubRows || definition.getFixed().isPrimitive()) {
1324              String s = buildJson(definition.getFixed());
1325              String link = null;
1326              if (Utilities.isAbsoluteUrl(s) && context.getPkp() != null)
1327                link = context.getPkp().getLinkForUrl(corePath, s);
1328              c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen")));
1329            } else {
1330              c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "As shown", null).addStyle("color: darkgreen")));
1331              genFixedValue(gen, row, definition.getFixed(), snapshot, false, corePath, false);
1332            }
1333            if (isCoded(definition.getFixed()) && !hasDescription(definition.getFixed())) {
1334              Piece p = describeCoded(gen, definition.getFixed());
1335              if (p != null)
1336                c.getPieces().add(p);
1337            }
1338          } else if (definition.hasPattern()) {
1339            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1340            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, translate("sd.table", "Required Pattern")+": ", null).addStyle("font-weight:bold")));
1341            if (!useTableForFixedValues || !allowSubRows || definition.getPattern().isPrimitive())
1342              c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen")));
1343            else {
1344              c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "At least the following", null).addStyle("color: darkgreen")));
1345              genFixedValue(gen, row, definition.getPattern(), snapshot, true, corePath, mustSupportOnly);
1346            }
1347          } else if (definition.hasExample()) {
1348            for (ElementDefinitionExampleComponent ex : definition.getExample()) {
1349              if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1350              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, translate("sd.table", "Example")+("".equals("General")? "" : " "+ex.getLabel())+": ", null).addStyle("font-weight:bold")));
1351              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen")));
1352            }
1353          }
1354          if (definition.hasMaxLength() && definition.getMaxLength()!=0) {
1355            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1356            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold")));
1357            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen")));
1358          }
1359          if (profile != null) {
1360            for (StructureDefinitionMappingComponent md : profile.getMapping()) {
1361              if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) {
1362                ElementDefinitionMappingComponent map = null;
1363                for (ElementDefinitionMappingComponent m : definition.getMapping()) 
1364                  if (m.getIdentity().equals(md.getIdentity()))
1365                    map = m;
1366                if (map != null) {
1367                  for (int i = 0; i<definition.getMapping().size(); i++){
1368                    c.addPiece(gen.new Piece("br"));
1369                    c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null));
1370                  }
1371                }
1372              }
1373            }
1374          }
1375        }
1376      }
1377    }
1378    return c;
1379  }
1380  
1381
1382  private Piece checkForNoChange(Element source, Piece piece) {
1383    if (source.hasUserData(ProfileUtilities.UD_DERIVATION_EQUALS)) {
1384      piece.addStyle("opacity: 0.5");
1385    }
1386    return piece;
1387  }
1388
1389  private String checkForNoChange(Element source) {
1390    if (source.hasUserData(ProfileUtilities.UD_DERIVATION_EQUALS)) {
1391      return "opacity: 0.5";
1392    } else { 
1393      return null;
1394    }
1395  }
1396
1397  private Cell genTypes(HierarchicalTableGenerator gen, Row r, ElementDefinition e, String profileBaseFileName, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean mustSupportMode) {
1398    Cell c = gen.new Cell();
1399    r.getCells().add(c);
1400    if (e.hasContentReference()) {
1401      ElementInStructure ed = getElementByName(profile.getSnapshot().getElement(), e.getContentReference(), profile);
1402      if (ed == null)
1403        c.getPieces().add(gen.new Piece(null, translate("sd.table", "Unknown reference to %s", e.getContentReference()), null));
1404      else {
1405        if (ed.getSource() == profile) {
1406          c.getPieces().add(gen.new Piece(null, translate("sd.table", "See ", ed.getElement().getPath()), null));
1407          c.getPieces().add(gen.new Piece("#"+ed.getElement().getPath(), tail(ed.getElement().getPath()), ed.getElement().getPath()));
1408        } else {
1409          c.getPieces().add(gen.new Piece(null, translate("sd.table", "See ", ed.getElement().getPath()), null));
1410          c.getPieces().add(gen.new Piece(pfx(corePath, ed.getSource().getUserString("path"))+"#"+ed.getElement().getPath(), tail(ed.getElement().getPath())+" ("+ed.getSource().getTypeName()+")", ed.getElement().getPath()));
1411        }
1412      }
1413      return c;
1414    }
1415    List<TypeRefComponent> types = e.getType();
1416    if (!e.hasType()) {
1417      if (root) { // we'll use base instead of types then
1418        StructureDefinition bsd = profile == null ? null : context.getWorker().fetchResource(StructureDefinition.class, profile.getBaseDefinition(), profile);
1419        if (bsd != null) {
1420          if (bsd.hasUserData("path")) {
1421            c.getPieces().add(gen.new Piece(Utilities.isAbsoluteUrl(bsd.getUserString("path")) ? bsd.getUserString("path") : imagePath +bsd.getUserString("path"), bsd.getName(), null));
1422          } else {
1423            c.getPieces().add(gen.new Piece(null, bsd.getName(), null));
1424          }
1425        }
1426        return c;
1427      } else if (e.hasContentReference()) {
1428        return c;
1429      } else {
1430        ElementDefinition d = (ElementDefinition) e.getUserData(ProfileUtilities.UD_DERIVATION_POINTER);
1431        if (d != null && d.hasType()) {
1432          types = new ArrayList<ElementDefinition.TypeRefComponent>();
1433          for (TypeRefComponent tr : d.getType()) {
1434            TypeRefComponent tt = tr.copy();
1435            tt.setUserData(ProfileUtilities.UD_DERIVATION_EQUALS, true);
1436            types.add(tt);
1437          }
1438        } else {
1439          return c;
1440        }
1441      }
1442    }
1443
1444    boolean first = true;
1445
1446    TypeRefComponent tl = null;
1447    for (TypeRefComponent t : types) {
1448      if (!mustSupportMode || allTypesMustSupport(e) || isMustSupport(t)) {
1449        if (first) {
1450          first = false;
1451        } else {
1452          c.addPiece(checkForNoChange(tl, gen.new Piece(null,", ", null)));
1453        }
1454        tl = t;
1455        if (t.hasTarget()) {
1456          c.getPieces().add(gen.new Piece(corePath+"references.html", t.getWorkingCode(), null));
1457          if (!mustSupportMode && isMustSupportDirect(t) && e.getMustSupport()) {
1458            c.addPiece(gen.new Piece(null, " ", null));
1459            c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false);
1460          }
1461          c.getPieces().add(gen.new Piece(null, "(", null));
1462          boolean tfirst = true;
1463          for (CanonicalType u : t.getTargetProfile()) {
1464            if (!mustSupportMode || allProfilesMustSupport(t.getTargetProfile()) || isMustSupport(u)) {
1465              if (tfirst)
1466                tfirst = false;
1467              else
1468                c.addPiece(gen.new Piece(null, " | ", null));
1469              genTargetLink(gen, profileBaseFileName, corePath, c, t, u.getValue(), null);
1470              if (!mustSupportMode && isMustSupport(u) && e.getMustSupport()) {
1471                c.addPiece(gen.new Piece(null, " ", null));
1472                c.addStyledText(translate("sd.table", "This target must be supported"), "S", "white", "red", null, false);
1473              }
1474            }
1475          }
1476          c.getPieces().add(gen.new Piece(null, ")", null));
1477          if (t.getAggregation().size() > 0) {
1478            c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", " {", null));
1479            boolean firstA = true;
1480            for (Enumeration<AggregationMode> a : t.getAggregation()) {
1481              if (firstA == true)
1482                firstA = false;
1483              else
1484                c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", ", ", null));
1485              c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", codeForAggregation(a.getValue()), hintForAggregation(a.getValue())));
1486            }
1487            c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", "}", null));
1488          }
1489        } else if (t.hasProfile() && (!t.getWorkingCode().equals("Extension") || isProfiledType(t.getProfile()))) { // a profiled type
1490          String ref;
1491          boolean pfirst = true;
1492          for (CanonicalType p : t.getProfile()) {
1493            if (!mustSupportMode || allProfilesMustSupport(t.getProfile()) || isMustSupport(p)) {
1494              if (pfirst) {
1495                pfirst = false;
1496              } else {
1497                c.addPiece(checkForNoChange(tl, gen.new Piece(null,", ", null)));
1498              }          
1499
1500              ref = context.getPkp() == null ? null : context.getPkp().getLinkForProfile(profile, p.getValue());
1501              if (ref != null) {
1502                String[] parts = ref.split("\\|");
1503                if (parts[0].startsWith("http:") || parts[0].startsWith("https:")) {
1504                  if (p.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT)) {
1505                    String pp = p.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT);
1506                    pp = pp.substring(pp.indexOf("."));
1507                    c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], parts[1]+pp, t.getWorkingCode())));
1508                  } else {
1509                    c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], parts[1], t.getWorkingCode())));
1510                  }
1511                } else {
1512                  c.addPiece(checkForNoChange(t, gen.new Piece((p.getValue().startsWith(corePath+"StructureDefinition")? corePath: "")+parts[0], parts[1], t.getWorkingCode())));
1513                }
1514              } else {
1515                c.addPiece(checkForNoChange(t, gen.new Piece((p.getValue().startsWith(corePath)? corePath: "")+ref, t.getWorkingCode(), null)));
1516              }
1517              if (!mustSupportMode && isMustSupport(p) && e.getMustSupport()) {
1518                c.addPiece(gen.new Piece(null, " ", null));
1519                c.addStyledText(translate("sd.table", "This profile must be supported"), "S", "white", "red", null, false);
1520              }
1521            }
1522          }
1523        } else {
1524          String tc = t.getWorkingCode();
1525          if (Utilities.isAbsoluteUrl(tc)) {
1526            StructureDefinition sd = context.getWorker().fetchTypeDefinition(tc);
1527            if (sd == null) {
1528              c.addPiece(checkForNoChange(t, gen.new Piece(context.getPkp().getLinkFor(corePath, tc), tc, null)));
1529            } else {
1530              c.addPiece(checkForNoChange(t, gen.new Piece(context.getPkp().getLinkFor(corePath, tc), sd.getTypeName(), null)));           
1531            }
1532          } else if (context.getPkp() != null && context.getPkp().hasLinkFor(tc)) {
1533            c.addPiece(checkForNoChange(t, gen.new Piece(context.getPkp().getLinkFor(corePath, tc), tc, null)));
1534          } else {
1535            c.addPiece(checkForNoChange(t, gen.new Piece(null, tc, null)));
1536          }
1537          if (!mustSupportMode && isMustSupportDirect(t) && e.getMustSupport()) {
1538            c.addPiece(gen.new Piece(null, " ", null));
1539            c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false);
1540          }
1541        }
1542      }
1543    }
1544    return c;
1545  }
1546
1547
1548  private String pfx(String prefix, String url) {
1549    return Utilities.isAbsoluteUrl(url) ? url : prefix + url;
1550  }
1551
1552  private void genTargetLink(HierarchicalTableGenerator gen, String profileBaseFileName, String corePath, Cell c, TypeRefComponent t, String u, Resource src) {
1553    if (u.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
1554      StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, u, src);
1555      if (sd != null) {
1556        String disp = sd.hasTitle() ? sd.getTitle() : sd.getName();
1557        c.addPiece(checkForNoChange(t, gen.new Piece(checkPrepend(corePath, sd.getUserString("path")), disp, null)));
1558      } else {
1559        String rn = u.substring(40);
1560        c.addPiece(checkForNoChange(t, gen.new Piece(context.getPkp().getLinkFor(corePath, rn), rn, null)));
1561      }
1562    } else if (Utilities.isAbsoluteUrl(u)) {
1563      StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, u, src);
1564      if (sd != null && context.getPkp() != null) {
1565        String disp = sd.hasTitle() ? sd.getTitle() : sd.getName();
1566        String ref = context.getPkp().getLinkForProfile(null, sd.getUrl());
1567        if (ref != null && ref.contains("|"))
1568          ref = ref.substring(0,  ref.indexOf("|"));
1569        c.addPiece(checkForNoChange(t, gen.new Piece(ref, disp, null)));
1570      } else
1571        c.addPiece(checkForNoChange(t, gen.new Piece(null, u, null)));        
1572    } else if (t.hasTargetProfile() && u.startsWith("#"))
1573      c.addPiece(checkForNoChange(t, gen.new Piece(corePath+profileBaseFileName+"."+u.substring(1).toLowerCase()+".html", u, null)));
1574  }
1575
1576  private boolean isProfiledType(List<CanonicalType> theProfile) {
1577    for (CanonicalType next : theProfile){
1578      if (StringUtils.defaultString(next.getValueAsString()).contains(":")) {
1579        return true;
1580      }
1581    }
1582    return false;
1583  }
1584
1585
1586  public String codeForAggregation(AggregationMode a) {
1587    switch (a) {
1588    case BUNDLED : return "b";
1589    case CONTAINED : return "c";
1590    case REFERENCED: return "r";
1591    default: return "?";
1592    }
1593  }
1594
1595  public String hintForAggregation(AggregationMode a) {
1596    if (a != null)
1597      return a.getDefinition();
1598    else 
1599      return null;
1600  }
1601
1602
1603  private String checkPrepend(String corePath, String path) {
1604    if (context.getPkp() != null && context.getPkp().prependLinks() && !(path.startsWith("http:") || path.startsWith("https:")))
1605      return corePath+path;
1606    else 
1607      return path;
1608  }
1609
1610
1611  private ElementDefinition findParent(List<ElementDefinition> list, int i, String path) {
1612    while (i > 0 && !path.startsWith(list.get(i).getPath()+".")) {
1613      i--;
1614    }
1615    return list.get(i);
1616  }
1617
1618  private boolean isSibling(String[] pathCurrent, String[] pathLast, int firstDiff) {
1619    return pathCurrent.length == pathLast.length && firstDiff == pathCurrent.length-1;
1620  }
1621
1622
1623  private boolean isChild(String[] pathCurrent, String[] pathLast, int firstDiff) {
1624    return pathCurrent.length == pathLast.length+1 && firstDiff == pathLast.length;
1625  }
1626
1627  private String makeTail(String[] pathCurrent, int start, int index) {
1628    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(".");
1629    for (int i = start; i <= index; i++) {
1630      b.append(pathCurrent[i]);
1631    }
1632    return b.toString();
1633  }
1634
1635  private void genGridElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element, List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName, Boolean extensions, String corePath, String imagePath, boolean root, boolean isConstraintMode) throws IOException, FHIRException {
1636    StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1);
1637    String s = tail(element.getPath());
1638    List<ElementDefinition> children = getChildren(all, element);
1639    boolean isExtension = (s.equals("extension") || s.equals("modifierExtension"));
1640
1641    if (!onlyInformationIsMapping(all, element)) {
1642      Row row = gen.new Row();
1643      row.setAnchor(element.getPath());
1644      row.setColor(context.getProfileUtilities().getRowColor(element, isConstraintMode));
1645      if (element.hasSlicing())
1646        row.setLineColor(1);
1647      else if (element.hasSliceName())
1648        row.setLineColor(2);
1649      else
1650        row.setLineColor(0);
1651      boolean hasDef = element != null;
1652      String ref = defPath == null ? null : defPath + element.getId();
1653      UnusedTracker used = new UnusedTracker();
1654      used.used = true;
1655      Cell left = gen.new Cell();
1656      if (element.getType().size() == 1 && element.getType().get(0).isPrimitive())
1657        left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())).addStyle("font-weight:bold"));
1658      else
1659        left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())));
1660      if (element.hasSliceName()) {
1661        left.getPieces().add(gen.new Piece("br"));
1662        String indent = StringUtils.repeat('\u00A0', 1+2*(element.getPath().split("\\.").length));
1663        left.getPieces().add(gen.new Piece(null, indent + "("+element.getSliceName() + ")", null));
1664      }
1665      row.getCells().add(left);
1666
1667      genCardinality(gen, element, row, hasDef, used, null);
1668      if (hasDef && !"0".equals(element.getMax()))
1669        genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath, root, false);
1670      else
1671        row.getCells().add(gen.new Cell());
1672      generateGridDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, null);
1673/*      if (element.hasSlicing()) {
1674        if (standardExtensionSlicing(element)) {
1675          used.used = element.hasType() && element.getType().get(0).hasProfile();
1676          showMissing = false;
1677        } else {
1678          row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE);
1679          row.getCells().get(2).getPieces().clear();
1680          for (Cell cell : row.getCells())
1681            for (Piece p : cell.getPieces()) {
1682              p.addStyle("font-style: italic");
1683            }
1684        }
1685      }*/
1686      rows.add(row);
1687      for (ElementDefinition child : children)
1688        if (child.getMustSupport())
1689          genGridElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, corePath, imagePath, false, isConstraintMode);
1690    }
1691  }
1692
1693
1694  private ExtensionContext locateExtension(Class<StructureDefinition> class1, String value)  {
1695    if (value.contains("#")) {
1696      StructureDefinition ext = context.getWorker().fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
1697      if (ext == null)
1698        return null;
1699      String tail = value.substring(value.indexOf("#")+1);
1700      ElementDefinition ed = null;
1701      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
1702        if (tail.equals(ted.getSliceName())) {
1703          ed = ted;
1704          return new ExtensionContext(ext, ed);
1705        }
1706      }
1707      return null;
1708    } else {
1709      StructureDefinition ext = context.getWorker().fetchResource(StructureDefinition.class, value);
1710      if (ext == null)
1711        return null;
1712      else 
1713        return new ExtensionContext(ext, ext.getSnapshot().getElement().get(0));
1714    }
1715  }
1716
1717
1718  private boolean extensionIsComplex(String value) {
1719    if (value.contains("#")) {
1720      StructureDefinition ext = context.getWorker().fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
1721    if (ext == null)
1722      return false;
1723      String tail = value.substring(value.indexOf("#")+1);
1724      ElementDefinition ed = null;
1725      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
1726        if (tail.equals(ted.getSliceName())) {
1727          ed = ted;
1728          break;
1729        }
1730      }
1731      if (ed == null)
1732        return false;
1733      int i = ext.getSnapshot().getElement().indexOf(ed);
1734      int j = i+1;
1735      while (j < ext.getSnapshot().getElement().size() && !ext.getSnapshot().getElement().get(j).getPath().equals(ed.getPath()))
1736        j++;
1737      return j - i > 5;
1738    } else {
1739      StructureDefinition ext = context.getWorker().fetchResource(StructureDefinition.class, value);
1740      return ext != null && ext.getSnapshot().getElement().size() > 5;
1741    }
1742  }
1743
1744
1745 
1746
1747  private BindingResolution makeNullBr(ElementDefinitionBindingComponent binding) {
1748    BindingResolution br = new BindingResolution();
1749    br.url = "http://none.none/none";
1750    br.display = "todo";
1751    return br;
1752  }
1753
1754  private ElementDefinitionBindingComponent makeUnifiedBinding(ElementDefinitionBindingComponent binding, ElementDefinition element) {
1755    if (!element.hasUserData(ProfileUtilities.UD_DERIVATION_POINTER)) {
1756      return binding;
1757    }
1758    ElementDefinition base = (ElementDefinition) element.getUserData(ProfileUtilities.UD_DERIVATION_POINTER);
1759    if (!base.hasBinding()) {
1760      return binding;
1761    }
1762    ElementDefinitionBindingComponent o = base.getBinding();
1763    ElementDefinitionBindingComponent b = new ElementDefinitionBindingComponent();
1764    b.setUserData(ProfileUtilities.UD_DERIVATION_POINTER, o);
1765    if (binding.hasValueSet()) {
1766      b.setValueSet(binding.getValueSet());
1767    } else if (o.hasValueSet()) {
1768      b.setValueSet(o.getValueSet());
1769      b.getValueSetElement().setUserData(ProfileUtilities.UD_DERIVATION_EQUALS, o.getValueSetElement());
1770    }
1771    if (binding.hasStrength()) {
1772      b.setStrength(binding.getStrength());
1773    } else if (o.hasStrength()) {
1774      b.setStrength(o.getStrength());
1775      b.getStrengthElement().setUserData(ProfileUtilities.UD_DERIVATION_EQUALS, o.getStrengthElement());
1776    }
1777    if (binding.hasDescription()) {
1778      b.setDescription(binding.getDescription());
1779    } else if (o.hasDescription()) {
1780      b.setDescription(o.getDescription());
1781      b.getDescriptionElement().setUserData(ProfileUtilities.UD_DERIVATION_EQUALS, o.getDescriptionElement());
1782    }
1783    // todo: derivation?
1784    b.getExtension().addAll(binding.getExtension());
1785    return b;
1786  }
1787
1788  private void genFixedValue(HierarchicalTableGenerator gen, Row erow, DataType value, boolean snapshot, boolean pattern, String corePath, boolean skipnoValue) {
1789    String ref = context.getPkp().getLinkFor(corePath, value.fhirType());
1790    if (ref != null && ref.contains(".html")) {
1791      ref = ref.substring(0, ref.indexOf(".html"))+"-definitions.html#";
1792    } else {
1793      ref = "?gen-fv?";
1794    }
1795    StructureDefinition sd = context.getWorker().fetchTypeDefinition(value.fhirType());
1796
1797    for (org.hl7.fhir.r5.model.Property t : value.children()) {
1798      if (t.getValues().size() > 0 || snapshot) {
1799        ElementDefinition ed = findElementDefinition(sd, t.getName());
1800        if (t.getValues().size() == 0 || (t.getValues().size() == 1 && t.getValues().get(0).isEmpty())) {
1801          if (!skipnoValue) {
1802            Row row = gen.new Row();
1803            erow.getSubRows().add(row);
1804            Cell c = gen.new Cell();
1805            row.getCells().add(c);
1806            c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref+ed.getPath() : corePath+(VersionUtilities.isR5Ver(context.getWorker().getVersion()) ? "types-definitions.html#"+ed.getBase().getPath() : "element-definitions.html#"+ed.getBase().getPath())), t.getName(), null));
1807            c = gen.new Cell();
1808            row.getCells().add(c);
1809            c.addPiece(gen.new Piece(null, null, null));
1810            c = gen.new Cell();
1811            row.getCells().add(c);
1812            if (!pattern) {
1813              c.addPiece(gen.new Piece(null, "0..0", null));
1814              row.setIcon("icon_fixed.gif", "Fixed Value" /*HierarchicalTableGenerator.TEXT_ICON_FIXED*/);
1815            } else if (isPrimitive(t.getTypeCode())) {
1816              row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
1817              c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null));
1818            } else if (isReference(t.getTypeCode())) { 
1819              row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
1820              c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null));
1821            } else { 
1822              row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
1823              c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null));
1824            }
1825            c = gen.new Cell();
1826            row.getCells().add(c);
1827            if (t.getTypeCode().contains("(")) {
1828              String tc = t.getTypeCode();
1829              String tn = tc.substring(0, tc.indexOf("("));
1830              c.addPiece(gen.new Piece(context.getPkp().getLinkFor(corePath, tn), tn, null));
1831              c.addPiece(gen.new Piece(null, "(", null));
1832              String[] p = tc.substring(tc.indexOf("(")+1, tc.indexOf(")")).split("\\|");
1833              for (String s : p) {
1834                c.addPiece(gen.new Piece(context.getPkp().getLinkFor(corePath, s), s, null));
1835              }
1836              c.addPiece(gen.new Piece(null, ")", null));            
1837            } else {
1838              c.addPiece(gen.new Piece(context.getPkp().getLinkFor(corePath, t.getTypeCode()), t.getTypeCode(), null));
1839            }
1840            c = gen.new Cell();
1841            c.addPiece(gen.new Piece(null, ed.getShort(), null));
1842            row.getCells().add(c);
1843          }
1844        } else {
1845          for (Base b : t.getValues()) {
1846            Row row = gen.new Row();
1847            erow.getSubRows().add(row);
1848            row.setIcon("icon_fixed.gif", "Fixed Value" /*HierarchicalTableGenerator.TEXT_ICON_FIXED*/);
1849
1850            Cell c = gen.new Cell();
1851            row.getCells().add(c);
1852            c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref+ed.getPath() : (VersionUtilities.isR5Ver(context.getWorker().getVersion()) ? corePath+"types-definitions.html#"+ed.getBase().getPath() : corePath+"element-definitions.html#"+ed.getBase().getPath())), t.getName(), null));
1853
1854            c = gen.new Cell();
1855            row.getCells().add(c);
1856            c.addPiece(gen.new Piece(null, null, null));
1857
1858            c = gen.new Cell();
1859            row.getCells().add(c);
1860            if (pattern)
1861              c.addPiece(gen.new Piece(null, "1.."+(t.getMaxCardinality() == 2147483647 ? "*" : Integer.toString(t.getMaxCardinality())), null));
1862            else
1863              c.addPiece(gen.new Piece(null, "1..1", null));
1864
1865            c = gen.new Cell();
1866            row.getCells().add(c);
1867            if (b.fhirType().contains("(")) {
1868              String tc = b.fhirType();
1869              String tn = tc.substring(0, tc.indexOf("("));
1870              c.addPiece(gen.new Piece(context.getPkp().getLinkFor(corePath, tn), tn, null));
1871              c.addPiece(gen.new Piece(null, "(", null));
1872              String[] p = tc.substring(tc.indexOf("(")+1, tc.indexOf(")")).split("\\|");
1873              for (String s : p) {
1874                c.addPiece(gen.new Piece(context.getPkp().getLinkFor(corePath, s), s, null));
1875              }
1876              c.addPiece(gen.new Piece(null, ")", null));            
1877            } else {
1878              c.addPiece(gen.new Piece(context.getPkp().getLinkFor(corePath, b.fhirType()), b.fhirType(), null));
1879            }
1880
1881            if (b.isPrimitive()) {
1882              c = gen.new Cell();
1883              row.getCells().add(c);
1884              c.addPiece(gen.new Piece(null, ed.getShort(), null));
1885              c.addPiece(gen.new Piece("br"));
1886              c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold"));
1887              String s = b.primitiveValue();
1888              // ok. let's see if we can find a relevant link for this
1889              String link = null;
1890              if (Utilities.isAbsoluteUrl(s)) {
1891                link = context.getPkp().getLinkForUrl(corePath, s);
1892              }
1893              c.getPieces().add(gen.new Piece(link, s, null).addStyle("color: darkgreen"));
1894            } else {
1895              c = gen.new Cell();
1896              row.getCells().add(c);
1897              c.addPiece(gen.new Piece(null, ed.getShort(), null));
1898              c.addPiece(gen.new Piece("br"));
1899              c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold"));
1900              c.getPieces().add(gen.new Piece(null, "(complex)", null).addStyle("color: darkgreen"));
1901              genFixedValue(gen, row, (DataType) b, snapshot, pattern, corePath, skipnoValue);
1902            }
1903          }
1904        }
1905      }
1906    }
1907  }
1908
1909
1910  private ElementDefinition findElementDefinition(StructureDefinition sd, String name) {
1911    String path = sd.getTypeName()+"."+name;
1912    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
1913      if (ed.getPath().equals(path))
1914        return ed;
1915    }
1916    throw new FHIRException(context.getWorker().formatMessage(I18nConstants.UNABLE_TO_FIND_ELEMENT_, path));
1917  }
1918
1919
1920  private String getFixedUrl(StructureDefinition sd) {
1921    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
1922      if (ed.getPath().equals("Extension.url")) {
1923        if (ed.hasFixed() && ed.getFixed() instanceof UriType)
1924          return ed.getFixed().primitiveValue();
1925      }
1926    }
1927    return null;
1928  }
1929
1930
1931  private Piece describeCoded(HierarchicalTableGenerator gen, DataType fixed) {
1932    if (fixed instanceof Coding) {
1933      Coding c = (Coding) fixed;
1934      ValidationResult vr = context.getWorker().validateCode(context.getTerminologyServiceOptions(), c.getSystem(), c.getVersion(), c.getCode(), c.getDisplay());
1935      if (vr.getDisplay() != null)
1936        return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen");
1937    } else if (fixed instanceof CodeableConcept) {
1938      CodeableConcept cc = (CodeableConcept) fixed;
1939      for (Coding c : cc.getCoding()) {
1940        ValidationResult vr = context.getWorker().validateCode(context.getTerminologyServiceOptions(), c.getSystem(), c.getVersion(), c.getCode(), c.getDisplay());
1941        if (vr.getDisplay() != null)
1942          return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen");
1943      }
1944    }
1945    return null;
1946  }
1947
1948
1949  private boolean hasDescription(DataType fixed) {
1950    if (fixed instanceof Coding) {
1951      return ((Coding) fixed).hasDisplay();
1952    } else if (fixed instanceof CodeableConcept) {
1953      CodeableConcept cc = (CodeableConcept) fixed;
1954      if (cc.hasText())
1955        return true;
1956      for (Coding c : cc.getCoding())
1957        if (c.hasDisplay())
1958         return true;
1959    } // (fixed instanceof CodeType) || (fixed instanceof Quantity);
1960    return false;
1961  }
1962
1963
1964  private boolean isCoded(DataType fixed) {
1965    return (fixed instanceof Coding) || (fixed instanceof CodeableConcept) || (fixed instanceof CodeType) || (fixed instanceof Quantity);
1966  }
1967
1968
1969  private Cell generateGridDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, ElementDefinition valueDefn) throws IOException, FHIRException {
1970    Cell c = gen.new Cell();
1971    row.getCells().add(c);
1972
1973    if (used) {
1974      if (definition.hasContentReference()) {
1975        ElementInStructure ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference(), profile);
1976        if (ed == null)
1977          c.getPieces().add(gen.new Piece(null, "Unknown reference to "+definition.getContentReference(), null));
1978        else {
1979          if (ed.getSource() == profile) {
1980            c.getPieces().add(gen.new Piece("#"+ed.getElement().getPath(), "See "+ed.getElement().getPath(), null));
1981          } else {
1982            c.getPieces().add(gen.new Piece(ed.getSource().getUserData("path")+"#"+ed.getElement().getPath(), "See "+ed.getSource().getTypeName()+"."+ed.getElement().getPath(), null));
1983          }          
1984        }
1985      }
1986      if (definition.getPath().endsWith("url") && definition.hasFixed()) {
1987        c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen")));
1988      } else {
1989        if (url != null) {
1990          if (!c.getPieces().isEmpty()) 
1991            c.addPiece(gen.new Piece("br"));
1992          String fullUrl = url.startsWith("#") ? baseURL+url : url;
1993          StructureDefinition ed = context.getWorker().fetchResource(StructureDefinition.class, url, profile);
1994          String ref = null;
1995          if (ed != null) {
1996            String p = ed.getUserString("path");
1997            if (p != null) {
1998              ref = p.startsWith("http:") || context.getRules() == GenerationRules.IG_PUBLISHER ? p : Utilities.pathURL(corePath, p);
1999            }
2000          }
2001          c.getPieces().add(gen.new Piece(null, "URL: ", null).addStyle("font-weight:bold"));
2002          c.getPieces().add(gen.new Piece(ref, fullUrl, null));
2003        }
2004
2005        if (definition.hasSlicing()) {
2006          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
2007          c.getPieces().add(gen.new Piece(null, "Slice: ", null).addStyle("font-weight:bold"));
2008          c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null));
2009        }
2010        if (definition != null) {
2011          ElementDefinitionBindingComponent binding = null;
2012          if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty())
2013            binding = valueDefn.getBinding();
2014          else if (definition.hasBinding())
2015            binding = definition.getBinding();
2016          if (binding!=null && !binding.isEmpty()) {
2017            if (!c.getPieces().isEmpty()) 
2018              c.addPiece(gen.new Piece("br"));
2019            BindingResolution br = context.getPkp().resolveBinding(profile, binding, definition.getPath());
2020            c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, "Binding: ", null).addStyle("font-weight:bold")));
2021            c.getPieces().add(checkForNoChange(binding, gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !context.getPkp().prependLinks() ? br.url : corePath+br.url, br.display, null)));
2022            if (binding.hasStrength()) {
2023              c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null)));
2024              c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), binding.getStrength().toCode(), binding.getStrength().getDefinition())));              c.getPieces().add(gen.new Piece(null, ")", null));
2025            }
2026            if (binding.hasDescription() && MarkDownProcessor.isSimpleMarkdown(binding.getDescription())) {
2027              c.getPieces().add(gen.new Piece(null, ": ", null));
2028              c.addMarkdownNoPara(PublicationHacker.fixBindingDescriptions(context.getWorker(), binding.getDescriptionElement()).asStringValue());
2029            }
2030          }
2031          for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
2032            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
2033            c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold")));
2034            if (inv.getHumanElement().hasExtension(ToolingExtensions.EXT_REND_MD)) {
2035              c.addMarkdown(inv.getHumanElement().getExtensionString(ToolingExtensions.EXT_REND_MD));
2036            } else {
2037              c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null)));
2038            }
2039          }
2040          if (definition.hasFixed()) {
2041            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
2042            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight:bold")));
2043            String s = buildJson(definition.getFixed());
2044            String link = null;
2045            if (Utilities.isAbsoluteUrl(s))
2046              link = context.getPkp().getLinkForUrl(corePath, s);
2047            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen")));
2048          } else if (definition.hasPattern()) {
2049            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
2050            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "Required Pattern: ", null).addStyle("font-weight:bold")));
2051            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen")));
2052          } else if (definition.hasExample()) {
2053            for (ElementDefinitionExampleComponent ex : definition.getExample()) {
2054              if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
2055              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, "Example'"+("".equals("General")? "" : " "+ex.getLabel()+"'")+": ", null).addStyle("font-weight:bold")));
2056              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen")));
2057            }
2058          }
2059          if (definition.hasMaxLength() && definition.getMaxLength()!=0) {
2060            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
2061            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold")));
2062            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen")));
2063          }
2064          if (profile != null) {
2065            for (StructureDefinitionMappingComponent md : profile.getMapping()) {
2066              if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) {
2067                ElementDefinitionMappingComponent map = null;
2068                for (ElementDefinitionMappingComponent m : definition.getMapping()) 
2069                  if (m.getIdentity().equals(md.getIdentity()))
2070                    map = m;
2071                if (map != null) {
2072                  for (int i = 0; i<definition.getMapping().size(); i++){
2073                    c.addPiece(gen.new Piece("br"));
2074                    c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null));
2075                  }
2076                }
2077              }
2078            }
2079          }
2080          if (definition.hasDefinition()) {
2081            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
2082            c.getPieces().add(gen.new Piece(null, "Definition: ", null).addStyle("font-weight:bold"));
2083            c.addPiece(gen.new Piece("br"));
2084            c.addMarkdown(definition.getDefinition());
2085//            c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null)));
2086          }
2087          if (definition.getComment()!=null) {
2088            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
2089            c.getPieces().add(gen.new Piece(null, "Comments: ", null).addStyle("font-weight:bold"));
2090            c.addPiece(gen.new Piece("br"));
2091            c.addMarkdown(definition.getComment());
2092//            c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null)));
2093          }
2094        }
2095      }
2096    }
2097    return c;
2098  }
2099
2100  private boolean onlyInformationIsMapping(List<ElementDefinition> list, ElementDefinition e) {
2101    return (!e.hasSliceName() && !e.hasSlicing() && (onlyInformationIsMapping(e))) &&
2102        getChildren(list, e).isEmpty();
2103  }
2104
2105  private boolean onlyInformationIsMapping(ElementDefinition d) {
2106    return !d.hasShort() && !d.hasDefinition() &&
2107        !d.hasRequirements() && !d.getAlias().isEmpty() && !d.hasMinElement() &&
2108        !d.hasMax() && !d.getType().isEmpty() && !d.hasContentReference() &&
2109        !d.hasExample() && !d.hasFixed() && !d.hasMaxLengthElement() &&
2110        !d.getCondition().isEmpty() && !d.getConstraint().isEmpty() && !d.hasMustSupportElement() &&
2111        !d.hasBinding();
2112  }
2113
2114  private boolean allAreReference(List<TypeRefComponent> types) {
2115    for (TypeRefComponent t : types) {
2116      if (!t.hasTarget())
2117        return false;
2118    }
2119    return true;
2120  }
2121
2122  private List<ElementDefinition> getChildren(List<ElementDefinition> all, ElementDefinition element) {
2123    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
2124    int i = all.indexOf(element)+1;
2125    while (i < all.size() && all.get(i).getPath().length() > element.getPath().length()) {
2126      if ((all.get(i).getPath().substring(0, element.getPath().length()+1).equals(element.getPath()+".")) && !all.get(i).getPath().substring(element.getPath().length()+1).contains("."))
2127        result.add(all.get(i));
2128      i++;
2129    }
2130    return result;
2131  }
2132
2133
2134  protected String tail(String path) {
2135    if (path == null) {
2136      return "";
2137    } else if (path.contains("."))
2138      return path.substring(path.lastIndexOf('.')+1);
2139    else
2140      return path;
2141  }
2142
2143
2144
2145  protected boolean isPrimitive(String value) {
2146    StructureDefinition sd = context.getWorker().fetchTypeDefinition(value);
2147    if (sd == null) // might be running before all SDs are available
2148      return Utilities.existsInList(value, "base64Binary", "boolean", "canonical", "code", "date", "dateTime", "decimal", "id", "instant", "integer", "integer64", "markdown", "oid", "positiveInt", "string", "time", "unsignedInt", "uri", "url", "uuid");
2149    else 
2150      return sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
2151  }
2152
2153
2154  private boolean isDataType(String value) {
2155    StructureDefinition sd = context.getWorker().fetchTypeDefinition(value);
2156    if (sd == null) // might be running before all SDs are available
2157      return Utilities.existsInList(value, "Address", "Age", "Annotation", "Attachment", "CodeableConcept", "Coding", "ContactPoint", "Count", "Distance", "Duration", "HumanName", "Identifier", "Money", "Period", "Quantity", "Range", "Ratio", "Reference", "SampledData", "Signature", "Timing", 
2158            "ContactDetail", "Contributor", "DataRequirement", "Expression", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition", "UsageContext");
2159    else 
2160      return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION;
2161  }
2162
2163  private boolean slicesExist(List<ElementDefinition> elements, ElementDefinition element) {
2164    if (elements == null) {
2165      return true;
2166    }
2167    boolean found = false;
2168    int start = elements.indexOf(element);
2169    if (start < 0) {
2170      return false;
2171    }
2172    for (int i = start; i < elements.size(); i++) {
2173      ElementDefinition ed = elements.get(i);
2174      if (ed.getPath().equals(element.getPath())) {
2175        if (ed.hasSliceName()) {
2176          found = true;
2177        }
2178      }
2179      if (ed.getPath().length() < element.getPath().length()) {
2180        break;
2181      }
2182    }
2183    return found;
2184  }
2185
2186
2187    private Cell addCell(Row row, Cell cell) {
2188    row.getCells().add(cell);
2189    return (cell);
2190  }
2191
2192  private String checkAdd(String src, String app) {
2193    return app == null ? src : src + app;
2194  }
2195
2196  public boolean hasNonBaseConditions(List<IdType> conditions) {
2197    for (IdType c : conditions) {
2198      if (!isBaseCondition(c)) {
2199        return true;
2200      }
2201    }
2202    return false;
2203  }
2204
2205
2206  public boolean hasNonBaseConstraints(List<ElementDefinitionConstraintComponent> constraints) {
2207    for (ElementDefinitionConstraintComponent c : constraints) {
2208      if (!isBaseConstraint(c)) {
2209        return true;
2210      }
2211    }
2212    return false;
2213  }
2214
2215  public String listConstraintsAndConditions(ElementDefinition element) {
2216    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
2217    for (ElementDefinitionConstraintComponent con : element.getConstraint()) {
2218      if (!isBaseConstraint(con)) {
2219        b.append(con.getKey());
2220      }
2221    }
2222    for (IdType id : element.getCondition()) {
2223      if (!isBaseCondition(id)) {
2224        b.append(id.asStringValue());
2225      }
2226    }
2227    return b.toString();
2228  }
2229
2230  private boolean isBaseCondition(IdType c) {
2231    String key = c.asStringValue();
2232    return key != null && (key.startsWith("ele-") || key.startsWith("res-") || key.startsWith("ext-") || key.startsWith("dom-") || key.startsWith("dr-"));
2233  }
2234
2235  private boolean isBaseConstraint(ElementDefinitionConstraintComponent con) {
2236    String key = con.getKey();
2237    return key != null && (key.startsWith("ele-") || key.startsWith("res-") || key.startsWith("ext-") || key.startsWith("dom-") || key.startsWith("dr-"));
2238  }
2239
2240  private void makeChoiceRows(List<Row> subRows, ElementDefinition element, HierarchicalTableGenerator gen, String corePath, String profileBaseFileName, boolean mustSupportMode, Resource src) {
2241    // create a child for each choice
2242    for (TypeRefComponent tr : element.getType()) {
2243      if (!mustSupportMode || allTypesMustSupport(element) || isMustSupport(tr)) {
2244        Row choicerow = gen.new Row();
2245        String t = tr.getWorkingCode();
2246        if (isReference(t)) {
2247          choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), null, null));
2248          choicerow.getCells().add(gen.new Cell());
2249          choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
2250          choicerow.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
2251          Cell c = gen.new Cell();
2252          choicerow.getCells().add(c);
2253          if (ADD_REFERENCE_TO_TABLE) {
2254            if (tr.getWorkingCode().equals("canonical"))
2255              c.getPieces().add(gen.new Piece(corePath+"datatypes.html#canonical", "canonical", null));
2256            else
2257              c.getPieces().add(gen.new Piece(corePath+"references.html#Reference", "Reference", null));
2258            if (!mustSupportMode && isMustSupportDirect(tr) && element.getMustSupport()) {
2259              c.addPiece(gen.new Piece(null, " ", null));
2260              c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false);
2261            }
2262            c.getPieces().add(gen.new Piece(null, "(", null));
2263          }
2264          boolean first = true;
2265          for (CanonicalType rt : tr.getTargetProfile()) {
2266            if (!mustSupportMode || allProfilesMustSupport(tr.getTargetProfile()) || isMustSupport(rt)) {
2267              if (!first)
2268                c.getPieces().add(gen.new Piece(null, " | ", null));
2269              genTargetLink(gen, profileBaseFileName, corePath, c, tr, rt.getValue(), src);
2270              if (!mustSupportMode && isMustSupport(rt) && element.getMustSupport()) {
2271                c.addPiece(gen.new Piece(null, " ", null));
2272                c.addStyledText(translate("sd.table", "This target must be supported"), "S", "white", "red", null, false);
2273              }
2274              first = false;
2275            }
2276          }
2277          if (first) {
2278            c.getPieces().add(gen.new Piece(null, "Any", null));
2279          }
2280
2281          if (ADD_REFERENCE_TO_TABLE) { 
2282            c.getPieces().add(gen.new Piece(null, ")", null));
2283          }
2284
2285        } else {
2286          StructureDefinition sd = context.getWorker().fetchTypeDefinition(t);
2287          if (sd == null) {
2288            System.out.println("Unable to find "+t);
2289            sd = context.getWorker().fetchTypeDefinition(t);
2290          } else if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) {
2291            choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]",  Utilities.capitalize(t)), sd.getDescription(), null));
2292            choicerow.getCells().add(gen.new Cell());
2293            choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
2294            choicerow.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
2295            Cell c = gen.new Cell(null, corePath+"datatypes.html#"+t, sd.getTypeName(), null, null);
2296            choicerow.getCells().add(c);
2297            if (!mustSupportMode && isMustSupport(tr) && element.getMustSupport()) {
2298              c.addPiece(gen.new Piece(null, " ", null));
2299              c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false);
2300            }
2301          } else {
2302            choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]",  Utilities.capitalize(t)), sd.getDescription(), null));
2303            choicerow.getCells().add(gen.new Cell());
2304            choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
2305            choicerow.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
2306            Cell c = gen.new Cell(null, context.getPkp().getLinkFor(corePath, t), sd.getTypeName(), null, null);
2307            choicerow.getCells().add(c);
2308            if (!mustSupportMode && isMustSupport(tr) && element.getMustSupport()) {
2309              c.addPiece(gen.new Piece(null, " ", null));
2310              c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false);
2311            }
2312          }
2313          if (tr.hasProfile()) {
2314            Cell typeCell = choicerow.getCells().get(3);
2315            typeCell.addPiece(gen.new Piece(null, "(", null));
2316            boolean first = true;
2317            for (CanonicalType pt : tr.getProfile()) {
2318              if (!mustSupportMode || allProfilesMustSupport(tr.getProfile()) || isMustSupport(pt)) {
2319                if (first) first = false; else typeCell.addPiece(gen.new Piece(null, " | ", null));
2320                StructureDefinition psd = context.getWorker().fetchResource(StructureDefinition.class, pt.getValue(), src);
2321                if (psd == null)
2322                  typeCell.addPiece(gen.new Piece(null, "?gen-e2?", null));
2323                else
2324                  typeCell.addPiece(gen.new Piece(psd.getUserString("path"), psd.getName(), psd.present()));
2325                if (!mustSupportMode && isMustSupport(pt) && element.getMustSupport()) {
2326                  typeCell.addPiece(gen.new Piece(null, " ", null));
2327                  typeCell.addStyledText(translate("sd.table", "This profile must be supported"), "S", "white", "red", null, false);
2328                }
2329              }
2330            }
2331            typeCell.addPiece(gen.new Piece(null, ")", null));
2332          }
2333        }    
2334        choicerow.getCells().add(gen.new Cell());
2335        subRows.add(choicerow);
2336      }
2337    }
2338  }
2339
2340  private boolean isReference(String t) {
2341    return t.equals("Reference") || t.equals("canonical"); 
2342  }  
2343
2344
2345
2346  private List<ElementChoiceGroup> readChoices(ElementDefinition ed, List<ElementDefinition> children) {
2347    List<ElementChoiceGroup> result = new ArrayList<>();
2348    for (ElementDefinitionConstraintComponent c : ed.getConstraint()) {
2349      ElementChoiceGroup grp = context.getProfileUtilities().processConstraint(children, c);
2350      if (grp != null) {
2351        result.add(grp);
2352      }
2353    }
2354    return result;
2355  }
2356
2357  private Piece checkForNoChange(Element src1, Element src2, Piece piece) {
2358    if (src1.hasUserData(ProfileUtilities.UD_DERIVATION_EQUALS) && src2.hasUserData(ProfileUtilities.UD_DERIVATION_EQUALS)) {
2359      piece.addStyle("opacity: 0.5");
2360    }
2361    return piece;
2362  }
2363
2364
2365  private String buildJson(DataType value) throws IOException {
2366    if (value instanceof PrimitiveType)
2367      return ((PrimitiveType<?>) value).asStringValue();
2368
2369    IParser json = new JsonParser();
2370    return json.composeString(value, null);
2371  }
2372
2373  private String describeSlice(ElementDefinitionSlicingComponent slicing) {
2374    return translate("sd.table", "%s, %s by %s", slicing.getOrdered() ? translate("sd.table", "Ordered") : translate("sd.table", "Unordered"), describe(slicing.getRules()), commas(slicing.getDiscriminator()));
2375  }
2376
2377
2378
2379  private String commas(List<ElementDefinitionSlicingDiscriminatorComponent> list) {
2380    CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder();
2381    for (ElementDefinitionSlicingDiscriminatorComponent id : list)
2382      c.append((id.hasType() ? id.getType().toCode() : "??")+":"+id.getPath());
2383    return c.toString();
2384  }
2385
2386
2387  private String describe(SlicingRules rules) {
2388    if (rules == null)
2389      return translate("sd.table", "Unspecified");
2390    switch (rules) {
2391    case CLOSED : return translate("sd.table", "Closed");
2392    case OPEN : return translate("sd.table", "Open");
2393    case OPENATEND : return translate("sd.table", "Open At End");
2394    default:
2395      return "?gen-sr?";
2396    }
2397  }
2398
2399  private boolean allTypesMustSupport(ElementDefinition e) {
2400    boolean all = true;
2401    boolean any = false;
2402    for (TypeRefComponent tr : e.getType()) {
2403      all = all && isMustSupport(tr);
2404      any = any || isMustSupport(tr);
2405    }
2406    return !all && !any;
2407  }
2408  
2409  private boolean allProfilesMustSupport(List<CanonicalType> profiles) {
2410    boolean all = true;
2411    boolean any = false;
2412    for (CanonicalType u : profiles) {
2413      all = all && isMustSupport(u);
2414      any = any || isMustSupport(u);
2415    }
2416    return !all && !any;
2417  }
2418  public boolean isMustSupportDirect(TypeRefComponent tr) {
2419    return ("true".equals(ToolingExtensions.readStringExtension(tr, ToolingExtensions.EXT_MUST_SUPPORT)));
2420  }
2421
2422  public boolean isMustSupport(TypeRefComponent tr) {
2423    if ("true".equals(ToolingExtensions.readStringExtension(tr, ToolingExtensions.EXT_MUST_SUPPORT))) {
2424      return true;
2425    }
2426    if (isMustSupport(tr.getProfile())) {
2427      return true;
2428    }
2429    return isMustSupport(tr.getTargetProfile());
2430  }
2431
2432  public boolean isMustSupport(List<CanonicalType> profiles) {
2433    for (CanonicalType ct : profiles) {
2434      if (isMustSupport(ct)) {
2435        return true;
2436      }
2437    }
2438    return false;
2439  }
2440
2441
2442  public boolean isMustSupport(CanonicalType profile) {
2443    return "true".equals(ToolingExtensions.readStringExtension(profile, ToolingExtensions.EXT_MUST_SUPPORT));
2444  }
2445
2446
2447
2448  private SpanEntry buildSpanEntryFromProfile(String name, String cardinality, StructureDefinition profile) throws IOException {
2449    SpanEntry res = new SpanEntry();
2450    res.setName(name);
2451    res.setCardinality(cardinality);
2452    res.setProfileLink(profile.getUserString("path"));
2453    res.setResType(profile.getTypeName());
2454    StructureDefinition base = context.getWorker().fetchResource(StructureDefinition.class, res.getResType());
2455    if (base != null)
2456      res.setResLink(base.getUserString("path"));
2457    res.setId(profile.getId());
2458    res.setProfile(profile.getDerivation() == TypeDerivationRule.CONSTRAINT);
2459    StringBuilder b = new StringBuilder();
2460    b.append(res.getResType());
2461    boolean first = true;
2462    boolean open = false;
2463    if (profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
2464      res.setDescription(profile.getName());
2465      for (ElementDefinition ed : profile.getSnapshot().getElement()) {
2466        if (isKeyProperty(ed.getBase().getPath()) && ed.hasFixed()) {
2467          if (first) {
2468            open = true;
2469            first = false;
2470            b.append("[");
2471          } else {
2472            b.append(", ");
2473          }
2474          b.append(tail(ed.getBase().getPath()));
2475          b.append("=");
2476          b.append(summarize(ed.getFixed()));
2477        }
2478      }
2479      if (open)
2480        b.append("]");
2481    } else
2482      res.setDescription("Base FHIR "+profile.getName());
2483    res.setType(b.toString());
2484    return res ;
2485  }
2486
2487
2488  private String summarize(DataType value) throws IOException {
2489    if (value instanceof Coding)
2490      return summarizeCoding((Coding) value);
2491    else if (value instanceof CodeableConcept)
2492      return summarizeCodeableConcept((CodeableConcept) value);
2493    else
2494      return buildJson(value);
2495  }
2496
2497
2498  private String summarizeCoding(Coding value) {
2499    String uri = value.getSystem();
2500    String system = TerminologyRenderer.describeSystem(uri);
2501    if (Utilities.isURL(system)) {
2502      if (system.equals("http://cap.org/protocols"))
2503        system = "CAP Code";
2504    }
2505    return system+" "+value.getCode();
2506  }
2507
2508
2509  private String summarizeCodeableConcept(CodeableConcept value) {
2510    if (value.hasCoding())
2511      return summarizeCoding(value.getCodingFirstRep());
2512    else
2513      return value.getText();
2514  }
2515
2516
2517  private boolean isKeyProperty(String path) {
2518    return Utilities.existsInList(path, "Observation.code");
2519  }
2520
2521
2522  private TableModel initSpanningTable(HierarchicalTableGenerator gen, String prefix, boolean isLogical, String id) {
2523    TableModel model = gen.new TableModel(id, true);
2524    
2525    model.setDocoImg(prefix+"help16.png");
2526    model.setDocoRef(Utilities.pathURL(prefix, "formats.html#table")); // todo: change to graph definition
2527    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Property", "A profiled resource", null, 0));
2528    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Card.", "Minimum and Maximum # of times the the element can appear in the instance", null, 0));
2529    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Content", "What goes here", null, 0));
2530    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Description", "Description of the profile", null, 0));
2531    return model;
2532  }
2533
2534  private void genSpanEntry(HierarchicalTableGenerator gen, List<Row> rows, SpanEntry span) throws IOException {
2535    Row row = gen.new Row();
2536    rows.add(row);
2537    row.setAnchor(span.getId());
2538    //row.setColor(..?);
2539    if (span.isProfile()) {
2540      row.setIcon("icon_profile.png", HierarchicalTableGenerator.TEXT_ICON_PROFILE);
2541    } else {
2542      row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
2543    }
2544    
2545    row.getCells().add(gen.new Cell(null, null, span.getName(), null, null));
2546    row.getCells().add(gen.new Cell(null, null, span.getCardinality(), null, null));
2547    row.getCells().add(gen.new Cell(null, span.getProfileLink(), span.getType(), null, null));
2548    row.getCells().add(gen.new Cell(null, null, span.getDescription(), null, null));
2549
2550    for (SpanEntry child : span.getChildren()) {
2551      genSpanEntry(gen, row.getSubRows(), child);
2552    }
2553  }
2554
2555
2556  public XhtmlNode generateSpanningTable(StructureDefinition profile, String imageFolder, boolean onlyConstraints, String constraintPrefix, Set<String> outputTracker) throws IOException, FHIRException {
2557    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, false, true);
2558    gen.setTranslator(getTranslator());
2559    TableModel model = initSpanningTable(gen, "", false, profile.getId());
2560    Set<String> processed = new HashSet<String>();
2561    SpanEntry span = buildSpanningTable("(focus)", "", profile, processed, onlyConstraints, constraintPrefix);
2562    
2563    genSpanEntry(gen, model.getRows(), span);
2564    return gen.generate(model, "", 0, outputTracker);
2565  }
2566
2567  private SpanEntry buildSpanningTable(String name, String cardinality, StructureDefinition profile, Set<String> processed, boolean onlyConstraints, String constraintPrefix) throws IOException {
2568    SpanEntry res = buildSpanEntryFromProfile(name, cardinality, profile);
2569    boolean wantProcess = !processed.contains(profile.getUrl());
2570    processed.add(profile.getUrl());
2571    if (wantProcess && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
2572      for (ElementDefinition ed : profile.getSnapshot().getElement()) {
2573        if (!"0".equals(ed.getMax()) && ed.getType().size() > 0) {
2574          String card = getCardinality(ed, profile.getSnapshot().getElement());
2575          if (!card.endsWith(".0")) {
2576            List<String> refProfiles = listReferenceProfiles(ed);
2577            if (refProfiles.size() > 0) {
2578              String uri = refProfiles.get(0);
2579              if (uri != null) {
2580                StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, uri);
2581                if (sd != null && (!onlyConstraints || (sd.getDerivation() == TypeDerivationRule.CONSTRAINT && (constraintPrefix == null || sd.getUrl().startsWith(constraintPrefix))))) {
2582                  res.getChildren().add(buildSpanningTable(nameForElement(ed), card, sd, processed, onlyConstraints, constraintPrefix));
2583                }
2584              }
2585            }
2586          }
2587        } 
2588      }
2589    }
2590    return res;
2591  }
2592
2593
2594  private String getCardinality(ElementDefinition ed, List<ElementDefinition> list) {
2595    int min = ed.getMin();
2596    int max = !ed.hasMax() || ed.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(ed.getMax());
2597    ElementDefinition ned = ed;
2598    while (ned != null && ned.getPath().contains(".")) {
2599      ned = findParent(ned, list);
2600      if (ned != null) { // todo: this can happen if we've walked into a resoruce. Not sure what to about that?
2601        if ("0".equals(ned.getMax()))
2602          max = 0;
2603        else if (!ned.getMax().equals("1") && !ned.hasSlicing())
2604          max = Integer.MAX_VALUE;
2605        if (ned.getMin() == 0) {
2606          min = 0;
2607        }
2608      }
2609    }
2610    return Integer.toString(min)+".."+(max == Integer.MAX_VALUE ? "*" : Integer.toString(max));
2611  }
2612
2613
2614  private ElementDefinition findParent(ElementDefinition ed, List<ElementDefinition> list) {
2615    int i = list.indexOf(ed)-1;
2616    while (i >= 0 && !ed.getPath().startsWith(list.get(i).getPath()+"."))
2617      i--;
2618    if (i == -1)
2619      return null;
2620    else
2621      return list.get(i);
2622  }
2623
2624
2625  private List<String> listReferenceProfiles(ElementDefinition ed) {
2626    List<String> res = new ArrayList<String>();
2627    for (TypeRefComponent tr : ed.getType()) {
2628      // code is null if we're dealing with "value" and profile is null if we just have Reference()
2629      if (tr.hasTarget() && tr.hasTargetProfile())
2630        for (UriType u : tr.getTargetProfile())
2631          res.add(u.getValue());
2632    }
2633    return res;
2634  }
2635
2636
2637  private String nameForElement(ElementDefinition ed) {
2638    return ed.getPath().substring(ed.getPath().indexOf(".")+1);
2639  }
2640
2641  public static String formatTypeSpecifiers(IWorkerContext context, ElementDefinition d) {
2642    StringBuilder b = new StringBuilder();
2643    boolean first = true;
2644    for (Extension e : d.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_SPEC)) {
2645      if (first) first = false; else b.append("<br/>");
2646      String cond = ToolingExtensions.readStringExtension(e, "condition");
2647      String type = ToolingExtensions.readStringExtension(e, "type");
2648      b.append("If <code>");
2649      b.append(Utilities.escapeXml(cond));
2650      b.append("</code> then the type is ");
2651      StructureDefinition sd = context.fetchTypeDefinition(type);
2652      if (sd == null) {
2653        b.append("<code>");
2654        b.append(Utilities.escapeXml(type));
2655        b.append("</code>");
2656      } else {
2657        b.append("<a href=\"");
2658        b.append(sd.getUserString("path"));
2659        b.append("\">");
2660        b.append(Utilities.escapeXml(sd.getTypeName()));        
2661        b.append("</a>");
2662      }
2663    }
2664    return b.toString();
2665  }
2666
2667  public XhtmlNode generateExtensionTable(String defFile, StructureDefinition ed, String imageFolder, boolean inlineGraphics, boolean full, String corePath, String imagePath, Set<String> outputTracker, RenderingContext rc) throws IOException, FHIRException {
2668    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true);
2669    gen.setTranslator(getTranslator());
2670    TableModel model = gen.initNormalTable(corePath, false, true, ed.getId()+(full ? "f" : "n"), true);
2671
2672    boolean deep = false;
2673    String m = "";
2674    boolean vdeep = false;
2675    if (ed.getSnapshot().getElementFirstRep().getIsModifier())
2676      m = "modifier_";
2677    for (ElementDefinition eld : ed.getSnapshot().getElement()) {
2678      deep = deep || eld.getPath().contains("Extension.extension.");
2679      vdeep = vdeep || eld.getPath().contains("Extension.extension.extension.");
2680    }
2681    Row r = gen.new Row();
2682    model.getRows().add(r);
2683    String en;
2684    if (!full)
2685      en = ed.getName();
2686    else if (ed.getSnapshot().getElement().get(0).getIsModifier())
2687      en = "modifierExtension";
2688    else 
2689      en = "extension";
2690
2691    r.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), en, null, null));
2692    r.getCells().add(gen.new Cell());
2693    r.getCells().add(gen.new Cell(null, null, describeCardinality(ed.getSnapshot().getElement().get(0), null, new UnusedTracker()), null, null));
2694
2695    ElementDefinition ved = null;
2696    if (full || vdeep) {
2697      r.getCells().add(gen.new Cell("", "", "Extension", null, null));
2698
2699      r.setIcon(deep ? "icon_"+m+"extension_complex.png" : "icon_extension_simple.png", deep ? HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX : HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
2700      List<ElementDefinition> children = getChildren(ed.getSnapshot().getElement(), ed.getSnapshot().getElement().get(0));
2701      for (ElementDefinition child : children)
2702        if (!child.getPath().endsWith(".id")) {
2703          List<StructureDefinition> sdl = new ArrayList<>();
2704          sdl.add(ed);
2705          genElement(defFile == null ? "" : defFile+"-definitions.html#extension.", gen, r.getSubRows(), child, ed.getSnapshot().getElement(), sdl, true, defFile, true, full, corePath, imagePath, true, false, false, false, null, false, rc, "", ed, null);
2706        }
2707    } else if (deep) {
2708      List<ElementDefinition> children = new ArrayList<ElementDefinition>();
2709      for (ElementDefinition ted : ed.getSnapshot().getElement()) {
2710        if (ted.getPath().equals("Extension.extension"))
2711          children.add(ted);
2712      }
2713
2714      r.getCells().add(gen.new Cell("", "", "Extension", null, null));
2715      r.setIcon("icon_"+m+"extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
2716
2717      for (ElementDefinition c : children) {
2718        ved = getValueFor(ed, c);
2719        ElementDefinition ued = getUrlFor(ed, c);
2720        if (ved != null && ued != null) {
2721          Row r1 = gen.new Row();
2722          r.getSubRows().add(r1);
2723          r1.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#"+ed.getId()+"."+c.getId(), ((UriType) ued.getFixed()).getValue(), null, null));
2724          r1.getCells().add(gen.new Cell());
2725          r1.getCells().add(gen.new Cell(null, null, describeCardinality(c, null, new UnusedTracker()), null, null));
2726          genTypes(gen, r1, ved, defFile, ed, corePath, imagePath, false, false);
2727          r1.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);      
2728          generateDescription(gen, r1, c, null, true, corePath, corePath, ed, corePath, imagePath, false, false, false, ved, false, false, false, rc);
2729        }
2730      }
2731    } else  {
2732      for (ElementDefinition ted : ed.getSnapshot().getElement()) {
2733        if (ted.getPath().startsWith("Extension.value"))
2734          ved = ted;
2735      }
2736
2737      genTypes(gen, r, ved, defFile, ed, corePath, imagePath, false, false);
2738
2739      r.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);      
2740    }
2741    Cell c = gen.new Cell("", "", "URL = "+ed.getUrl(), null, null);
2742    Piece cc = gen.new Piece(null, ed.getName()+": ", null);
2743    c.addPiece(gen.new Piece("br")).addPiece(cc);
2744    c.addMarkdown(ed.getDescription());
2745
2746    if (!full && !(deep || vdeep) && ved != null && ved.hasBinding()) {  
2747      c.addPiece(gen.new Piece("br"));
2748      BindingResolution br = context.getPkp().resolveBinding(ed, ved.getBinding(), ved.getPath());
2749      c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold")));
2750      c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !context.getPkp().prependLinks() ? br.url : corePath+br.url, br.display, null)));
2751      if (ved.getBinding().hasStrength()) {
2752        c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, " (", null)));
2753        c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(corePath+"terminologies.html#"+ved.getBinding().getStrength().toCode(), egt(ved.getBinding().getStrengthElement()), ved.getBinding().getStrength().getDefinition())));              
2754        c.getPieces().add(gen.new Piece(null, ")", null));
2755      }
2756      if (ved.getBinding().hasDescription() && MarkDownProcessor.isSimpleMarkdown(ved.getBinding().getDescription())) {
2757        c.getPieces().add(gen.new Piece(null, ": ", null));
2758        c.addMarkdownNoPara(PublicationHacker.fixBindingDescriptions(context.getWorker(), ved.getBinding().getDescriptionElement()).asStringValue());
2759      }
2760    }
2761    c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, ProfileUtilities.describeExtensionContext(ed), null));
2762    r.getCells().add(c);
2763
2764    try {
2765      return gen.generate(model, corePath, 0, outputTracker);
2766    } catch (org.hl7.fhir.exceptions.FHIRException e) {
2767      throw new FHIRException(e.getMessage(), e);
2768    }
2769  }
2770  
2771  private String describeCardinality(ElementDefinition definition, ElementDefinition fallback, UnusedTracker tracker) {
2772    IntegerType min = definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
2773    StringType max = definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
2774    if (min.isEmpty() && fallback != null)
2775      min = fallback.getMinElement();
2776    if (max.isEmpty() && fallback != null)
2777      max = fallback.getMaxElement();
2778
2779    tracker.used = !max.isEmpty() && !max.getValue().equals("0");
2780
2781    if (min.isEmpty() && max.isEmpty())
2782      return null;
2783    else
2784      return (!min.hasValue() ? "" : Integer.toString(min.getValue())) + ".." + (!max.hasValue() ? "" : max.getValue());
2785  }
2786
2787
2788  private ElementDefinition getValueFor(StructureDefinition ed, ElementDefinition c) {
2789    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
2790    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
2791      if (ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".value"))
2792        return ed.getSnapshot().getElement().get(i);
2793      i++;
2794    }
2795    return null;
2796  }
2797
2798
2799
2800  private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) {
2801    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
2802    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
2803      if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url"))
2804        return ed.getSnapshot().getElement().get(i);
2805      i++;
2806    }
2807    return null;
2808  }
2809
2810
2811
2812}