001package org.hl7.fhir.r5.renderers;
002
003import java.io.ByteArrayOutputStream;
004import java.io.IOException;
005import java.io.UnsupportedEncodingException;
006import java.util.ArrayList;
007import java.util.HashMap;
008import java.util.HashSet;
009import java.util.List;
010import java.util.Stack;
011import java.util.Map;
012import java.util.Set;
013
014import org.apache.commons.lang3.StringUtils;
015import org.hl7.fhir.exceptions.DefinitionException;
016import org.hl7.fhir.exceptions.FHIRException;
017import org.hl7.fhir.exceptions.FHIRFormatError;
018import org.hl7.fhir.r5.comparison.VersionComparisonAnnotation;
019import org.hl7.fhir.r5.conformance.profile.BindingResolution;
020import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
021import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.ElementChoiceGroup;
022import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.ExtensionContext;
023import org.hl7.fhir.r5.context.IWorkerContext.ValidationResult;
024import org.hl7.fhir.r5.formats.IParser;
025import org.hl7.fhir.r5.formats.IParser.OutputStyle;
026import org.hl7.fhir.r5.formats.JsonParser;
027import org.hl7.fhir.r5.formats.XmlParser;
028import org.hl7.fhir.r5.model.ActorDefinition;
029import org.hl7.fhir.r5.model.Base;
030import org.hl7.fhir.r5.model.BooleanType;
031import org.hl7.fhir.r5.model.CanonicalResource;
032import org.hl7.fhir.r5.model.CanonicalType;
033import org.hl7.fhir.r5.model.CodeSystem;
034import org.hl7.fhir.r5.model.CodeType;
035import org.hl7.fhir.r5.model.CodeableConcept;
036import org.hl7.fhir.r5.model.Coding;
037import org.hl7.fhir.r5.model.DataType;
038import org.hl7.fhir.r5.model.Element;
039import org.hl7.fhir.r5.model.ElementDefinition;
040import org.hl7.fhir.r5.model.ElementDefinition.AdditionalBindingPurposeVS;
041import org.hl7.fhir.r5.model.ElementDefinition.AggregationMode;
042import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType;
043import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingAdditionalComponent;
044import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
045import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent;
046import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionExampleComponent;
047import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent;
048import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent;
049import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent;
050import org.hl7.fhir.r5.model.ElementDefinition.PropertyRepresentation;
051import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules;
052import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
053import org.hl7.fhir.r5.model.Enumeration;
054import org.hl7.fhir.r5.model.Enumerations.BindingStrength;
055import org.hl7.fhir.r5.model.Extension;
056import org.hl7.fhir.r5.model.IdType;
057import org.hl7.fhir.r5.model.IntegerType;
058import org.hl7.fhir.r5.model.MarkdownType;
059import org.hl7.fhir.r5.model.PrimitiveType;
060import org.hl7.fhir.r5.model.Quantity;
061import org.hl7.fhir.r5.model.Resource;
062import org.hl7.fhir.r5.model.StringType;
063import org.hl7.fhir.r5.model.StructureDefinition;
064import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
065import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionMappingComponent;
066import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
067import org.hl7.fhir.r5.model.UriType;
068import org.hl7.fhir.r5.model.ValueSet;
069import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper;
070import org.hl7.fhir.r5.renderers.StructureDefinitionRenderer.InternalMarkdownProcessor;
071import org.hl7.fhir.r5.renderers.utils.RenderingContext;
072import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules;
073import org.hl7.fhir.r5.renderers.utils.RenderingContext.KnownLinkType;
074import org.hl7.fhir.r5.renderers.utils.RenderingContext.StructureDefinitionRendererMode;
075import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext;
076import org.hl7.fhir.r5.utils.PublicationHacker;
077import org.hl7.fhir.r5.utils.ToolingExtensions;
078import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
079import org.hl7.fhir.utilities.MarkDownProcessor;
080import org.hl7.fhir.utilities.StandardsStatus;
081import org.hl7.fhir.utilities.Utilities;
082import org.hl7.fhir.utilities.VersionUtilities;
083import org.hl7.fhir.utilities.i18n.I18nConstants;
084import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
085import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
086import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
087import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
088import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
089import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableGenerationMode;
090import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
091import org.hl7.fhir.utilities.xhtml.NodeType;
092import org.hl7.fhir.utilities.xhtml.XhtmlNode;
093import org.hl7.fhir.utilities.xhtml.XhtmlParser;
094
095public class StructureDefinitionRenderer extends ResourceRenderer {
096
097  //  public class ObligationWrapper {
098  //
099  //    private Extension ext;
100  //
101  //    public ObligationWrapper(Extension ext) {
102  //      this.ext = ext;
103  //    }
104  //
105  //    public boolean hasActor() {
106  //      return ext.hasExtension("actor");
107  //    }
108  //
109  //    public boolean hasActor(String id) {
110  //      return ext.hasExtension("actor") && id.equals(ext.getExtensionByUrl("actor").getValue().primitiveValue());
111  //    }
112  //
113  //    public Coding getCode() {
114  //      Extension code = ext.getExtensionByUrl("obligation");
115  //      if (code != null && code.hasValueCoding()) {
116  //        return code.getValueCoding();
117  //      }
118  //      if (code != null && code.hasValueCodeType()) {
119  //        return new Coding().setSystem("http://hl7.org/fhir/tools/CodeSystem/obligation").setCode(code.getValueCodeType().primitiveValue());
120  //      }
121  //      return null;
122  //    }
123  //
124  //    public boolean hasFilter() {
125  //      return ext.hasExtension("filter");
126  //    }
127  //
128  //    public String getFilter() {
129  //      Extension code = ext.getExtensionByUrl("filter");
130  //      if (code != null && code.getValue() != null) {
131  //        return code.getValue().primitiveValue();
132  //      }
133  //      return null;
134  //    }
135  //
136  //    public boolean hasUsage() {
137  //      return ext.hasExtension("usage");
138  //    }
139  //
140  //    public String getFilterDocumentation() {
141  //      Extension code = ext.getExtensionByUrl("filter-desc");
142  //      if (code != null && code.getValue() != null) {
143  //        return code.getValue().primitiveValue();
144  //      }
145  //      return null;
146  //    }
147  //
148  //    public List<UsageContext> getUsage() {
149  //      List<UsageContext> usage = new ArrayList<>();
150  //      for (Extension u : ext.getExtensionsByUrl("usage" )) {
151  //        if (u.hasValueUsageContext()) {
152  //          usage.add(u.getValueUsageContext());
153  //        }
154  //      }
155  //      return usage;
156  //    }
157  //
158  //  }
159
160  public class InternalMarkdownProcessor implements IMarkdownProcessor {
161
162    @Override
163    public String processMarkdown(String location, PrimitiveType md) throws FHIRException {
164      return context.getMarkdown().process(md.primitiveValue(), location);
165    }
166
167    @Override
168    public String processMarkdown(String location, String text) throws FHIRException {
169      return context.getMarkdown().process(text, location);
170    }
171  }
172
173  private enum ListItemStatus { New, Unchanged, Removed};
174
175  private abstract class ItemWithStatus {
176    ListItemStatus status = ListItemStatus.New; // new, unchanged, removed    
177
178    protected abstract void renderDetails(XhtmlNode f) throws IOException;
179    protected abstract boolean matches(ItemWithStatus other);
180
181    public void render(XhtmlNode x) throws IOException {
182      XhtmlNode f = x;
183      if (status == ListItemStatus.Unchanged) {
184        f = unchanged(f);
185      } else if (status == ListItemStatus.Removed) {
186        f = removed(f);
187      }
188      renderDetails(f);
189    }
190  }
191
192  protected class StatusList<T extends ItemWithStatus> extends ArrayList<T> implements List<T> {
193
194    public boolean merge(T item) {
195      if (item == null) {
196        return false;
197      }
198      boolean found = false;
199      for (T t : this) {
200        if (t.matches(item)) {
201          found = true;
202          t.status = ListItemStatus.Unchanged;
203        }
204      }
205      if (!found) {
206        item.status = ListItemStatus.Removed;
207        return add(item);        
208      } else {
209        return false;
210      }
211    }
212    
213    public boolean add(T item) {
214      if (item != null) {
215        return super.add(item);
216      } else {
217        return false;
218      }
219    }
220  }
221
222  private class ResolvedCanonical extends ItemWithStatus {
223    String url; // what we used to resolve
224    CanonicalResource cr; // what we resolved
225
226    public ResolvedCanonical(String url, CanonicalResource cr) {
227      this.url = url;
228      this.cr = cr;
229    }
230    public void renderDetails(XhtmlNode f) {
231      if (cr != null && cr.hasWebPath()) {
232        f.ah(cr.getWebPath()).tx(cr.present());
233      } else {
234        f.code().tx(url);            
235      }
236    }
237    protected boolean matches(ItemWithStatus other) {
238      return ((ResolvedCanonical) other).url.equals(url);
239    }
240  }
241
242  private class InvariantWithStatus extends ItemWithStatus {
243    ElementDefinitionConstraintComponent value;
244    protected InvariantWithStatus(ElementDefinitionConstraintComponent value) {
245      this.value = value;
246    }
247
248    protected boolean matches(ItemWithStatus other) {
249      return ((InvariantWithStatus) other).value.equalsDeep(value);
250    }
251    
252    public void renderDetails(XhtmlNode f) {
253      f = renderStatus(value, f);
254      f.b().attribute("title", "Formal Invariant Identifier").tx(value.getKey());
255      f.tx(": ");
256      if (value.hasHuman()) {
257        renderStatus(value.getHumanElement(), f).tx(value.getHuman());
258      } else if (VersionComparisonAnnotation.hasDeleted(value, "human")) {
259        Base b =VersionComparisonAnnotation.getDeletedItem(value, "human");
260        renderStatus(b, f).tx(b.primitiveValue());        
261      }
262      f.tx(" (");
263      if (status == ListItemStatus.New) {
264        if (value.hasExpression()) {
265          renderStatus(value.getExpressionElement(), f).code().tx(value.getExpression());
266        } else if (VersionComparisonAnnotation.hasDeleted(value, "expression")) {
267          Base b = VersionComparisonAnnotation.getDeletedItem(value, "expression");
268          renderStatus(b, f).code().tx(b.primitiveValue());        
269        }
270      } else {
271        renderStatus(value.getExpressionElement(), f).tx(value.getExpression());
272      }
273      f.tx(")");      
274    }
275  }
276  
277  private class DiscriminatorWithStatus extends ItemWithStatus {
278    ElementDefinitionSlicingDiscriminatorComponent value;
279    protected DiscriminatorWithStatus(ElementDefinitionSlicingDiscriminatorComponent value) {
280      this.value = value;
281    }
282
283    protected boolean matches(ItemWithStatus other) {
284      return ((DiscriminatorWithStatus) other).value.equalsDeep(value);
285    }
286    
287    public void renderDetails(XhtmlNode f) {
288      f.tx(value.getType().toCode());
289      f.tx(" @ ");
290      f.tx(value.getPath());
291    }
292  }
293  
294  private class ValueWithStatus extends ItemWithStatus {
295    PrimitiveType value;
296    protected ValueWithStatus(PrimitiveType value) {
297      this.value = value;
298    }
299
300    protected boolean matches(ItemWithStatus other) {
301      return ((ValueWithStatus) other).value.equalsDeep(value);
302    }
303    
304    public void renderDetails(XhtmlNode f) {
305      if (value.hasUserData("render.link")) {
306        f = f.ah(value.getUserString("render.link"));
307      }
308      f.tx(value.asStringValue());
309    }
310    
311  }
312
313  private class DataValueWithStatus extends ItemWithStatus {
314    DataType value;
315    protected DataValueWithStatus(DataType value) {
316      this.value = value;
317    }
318
319    protected boolean matches(ItemWithStatus other) {
320      return ((ValueWithStatus) other).value.equalsDeep(value);
321    }
322
323    public void renderDetails(XhtmlNode f) throws IOException {
324
325      if (value.hasUserData("render.link")) {
326        f = f.ah(value.getUserString("render.link"));
327      }
328      f.tx(summarize(value));
329    }
330
331  }
332  
333
334  private List<String> keyRows = new ArrayList<>();
335  private Map<String, Map<String, ElementDefinition>> sdMapCache = new HashMap<>();
336  private IMarkdownProcessor hostMd;
337
338  public StructureDefinitionRenderer(RenderingContext context) {
339    super(context);
340    hostMd = new InternalMarkdownProcessor();
341    corePath = context.getContext().getSpecUrl();
342  }
343
344  public StructureDefinitionRenderer(RenderingContext context, ResourceContext rcontext) {
345    super(context, rcontext);
346  }
347
348  
349  public Map<String, Map<String, ElementDefinition>> getSdMapCache() {
350    return sdMapCache;
351  }
352
353  public void setSdMapCache(Map<String, Map<String, ElementDefinition>> sdMapCache) {
354    this.sdMapCache = sdMapCache;
355  }
356
357  public IMarkdownProcessor getHostMd() {
358    return hostMd;
359  }
360
361  public void setHostMd(IMarkdownProcessor hostMd) {
362    this.hostMd = hostMd;
363  }
364
365  public boolean render(XhtmlNode x, Resource dr) throws FHIRFormatError, DefinitionException, IOException {
366    return render(x, (StructureDefinition) dr);
367  }
368
369  public boolean render(XhtmlNode x, StructureDefinition sd) throws FHIRFormatError, DefinitionException, IOException {
370    if (context.getStructureMode() == StructureDefinitionRendererMode.DATA_DICT) {
371      renderDict(sd, sd.getDifferential().getElement(), x.table("dict"), false, GEN_MODE_DIFF, "");
372    } else {
373      x.getChildNodes().add(generateTable(context.getDefinitionsTarget(), sd, true, context.getDestDir(), false, sd.getId(), false, 
374        context.getLink(KnownLinkType.SPEC), "", sd.getKind() == StructureDefinitionKind.LOGICAL, false, null, false, context, ""));
375    }
376    return true;
377  }
378
379  public void describe(XhtmlNode x, StructureDefinition sd) {
380    x.tx(display(sd));
381  }
382
383  public String display(StructureDefinition sd) {
384    return sd.present();
385  }
386
387  @Override
388  public String display(Resource r) throws UnsupportedEncodingException, IOException {
389    return ((StructureDefinition) r).present();
390  }
391
392  public String display(ResourceWrapper r) throws UnsupportedEncodingException, IOException {
393    if (r.has("title")) {
394      return r.children("title").get(0).getBase().primitiveValue();
395    }
396    if (r.has("name")) {
397      return r.children("name").get(0).getBase().primitiveValue();
398    }
399    return "??";
400  }
401
402
403  //  private static final int AGG_NONE = 0;
404  //  private static final int AGG_IND = 1;
405  //  private static final int AGG_GR = 2;
406  //  private static final boolean TABLE_FORMAT_FOR_FIXED_VALUES = false;
407  public static final String CONSTRAINT_CHAR = "C";
408  public static final String CONSTRAINT_STYLE = "padding-left: 3px; padding-right: 3px; border: 1px maroon solid; font-weight: bold; color: #301212; background-color: #fdf4f4;";
409  public static final int GEN_MODE_SNAP = 1;
410  public static final int GEN_MODE_DIFF = 2;
411  public static final int GEN_MODE_MS = 3;
412  public static final int GEN_MODE_KEY = 4;
413  public static final String RIM_MAPPING = "http://hl7.org/v3";
414  public static final String v2_MAPPING = "http://hl7.org/v2";
415  public static final String LOINC_MAPPING = "http://loinc.org";
416  public static final String SNOMED_MAPPING = "http://snomed.info";
417
418  private final boolean ADD_REFERENCE_TO_TABLE = true;
419
420  private boolean useTableForFixedValues = true;
421  private String corePath;
422
423  public static class UnusedTracker {
424    private boolean used;
425  }
426
427  private class SpanEntry {
428    private List<SpanEntry> children = new ArrayList<SpanEntry>();
429    private boolean profile;
430    private String id;
431    private String name;
432    private String resType;
433    private String cardinality;
434    private String description;
435    private String profileLink;
436    private String resLink;
437    private String type;
438
439    public String getName() {
440      return name;
441    }
442    public void setName(String name) {
443      this.name = name;
444    }
445    public String getResType() {
446      return resType;
447    }
448    public void setResType(String resType) {
449      this.resType = resType;
450    }
451    public String getCardinality() {
452      return cardinality;
453    }
454    public void setCardinality(String cardinality) {
455      this.cardinality = cardinality;
456    }
457    public String getDescription() {
458      return description;
459    }
460    public void setDescription(String description) {
461      this.description = description;
462    }
463    public String getProfileLink() {
464      return profileLink;
465    }
466    public void setProfileLink(String profileLink) {
467      this.profileLink = profileLink;
468    }
469    public String getResLink() {
470      return resLink;
471    }
472    public void setResLink(String resLink) {
473      this.resLink = resLink;
474    }
475    public String getId() {
476      return id;
477    }
478    public void setId(String id) {
479      this.id = id;
480    }
481    public boolean isProfile() {
482      return profile;
483    }
484    public void setProfile(boolean profile) {
485      this.profile = profile;
486    }
487    public List<SpanEntry> getChildren() {
488      return children;
489    }
490    public String getType() {
491      return type;
492    }
493    public void setType(String type) {
494      this.type = type;
495    }
496
497  }
498
499  private class ElementInStructure {
500
501    private StructureDefinition source;
502    private ElementDefinition element;
503
504    public ElementInStructure(StructureDefinition source, ElementDefinition ed) {
505      this.source = source;
506      this.element = ed;
507    }
508
509    public StructureDefinition getSource() {
510      return source;
511    }
512
513    public ElementDefinition getElement() {
514      return element;
515    }
516
517  }
518  private ElementInStructure getElementByName(List<ElementDefinition> elements, String contentReference, StructureDefinition source) {
519    if (contentReference.contains("#")) {
520      String url = contentReference.substring(0, contentReference.indexOf("#"));
521      contentReference = contentReference.substring(contentReference.indexOf("#"));
522      if (Utilities.noString(url)) {
523        url = source.getUrl();
524      }
525      if (!url.equals(source.getUrl())) {
526        source = context.getWorker().fetchResource(StructureDefinition.class, url, source);
527        if (source == null) {
528          throw new FHIRException("Unable to resolve StructureDefinition "+url+" resolving content reference "+contentReference);
529        }
530        elements = source.getSnapshot().getElement();
531      }
532    } 
533    for (ElementDefinition ed : elements) {
534      if (("#"+ed.getPath()).equals(contentReference)) {
535        return new ElementInStructure(source, ed);
536      }
537      if (("#"+ed.getId()).equals(contentReference)) {
538        return new ElementInStructure(source, ed);
539      }
540    }
541    throw new Error("getElementByName: can't find "+contentReference+" in "+elements.toString()+" from "+source.getUrl());
542    //    return null;
543  }
544
545  public XhtmlNode generateGrid(String defFile, StructureDefinition profile, String imageFolder, boolean inlineGraphics, String profileBaseFileName, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException {
546    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true);
547    gen.setTranslator(getTranslator());
548    TableModel model = gen.initGridTable(corePath, profile.getId());
549    List<ElementDefinition> list = profile.getSnapshot().getElement();
550    List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
551    profiles.add(profile);
552    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));
553    try {
554      return gen.generate(model, imagePath, 1, outputTracker);
555    } catch (org.hl7.fhir.exceptions.FHIRException e) {
556      throw new FHIRException(e.getMessage(), e);
557    }
558  }
559
560
561  private static class Column {
562    String id;
563    String title;
564    String hint;
565    private String link;
566
567    protected Column(String id, String title, String hint) {
568      super();
569      this.id = id;
570      this.title = title;
571      this.hint = hint;
572    }
573    protected Column(String id, String title, String hint, String link) {
574      super();
575      this.id = id;
576      this.title = title;
577      this.hint = hint;
578      this.link = link;
579    }
580
581  }
582  public XhtmlNode generateTable(String defFile, StructureDefinition profile, boolean diff, String imageFolder, boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, String imagePath,
583      boolean logicalModel, boolean allInvariants, Set<String> outputTracker, boolean mustSupport, RenderingContext rc, String anchorPrefix) throws IOException, FHIRException {
584    assert(diff != snapshot);// check it's ok to get rid of one of these
585    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true);
586    gen.setTranslator(getTranslator());
587
588    List<ElementDefinition> list;
589    if (diff)
590      list = supplementMissingDiffElements(profile);
591    else {
592      list = new ArrayList<>();
593      list.addAll(profile.getSnapshot().getElement());
594    }
595
596    List<Column> columns = new ArrayList<>();
597    TableModel model;
598    switch (context.getStructureMode()) {
599    case BINDINGS:
600      scanBindings(columns, list);
601      model = initCustomTable(gen, corePath, false, true, profile.getId()+(diff ? "d" : "s"), rc.getRules() == GenerationRules.IG_PUBLISHER, columns);    
602      break;
603    case OBLIGATIONS:
604      scanObligations(columns, list);
605      model = initCustomTable(gen, corePath, false, true, profile.getId()+(diff ? "d" : "s"), rc.getRules() == GenerationRules.IG_PUBLISHER, columns);    
606      break;
607    case SUMMARY:
608      model = gen.initNormalTable(corePath, false, true, profile.getId()+(diff ? "d" : "s"), rc.getRules() == GenerationRules.IG_PUBLISHER, rc.getRules() == GenerationRules.IG_PUBLISHER ? TableGenerationMode.XHTML : TableGenerationMode.XML);
609      break;
610    default:
611      throw new Error("Unknown structure mode");
612    }
613
614    List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
615    profiles.add(profile);
616    keyRows.clear();
617
618    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);
619    try {
620      return gen.generate(model, imagePath, 0, outputTracker);
621    } catch (org.hl7.fhir.exceptions.FHIRException e) {
622      throw new FHIRException(context.getWorker().formatMessage(I18nConstants.ERROR_GENERATING_TABLE_FOR_PROFILE__, profile.getUrl(), e.getMessage()), e);
623    }
624  }
625
626  private void scanBindings(List<Column> columns, List<ElementDefinition> list) {
627    Set<String> cols = new HashSet<>();
628    scanBindings(cols, list, list.get(0));
629    if (cols.contains("required")) {
630      columns.add(new Column("required", "Required", "Concepts must come from this value set"));
631    }
632    if (cols.contains("extensible")) {
633      columns.add(new Column("extensible", "Extensible", "Concepts must come from this value set if an appropriate concept is in the value set "));
634    }
635    if (cols.contains("maximum")) {
636      columns.add(new Column("maximum", "Maximum", "A required binding for additional codes, for use when the binding strength is 'extensible' or 'preferred'"));
637    }
638    if (cols.contains("minimum")) {
639      columns.add(new Column("minimum", "Minimum", "The minimum allowable value set - any conformant system SHALL support all these codes"));
640    }
641    if (cols.contains("candidate")) {
642      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"));
643    }
644    if (cols.contains("current")) {
645      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"));
646    }
647    if (cols.contains("preferred")) {
648      columns.add(new Column("preferred", "Preferred", "This is the value set that is preferred in a given context (documentation should explain why)"));
649    }
650    if (cols.contains("ui")) {
651      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"));
652    }
653    if (cols.contains("starter")) {
654      columns.add(new Column("starter", "Starter", "This value set is a good set of codes to start with when designing your system"));
655    }
656    if (cols.contains("component")) {
657      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"));
658    }
659    if (cols.contains("example")) {
660      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."));
661    }
662  }
663
664  public void scanBindings(Set<String> cols, List<ElementDefinition> list, ElementDefinition ed) {
665    if (ed.hasBinding()) {
666      if (ed.getBinding().hasValueSet() && ed.getBinding().hasStrength()) {
667        switch (ed.getBinding().getStrength()) {
668        case EXAMPLE:
669          cols.add("example");
670          break;
671        case EXTENSIBLE:
672          cols.add("extensible");
673          break;
674        case PREFERRED:
675          cols.add("preferred");
676          break;
677        case REQUIRED:
678          cols.add("required");
679          break;
680        default:
681          break;
682        }
683      }
684      for (ElementDefinitionBindingAdditionalComponent ab : ed.getBinding().getAdditional()) {
685        cols.add(ab.getPurpose().toCode());
686      }
687      for (Extension ext : ed.getBinding().getExtensionsByUrl(ToolingExtensions.EXT_BINDING_ADDITIONAL)) {
688        cols.add(ext.getExtensionString("purpose"));        
689      }
690    }
691
692    List<ElementDefinition> children = getChildren(list, ed);
693    for (ElementDefinition element : children) {
694      scanBindings(cols, list, element);
695    }
696  }
697
698  private void scanObligations(List<Column> columns, List<ElementDefinition> list) {
699    Set<String> cols = new HashSet<>();
700    scanObligations(cols, list, list.get(0));
701
702    if (cols.contains("$all")) {
703      columns.add(new Column("$all", "All Actors", "Obligations that apply to all actors"));
704    }
705    for (String col : cols) {
706      if (!"$all".equals(col)) {
707        ActorDefinition actor = context.getWorker().fetchResource(ActorDefinition.class, col);
708        if (actor == null) {
709          columns.add(new Column(col, tail(col), "Obligations that apply to the undefined actor "+col, col));          
710        } else {
711          columns.add(new Column(col, actor.getName(), "Obligations that apply to the actor "+actor.present(), actor.getWebPath()));                    
712        }
713      }
714    }
715  }
716
717  private void scanObligations(Set<String> cols, List<ElementDefinition> list, ElementDefinition ed) {
718
719    for (Extension ob : ed.getExtensionsByUrl(ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS)) {
720      if (ob.hasExtension("actor", "actorId")) {
721        for (Extension a : ob.getExtensionsByUrl("actor", "actorId")) {
722          cols.add(a.getValueCanonicalType().primitiveValue());
723        }
724      } else 
725        cols.add("$all");
726    }
727
728    List<ElementDefinition> children = getChildren(list, ed);
729    for (ElementDefinition element : children) {
730      scanObligations(cols, list, element);
731    }
732  }
733
734  public TableModel initCustomTable(HierarchicalTableGenerator gen, String prefix, boolean isLogical, boolean alternating, String id, boolean isActive, List<Column> columns) throws IOException {
735    TableModel model = gen.new TableModel(id, isActive);
736
737    model.setAlternating(alternating);
738    if (context.getRules() == GenerationRules.VALID_RESOURCE || context.isInlineGraphics()) {
739      model.setDocoImg(HierarchicalTableGenerator.help16AsData());       
740    } else {
741      model.setDocoImg(Utilities.pathURL(prefix, "help16.png"));
742    }
743    model.setDocoRef(Utilities.pathURL("https://build.fhir.org/ig/FHIR/ig-guidance", "readingIgs.html#table-views"));
744    model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", "Name"), translate("sd.hint", "The logical name of the element"), null, 0));
745    for (Column col : columns) {
746      model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", col.title), translate("sd.hint", col.hint), null, 0));      
747    }
748    return model;
749  }
750
751  private Row genElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element, List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName, Boolean extensions, 
752      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 {
753    Row originalRow = slicingRow;
754    StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1);
755    Row typesRow = null;
756
757    List<ElementDefinition> children = getChildren(all, element);
758    //    if (!snapshot && isExtension && extensions != null && extensions != isExtension)
759    //      return;
760
761    if (!onlyInformationIsMapping(all, element)) {
762      Row row = gen.new Row();
763      row.setAnchor(element.getPath());
764      row.setColor(context.getProfileUtilities().getRowColor(element, isConstraintMode));
765      if (element.hasSlicing())
766        row.setLineColor(1);
767      else if (element.hasSliceName())
768        row.setLineColor(2);
769      else
770        row.setLineColor(0);
771      boolean hasDef = element != null;
772      boolean ext = false;
773      if (tail(element.getPath()).equals("extension")) {
774        if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue()))
775          row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
776        else
777          row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
778        ext = true;
779      } else if (tail(element.getPath()).equals("modifierExtension")) {
780        if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue()))
781          row.setIcon("icon_modifier_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
782        else
783          row.setIcon("icon_modifier_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
784      } else if (!hasDef || element.getType().size() == 0) {
785        if (root && profile != null && context.getWorker().getResourceNames().contains(profile.getType())) {
786          row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
787        } else if (hasDef && element.hasExtension(ToolingExtensions.EXT_JSON_PROP_KEY)) {
788          row.setIcon("icon-object-box.png", HierarchicalTableGenerator.TEXT_ICON_OBJECT_BOX);
789          keyRows.add(element.getId()+"."+ToolingExtensions.readStringExtension(element, ToolingExtensions.EXT_JSON_PROP_KEY));
790        } else {
791          row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
792        }
793      } else if (hasDef && element.getType().size() > 1) {
794        if (allAreReference(element.getType())) {
795          row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
796        } else if (element.hasExtension(ToolingExtensions.EXT_JSON_PRIMITIVE_CHOICE)) {
797          row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE);
798        } else {
799          row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE);
800          typesRow = row;
801        }
802      } else if (hasDef && element.getType().get(0).getWorkingCode() != null && element.getType().get(0).getWorkingCode().startsWith("@")) {
803        row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE);
804      } else if (hasDef && isPrimitive(element.getType().get(0).getWorkingCode())) {
805        if (keyRows.contains(element.getId())) {
806          row.setIcon("icon-key.png", HierarchicalTableGenerator.TEXT_ICON_KEY);
807        } else {
808          row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
809        }
810      } else if (hasDef && element.getType().get(0).hasTarget()) {
811        row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
812      } else if (hasDef && isDataType(element.getType().get(0).getWorkingCode())) {
813        row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
814      } else if (hasDef && element.hasExtension(ToolingExtensions.EXT_JSON_PROP_KEY)) {
815        row.setIcon("icon-object-box.png", HierarchicalTableGenerator.TEXT_ICON_OBJECT_BOX);
816        keyRows.add(element.getId()+"."+ToolingExtensions.readStringExtension(element, ToolingExtensions.EXT_JSON_PROP_KEY));
817      } else if (hasDef && Utilities.existsInList(element.getType().get(0).getWorkingCode(), "Base", "Element", "BackboneElement")) {
818        row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
819      } else {
820        row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
821      }
822      if (element.hasUserData("render.opaque")) {
823        row.setOpacity("0.5");
824      }
825      UnusedTracker used = new UnusedTracker();
826      String ref = defPath == null ? null : defPath + anchorPrefix + element.getId();
827      String sName = tail(element.getPath());
828      if (element.hasSliceName())
829        sName = sName +":"+element.getSliceName();
830      used.used = true;
831      if (logicalModel && element.hasRepresentation(PropertyRepresentation.XMLATTR))
832        sName = "@"+sName;
833      Cell nc = genElementNameCell(gen, element, profileBaseFileName, snapshot, corePath, imagePath, root, logicalModel, allInvariants, profile, typesRow, row, hasDef, ext, used, ref, sName, all);
834      switch (context.getStructureMode()) {
835      case BINDINGS:
836        genElementBindings(gen, element, columns, row, profile, corePath);
837        break;
838      case OBLIGATIONS:
839        genElementObligations(gen, element, columns, row, corePath, profile);
840        break;
841      case SUMMARY:
842        genElementCells(gen, element, profileBaseFileName, snapshot, corePath, imagePath, root, logicalModel, allInvariants, profile, typesRow, row, hasDef, ext, used, ref, sName, nc, mustSupport, true, rc);
843        break;
844
845      }
846      if (element.hasSlicing()) {
847        if (standardExtensionSlicing(element)) {
848          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();
849          showMissing = false; //?
850        } else {
851          row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE);
852          slicingRow = row;
853          for (Cell cell : row.getCells())
854            for (Piece p : cell.getPieces()) {
855              p.addStyle("font-style: italic");
856            }
857        }
858      } else if (element.hasSliceName()) {
859        row.setIcon("icon_slice_item.png", HierarchicalTableGenerator.TEXT_ICON_SLICE_ITEM);
860      }
861      if (used.used || showMissing)
862        rows.add(row);
863      if (!used.used && !element.hasSlicing()) {
864        for (Cell cell : row.getCells())
865          for (Piece p : cell.getPieces()) {
866            p.setStyle("text-decoration:line-through");
867            p.setReference(null);
868          }
869      } else {
870        if (slicingRow != originalRow && !children.isEmpty()) {
871          // we've entered a slice; we're going to create a holder row for the slice children
872          Row hrow = gen.new Row();
873          hrow.setAnchor(element.getPath());
874          hrow.setColor(context.getProfileUtilities().getRowColor(element, isConstraintMode));
875          hrow.setLineColor(1);
876          hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
877          hrow.getCells().add(gen.new Cell(null, null, sName+":All Slices", "", null));
878          switch (context.getStructureMode()) {
879          case BINDINGS:
880          case OBLIGATIONS:
881            for (Column col : columns) {
882              hrow.getCells().add(gen.new Cell());              
883            }
884            break;
885          case SUMMARY:
886            hrow.getCells().add(gen.new Cell());
887            hrow.getCells().add(gen.new Cell());
888            hrow.getCells().add(gen.new Cell());
889            hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all slices", "", null));
890            break;            
891          }
892          row.getSubRows().add(hrow);
893          row = hrow;
894        }
895        if (typesRow != null && !children.isEmpty()) {
896          // we've entered a typing slice; we're going to create a holder row for the all types children
897          Row hrow = gen.new Row();
898          hrow.setAnchor(element.getPath());
899          hrow.setColor(context.getProfileUtilities().getRowColor(element, isConstraintMode));
900          hrow.setLineColor(1);
901          hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
902          hrow.getCells().add(gen.new Cell(null, null, sName+":All Types", "", null));
903          switch (context.getStructureMode()) {
904          case BINDINGS:
905          case OBLIGATIONS:
906            for (Column col : columns) {
907              hrow.getCells().add(gen.new Cell());              
908            }
909            break;
910          case SUMMARY:
911            hrow.getCells().add(gen.new Cell());
912            hrow.getCells().add(gen.new Cell());
913            hrow.getCells().add(gen.new Cell());
914            hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all Types", "", null));
915          }
916          row.getSubRows().add(hrow);
917          row = hrow;
918        }
919
920        Row currRow = row;
921        List<ElementChoiceGroup> groups = readChoices(element, children);
922        boolean isExtension = Utilities.existsInList(tail(element.getPath()), "extension", "modifierExtension");
923        if (!element.prohibited()) {
924          for (ElementDefinition child : children) {
925            if (!child.hasSliceName()) {
926              currRow = row; 
927            }
928            Row childRow = chooseChildRowByGroup(gen, currRow, groups, child, element, isConstraintMode);
929
930            if (logicalModel || !child.getPath().endsWith(".id") || (child.getPath().endsWith(".id") && (profile != null) && (profile.getDerivation() == TypeDerivationRule.CONSTRAINT))) {  
931              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);
932            }
933          }
934        }
935        //        if (!snapshot && (extensions == null || !extensions))
936        //          for (ElementDefinition child : children)
937        //            if (child.getPath().endsWith(".extension") || child.getPath().endsWith(".modifierExtension"))
938        //              genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, true, false, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants);
939      }
940      if (typesRow != null && !element.prohibited() && context.getStructureMode() == StructureDefinitionRendererMode.SUMMARY) {
941        makeChoiceRows(typesRow.getSubRows(), element, gen, corePath, profileBaseFileName, mustSupport, srcSD);
942      }
943    }
944    return slicingRow;
945  }
946
947  private void genElementObligations(HierarchicalTableGenerator gen, ElementDefinition element, List<Column> columns, Row row, String corePath, StructureDefinition profile) throws IOException {
948    for (Column col : columns) { 
949      Cell gc = gen.new Cell();
950      row.getCells().add(gc);
951      ObligationsRenderer obr = new ObligationsRenderer(corePath, profile, element.getPath(), context, null, this);
952      obr.seeObligations(element, col.id);
953      obr.renderList(gen, gc);      
954    }
955  }
956
957  private String displayForUsage(Coding c) {
958    if (c.hasDisplay()) {
959      return c.getDisplay();
960    }
961    if ("http://terminology.hl7.org/CodeSystem/usage-context-type".equals(c.getSystem())) {
962      return c.getCode();
963    }
964    return c.getCode();
965  }
966
967  private void genElementBindings(HierarchicalTableGenerator gen, ElementDefinition element, List<Column> columns, Row row, StructureDefinition profile, String corepath) {
968    for (Column col : columns) { 
969      Cell gc = gen.new Cell();
970      row.getCells().add(gc);
971      List<ElementDefinitionBindingAdditionalComponent> bindings = collectBindings(element, col.id);
972      if (bindings.size() > 0) {
973        Piece p = gen.new Piece(null);
974        gc.addPiece(p);
975        new AdditionalBindingsRenderer(context.getPkp(), corepath, profile, element.getPath(), context, null, this).render(p.getChildren(), bindings);
976      }
977    }
978  }
979
980  private List<ElementDefinitionBindingAdditionalComponent> collectBindings(ElementDefinition element, String type) {
981    List<ElementDefinitionBindingAdditionalComponent> res = new ArrayList<>();
982    if (element.hasBinding()) {
983      ElementDefinitionBindingComponent b = element.getBinding();
984      if (b.hasStrength() && type.equals(b.getStrength().toCode())) {
985        ElementDefinitionBindingAdditionalComponent ab = new ElementDefinitionBindingAdditionalComponent();
986        res.add(ab.setAny(false).setDocumentation(b.getDescription()).setValueSet(b.getValueSet()));
987      }
988      if ("maximum".equals(type) && b.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) {
989        ElementDefinitionBindingAdditionalComponent ab = new ElementDefinitionBindingAdditionalComponent();
990        res.add(ab.setAny(false).setValueSet(ToolingExtensions.readStringExtension(b, ToolingExtensions.EXT_MAX_VALUESET)));
991      }
992      if ("minimum".equals(type) && b.hasExtension(ToolingExtensions.EXT_MIN_VALUESET)) {
993        ElementDefinitionBindingAdditionalComponent ab = new ElementDefinitionBindingAdditionalComponent();
994        res.add(ab.setAny(false).setValueSet(ToolingExtensions.readStringExtension(b, ToolingExtensions.EXT_MIN_VALUESET)));
995      }
996      for (ElementDefinitionBindingAdditionalComponent t : b.getAdditional()) {
997        if (type.equals(t.getPurpose().toCode())) {
998          res.add(t);
999        }
1000      }
1001      for (Extension ext : b.getExtensionsByUrl(ToolingExtensions.EXT_BINDING_ADDITIONAL)) {
1002        if (type.equals(ext.getExtensionString("purpose"))) {
1003          ElementDefinitionBindingAdditionalComponent ab = new ElementDefinitionBindingAdditionalComponent();
1004          if (ext.hasExtension("any")) {
1005            ab.setAny(ToolingExtensions.readBooleanExtension(ext, "any"));
1006          }
1007          if (ext.hasExtension("purpose")) {
1008            ab.setPurpose(AdditionalBindingPurposeVS.fromCode(ToolingExtensions.readStringExtension(ext, "purpose")));
1009          }
1010          if (ext.hasExtension("documentation")) {
1011            ab.setDocumentation(ToolingExtensions.readStringExtension(ext, "documentation"));
1012          }
1013          if (ext.hasExtension("shortDoco")) {
1014            ab.setShortDoco(ToolingExtensions.readStringExtension(ext, "shortDoco"));
1015          }
1016          if (ToolingExtensions.hasExtension(ext, "usage")) {
1017            ab.addUsage(ext.getExtensionByUrl("usage").getValueUsageContext());
1018          }
1019          if (ext.hasExtension("valueSet")) {
1020            ab.setValueSet(ToolingExtensions.readStringExtension(ext, "valueSet"));
1021          }
1022          res.add(ab);        
1023        }
1024      }
1025    }
1026    return res;
1027  }
1028
1029  public Cell genElementNameCell(HierarchicalTableGenerator gen, ElementDefinition element, String profileBaseFileName, boolean snapshot, String corePath,
1030      String imagePath, boolean root, boolean logicalModel, boolean allInvariants, StructureDefinition profile, Row typesRow, Row row, boolean hasDef,
1031      boolean ext, UnusedTracker used, String ref, String sName, List<ElementDefinition> elements) throws IOException {
1032    String hint = "";
1033    hint = checkAdd(hint, (element.hasSliceName() ? translate("sd.table", "Slice")+" "+element.getSliceName() : ""));
1034    if (hasDef && element.hasDefinition()) {
1035      hint = checkAdd(hint, (hasDef && element.hasSliceName() ? ": " : ""));
1036      hint = checkAdd(hint, !hasDef ? null : gt(element.getDefinitionElement()));
1037    }
1038    if (element.hasSlicing() && slicesExist(elements, element)) { // some elements set up slicing but don't actually slice, so we don't augment the name 
1039      sName = "Slices for "+sName; 
1040    }
1041    Cell left = gen.new Cell(null, ref, sName, hint, null);
1042    row.getCells().add(left);
1043    return left;
1044  }
1045
1046  public List<Cell> genElementCells(HierarchicalTableGenerator gen, ElementDefinition element, String profileBaseFileName, boolean snapshot, String corePath,
1047      String imagePath, boolean root, boolean logicalModel, boolean allInvariants, StructureDefinition profile, Row typesRow, Row row, boolean hasDef,
1048      boolean ext, UnusedTracker used, String ref, String sName, Cell nameCell, boolean mustSupport, boolean allowSubRows, RenderingContext rc) throws IOException {
1049    List<Cell> res = new ArrayList<>();
1050    Cell gc = gen.new Cell();
1051    row.getCells().add(gc);
1052    res.add(gc);
1053    if (element != null && element.getIsModifier()) {
1054      checkForNoChange(element.getIsModifierElement(), gc.addStyledText(translate("sd.table", "This element is a modifier element"), "?!", null, null, null, false));
1055    }
1056    if (element != null) {
1057      if (element.getMustSupport() && element.hasExtension(ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS)) {
1058        checkForNoChange(element.getMustSupportElement(), gc.addStyledText(translate("sd.table", "This element has obligations and must be supported"), "SO", "white", "red", null, false));
1059      } else if (element.getMustSupport()) {
1060          checkForNoChange(element.getMustSupportElement(), gc.addStyledText(translate("sd.table", "This element must be supported"), "S", "white", "red", null, false));
1061      } else if (element != null && element.hasExtension(ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS)) {
1062       checkForNoChange(element.getMustSupportElement(), gc.addStyledText(translate("sd.table", "This element has obligations"), "O", "white", "red", null, false));
1063      }
1064    }
1065    if (element != null && element.getIsSummary()) {
1066      checkForNoChange(element.getIsSummaryElement(), gc.addStyledText(translate("sd.table", "This element is included in summaries"), "\u03A3", null, null, null, false));
1067    }
1068    if (element != null && element.getMustHaveValue()) {
1069      checkForNoChange(element.getMustHaveValueElement(), gc.addStyledText(translate("sd.table", "This primitive element must have a value"), "V", "maroon", null, null, true));
1070    }
1071    if (element != null && (hasNonBaseConstraints(element.getConstraint()) || hasNonBaseConditions(element.getCondition()))) {
1072      Piece p = gc.addText(CONSTRAINT_CHAR);
1073      p.setHint(translate("sd.table", "This element has or is affected by constraints ("+listConstraintsAndConditions(element)+")"));
1074      p.addStyle(CONSTRAINT_STYLE);
1075      p.setReference(Utilities.pathURL(VersionUtilities.getSpecUrl(context.getWorker().getVersion()), "conformance-rules.html#constraints"));
1076    }
1077    if (element != null && element.hasExtension(ToolingExtensions.EXT_STANDARDS_STATUS)) {
1078      StandardsStatus ss = StandardsStatus.fromCode(element.getExtensionString(ToolingExtensions.EXT_STANDARDS_STATUS));
1079      gc.addStyledText("Standards Status = "+ss.toDisplay(), ss.getAbbrev(), "black", ss.getColor(), context.getWorker().getSpecUrl()+"versions.html#std-process", true);
1080    }
1081
1082    ExtensionContext extDefn = null;
1083    if (ext) {
1084      if (element != null) {
1085        if (element.getType().size() == 1 && element.getType().get(0).hasProfile()) {
1086          String eurl = element.getType().get(0).getProfile().get(0).getValue();
1087          extDefn = locateExtension(StructureDefinition.class, eurl);
1088          if (extDefn == null) {
1089            res.add(genCardinality(gen, element, row, hasDef, used, null));
1090            res.add(addCell(row, gen.new Cell(null, null, "?gen-e1? "+element.getType().get(0).getProfile(), null, null)));
1091            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));
1092          } else {
1093            String name = element.hasSliceName() ? element.getSliceName() : urltail(eurl);
1094            nameCell.getPieces().get(0).setText(name);
1095            // left.getPieces().get(0).setReference((String) extDefn.getExtensionStructure().getTag("filename"));
1096            nameCell.getPieces().get(0).setHint(translate("sd.table", "Extension URL")+" = "+extDefn.getUrl());
1097            res.add(genCardinality(gen, element, row, hasDef, used, extDefn.getElement()));
1098            ElementDefinition valueDefn = extDefn.getExtensionValueDefinition();
1099            if (valueDefn != null && !"0".equals(valueDefn.getMax()))
1100              res.add(genTypes(gen, row, valueDefn, profileBaseFileName, profile, corePath, imagePath, root, mustSupport));
1101            else // if it's complex, we just call it nothing
1102              // genTypes(gen, row, extDefn.getSnapshot().getElement().get(0), profileBaseFileName, profile);
1103              res.add(addCell(row, gen.new Cell(null, null, "("+translate("sd.table", "Complex")+")", null, null)));
1104            res.add(generateDescription(gen, row, element, extDefn.getElement(), used.used, null, extDefn.getUrl(), profile, corePath, imagePath, root, logicalModel, allInvariants, valueDefn, snapshot, mustSupport, allowSubRows, rc));
1105          }
1106        } else {
1107          res.add(genCardinality(gen, element, row, hasDef, used, null));
1108          if ("0".equals(element.getMax()))
1109            res.add(addCell(row, gen.new Cell()));            
1110          else
1111            res.add(genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath, root, mustSupport));
1112          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));
1113        }
1114      }
1115    } else if (element != null) {
1116      res.add(genCardinality(gen, element, row, hasDef, used, null));
1117      if (hasDef && !"0".equals(element.getMax()) && typesRow == null)
1118        res.add(genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath, root, mustSupport));
1119      else
1120        res.add(addCell(row, gen.new Cell()));
1121      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));
1122    }
1123    return res;
1124  }
1125
1126  private Cell genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef, UnusedTracker tracker, ElementDefinition fallback) {
1127    IntegerType min = !hasDef ? new IntegerType() : definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
1128    StringType max = !hasDef ? new StringType() : definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
1129    if (min.isEmpty() && definition.getUserData(ProfileUtilities.UD_DERIVATION_POINTER) != null) {
1130      ElementDefinition base = (ElementDefinition) definition.getUserData(ProfileUtilities.UD_DERIVATION_POINTER);
1131      if (base.hasMinElement()) {
1132        min = base.getMinElement().copy();
1133        min.setUserData(ProfileUtilities.UD_DERIVATION_EQUALS, true);
1134      }
1135    }
1136    if (max.isEmpty() && definition.getUserData(ProfileUtilities.UD_DERIVATION_POINTER) != null) {
1137      ElementDefinition base = (ElementDefinition) definition.getUserData(ProfileUtilities.UD_DERIVATION_POINTER);
1138      if (base.hasMaxElement()) {
1139        max = base.getMaxElement().copy();
1140        max.setUserData(ProfileUtilities.UD_DERIVATION_EQUALS, true);
1141      }
1142    }
1143    if (min.isEmpty() && fallback != null)
1144      min = fallback.getMinElement();
1145    if (max.isEmpty() && fallback != null)
1146      max = fallback.getMaxElement();
1147
1148    if (!max.isEmpty())
1149      tracker.used = !max.getValue().equals("0");
1150
1151    String hint = null;
1152    if (max.hasValue() && min.hasValue() && "*".equals(max.getValue()) && 0 == min.getValue()) {
1153      if (definition.hasExtension(ToolingExtensions.EXT_JSON_EMPTY)) {
1154        String code = ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_JSON_EMPTY);
1155        if ("present".equals(code)) {
1156          hint = "This element is present as a JSON Array even when there are no items in the instance";
1157        } else {
1158          hint = "This element may be present as a JSON Array even when there are no items in the instance";          
1159        }
1160      }
1161    }
1162    Cell cell = gen.new Cell(null, null, null, null, null);
1163    row.getCells().add(cell);
1164    if (!min.isEmpty() || !max.isEmpty()) {
1165      cell.addPiece(checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), hint)));
1166      cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", hint)));
1167      cell.addPiece(checkForNoChange(max, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), hint)));
1168    }
1169    return cell;
1170  }
1171
1172  public List<ElementDefinition> supplementMissingDiffElements(StructureDefinition profile) {
1173    List<ElementDefinition> list = new ArrayList<>();
1174    list.addAll(profile.getDifferential().getElement());
1175    if (list.isEmpty()) {
1176      ElementDefinition root = new ElementDefinition().setPath(profile.getTypeName());
1177      root.setId(profile.getTypeName());
1178      list.add(root);
1179    } else {
1180      if (list.get(0).getPath().contains(".")) {
1181        ElementDefinition root = new ElementDefinition().setPath(profile.getTypeName());
1182        root.setId(profile.getTypeName());
1183        list.add(0, root);
1184      }
1185    }
1186    insertMissingSparseElements(list);
1187    return list;
1188  }
1189
1190  private boolean usesMustSupport(List<ElementDefinition> list) {
1191    for (ElementDefinition ed : list)
1192      if (ed.hasMustSupport() && ed.getMustSupport())
1193        return true;
1194    return false;
1195  }
1196
1197
1198
1199  private Row chooseChildRowByGroup(HierarchicalTableGenerator gen, Row row, List<ElementChoiceGroup> groups, ElementDefinition element, ElementDefinition parent, boolean isConstraintMode) {
1200    String name = tail(element.getPath());
1201    for (ElementChoiceGroup grp : groups) {
1202      if (grp.getElements().contains(name)) {
1203        if (grp.getRow() == null) {
1204          grp.setRow(makeChoiceElementRow(gen, row, grp, parent, isConstraintMode));
1205        }
1206        return grp.getRow();
1207      }
1208    }
1209    return row;
1210  }
1211
1212  private Row makeChoiceElementRow(HierarchicalTableGenerator gen, Row prow, ElementChoiceGroup grp, ElementDefinition parent, boolean isConstraintMode) {
1213    if (context.getStructureMode() != StructureDefinitionRendererMode.SUMMARY) {
1214      return prow;
1215    }
1216    Row row = gen.new Row();
1217    row.setAnchor(parent.getPath()+"-"+grp.getName());
1218    row.setColor(context.getProfileUtilities().getRowColor(parent, isConstraintMode));
1219    row.setLineColor(1);
1220    row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE);
1221    row.getCells().add(gen.new Cell(null, null, "(Choice of one)", "", null));
1222    row.getCells().add(gen.new Cell());
1223    row.getCells().add(gen.new Cell(null, null, (grp.isMandatory() ? "1" : "0")+"..1", "", null));
1224    row.getCells().add(gen.new Cell());
1225    row.getCells().add(gen.new Cell());
1226    prow.getSubRows().add(row);
1227    return row;
1228  }
1229
1230
1231  private void insertMissingSparseElements(List<ElementDefinition> list) {
1232    int i = 1;
1233    while (i < list.size()) {
1234      String[] pathCurrent = list.get(i).getPath().split("\\.");
1235      String[] pathLast = list.get(i-1).getPath().split("\\.");
1236      int firstDiff = 0; // the first entry must be a match
1237      while (firstDiff < pathCurrent.length && firstDiff < pathLast.length && pathCurrent[firstDiff].equals(pathLast[firstDiff])) {
1238        firstDiff++;
1239      }
1240      if (!(isSibling(pathCurrent, pathLast, firstDiff) || isChild(pathCurrent, pathLast, firstDiff))) {
1241        // now work backwards down to lastMatch inserting missing path nodes
1242        ElementDefinition parent = findParent(list, i, list.get(i).getPath());
1243        int parentDepth = Utilities.charCount(parent.getPath(), '.')+1;
1244        int childDepth =  Utilities.charCount(list.get(i).getPath(), '.')+1;
1245        if (childDepth > parentDepth + 1) {
1246          String basePath = parent.getPath();
1247          String baseId = parent.getId();
1248          for (int index = parentDepth; index >= firstDiff; index--) {
1249            String mtail = makeTail(pathCurrent, parentDepth, index);
1250            ElementDefinition root = new ElementDefinition().setPath(basePath+"."+mtail);
1251            root.setId(baseId+"."+mtail);
1252            list.add(i, root);
1253          }
1254        }
1255      } 
1256      i++;
1257    }
1258  }
1259
1260
1261  private String urltail(String path) {
1262    if (path.contains("#"))
1263      return path.substring(path.lastIndexOf('#')+1);
1264    if (path.contains("/"))
1265      return path.substring(path.lastIndexOf('/')+1);
1266    else
1267      return path;
1268
1269  }
1270
1271  private boolean standardExtensionSlicing(ElementDefinition element) {
1272    String t = tail(element.getPath());
1273    return (t.equals("extension") || t.equals("modifierExtension"))
1274        && 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);
1275  }
1276
1277  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 {
1278    return generateDescription(gen, row, definition, fallback, used, baseURL, url, profile, corePath, imagePath, root, logicalModel, allInvariants, null, snapshot, mustSupportOnly, allowSubRows, rc);
1279  }
1280
1281  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 {
1282    Cell c = gen.new Cell();
1283    row.getCells().add(c);
1284
1285    if (used) {
1286      if (logicalModel && ToolingExtensions.hasExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) {
1287        if (root) {
1288          c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold"));
1289          c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null));        
1290        } else if (!root && ToolingExtensions.hasExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace") && 
1291            !ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace").equals(ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))) {
1292          c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold"));
1293          c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null));        
1294        }
1295      }
1296      if (root) {
1297        if (profile != null && profile.getAbstract()) {
1298          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1299          c.addPiece(gen.new Piece(null, "This is an abstract "+(profile.getDerivation() == TypeDerivationRule.CONSTRAINT ? "profile" : "type")+". ", null));
1300
1301          List<StructureDefinition> children = new ArrayList<>();
1302          for (StructureDefinition sd : context.getWorker().fetchResourcesByType(StructureDefinition.class)) {
1303            if (sd.hasBaseDefinition() && sd.getBaseDefinition().equals(profile.getUrl())) {
1304              children.add(sd);
1305            }
1306          }
1307          if (!children.isEmpty()) {
1308            c.addPiece(gen.new Piece(null, "Child "+(profile.getDerivation() == TypeDerivationRule.CONSTRAINT ? "profiles" : "types")+": ", null));
1309            boolean first = true;
1310            for (StructureDefinition sd : children) {
1311              if (first) first = false; else c.addPiece(gen.new Piece(null, ", ", null));
1312              c.addPiece(gen.new Piece(sd.getWebPath(), sd.getTypeName(), null));
1313            }
1314          }
1315        }
1316      }
1317      if (definition.getPath().endsWith("url") && definition.hasFixed()) {
1318        c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen")));
1319      } else {
1320        if (definition != null && definition.hasShort()) {
1321          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1322          c.addPiece(checkForNoChange(definition.getShortElement(), gen.new Piece(null, gt(definition.getShortElement()), null)));
1323        } else if (fallback != null && fallback.hasShort()) {
1324          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1325          c.addPiece(gen.new Piece(null, gt(fallback.getShortElement()), null).addStyle("opacity: 0.5"));
1326        }
1327        if (url != null) {
1328          if (!c.getPieces().isEmpty()) 
1329            c.addPiece(gen.new Piece("br"));
1330          String fullUrl = url.startsWith("#") ? baseURL+url : url;
1331          StructureDefinition ed = context.getWorker().fetchResource(StructureDefinition.class, url, profile);
1332          String ref = null;
1333          String ref2 = null;
1334          String fixedUrl = null;
1335          if (ed != null) {
1336            String p = ed.getWebPath();
1337            if (p != null) {
1338              ref = p.startsWith("http:") || context.getRules() == GenerationRules.IG_PUBLISHER ? p : Utilities.pathURL(corePath, p);
1339            }             
1340            fixedUrl = getFixedUrl(ed);
1341            if (fixedUrl != null) {// if its null, we guess that it's not a profiled extension?
1342              if (fixedUrl.equals(url))
1343                fixedUrl = null;
1344              else {
1345                StructureDefinition ed2 = context.getWorker().fetchResource(StructureDefinition.class, fixedUrl);
1346                if (ed2 != null) {
1347                  String p2 = ed2.getWebPath();
1348                  if (p2 != null) {
1349                    ref2 = p2.startsWith("http:") || context.getRules() == GenerationRules.IG_PUBLISHER ? p2 : Utilities.pathURL(corePath, p2);
1350                  }                              
1351                }
1352              }
1353            }
1354          }
1355          if (fixedUrl == null) {
1356            if (!Utilities.noString(fullUrl)) {
1357              c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold"));
1358              c.getPieces().add(gen.new Piece(ref, fullUrl, null));
1359            }
1360          } else { 
1361            // reference to a profile take on the extension show the base URL
1362            c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold"));
1363            c.getPieces().add(gen.new Piece(ref2, fixedUrl, null));
1364            c.getPieces().add(gen.new Piece(null, translate("sd.table", " profiled by ")+" ", null).addStyle("font-weight:bold"));
1365            c.getPieces().add(gen.new Piece(ref, fullUrl, null));
1366
1367          }
1368        }
1369
1370        if (definition.hasSlicing()) {
1371          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1372          c.getPieces().add(gen.new Piece(null, translate("sd.table", "Slice")+": ", null).addStyle("font-weight:bold"));
1373          c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null));
1374        }
1375        if (!definition.getPath().contains(".") && ToolingExtensions.hasExtension(profile, ToolingExtensions.EXT_BINDING_STYLE)) {
1376          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1377          c.getPieces().add(gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold"));
1378          c.getPieces().add(gen.new Piece(null, "This type can be bound to a value set using the ", null));
1379          c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(profile, ToolingExtensions.EXT_BINDING_STYLE), null));
1380          c.getPieces().add(gen.new Piece(null, " binding style", null));            
1381        }
1382        if (definition.hasValueAlternatives()) {
1383          addCanonicalList(gen, c, definition.getValueAlternatives(), "The primitive value may be replaced by the extension", true);
1384        }
1385        if (definition.hasExtension(ToolingExtensions.EXT_IMPLIED_PREFIX)) {
1386          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1387          c.getPieces().add(gen.new Piece(null, "When this element is read ", null));          
1388          Piece piece = gen.new Piece("code");
1389          piece.addHtml(new XhtmlNode(NodeType.Text).setContent(ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_IMPLIED_PREFIX)));
1390          c.getPieces().add(piece);          
1391          c.getPieces().add(gen.new Piece(null, " is prefixed to the value before validation", null));          
1392        }
1393
1394        if (definition.hasExtension(ToolingExtensions.EXT_EXTENSION_STYLE)) {
1395          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1396          String es = definition.getExtensionString(ToolingExtensions.EXT_EXTENSION_STYLE);
1397          if ("named-elements".equals(es)) {
1398            if (rc.hasLink(KnownLinkType.JSON_NAMES)) {
1399              c.getPieces().add(gen.new Piece(rc.getLink(KnownLinkType.JSON_NAMES), "This element can be extended by named JSON elements", null));                        
1400            } else {
1401              c.getPieces().add(gen.new Piece(ToolingExtensions.WEB_EXTENSION_STYLE, "This element can be extended by named JSON elements", null));                        
1402            }
1403          }
1404        }
1405        if (definition.hasExtension(ToolingExtensions.EXT_DATE_FORMAT)) {
1406          String df = ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_DATE_FORMAT);
1407          if (df != null) {
1408            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1409            c.getPieces().add(gen.new Piece(null, "Date Format: "+df, null));
1410          }
1411        }
1412        if (definition.hasExtension(ToolingExtensions.EXT_ID_EXPECTATION)) {
1413          String ide = ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_ID_EXPECTATION);
1414          if (ide.equals("optional")) {
1415            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1416            c.getPieces().add(gen.new Piece(null, "Id may or not be present (this is the default for elements but not resources)", null));     
1417          } else if (ide.equals("required")) {
1418            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1419            c.getPieces().add(gen.new Piece(null, "Id is required to be present (this is the default for resources but not elements)", null));     
1420          } else if (ide.equals("required")) {
1421            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1422            c.getPieces().add(gen.new Piece(null, "An ID is not allowed in this context", null));     
1423          }
1424        }
1425        if (definition.hasExtension(ToolingExtensions.EXT_XML_NAME)) {
1426          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1427          if (definition.hasExtension(ToolingExtensions.EXT_XML_NAMESPACE)) {
1428            c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML")+": ", null).addStyle("font-weight:bold"));
1429            c.getPieces().add(gen.new Piece(null, definition.getExtensionString(ToolingExtensions.EXT_XML_NAME), null));
1430            c.getPieces().add(gen.new Piece(null, " (", null));
1431            c.getPieces().add(gen.new Piece(null, definition.getExtensionString(ToolingExtensions.EXT_XML_NAMESPACE), null));
1432            c.getPieces().add(gen.new Piece(null, ")", null));            
1433          } else {
1434            c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Element Name")+": ", null).addStyle("font-weight:bold"));
1435            c.getPieces().add(gen.new Piece(null, definition.getExtensionString(ToolingExtensions.EXT_XML_NAME), null));
1436          }            
1437        } else if (definition.hasExtension(ToolingExtensions.EXT_XML_NAMESPACE)) {
1438          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1439          c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold"));
1440          c.getPieces().add(gen.new Piece(null, definition.getExtensionString(ToolingExtensions.EXT_XML_NAMESPACE), null));          
1441        }
1442        if (definition.hasExtension(ToolingExtensions.EXT_JSON_EMPTY)) {
1443          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1444          String code = ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_JSON_EMPTY);
1445          if ("present".equals(code)) {
1446            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));     
1447          } else {
1448            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));     
1449          }
1450        }
1451        String jn = ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_JSON_NAME);
1452        if (!Utilities.noString(jn)) {
1453          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1454          if (definition.getPath().contains(".")) {
1455            c.getPieces().add(gen.new Piece(null, translate("sd.table", "JSON Property Name")+": ", null).addStyle("font-weight:bold"));
1456            c.getPieces().add(gen.new Piece(null, jn, null));
1457          } else {
1458            c.getPieces().add(gen.new Piece(null, translate("sd.table", "JSON Property Name for Type")+": ", null).addStyle("font-weight:bold"));
1459            Piece piece = gen.new Piece("code");
1460            piece.addHtml(new XhtmlNode(NodeType.Text).setContent(jn));
1461            c.getPieces().add(piece);            
1462          }
1463        }
1464
1465        if (ToolingExtensions.readBoolExtension(definition, ToolingExtensions.EXT_JSON_PRIMITIVE_CHOICE)) {
1466          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1467          c.getPieces().add(gen.new Piece(null, "JSON: The type of this element is inferred from the JSON type in the instance", null));     
1468        }
1469        if (ToolingExtensions.readBoolExtension(definition, ToolingExtensions.EXT_JSON_NULLABLE)) {
1470          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1471          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));     
1472        }
1473        if (definition.hasExtension(ToolingExtensions.EXT_JSON_PROP_KEY)) {
1474          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1475          String code = ToolingExtensions.readStringExtension(definition, ToolingExtensions.EXT_JSON_PROP_KEY);
1476          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));     
1477        }      
1478        if (definition.hasExtension(ToolingExtensions.EXT_TYPE_SPEC)) {
1479          for (Extension e : definition.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_SPEC)) {
1480            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1481            String cond = ToolingExtensions.readStringExtension(e, "condition");
1482            String type = ToolingExtensions.readStringExtension(e, "type");
1483            c.getPieces().add(gen.new Piece(null, "JSON: If ", null));          
1484            Piece piece = gen.new Piece("code");
1485            piece.addHtml(new XhtmlNode(NodeType.Text).setContent(cond));
1486            c.getPieces().add(piece);          
1487            c.getPieces().add(gen.new Piece(null, "then the type is ", null));          
1488            StructureDefinition sd = context.getWorker().fetchTypeDefinition(type);
1489            if (sd == null) {
1490              c.getPieces().add(gen.new Piece("<code>"));          
1491              c.getPieces().add(gen.new Piece(null, type, null));          
1492              c.getPieces().add(gen.new Piece("</code>"));          
1493            } else {
1494              c.getPieces().add(gen.new Piece(sd.getWebPath(), sd.getTypeName(), null));          
1495            }
1496          }
1497        }
1498        if (root) {
1499          if (ToolingExtensions.readBoolExtension(profile, ToolingExtensions.EXT_OBLIGATION_PROFILE_FLAG)) {
1500            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1501            c.addPiece(gen.new Piece(null, "This is an obligation profile that only contains obligations and additional bindings", null).addStyle("font-weight:bold"));          
1502          }
1503          addCanonicalListExt(gen, c, profile.getExtensionsByUrl(ToolingExtensions.EXT_OBLIGATION_INHERITS), "This profile picks up obligations and additional bindings from the profile", true);
1504          addCanonicalListExt(gen, c, profile.getExtensionsByUrl(ToolingExtensions.EXT_SD_IMPOSE_PROFILE), "This profile also imposes the profile", true);
1505          addCanonicalListExt(gen, c, profile.getExtensionsByUrl(ToolingExtensions.EXT_SD_COMPLIES_WITH_PROFILE), "This profile also complies with the profile", true);
1506
1507          if (profile.getKind() == StructureDefinitionKind.LOGICAL) {
1508            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1509            if (ToolingExtensions.readBoolExtension(profile, ToolingExtensions.EXT_LOGICAL_TARGET)) {
1510              c.addPiece(gen.new Piece(null, "This logical model can be the target of a reference", null).addStyle("font-weight:bold"));  
1511            } else {
1512              c.addPiece(gen.new Piece(null, "This logical model cannot be the target of a reference", null).addStyle("font-weight:bold"));                
1513            }
1514          }
1515        }
1516        if (definition != null) {
1517          ElementDefinitionBindingComponent binding = null;
1518          if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty())
1519            binding = makeUnifiedBinding(valueDefn.getBinding(), valueDefn);
1520          else if (definition.hasBinding())
1521            binding = makeUnifiedBinding(definition.getBinding(), definition);
1522          if (binding!=null && !binding.isEmpty()) {
1523            if (!c.getPieces().isEmpty()) 
1524              c.addPiece(gen.new Piece("br"));
1525            BindingResolution br = context.getPkp() == null ? makeNullBr(binding) : context.getPkp().resolveBinding(profile, binding, definition.getPath());
1526            c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold")));
1527            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)));
1528            if (binding.hasStrength()) {
1529              c.getPieces().add(checkForNoChange(binding.getStrengthElement(), gen.new Piece(null, " (", null)));
1530              c.getPieces().add(checkForNoChange(binding.getStrengthElement(), gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), egt(binding.getStrengthElement()), binding.getStrength().getDefinition())));                            
1531              c.getPieces().add(checkForNoChange(binding.getStrengthElement(), gen.new Piece(null, ")", null)));
1532            }
1533            if (binding.hasDescription() && MarkDownProcessor.isSimpleMarkdown(binding.getDescription())) {
1534              c.getPieces().add(gen.new Piece(null, ": ", null));
1535              c.addMarkdownNoPara(PublicationHacker.fixBindingDescriptions(context.getWorker(), binding.getDescriptionElement()).asStringValue(), checkForNoChange(PublicationHacker.fixBindingDescriptions(context.getWorker(), binding.getDescriptionElement())));
1536            } 
1537
1538            AdditionalBindingsRenderer abr = new AdditionalBindingsRenderer(context.getPkp(), corePath, profile, definition.getPath(), rc, null, this);
1539            if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) {
1540              abr.seeMaxBinding(ToolingExtensions.getExtension(binding, ToolingExtensions.EXT_MAX_VALUESET));
1541            }
1542            if (binding.hasExtension(ToolingExtensions.EXT_MIN_VALUESET)) {
1543              abr.seeMinBinding(ToolingExtensions.getExtension(binding, ToolingExtensions.EXT_MIN_VALUESET));
1544            }
1545            if (binding.hasExtension(ToolingExtensions.EXT_BINDING_ADDITIONAL)) {
1546              abr.seeAdditionalBindings(binding.getExtensionsByUrl(ToolingExtensions.EXT_BINDING_ADDITIONAL));
1547            }
1548            abr.render(gen, c);
1549          }
1550          for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
1551            if (!inv.hasSource() || profile == null || inv.getSource().equals(profile.getUrl()) || allInvariants) {
1552              if (!c.getPieces().isEmpty()) 
1553                c.addPiece(gen.new Piece("br"));
1554              c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold")));
1555              c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, gt(inv.getHumanElement()), null)));
1556            }
1557          }
1558          if ((definition.hasBase() && "*".equals(definition.getBase().getMax())) || (definition.hasMax() && "*".equals(definition.getMax()))) {
1559            if (c.getPieces().size() > 0)
1560              c.addPiece(gen.new Piece("br"));
1561            if (definition.hasOrderMeaning()) {
1562              c.getPieces().add(gen.new Piece(null, "This repeating element order: "+definition.getOrderMeaning(), null));
1563            } else {
1564              // don't show this, this it's important: c.getPieces().add(gen.new Piece(null, "This repeating element has no defined order", null));
1565            }           
1566          }
1567          if (definition.hasFixed()) {
1568            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1569            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, translate("sd.table", "Fixed Value")+": ", null).addStyle("font-weight:bold")));
1570            if (!useTableForFixedValues || !allowSubRows || definition.getFixed().isPrimitive()) {
1571              String s = buildJson(definition.getFixed());
1572              String link = null;
1573              if (Utilities.isAbsoluteUrl(s) && context.getPkp() != null)
1574                link = context.getPkp().getLinkForUrl(corePath, s);
1575              c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen")));
1576            } else {
1577              c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "As shown", null).addStyle("color: darkgreen")));
1578              genFixedValue(gen, row, definition.getFixed(), snapshot, false, corePath, false);
1579            }
1580            if (isCoded(definition.getFixed()) && !hasDescription(definition.getFixed())) {
1581              Piece p = describeCoded(gen, definition.getFixed());
1582              if (p != null)
1583                c.getPieces().add(p);
1584            }
1585          } else if (definition.hasPattern()) {
1586            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1587            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, translate("sd.table", "Required Pattern")+": ", null).addStyle("font-weight:bold")));
1588            if (!useTableForFixedValues || !allowSubRows || definition.getPattern().isPrimitive())
1589              c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen")));
1590            else {
1591              c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "At least the following", null).addStyle("color: darkgreen")));
1592              genFixedValue(gen, row, definition.getPattern(), snapshot, true, corePath, mustSupportOnly);
1593            }
1594          } else if (definition.hasExample()) {
1595            for (ElementDefinitionExampleComponent ex : definition.getExample()) {
1596              if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1597              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, translate("sd.table", "Example")+("".equals("General")? "" : " "+ex.getLabel())+": ", null).addStyle("font-weight:bold")));
1598              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen")));
1599            }
1600          }
1601
1602          ObligationsRenderer obr = new ObligationsRenderer(corePath, profile, definition.getPath(), rc, null, this);
1603          if (definition.hasExtension(ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS)) {
1604            obr.seeObligations(definition.getExtensionsByUrl(ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS));
1605          }
1606          if (!definition.getPath().contains(".") && profile.hasExtension(ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS)) {
1607            obr.seeObligations(profile.getExtensionsByUrl(ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS));
1608          }
1609          obr.renderTable(gen, c);
1610
1611          if (definition.hasMaxLength() && definition.getMaxLength()!=0) {
1612            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1613            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold")));
1614            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen")));
1615          }
1616          if (profile != null) {
1617            for (StructureDefinitionMappingComponent md : profile.getMapping()) {
1618              if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) {
1619                ElementDefinitionMappingComponent map = null;
1620                for (ElementDefinitionMappingComponent m : definition.getMapping()) 
1621                  if (m.getIdentity().equals(md.getIdentity()))
1622                    map = m;
1623                if (map != null) {
1624                  for (int i = 0; i<definition.getMapping().size(); i++){
1625                    c.addPiece(gen.new Piece("br"));
1626                    c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null));
1627                  }
1628                }
1629              }
1630            }
1631          }
1632        }
1633      }
1634    }
1635    return c;
1636  }
1637
1638
1639  private void addCanonicalListExt(HierarchicalTableGenerator gen, Cell c, List<Extension> list, String start, boolean bold) {
1640    List<CanonicalType> clist = new ArrayList<>();
1641    for (Extension ext : list) {
1642      if (ext.hasValueCanonicalType()) {
1643        clist.add(ext.getValueCanonicalType());
1644      }
1645    }
1646    addCanonicalList(gen, c, clist, start, bold);
1647  }
1648  
1649  private void addCanonicalList(HierarchicalTableGenerator gen, Cell c, List<CanonicalType> list, String start, boolean bold) {
1650    if (!list.isEmpty()) {
1651
1652      if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
1653      Piece p = gen.new Piece(null, start+(list.size() != 1 ? "s" : "")+" ", null);
1654      c.addPiece(p);
1655      if (bold) p.addStyle("font-weight:bold");
1656      
1657      for (int i = 0; i < list.size(); i++) {
1658        CanonicalType ct = list.get(i);
1659        if (i > 0) {
1660          if (i < list.size() - 1) {
1661            c.addPiece(gen.new Piece(null, ", ", null));                      
1662          } else {
1663            c.addPiece(gen.new Piece(null, " and ", null));                      
1664          }
1665        }
1666        String iu = ct.primitiveValue();
1667        StructureDefinition sd = context.getContext().fetchResource(StructureDefinition.class, iu);
1668        if (sd == null) {
1669          p = gen.new Piece(null, iu, null).addStyle("font-weight:bold");
1670          c.addPiece(p);                      
1671        } else if (sd.hasWebPath()) {
1672          p = gen.new Piece(sd.getWebPath(), sd.present(), null).addStyle("font-weight:bold");
1673          c.addPiece(p);                      
1674        } else {
1675          p = gen.new Piece(iu, sd.present(), null).addStyle("font-weight:bold");
1676          c.addPiece(p);                      
1677        }
1678        if (bold) p.addStyle("font-weight:bold");
1679      }
1680    }    
1681  }
1682
1683  private Piece checkForNoChange(Element source, Piece piece) {
1684    if (source.hasUserData(ProfileUtilities.UD_DERIVATION_EQUALS)) {
1685      piece.addStyle("opacity: 0.5");
1686    }
1687    return piece;
1688  }
1689
1690  private String checkForNoChange(Element source) {
1691    if (source.hasUserData(ProfileUtilities.UD_DERIVATION_EQUALS)) {
1692      return "opacity: 0.5";
1693    } else { 
1694      return null;
1695    }
1696  }
1697
1698  private Cell genTypes(HierarchicalTableGenerator gen, Row r, ElementDefinition e, String profileBaseFileName, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean mustSupportMode) {
1699    Cell c = gen.new Cell();
1700    r.getCells().add(c);
1701    if (e.hasContentReference()) {
1702      ElementInStructure ed = getElementByName(profile.getSnapshot().getElement(), e.getContentReference(), profile);
1703      if (ed == null)
1704        c.getPieces().add(gen.new Piece(null, translate("sd.table", "Unknown reference to %s", e.getContentReference()), null));
1705      else {
1706        if (ed.getSource() == profile) {
1707          c.getPieces().add(gen.new Piece(null, translate("sd.table", "See ", ed.getElement().getPath()), null));
1708          c.getPieces().add(gen.new Piece("#"+ed.getElement().getPath(), tail(ed.getElement().getPath()), ed.getElement().getPath()));
1709        } else {
1710          c.getPieces().add(gen.new Piece(null, translate("sd.table", "See ", ed.getElement().getPath()), null));
1711          c.getPieces().add(gen.new Piece(pfx(corePath, ed.getSource().getWebPath())+"#"+ed.getElement().getPath(), tail(ed.getElement().getPath())+" ("+ed.getSource().getTypeName()+")", ed.getElement().getPath()));
1712        }
1713      }
1714      return c;
1715    }
1716    List<TypeRefComponent> types = e.getType();
1717    if (!e.hasType()) {
1718      if (root) { // we'll use base instead of types then
1719        StructureDefinition bsd = profile == null ? null : context.getWorker().fetchResource(StructureDefinition.class, profile.getBaseDefinition(), profile);
1720        if (bsd != null) {
1721          if (bsd.hasWebPath()) {
1722            c.getPieces().add(gen.new Piece(Utilities.isAbsoluteUrl(bsd.getWebPath()) ? bsd.getWebPath() : imagePath +bsd.getWebPath(), bsd.getName(), null));
1723          } else {
1724            c.getPieces().add(gen.new Piece(null, bsd.getName(), null));
1725          }
1726        }
1727        return c;
1728      } else if (e.hasContentReference()) {
1729        return c;
1730      } else {
1731        ElementDefinition d = (ElementDefinition) e.getUserData(ProfileUtilities.UD_DERIVATION_POINTER);
1732        if (d != null && d.hasType()) {
1733          types = new ArrayList<ElementDefinition.TypeRefComponent>();
1734          for (TypeRefComponent tr : d.getType()) {
1735            TypeRefComponent tt = tr.copy();
1736            tt.setUserData(ProfileUtilities.UD_DERIVATION_EQUALS, true);
1737            types.add(tt);
1738          }
1739        } else {
1740          return c;
1741        }
1742      }
1743    }
1744
1745    boolean first = true;
1746
1747    TypeRefComponent tl = null;
1748    for (TypeRefComponent t : types) {
1749      if (!mustSupportMode || allTypesMustSupport(e) || isMustSupport(t)) {
1750        if (first) {
1751          first = false;
1752        } else {
1753          c.addPiece(checkForNoChange(tl, gen.new Piece(null,", ", null)));
1754        }
1755        tl = t;
1756        if (t.hasTarget()) {
1757          c.getPieces().add(gen.new Piece(corePath+"references.html", t.getWorkingCode(), null));
1758          if (!mustSupportMode && isMustSupportDirect(t) && e.getMustSupport()) {
1759            c.addPiece(gen.new Piece(null, " ", null));
1760            c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false);
1761          }
1762          c.getPieces().add(gen.new Piece(null, "(", null));
1763          boolean tfirst = true;
1764          for (CanonicalType u : t.getTargetProfile()) {
1765            if (!mustSupportMode || allProfilesMustSupport(t.getTargetProfile()) || isMustSupport(u)) {
1766              if (tfirst)
1767                tfirst = false;
1768              else
1769                c.addPiece(gen.new Piece(null, " | ", null));
1770              genTargetLink(gen, profileBaseFileName, corePath, c, t, u.getValue(), null);
1771              if (!mustSupportMode && isMustSupport(u) && e.getMustSupport()) {
1772                c.addPiece(gen.new Piece(null, " ", null));
1773                c.addStyledText(translate("sd.table", "This target must be supported"), "S", "white", "red", null, false);
1774              }
1775            }
1776          }
1777          c.getPieces().add(gen.new Piece(null, ")", null));
1778          if (t.getAggregation().size() > 0) {
1779            c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", " {", null));
1780            boolean firstA = true;
1781            for (Enumeration<AggregationMode> a : t.getAggregation()) {
1782              if (firstA == true)
1783                firstA = false;
1784              else
1785                c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", ", ", null));
1786              c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", codeForAggregation(a.getValue()), hintForAggregation(a.getValue())));
1787            }
1788            c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", "}", null));
1789          }
1790        } else if (t.hasProfile() && (!t.getWorkingCode().equals("Extension") || isProfiledType(t.getProfile()))) { // a profiled type
1791          String ref;
1792          boolean pfirst = true;
1793          for (CanonicalType p : t.getProfile()) {
1794            if (!mustSupportMode || allProfilesMustSupport(t.getProfile()) || isMustSupport(p)) {
1795              if (pfirst) {
1796                pfirst = false;
1797              } else {
1798                c.addPiece(checkForNoChange(tl, gen.new Piece(null,", ", null)));
1799              }          
1800
1801              ref = context.getPkp() == null ? null : context.getPkp().getLinkForProfile(profile, p.getValue());
1802              if (ref != null) {
1803                String[] parts = ref.split("\\|");
1804                if (parts[0].startsWith("http:") || parts[0].startsWith("https:")) {
1805                  if (p.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT)) {
1806                    String pp = p.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT);
1807                    pp = pp.substring(pp.indexOf("."));
1808                    c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], parts[1]+pp, t.getWorkingCode())));
1809                  } else {
1810                    c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], parts[1], t.getWorkingCode())));
1811                  }
1812                } else {
1813                  c.addPiece(checkForNoChange(t, gen.new Piece((p.getValue().startsWith(corePath+"StructureDefinition")? corePath: "")+parts[0], parts[1], t.getWorkingCode())));
1814                }
1815              } else {
1816                c.addPiece(checkForNoChange(t, gen.new Piece((p.getValue().startsWith(corePath)? corePath: "")+ref, t.getWorkingCode(), null)));
1817              }
1818              if (!mustSupportMode && isMustSupport(p) && e.getMustSupport()) {
1819                c.addPiece(gen.new Piece(null, " ", null));
1820                c.addStyledText(translate("sd.table", "This profile must be supported"), "S", "white", "red", null, false);
1821              }
1822            }
1823          }
1824        } else {
1825          String tc = t.getWorkingCode();
1826          if (Utilities.isAbsoluteUrl(tc)) {
1827            StructureDefinition sd = context.getWorker().fetchTypeDefinition(tc);
1828            if (sd == null) {
1829              c.addPiece(checkForNoChange(t, gen.new Piece(context.getPkp().getLinkFor(corePath, tc), tc, null)));
1830            } else {
1831              c.addPiece(checkForNoChange(t, gen.new Piece(context.getPkp().getLinkFor(corePath, tc), sd.getTypeName(), null)));           
1832            }
1833          } else if (context.getPkp() != null && context.getPkp().hasLinkFor(tc)) {
1834            c.addPiece(checkForNoChange(t, gen.new Piece(context.getPkp().getLinkFor(corePath, tc), tc, null)));
1835          } else {
1836            c.addPiece(checkForNoChange(t, gen.new Piece(null, tc, null)));
1837          }
1838          if (!mustSupportMode && isMustSupportDirect(t) && e.getMustSupport()) {
1839            c.addPiece(gen.new Piece(null, " ", null));
1840            c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false);
1841          }
1842        }
1843      }
1844    }
1845    return c;
1846  }
1847
1848
1849  private String pfx(String prefix, String url) {
1850    return Utilities.isAbsoluteUrl(url) ? url : prefix + url;
1851  }
1852
1853  private void genTargetLink(HierarchicalTableGenerator gen, String profileBaseFileName, String corePath, Cell c, TypeRefComponent t, String u, Resource src) {
1854    if (u.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
1855      StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, u, src);
1856      if (sd != null) {
1857        String disp = sd.hasTitle() ? sd.getTitle() : sd.getName();
1858        c.addPiece(checkForNoChange(t, gen.new Piece(checkPrepend(corePath, sd.getWebPath()), disp, null)));
1859      } else {
1860        String rn = u.substring(40);
1861        c.addPiece(checkForNoChange(t, gen.new Piece(context.getPkp().getLinkFor(corePath, rn), rn, null)));
1862      }
1863    } else if (Utilities.isAbsoluteUrl(u)) {
1864      StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, u, src);
1865      if (sd != null && context.getPkp() != null) {
1866        String disp = sd.hasTitle() ? sd.getTitle() : sd.getName();
1867        String ref = context.getPkp().getLinkForProfile(null, sd.getUrl());
1868        if (ref != null && ref.contains("|"))
1869          ref = ref.substring(0,  ref.indexOf("|"));
1870        c.addPiece(checkForNoChange(t, gen.new Piece(ref, disp, null)));
1871      } else
1872        c.addPiece(checkForNoChange(t, gen.new Piece(null, u, null)));        
1873    } else if (t.hasTargetProfile() && u.startsWith("#"))
1874      c.addPiece(checkForNoChange(t, gen.new Piece(corePath+profileBaseFileName+"."+u.substring(1).toLowerCase()+".html", u, null)));
1875  }
1876
1877  private boolean isProfiledType(List<CanonicalType> theProfile) {
1878    for (CanonicalType next : theProfile){
1879      if (StringUtils.defaultString(next.getValueAsString()).contains(":")) {
1880        return true;
1881      }
1882    }
1883    return false;
1884  }
1885
1886
1887  public String codeForAggregation(AggregationMode a) {
1888    switch (a) {
1889    case BUNDLED : return "b";
1890    case CONTAINED : return "c";
1891    case REFERENCED: return "r";
1892    default: return "?";
1893    }
1894  }
1895
1896  public String hintForAggregation(AggregationMode a) {
1897    if (a != null)
1898      return a.getDefinition();
1899    else 
1900      return null;
1901  }
1902
1903
1904  private String checkPrepend(String corePath, String path) {
1905    if (context.getPkp() != null && context.getPkp().prependLinks() && !(path.startsWith("http:") || path.startsWith("https:")))
1906      return corePath+path;
1907    else 
1908      return path;
1909  }
1910
1911
1912  private ElementDefinition findParent(List<ElementDefinition> list, int i, String path) {
1913    while (i > 0 && !path.startsWith(list.get(i).getPath()+".")) {
1914      i--;
1915    }
1916    return list.get(i);
1917  }
1918
1919  private boolean isSibling(String[] pathCurrent, String[] pathLast, int firstDiff) {
1920    return pathCurrent.length == pathLast.length && firstDiff == pathCurrent.length-1;
1921  }
1922
1923
1924  private boolean isChild(String[] pathCurrent, String[] pathLast, int firstDiff) {
1925    return pathCurrent.length == pathLast.length+1 && firstDiff == pathLast.length;
1926  }
1927
1928  private String makeTail(String[] pathCurrent, int start, int index) {
1929    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(".");
1930    for (int i = start; i <= index; i++) {
1931      b.append(pathCurrent[i]);
1932    }
1933    return b.toString();
1934  }
1935
1936  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 {
1937    StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1);
1938    String s = tail(element.getPath());
1939    List<ElementDefinition> children = getChildren(all, element);
1940    boolean isExtension = (s.equals("extension") || s.equals("modifierExtension"));
1941
1942    if (!onlyInformationIsMapping(all, element)) {
1943      Row row = gen.new Row();
1944      row.setAnchor(element.getPath());
1945      row.setColor(context.getProfileUtilities().getRowColor(element, isConstraintMode));
1946      if (element.hasSlicing())
1947        row.setLineColor(1);
1948      else if (element.hasSliceName())
1949        row.setLineColor(2);
1950      else
1951        row.setLineColor(0);
1952      boolean hasDef = element != null;
1953      String ref = defPath == null ? null : defPath + element.getId();
1954      UnusedTracker used = new UnusedTracker();
1955      used.used = true;
1956      Cell left = gen.new Cell();
1957      if (element.getType().size() == 1 && element.getType().get(0).isPrimitive())
1958        left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())).addStyle("font-weight:bold"));
1959      else
1960        left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())));
1961      if (element.hasSliceName()) {
1962        left.getPieces().add(gen.new Piece("br"));
1963        String indent = StringUtils.repeat('\u00A0', 1+2*(element.getPath().split("\\.").length));
1964        left.getPieces().add(gen.new Piece(null, indent + "("+element.getSliceName() + ")", null));
1965      }
1966      row.getCells().add(left);
1967
1968      genCardinality(gen, element, row, hasDef, used, null);
1969      if (hasDef && !"0".equals(element.getMax()))
1970        genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath, root, false);
1971      else
1972        row.getCells().add(gen.new Cell());
1973      generateGridDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, null);
1974      /*      if (element.hasSlicing()) {
1975        if (standardExtensionSlicing(element)) {
1976          used.used = element.hasType() && element.getType().get(0).hasProfile();
1977          showMissing = false;
1978        } else {
1979          row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE);
1980          row.getCells().get(2).getPieces().clear();
1981          for (Cell cell : row.getCells())
1982            for (Piece p : cell.getPieces()) {
1983              p.addStyle("font-style: italic");
1984            }
1985        }
1986      }*/
1987      rows.add(row);
1988      for (ElementDefinition child : children)
1989        if (child.getMustSupport())
1990          genGridElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, corePath, imagePath, false, isConstraintMode);
1991    }
1992  }
1993
1994
1995  private ExtensionContext locateExtension(Class<StructureDefinition> class1, String value)  {
1996    if (value.contains("#")) {
1997      StructureDefinition ext = context.getWorker().fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
1998      if (ext == null)
1999        return null;
2000      String tail = value.substring(value.indexOf("#")+1);
2001      ElementDefinition ed = null;
2002      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
2003        if (tail.equals(ted.getSliceName())) {
2004          ed = ted;
2005          return new ExtensionContext(ext, ed);
2006        }
2007      }
2008      return null;
2009    } else {
2010      StructureDefinition ext = context.getWorker().fetchResource(StructureDefinition.class, value);
2011      if (ext == null)
2012        return null;
2013      else 
2014        return new ExtensionContext(ext, ext.getSnapshot().getElement().get(0));
2015    }
2016  }
2017
2018
2019  private boolean extensionIsComplex(String value) {
2020    if (value.contains("#")) {
2021      StructureDefinition ext = context.getWorker().fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
2022      if (ext == null)
2023        return false;
2024      String tail = value.substring(value.indexOf("#")+1);
2025      ElementDefinition ed = null;
2026      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
2027        if (tail.equals(ted.getSliceName())) {
2028          ed = ted;
2029          break;
2030        }
2031      }
2032      if (ed == null)
2033        return false;
2034      int i = ext.getSnapshot().getElement().indexOf(ed);
2035      int j = i+1;
2036      while (j < ext.getSnapshot().getElement().size() && !ext.getSnapshot().getElement().get(j).getPath().equals(ed.getPath()))
2037        j++;
2038      return j - i > 5;
2039    } else {
2040      StructureDefinition ext = context.getWorker().fetchResource(StructureDefinition.class, value);
2041      return ext != null && ext.getSnapshot().getElement().size() > 5;
2042    }
2043  }
2044
2045
2046
2047
2048  private BindingResolution makeNullBr(ElementDefinitionBindingComponent binding) {
2049    BindingResolution br = new BindingResolution();
2050    br.url = "http://none.none/none";
2051    br.display = "todo";
2052    return br;
2053  }
2054
2055  private ElementDefinitionBindingComponent makeUnifiedBinding(ElementDefinitionBindingComponent binding, ElementDefinition element) {
2056    if (!element.hasUserData(ProfileUtilities.UD_DERIVATION_POINTER)) {
2057      return binding;
2058    }
2059    ElementDefinition base = (ElementDefinition) element.getUserData(ProfileUtilities.UD_DERIVATION_POINTER);
2060    if (!base.hasBinding()) {
2061      return binding;
2062    }
2063    ElementDefinitionBindingComponent o = base.getBinding();
2064    ElementDefinitionBindingComponent b = new ElementDefinitionBindingComponent();
2065    b.setUserData(ProfileUtilities.UD_DERIVATION_POINTER, o);
2066    if (binding.hasValueSet()) {
2067      b.setValueSet(binding.getValueSet());
2068    } else if (o.hasValueSet()) {
2069      b.setValueSet(o.getValueSet());
2070      b.getValueSetElement().setUserData(ProfileUtilities.UD_DERIVATION_EQUALS, o.getValueSetElement());
2071    }
2072    if (binding.hasStrength()) {
2073      b.setStrength(binding.getStrength());
2074    } else if (o.hasStrength()) {
2075      b.setStrength(o.getStrength());
2076      b.getStrengthElement().setUserData(ProfileUtilities.UD_DERIVATION_EQUALS, o.getStrengthElement());
2077    }
2078    if (binding.hasDescription()) {
2079      b.setDescription(binding.getDescription());
2080    } else if (o.hasDescription()) {
2081      b.setDescription(o.getDescription());
2082      b.getDescriptionElement().setUserData(ProfileUtilities.UD_DERIVATION_EQUALS, o.getDescriptionElement());
2083    }
2084    // todo: derivation?
2085    b.getExtension().addAll(binding.getExtension());
2086    return b;
2087  }
2088
2089  private void genFixedValue(HierarchicalTableGenerator gen, Row erow, DataType value, boolean snapshot, boolean pattern, String corePath, boolean skipnoValue) {
2090    String ref = context.getPkp().getLinkFor(corePath, value.fhirType());
2091    if (ref != null && ref.contains(".html")) {
2092      ref = ref.substring(0, ref.indexOf(".html"))+"-definitions.html#";
2093    } else {
2094      ref = "?gen-fv?";
2095    }
2096    StructureDefinition sd = context.getWorker().fetchTypeDefinition(value.fhirType());
2097
2098    for (org.hl7.fhir.r5.model.Property t : value.children()) {
2099      if (t.getValues().size() > 0 || snapshot) {
2100        ElementDefinition ed = findElementDefinition(sd, t.getName());
2101        if (t.getValues().size() == 0 || (t.getValues().size() == 1 && t.getValues().get(0).isEmpty())) {
2102          if (!skipnoValue) {
2103            Row row = gen.new Row();
2104            erow.getSubRows().add(row);
2105            Cell c = gen.new Cell();
2106            row.getCells().add(c);
2107            c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref+ed.getPath() : corePath+(VersionUtilities.isR5Plus(context.getWorker().getVersion()) ? "types-definitions.html#"+ed.getBase().getPath() : "element-definitions.html#"+ed.getBase().getPath())), t.getName(), null));
2108            c = gen.new Cell();
2109            row.getCells().add(c);
2110            c.addPiece(gen.new Piece(null, null, null));
2111            c = gen.new Cell();
2112            row.getCells().add(c);
2113            if (!pattern) {
2114              c.addPiece(gen.new Piece(null, "0..0", null));
2115              row.setIcon("icon_fixed.gif", "Fixed Value" /*HierarchicalTableGenerator.TEXT_ICON_FIXED*/);
2116            } else if (isPrimitive(t.getTypeCode())) {
2117              row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
2118              c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null));
2119            } else if (isReference(t.getTypeCode())) { 
2120              row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
2121              c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null));
2122            } else { 
2123              row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
2124              c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null));
2125            }
2126            c = gen.new Cell();
2127            row.getCells().add(c);
2128            if (t.getTypeCode().contains("(")) {
2129              String tc = t.getTypeCode();
2130              String tn = tc.substring(0, tc.indexOf("("));
2131              c.addPiece(gen.new Piece(context.getPkp().getLinkFor(corePath, tn), tn, null));
2132              c.addPiece(gen.new Piece(null, "(", null));
2133              String[] p = tc.substring(tc.indexOf("(")+1, tc.indexOf(")")).split("\\|");
2134              for (String s : p) {
2135                c.addPiece(gen.new Piece(context.getPkp().getLinkFor(corePath, s), s, null));
2136              }
2137              c.addPiece(gen.new Piece(null, ")", null));            
2138            } else {
2139              c.addPiece(gen.new Piece(context.getPkp().getLinkFor(corePath, t.getTypeCode()), t.getTypeCode(), null));
2140            }
2141            c = gen.new Cell();
2142            c.addPiece(gen.new Piece(null, ed.getShort(), null));
2143            row.getCells().add(c);
2144          }
2145        } else {
2146          for (Base b : t.getValues()) {
2147            Row row = gen.new Row();
2148            erow.getSubRows().add(row);
2149            row.setIcon("icon_fixed.gif", "Fixed Value" /*HierarchicalTableGenerator.TEXT_ICON_FIXED*/);
2150
2151            Cell c = gen.new Cell();
2152            row.getCells().add(c);
2153            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));
2154
2155            c = gen.new Cell();
2156            row.getCells().add(c);
2157            c.addPiece(gen.new Piece(null, null, null));
2158
2159            c = gen.new Cell();
2160            row.getCells().add(c);
2161            if (pattern)
2162              c.addPiece(gen.new Piece(null, "1.."+(t.getMaxCardinality() == 2147483647 ? "*" : Integer.toString(t.getMaxCardinality())), null));
2163            else
2164              c.addPiece(gen.new Piece(null, "1..1", null));
2165
2166            c = gen.new Cell();
2167            row.getCells().add(c);
2168            if (b.fhirType().contains("(")) {
2169              String tc = b.fhirType();
2170              String tn = tc.substring(0, tc.indexOf("("));
2171              c.addPiece(gen.new Piece(context.getPkp().getLinkFor(corePath, tn), tn, null));
2172              c.addPiece(gen.new Piece(null, "(", null));
2173              String[] p = tc.substring(tc.indexOf("(")+1, tc.indexOf(")")).split("\\|");
2174              for (String s : p) {
2175                c.addPiece(gen.new Piece(context.getPkp().getLinkFor(corePath, s), s, null));
2176              }
2177              c.addPiece(gen.new Piece(null, ")", null));            
2178            } else {
2179              c.addPiece(gen.new Piece(context.getPkp().getLinkFor(corePath, b.fhirType()), b.fhirType(), null));
2180            }
2181
2182            if (b.isPrimitive()) {
2183              c = gen.new Cell();
2184              row.getCells().add(c);
2185              c.addPiece(gen.new Piece(null, ed.getShort(), null));
2186              c.addPiece(gen.new Piece("br"));
2187              c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold"));
2188              String s = b.primitiveValue();
2189              // ok. let's see if we can find a relevant link for this
2190              String link = null;
2191              if (Utilities.isAbsoluteUrl(s)) {
2192                link = context.getPkp().getLinkForUrl(corePath, s);
2193              }
2194              c.getPieces().add(gen.new Piece(link, s, null).addStyle("color: darkgreen"));
2195            } else {
2196              c = gen.new Cell();
2197              row.getCells().add(c);
2198              c.addPiece(gen.new Piece(null, ed.getShort(), null));
2199              c.addPiece(gen.new Piece("br"));
2200              c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold"));
2201              c.getPieces().add(gen.new Piece(null, "(complex)", null).addStyle("color: darkgreen"));
2202              genFixedValue(gen, row, (DataType) b, snapshot, pattern, corePath, skipnoValue);
2203            }
2204          }
2205        }
2206      }
2207    }
2208  }
2209
2210
2211  private ElementDefinition findElementDefinition(StructureDefinition sd, String name) {
2212    String path = sd.getTypeName()+"."+name;
2213    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
2214      if (ed.getPath().equals(path))
2215        return ed;
2216    }
2217    throw new FHIRException(context.getWorker().formatMessage(I18nConstants.UNABLE_TO_FIND_ELEMENT_, path));
2218  }
2219
2220
2221  private String getFixedUrl(StructureDefinition sd) {
2222    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
2223      if (ed.getPath().equals("Extension.url")) {
2224        if (ed.hasFixed() && ed.getFixed() instanceof UriType)
2225          return ed.getFixed().primitiveValue();
2226      }
2227    }
2228    return null;
2229  }
2230
2231
2232  private Piece describeCoded(HierarchicalTableGenerator gen, DataType fixed) {
2233    if (fixed instanceof Coding) {
2234      Coding c = (Coding) fixed;
2235      ValidationResult vr = context.getWorker().validateCode(context.getTerminologyServiceOptions(), c.getSystem(), c.getVersion(), c.getCode(), c.getDisplay());
2236      if (vr.getDisplay() != null)
2237        return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen");
2238    } else if (fixed instanceof CodeableConcept) {
2239      CodeableConcept cc = (CodeableConcept) fixed;
2240      for (Coding c : cc.getCoding()) {
2241        ValidationResult vr = context.getWorker().validateCode(context.getTerminologyServiceOptions(), c.getSystem(), c.getVersion(), c.getCode(), c.getDisplay());
2242        if (vr.getDisplay() != null)
2243          return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen");
2244      }
2245    }
2246    return null;
2247  }
2248
2249
2250  private boolean hasDescription(DataType fixed) {
2251    if (fixed instanceof Coding) {
2252      return ((Coding) fixed).hasDisplay();
2253    } else if (fixed instanceof CodeableConcept) {
2254      CodeableConcept cc = (CodeableConcept) fixed;
2255      if (cc.hasText())
2256        return true;
2257      for (Coding c : cc.getCoding())
2258        if (c.hasDisplay())
2259          return true;
2260    } // (fixed instanceof CodeType) || (fixed instanceof Quantity);
2261    return false;
2262  }
2263
2264
2265  private boolean isCoded(DataType fixed) {
2266    return (fixed instanceof Coding) || (fixed instanceof CodeableConcept) || (fixed instanceof CodeType) || (fixed instanceof Quantity);
2267  }
2268
2269
2270  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 {
2271    Cell c = gen.new Cell();
2272    row.getCells().add(c);
2273
2274    if (used) {
2275      if (definition.hasContentReference()) {
2276        ElementInStructure ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference(), profile);
2277        if (ed == null)
2278          c.getPieces().add(gen.new Piece(null, "Unknown reference to "+definition.getContentReference(), null));
2279        else {
2280          if (ed.getSource() == profile) {
2281            c.getPieces().add(gen.new Piece("#"+ed.getElement().getPath(), "See "+ed.getElement().getPath(), null));
2282          } else {
2283            c.getPieces().add(gen.new Piece(ed.getSource().getWebPath()+"#"+ed.getElement().getPath(), "See "+ed.getSource().getTypeName()+"."+ed.getElement().getPath(), null));
2284          }          
2285        }
2286      }
2287      if (definition.getPath().endsWith("url") && definition.hasFixed()) {
2288        c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen")));
2289      } else {
2290        if (url != null) {
2291          if (!c.getPieces().isEmpty()) 
2292            c.addPiece(gen.new Piece("br"));
2293          String fullUrl = url.startsWith("#") ? baseURL+url : url;
2294          StructureDefinition ed = context.getWorker().fetchResource(StructureDefinition.class, url, profile);
2295          String ref = null;
2296          if (ed != null) {
2297            String p = ed.getWebPath();
2298            if (p != null) {
2299              ref = p.startsWith("http:") || context.getRules() == GenerationRules.IG_PUBLISHER ? p : Utilities.pathURL(corePath, p);
2300            }
2301          }
2302          c.getPieces().add(gen.new Piece(null, "URL: ", null).addStyle("font-weight:bold"));
2303          c.getPieces().add(gen.new Piece(ref, fullUrl, null));
2304        }
2305
2306        if (definition.hasSlicing()) {
2307          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
2308          c.getPieces().add(gen.new Piece(null, "Slice: ", null).addStyle("font-weight:bold"));
2309          c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null));
2310        }
2311        if (definition != null) {
2312          ElementDefinitionBindingComponent binding = null;
2313          if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty())
2314            binding = valueDefn.getBinding();
2315          else if (definition.hasBinding())
2316            binding = definition.getBinding();
2317          if (binding!=null && !binding.isEmpty()) {
2318            if (!c.getPieces().isEmpty()) 
2319              c.addPiece(gen.new Piece("br"));
2320            BindingResolution br = context.getPkp().resolveBinding(profile, binding, definition.getPath());
2321            c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, "Binding: ", null).addStyle("font-weight:bold")));
2322            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)));
2323            if (binding.hasStrength()) {
2324              c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null)));
2325              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));
2326            }
2327            if (binding.hasDescription() && MarkDownProcessor.isSimpleMarkdown(binding.getDescription())) {
2328              c.getPieces().add(gen.new Piece(null, ": ", null));
2329              c.addMarkdownNoPara(PublicationHacker.fixBindingDescriptions(context.getWorker(), binding.getDescriptionElement()).asStringValue());
2330            }
2331          }
2332          for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
2333            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
2334            c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold")));
2335            if (inv.getHumanElement().hasExtension(ToolingExtensions.EXT_REND_MD)) {
2336              c.addMarkdown(inv.getHumanElement().getExtensionString(ToolingExtensions.EXT_REND_MD));
2337            } else {
2338              c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null)));
2339            }
2340          }
2341          if (definition.hasFixed()) {
2342            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
2343            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight:bold")));
2344            String s = buildJson(definition.getFixed());
2345            String link = null;
2346            if (Utilities.isAbsoluteUrl(s))
2347              link = context.getPkp().getLinkForUrl(corePath, s);
2348            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen")));
2349          } else if (definition.hasPattern()) {
2350            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
2351            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "Required Pattern: ", null).addStyle("font-weight:bold")));
2352            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen")));
2353          } else if (definition.hasExample()) {
2354            for (ElementDefinitionExampleComponent ex : definition.getExample()) {
2355              if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
2356              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, "Example'"+("".equals("General")? "": " "+ex.getLabel()+"'")+": ", "").addStyle("font-weight:bold")));
2357              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen")));
2358            }
2359          }
2360          if (definition.hasMaxLength() && definition.getMaxLength()!=0) {
2361            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
2362            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold")));
2363            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen")));
2364          }
2365          if (profile != null) {
2366            for (StructureDefinitionMappingComponent md : profile.getMapping()) {
2367              if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) {
2368                ElementDefinitionMappingComponent map = null;
2369                for (ElementDefinitionMappingComponent m : definition.getMapping()) 
2370                  if (m.getIdentity().equals(md.getIdentity()))
2371                    map = m;
2372                if (map != null) {
2373                  for (int i = 0; i<definition.getMapping().size(); i++){
2374                    c.addPiece(gen.new Piece("br"));
2375                    c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null));
2376                  }
2377                }
2378              }
2379            }
2380          }
2381          if (definition.hasDefinition()) {
2382            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
2383            c.getPieces().add(gen.new Piece(null, "Definition: ", null).addStyle("font-weight:bold"));
2384            c.addPiece(gen.new Piece("br"));
2385            c.addMarkdown(definition.getDefinition());
2386            //            c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null)));
2387          }
2388          if (definition.getComment()!=null) {
2389            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
2390            c.getPieces().add(gen.new Piece(null, "Comments: ", null).addStyle("font-weight:bold"));
2391            c.addPiece(gen.new Piece("br"));
2392            c.addMarkdown(definition.getComment());
2393            //            c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null)));
2394          }
2395        }
2396      }
2397    }
2398    return c;
2399  }
2400
2401  private boolean onlyInformationIsMapping(List<ElementDefinition> list, ElementDefinition e) {
2402    return (!e.hasSliceName() && !e.hasSlicing() && (onlyInformationIsMapping(e))) &&
2403        getChildren(list, e).isEmpty();
2404  }
2405
2406  private boolean onlyInformationIsMapping(ElementDefinition d) {
2407    return !d.hasShort() && !d.hasDefinition() &&
2408        !d.hasRequirements() && !d.getAlias().isEmpty() && !d.hasMinElement() &&
2409        !d.hasMax() && !d.getType().isEmpty() && !d.hasContentReference() &&
2410        !d.hasExample() && !d.hasFixed() && !d.hasMaxLengthElement() &&
2411        !d.getCondition().isEmpty() && !d.getConstraint().isEmpty() && !d.hasMustSupportElement() &&
2412        !d.hasBinding();
2413  }
2414
2415  private boolean allAreReference(List<TypeRefComponent> types) {
2416    for (TypeRefComponent t : types) {
2417      if (!t.hasTarget())
2418        return false;
2419    }
2420    return true;
2421  }
2422
2423  private List<ElementDefinition> getChildren(List<ElementDefinition> all, ElementDefinition element) {
2424    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
2425    int i = all.indexOf(element)+1;
2426    while (i < all.size() && all.get(i).getPath().length() > element.getPath().length()) {
2427      if ((all.get(i).getPath().substring(0, element.getPath().length()+1).equals(element.getPath()+".")) && !all.get(i).getPath().substring(element.getPath().length()+1).contains("."))
2428        result.add(all.get(i));
2429      i++;
2430    }
2431    return result;
2432  }
2433
2434
2435  protected String tail(String path) {
2436    if (path == null) {
2437      return "";
2438    } else if (path.contains("."))
2439      return path.substring(path.lastIndexOf('.')+1);
2440    else
2441      return path;
2442  }
2443
2444
2445
2446
2447
2448  protected boolean isPrimitive(String value) {
2449    StructureDefinition sd = context.getWorker().fetchTypeDefinition(value);
2450    if (sd == null) // might be running before all SDs are available
2451      return Utilities.existsInList(value, "base64Binary", "boolean", "canonical", "code", "date", "dateTime", "decimal", "id", "instant", "integer", "integer64", "markdown", "oid", "positiveInt", "string", "time", "unsignedInt", "uri", "url", "uuid");
2452    else 
2453      return sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
2454  }
2455
2456
2457  private boolean isDataType(String value) {
2458    StructureDefinition sd = context.getWorker().fetchTypeDefinition(value);
2459    if (sd == null) // might be running before all SDs are available
2460      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", 
2461          "ContactDetail", "Contributor", "DataRequirement", "Expression", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition", "UsageContext");
2462    else 
2463      return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION;
2464  }
2465
2466  private boolean slicesExist(List<ElementDefinition> elements, ElementDefinition element) {
2467    if (elements == null) {
2468      return true;
2469    }
2470    boolean found = false;
2471    int start = elements.indexOf(element);
2472    if (start < 0) {
2473      return false;
2474    }
2475    for (int i = start; i < elements.size(); i++) {
2476      ElementDefinition ed = elements.get(i);
2477      if (ed.getPath().equals(element.getPath())) {
2478        if (ed.hasSliceName()) {
2479          found = true;
2480        }
2481      }
2482      if (ed.getPath().length() < element.getPath().length()) {
2483        break;
2484      }
2485    }
2486    return found;
2487  }
2488
2489
2490  private Cell addCell(Row row, Cell cell) {
2491    row.getCells().add(cell);
2492    return (cell);
2493  }
2494
2495  private String checkAdd(String src, String app) {
2496    return app == null ? src : src + app;
2497  }
2498
2499  public boolean hasNonBaseConditions(List<IdType> conditions) {
2500    for (IdType c : conditions) {
2501      if (!isBaseCondition(c)) {
2502        return true;
2503      }
2504    }
2505    return false;
2506  }
2507
2508
2509  public boolean hasNonBaseConstraints(List<ElementDefinitionConstraintComponent> constraints) {
2510    for (ElementDefinitionConstraintComponent c : constraints) {
2511      if (!isBaseConstraint(c)) {
2512        return true;
2513      }
2514    }
2515    return false;
2516  }
2517
2518  public String listConstraintsAndConditions(ElementDefinition element) {
2519    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
2520    for (ElementDefinitionConstraintComponent con : element.getConstraint()) {
2521      if (!isBaseConstraint(con)) {
2522        b.append(con.getKey());
2523      }
2524    }
2525    for (IdType id : element.getCondition()) {
2526      if (!isBaseCondition(id)) {
2527        b.append(id.asStringValue());
2528      }
2529    }
2530    return b.toString();
2531  }
2532
2533  private boolean isBaseCondition(IdType c) {
2534    String key = c.asStringValue();
2535    return key != null && (key.startsWith("ele-") || key.startsWith("res-") || key.startsWith("ext-") || key.startsWith("dom-") || key.startsWith("dr-"));
2536  }
2537
2538  private boolean isBaseConstraint(ElementDefinitionConstraintComponent con) {
2539    String key = con.getKey();
2540    return key != null && (key.startsWith("ele-") || key.startsWith("res-") || key.startsWith("ext-") || key.startsWith("dom-") || key.startsWith("dr-"));
2541  }
2542
2543  private void makeChoiceRows(List<Row> subRows, ElementDefinition element, HierarchicalTableGenerator gen, String corePath, String profileBaseFileName, boolean mustSupportMode, Resource src) {
2544    // create a child for each choice
2545    for (TypeRefComponent tr : element.getType()) {
2546      if (!mustSupportMode || allTypesMustSupport(element) || isMustSupport(tr)) {
2547        Row choicerow = gen.new Row();
2548        String t = tr.getWorkingCode();
2549        if (isReference(t)) {
2550          choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), null, null));
2551          choicerow.getCells().add(gen.new Cell());
2552          choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
2553          choicerow.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
2554          Cell c = gen.new Cell();
2555          choicerow.getCells().add(c);
2556          if (ADD_REFERENCE_TO_TABLE) {
2557            if (tr.getWorkingCode().equals("canonical"))
2558              c.getPieces().add(gen.new Piece(corePath+"datatypes.html#canonical", "canonical", null));
2559            else
2560              c.getPieces().add(gen.new Piece(corePath+"references.html#Reference", "Reference", null));
2561            if (!mustSupportMode && isMustSupportDirect(tr) && element.getMustSupport()) {
2562              c.addPiece(gen.new Piece(null, " ", null));
2563              c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false);
2564            }
2565            c.getPieces().add(gen.new Piece(null, "(", null));
2566          }
2567          boolean first = true;
2568          for (CanonicalType rt : tr.getTargetProfile()) {
2569            if (!mustSupportMode || allProfilesMustSupport(tr.getTargetProfile()) || isMustSupport(rt)) {
2570              if (!first)
2571                c.getPieces().add(gen.new Piece(null, " | ", null));
2572              genTargetLink(gen, profileBaseFileName, corePath, c, tr, rt.getValue(), src);
2573              if (!mustSupportMode && isMustSupport(rt) && element.getMustSupport()) {
2574                c.addPiece(gen.new Piece(null, " ", null));
2575                c.addStyledText(translate("sd.table", "This target must be supported"), "S", "white", "red", null, false);
2576              }
2577              first = false;
2578            }
2579          }
2580          if (first) {
2581            c.getPieces().add(gen.new Piece(null, "Any", null));
2582          }
2583
2584          if (ADD_REFERENCE_TO_TABLE) { 
2585            c.getPieces().add(gen.new Piece(null, ")", null));
2586          }
2587
2588        } else {
2589          StructureDefinition sd = context.getWorker().fetchTypeDefinition(t);
2590          if (sd == null) {
2591            System.out.println("Unable to find "+t);
2592            sd = context.getWorker().fetchTypeDefinition(t);
2593          } else if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) {
2594            choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]",  Utilities.capitalize(t)), sd.getDescription(), null));
2595            choicerow.getCells().add(gen.new Cell());
2596            choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
2597            choicerow.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
2598            Cell c = gen.new Cell(null, corePath+"datatypes.html#"+t, sd.getTypeName(), null, null);
2599            choicerow.getCells().add(c);
2600            if (!mustSupportMode && isMustSupport(tr) && element.getMustSupport()) {
2601              c.addPiece(gen.new Piece(null, " ", null));
2602              c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false);
2603            }
2604          } else {
2605            choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]",  Utilities.capitalize(t)), sd.getDescription(), null));
2606            choicerow.getCells().add(gen.new Cell());
2607            choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
2608            choicerow.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
2609            Cell c = gen.new Cell(null, context.getPkp().getLinkFor(corePath, t), sd.getTypeName(), null, null);
2610            choicerow.getCells().add(c);
2611            if (!mustSupportMode && isMustSupport(tr) && element.getMustSupport()) {
2612              c.addPiece(gen.new Piece(null, " ", null));
2613              c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false);
2614            }
2615          }
2616          if (tr.hasProfile()) {
2617            Cell typeCell = choicerow.getCells().get(3);
2618            typeCell.addPiece(gen.new Piece(null, "(", null));
2619            boolean first = true;
2620            for (CanonicalType pt : tr.getProfile()) {
2621              if (!mustSupportMode || allProfilesMustSupport(tr.getProfile()) || isMustSupport(pt)) {
2622                if (first) first = false; else typeCell.addPiece(gen.new Piece(null, " | ", null));
2623                StructureDefinition psd = context.getWorker().fetchResource(StructureDefinition.class, pt.getValue(), src);
2624                if (psd == null)
2625                  typeCell.addPiece(gen.new Piece(null, "?gen-e2?", null));
2626                else
2627                  typeCell.addPiece(gen.new Piece(psd.getWebPath(), psd.getName(), psd.present()));
2628                if (!mustSupportMode && isMustSupport(pt) && element.getMustSupport()) {
2629                  typeCell.addPiece(gen.new Piece(null, " ", null));
2630                  typeCell.addStyledText(translate("sd.table", "This profile must be supported"), "S", "white", "red", null, false);
2631                }
2632              }
2633            }
2634            typeCell.addPiece(gen.new Piece(null, ")", null));
2635          }
2636        }    
2637        choicerow.getCells().add(gen.new Cell());
2638        subRows.add(choicerow);
2639      }
2640    }
2641  }
2642
2643  private boolean isReference(String t) {
2644    return t.equals("Reference") || t.equals("canonical"); 
2645  }  
2646
2647
2648
2649  private List<ElementChoiceGroup> readChoices(ElementDefinition ed, List<ElementDefinition> children) {
2650    List<ElementChoiceGroup> result = new ArrayList<>();
2651    for (ElementDefinitionConstraintComponent c : ed.getConstraint()) {
2652      ElementChoiceGroup grp = context.getProfileUtilities().processConstraint(children, c);
2653      if (grp != null) {
2654        result.add(grp);
2655      }
2656    }
2657    return result;
2658  }
2659
2660  private Piece checkForNoChange(Element src1, Element src2, Piece piece) {
2661    if (src1.hasUserData(ProfileUtilities.UD_DERIVATION_EQUALS) && src2.hasUserData(ProfileUtilities.UD_DERIVATION_EQUALS)) {
2662      piece.addStyle("opacity: 0.5");
2663    }
2664    return piece;
2665  }
2666
2667
2668  private String buildJson(DataType value) throws IOException {
2669    if (value instanceof PrimitiveType)
2670      return ((PrimitiveType<?>) value).asStringValue();
2671
2672    IParser json = new JsonParser();
2673    return json.composeString(value, null);
2674  }
2675
2676  private String describeSlice(ElementDefinitionSlicingComponent slicing) {
2677    return translate("sd.table", "%s, %s by %s", slicing.getOrdered() ? translate("sd.table", "Ordered") : translate("sd.table", "Unordered"), describe(slicing.getRules()), commas(slicing.getDiscriminator()));
2678  }
2679
2680
2681
2682  private String commas(List<ElementDefinitionSlicingDiscriminatorComponent> list) {
2683    CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder();
2684    for (ElementDefinitionSlicingDiscriminatorComponent id : list)
2685      c.append((id.hasType() ? id.getType().toCode() : "??")+":"+id.getPath());
2686    return c.toString();
2687  }
2688
2689
2690  private String describe(SlicingRules rules) {
2691    if (rules == null)
2692      return translate("sd.table", "Unspecified");
2693    switch (rules) {
2694    case CLOSED : return translate("sd.table", "Closed");
2695    case OPEN : return translate("sd.table", "Open");
2696    case OPENATEND : return translate("sd.table", "Open At End");
2697    default:
2698      return "?gen-sr?";
2699    }
2700  }
2701
2702  private boolean allTypesMustSupport(ElementDefinition e) {
2703    boolean all = true;
2704    boolean any = false;
2705    for (TypeRefComponent tr : e.getType()) {
2706      all = all && isMustSupport(tr);
2707      any = any || isMustSupport(tr);
2708    }
2709    return !all && !any;
2710  }
2711
2712  private boolean allProfilesMustSupport(List<CanonicalType> profiles) {
2713    boolean all = true;
2714    boolean any = false;
2715    for (CanonicalType u : profiles) {
2716      all = all && isMustSupport(u);
2717      any = any || isMustSupport(u);
2718    }
2719    return !all && !any;
2720  }
2721  public boolean isMustSupportDirect(TypeRefComponent tr) {
2722    return ("true".equals(ToolingExtensions.readStringExtension(tr, ToolingExtensions.EXT_MUST_SUPPORT)));
2723  }
2724
2725  public boolean isMustSupport(TypeRefComponent tr) {
2726    if ("true".equals(ToolingExtensions.readStringExtension(tr, ToolingExtensions.EXT_MUST_SUPPORT))) {
2727      return true;
2728    }
2729    if (isMustSupport(tr.getProfile())) {
2730      return true;
2731    }
2732    return isMustSupport(tr.getTargetProfile());
2733  }
2734
2735  public boolean isMustSupport(List<CanonicalType> profiles) {
2736    for (CanonicalType ct : profiles) {
2737      if (isMustSupport(ct)) {
2738        return true;
2739      }
2740    }
2741    return false;
2742  }
2743
2744
2745  public boolean isMustSupport(CanonicalType profile) {
2746    return "true".equals(ToolingExtensions.readStringExtension(profile, ToolingExtensions.EXT_MUST_SUPPORT));
2747  }
2748
2749
2750
2751  private SpanEntry buildSpanEntryFromProfile(String name, String cardinality, StructureDefinition profile) throws IOException {
2752    SpanEntry res = new SpanEntry();
2753    res.setName(name);
2754    res.setCardinality(cardinality);
2755    res.setProfileLink(profile.getWebPath());
2756    res.setResType(profile.getTypeName());
2757    StructureDefinition base = context.getWorker().fetchResource(StructureDefinition.class, res.getResType());
2758    if (base != null)
2759      res.setResLink(base.getWebPath());
2760    res.setId(profile.getId());
2761    res.setProfile(profile.getDerivation() == TypeDerivationRule.CONSTRAINT);
2762    StringBuilder b = new StringBuilder();
2763    b.append(res.getResType());
2764    boolean first = true;
2765    boolean open = false;
2766    if (profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
2767      res.setDescription(profile.getName());
2768      for (ElementDefinition ed : profile.getSnapshot().getElement()) {
2769        if (isKeyProperty(ed.getBase().getPath()) && ed.hasFixed()) {
2770          if (first) {
2771            open = true;
2772            first = false;
2773            b.append("[");
2774          } else {
2775            b.append(", ");
2776          }
2777          b.append(tail(ed.getBase().getPath()));
2778          b.append("=");
2779          b.append(summarize(ed.getFixed()));
2780        }
2781      }
2782      if (open)
2783        b.append("]");
2784    } else
2785      res.setDescription("Base FHIR "+profile.getName());
2786    res.setType(b.toString());
2787    return res ;
2788  }
2789
2790
2791  private String summarize(DataType value) throws IOException {
2792    if (value instanceof Coding)
2793      return summarizeCoding((Coding) value);
2794    else if (value instanceof CodeableConcept)
2795      return summarizeCodeableConcept((CodeableConcept) value);
2796    else
2797      return buildJson(value);
2798  }
2799
2800
2801  private String summarizeCoding(Coding value) {
2802    String uri = value.getSystem();
2803    String system = TerminologyRenderer.describeSystem(uri);
2804    if (Utilities.isURL(system)) {
2805      if (system.equals("http://cap.org/protocols"))
2806        system = "CAP Code";
2807    }
2808    return system+" "+value.getCode();
2809  }
2810
2811
2812  private String summarizeCodeableConcept(CodeableConcept value) {
2813    if (value.hasCoding())
2814      return summarizeCoding(value.getCodingFirstRep());
2815    else
2816      return value.getText();
2817  }
2818
2819
2820  private boolean isKeyProperty(String path) {
2821    return Utilities.existsInList(path, "Observation.code");
2822  }
2823
2824
2825  private TableModel initSpanningTable(HierarchicalTableGenerator gen, String prefix, boolean isLogical, String id) throws IOException {
2826    TableModel model = gen.new TableModel(id, true);
2827
2828    if (context.getRules() == GenerationRules.VALID_RESOURCE || context.isInlineGraphics()) {
2829      model.setDocoImg(HierarchicalTableGenerator.help16AsData());     
2830    } else {
2831      model.setDocoImg(Utilities.pathURL(prefix, "help16.png"));
2832    }
2833    model.setDocoRef(Utilities.pathURL(prefix, "formats.html#table")); // todo: change to graph definition
2834    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Property", "A profiled resource", null, 0));
2835    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));
2836    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Content", "What goes here", null, 0));
2837    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Description", "Description of the profile", null, 0));
2838    return model;
2839  }
2840
2841  private void genSpanEntry(HierarchicalTableGenerator gen, List<Row> rows, SpanEntry span) throws IOException {
2842    Row row = gen.new Row();
2843    rows.add(row);
2844    row.setAnchor(span.getId());
2845    //row.setColor(..?);
2846    if (span.isProfile()) {
2847      row.setIcon("icon_profile.png", HierarchicalTableGenerator.TEXT_ICON_PROFILE);
2848    } else {
2849      row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
2850    }
2851
2852    row.getCells().add(gen.new Cell(null, null, span.getName(), null, null));
2853    row.getCells().add(gen.new Cell(null, null, span.getCardinality(), null, null));
2854    row.getCells().add(gen.new Cell(null, span.getProfileLink(), span.getType(), null, null));
2855    row.getCells().add(gen.new Cell(null, null, span.getDescription(), null, null));
2856
2857    for (SpanEntry child : span.getChildren()) {
2858      genSpanEntry(gen, row.getSubRows(), child);
2859    }
2860  }
2861
2862
2863  public XhtmlNode generateSpanningTable(StructureDefinition profile, String imageFolder, boolean onlyConstraints, String constraintPrefix, Set<String> outputTracker) throws IOException, FHIRException {
2864    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, false, true);
2865    gen.setTranslator(getTranslator());
2866    TableModel model = initSpanningTable(gen, "", false, profile.getId());
2867    Set<String> processed = new HashSet<String>();
2868    SpanEntry span = buildSpanningTable("(focus)", "", profile, processed, onlyConstraints, constraintPrefix);
2869
2870    genSpanEntry(gen, model.getRows(), span);
2871    return gen.generate(model, "", 0, outputTracker);
2872  }
2873
2874  private SpanEntry buildSpanningTable(String name, String cardinality, StructureDefinition profile, Set<String> processed, boolean onlyConstraints, String constraintPrefix) throws IOException {
2875    SpanEntry res = buildSpanEntryFromProfile(name, cardinality, profile);
2876    boolean wantProcess = !processed.contains(profile.getUrl());
2877    processed.add(profile.getUrl());
2878    if (wantProcess && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
2879      for (ElementDefinition ed : profile.getSnapshot().getElement()) {
2880        if (!"0".equals(ed.getMax()) && ed.getType().size() > 0) {
2881          String card = getCardinality(ed, profile.getSnapshot().getElement());
2882          if (!card.endsWith(".0")) {
2883            List<String> refProfiles = listReferenceProfiles(ed);
2884            if (refProfiles.size() > 0) {
2885              String uri = refProfiles.get(0);
2886              if (uri != null) {
2887                StructureDefinition sd = context.getWorker().fetchResource(StructureDefinition.class, uri);
2888                if (sd != null && (!onlyConstraints || (sd.getDerivation() == TypeDerivationRule.CONSTRAINT && (constraintPrefix == null || sd.getUrl().startsWith(constraintPrefix))))) {
2889                  res.getChildren().add(buildSpanningTable(nameForElement(ed), card, sd, processed, onlyConstraints, constraintPrefix));
2890                }
2891              }
2892            }
2893          }
2894        } 
2895      }
2896    }
2897    return res;
2898  }
2899
2900
2901  private String getCardinality(ElementDefinition ed, List<ElementDefinition> list) {
2902    int min = ed.getMin();
2903    int max = !ed.hasMax() || ed.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(ed.getMax());
2904    ElementDefinition ned = ed;
2905    while (ned != null && ned.getPath().contains(".")) {
2906      ned = findParent(ned, list);
2907      if (ned != null) { // todo: this can happen if we've walked into a resoruce. Not sure what to about that?
2908        if ("0".equals(ned.getMax()))
2909          max = 0;
2910        else if (!ned.getMax().equals("1") && !ned.hasSlicing())
2911          max = Integer.MAX_VALUE;
2912        if (ned.getMin() == 0) {
2913          min = 0;
2914        }
2915      }
2916    }
2917    return Integer.toString(min)+".."+(max == Integer.MAX_VALUE ? "*" : Integer.toString(max));
2918  }
2919
2920
2921  private ElementDefinition findParent(ElementDefinition ed, List<ElementDefinition> list) {
2922    int i = list.indexOf(ed)-1;
2923    while (i >= 0 && !ed.getPath().startsWith(list.get(i).getPath()+"."))
2924      i--;
2925    if (i == -1)
2926      return null;
2927    else
2928      return list.get(i);
2929  }
2930
2931
2932  private List<String> listReferenceProfiles(ElementDefinition ed) {
2933    List<String> res = new ArrayList<String>();
2934    for (TypeRefComponent tr : ed.getType()) {
2935      // code is null if we're dealing with "value" and profile is null if we just have Reference()
2936      if (tr.hasTarget() && tr.hasTargetProfile())
2937        for (UriType u : tr.getTargetProfile())
2938          res.add(u.getValue());
2939    }
2940    return res;
2941  }
2942
2943
2944  private String nameForElement(ElementDefinition ed) {
2945    return ed.getPath().substring(ed.getPath().indexOf(".")+1);
2946  }
2947
2948  public XhtmlNode formatTypeSpecifiers(ElementDefinition d) {
2949    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
2950    boolean first = true;
2951    for (Extension e : d.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_SPEC)) {
2952      if (first) first = false; else x.br();
2953      String cond = ToolingExtensions.readStringExtension(e, "condition");
2954      String type = ToolingExtensions.readStringExtension(e, "type");
2955      x.tx("If ");
2956      x.code().tx(cond);
2957      x.tx(" then the type is ");
2958      StructureDefinition sd = context.getContext().fetchTypeDefinition(type);
2959      if (sd == null) {
2960        x.code().tx(type);
2961      } else {
2962        x.ah(sd.getWebPath()).tx(sd.getTypeName());
2963      }
2964    }
2965    return first ? null : x;
2966  }
2967
2968  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 {
2969    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true);
2970    gen.setTranslator(getTranslator());
2971    TableModel model = gen.initNormalTable(corePath, false, true, ed.getId()+(full ? "f" : "n"), true, TableGenerationMode.XHTML);
2972
2973    boolean deep = false;
2974    String m = "";
2975    boolean vdeep = false;
2976    if (ed.getSnapshot().getElementFirstRep().getIsModifier())
2977      m = "modifier_";
2978    for (ElementDefinition eld : ed.getSnapshot().getElement()) {
2979      deep = deep || eld.getPath().contains("Extension.extension.");
2980      vdeep = vdeep || eld.getPath().contains("Extension.extension.extension.");
2981    }
2982    Row r = gen.new Row();
2983    model.getRows().add(r);
2984    String en;
2985    if (!full)
2986      en = ed.getName();
2987    else if (ed.getSnapshot().getElement().get(0).getIsModifier())
2988      en = "modifierExtension";
2989    else 
2990      en = "extension";
2991
2992    r.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), en, null, null));
2993    r.getCells().add(gen.new Cell());
2994    r.getCells().add(gen.new Cell(null, null, describeCardinality(ed.getSnapshot().getElement().get(0), null, new UnusedTracker()), null, null));
2995
2996    ElementDefinition ved = null;
2997    if (full || vdeep) {
2998      r.getCells().add(gen.new Cell("", "", "Extension", null, null));
2999
3000      r.setIcon(deep ? "icon_"+m+"extension_complex.png" : "icon_extension_simple.png", deep ? HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX : HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
3001      List<ElementDefinition> children = getChildren(ed.getSnapshot().getElement(), ed.getSnapshot().getElement().get(0));
3002      for (ElementDefinition child : children)
3003        if (!child.getPath().endsWith(".id")) {
3004          List<StructureDefinition> sdl = new ArrayList<>();
3005          sdl.add(ed);
3006          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);
3007        }
3008    } else if (deep) {
3009      List<ElementDefinition> children = new ArrayList<ElementDefinition>();
3010      for (ElementDefinition ted : ed.getSnapshot().getElement()) {
3011        if (ted.getPath().equals("Extension.extension"))
3012          children.add(ted);
3013      }
3014
3015      r.getCells().add(gen.new Cell("", "", "Extension", null, null));
3016      r.setIcon("icon_"+m+"extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
3017
3018      for (ElementDefinition c : children) {
3019        ved = getValueFor(ed, c);
3020        ElementDefinition ued = getUrlFor(ed, c);
3021        if (ved != null && ued != null) {
3022          Row r1 = gen.new Row();
3023          r.getSubRows().add(r1);
3024          r1.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#"+ed.getId()+"."+c.getId(), ((UriType) ued.getFixed()).getValue(), null, null));
3025          r1.getCells().add(gen.new Cell());
3026          r1.getCells().add(gen.new Cell(null, null, describeCardinality(c, null, new UnusedTracker()), null, null));
3027          genTypes(gen, r1, ved, defFile, ed, corePath, imagePath, false, false);
3028          r1.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);      
3029          generateDescription(gen, r1, c, null, true, corePath, corePath, ed, corePath, imagePath, false, false, false, ved, false, false, false, rc);
3030        }
3031      }
3032    } else  {
3033      for (ElementDefinition ted : ed.getSnapshot().getElement()) {
3034        if (ted.getPath().startsWith("Extension.value"))
3035          ved = ted;
3036      }
3037
3038      genTypes(gen, r, ved, defFile, ed, corePath, imagePath, false, false);
3039
3040      r.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);      
3041    }
3042    Cell c = gen.new Cell("", "", "URL = "+ed.getUrl(), null, null);
3043    Piece cc = gen.new Piece(null, ed.getName()+": ", null);
3044    c.addPiece(gen.new Piece("br")).addPiece(cc);
3045    c.addMarkdown(ed.getDescription());
3046
3047    if (!full && !(deep || vdeep) && ved != null && ved.hasBinding()) {  
3048      c.addPiece(gen.new Piece("br"));
3049      BindingResolution br = context.getPkp().resolveBinding(ed, ved.getBinding(), ved.getPath());
3050      c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold")));
3051      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)));
3052      if (ved.getBinding().hasStrength()) {
3053        c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, " (", null)));
3054        c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(corePath+"terminologies.html#"+ved.getBinding().getStrength().toCode(), egt(ved.getBinding().getStrengthElement()), ved.getBinding().getStrength().getDefinition())));              
3055        c.getPieces().add(gen.new Piece(null, ")", null));
3056      }
3057      if (ved.getBinding().hasDescription() && MarkDownProcessor.isSimpleMarkdown(ved.getBinding().getDescription())) {
3058        c.getPieces().add(gen.new Piece(null, ": ", null));
3059        c.addMarkdownNoPara(PublicationHacker.fixBindingDescriptions(context.getWorker(), ved.getBinding().getDescriptionElement()).asStringValue());
3060      }
3061    }
3062    c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, ProfileUtilities.describeExtensionContext(ed), null));
3063    r.getCells().add(c);
3064
3065    try {
3066      return gen.generate(model, corePath, 0, outputTracker);
3067    } catch (org.hl7.fhir.exceptions.FHIRException e) {
3068      throw new FHIRException(e.getMessage(), e);
3069    }
3070  }
3071
3072  private String describeCardinality(ElementDefinition definition, ElementDefinition fallback, UnusedTracker tracker) {
3073    IntegerType min = definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
3074    StringType max = definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
3075    if (min.isEmpty() && fallback != null)
3076      min = fallback.getMinElement();
3077    if (max.isEmpty() && fallback != null)
3078      max = fallback.getMaxElement();
3079
3080    tracker.used = !max.isEmpty() && !max.getValue().equals("0");
3081
3082    if (min.isEmpty() && max.isEmpty())
3083      return null;
3084    else
3085      return (!min.hasValue() ? "" : Integer.toString(min.getValue())) + ".." + (!max.hasValue() ? "" : max.getValue());
3086  }
3087
3088
3089  private ElementDefinition getValueFor(StructureDefinition ed, ElementDefinition c) {
3090    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
3091    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
3092      if (ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".value"))
3093        return ed.getSnapshot().getElement().get(i);
3094      i++;
3095    }
3096    return null;
3097  }
3098
3099  private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) {
3100    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
3101    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
3102      if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url"))
3103        return ed.getSnapshot().getElement().get(i);
3104      i++;
3105    }
3106    return null;
3107  }
3108
3109  public void renderDict(StructureDefinition sd, List<ElementDefinition> elements, XhtmlNode t, boolean incProfiledOut, int mode, String anchorPrefix) throws FHIRException, IOException {
3110    int i = 0;
3111    Map<String, ElementDefinition> allAnchors = new HashMap<>();
3112    List<ElementDefinition> excluded = new ArrayList<>();
3113    List<ElementDefinition> stack = new ArrayList<>(); // keeps track of parents, for anchor generation
3114    
3115    for (ElementDefinition ec : elements) {
3116      addToStack(stack, ec);
3117      generateAnchors(stack, allAnchors);
3118      checkInScope(stack, excluded);
3119    }
3120    Stack<ElementDefinition> dstack = new Stack<>();
3121    for (ElementDefinition ec : elements) {
3122      if ((incProfiledOut || !"0".equals(ec.getMax())) && !excluded.contains(ec)) {
3123        ElementDefinition compareElement = null;
3124        if (mode==GEN_MODE_DIFF)
3125          compareElement = getBaseElement(ec, sd.getBaseDefinition());
3126        else if (mode==GEN_MODE_KEY)
3127          compareElement = getRootElement(ec);
3128
3129        List<String> anchors = makeAnchors(ec, anchorPrefix);
3130        String title = ec.getId();
3131        XhtmlNode tr = t.tr();
3132        XhtmlNode sp = renderStatus(ec, tr.td("structure").colspan(2).spanClss("self-link-parent"));
3133        for (String s : anchors) {
3134          sp.an(s).tx(" ");
3135        }
3136        sp.span("color: grey", null).tx(Integer.toString(i++));
3137        sp.b().tx(". "+title);
3138        link(sp, ec.getId(), anchorPrefix);
3139        if (isProfiledExtension(ec)) {
3140          StructureDefinition extDefn = context.getContext().fetchResource(StructureDefinition.class, ec.getType().get(0).getProfile().get(0).getValue());
3141          if (extDefn == null) {
3142            generateElementInner(t, sd, ec, 1, null, compareElement, null, false);
3143          } else {
3144            ElementDefinition valueDefn = getExtensionValueDefinition(extDefn);
3145            ElementDefinition compareValueDefn = null;
3146            try {
3147              StructureDefinition compareExtDefn = context.getContext().fetchResource(StructureDefinition.class, compareElement.getType().get(0).getProfile().get(0).getValue());
3148              compareValueDefn = getExtensionValueDefinition(extDefn);
3149            } catch (Exception except) {}
3150            generateElementInner(t, sd, ec, valueDefn == null || valueDefn.prohibited() ? 2 : 3, valueDefn, compareElement, compareValueDefn, false);
3151            // generateElementInner(b, extDefn, extDefn.getSnapshot().getElement().get(0), valueDefn == null ? 2 : 3, valueDefn);
3152          }
3153        } else {
3154          while (!dstack.isEmpty() && !isParent(dstack.peek(), ec)) {
3155            finish(t, sd, dstack.pop(), mode);
3156          }
3157          dstack.push(ec);            
3158          generateElementInner(t, sd, ec, mode, null, compareElement, null, false);
3159          if (ec.hasSlicing()) {
3160            generateSlicing(t, sd, ec, ec.getSlicing(), compareElement, mode, false);
3161          }
3162        }
3163      }
3164      t.tx("\r\n");
3165      i++;
3166    }
3167    while (!dstack.isEmpty()) {
3168      finish(t, sd, dstack.pop(), mode);
3169    }
3170    finish(t, sd, null, mode);
3171  }
3172
3173  private void finish(XhtmlNode t, StructureDefinition sd, ElementDefinition ed, int mode) throws FHIRException, IOException {
3174
3175    for (Base b : VersionComparisonAnnotation.getDeleted(ed == null ? sd : ed, "element")) {
3176      ElementDefinition ec = (ElementDefinition) b;
3177      String title = ec.getId();
3178      XhtmlNode tr = t.tr();
3179      XhtmlNode sp = renderStatus(ec, tr.td("structure").colspan(2).spanClss("self-link-parent"));
3180      sp.span("color: grey", null).tx("--");
3181      sp.b().tx(". "+title);
3182      
3183      generateElementInner(t, sd, ec, mode, null, null, null, true);
3184      if (ec.hasSlicing()) {
3185        generateSlicing(t, sd, ec, ec.getSlicing(), null, mode, true);
3186      }      
3187    }
3188  }
3189
3190  public ElementDefinition getElementById(String url, String id) {
3191    Map<String, ElementDefinition> sdCache = sdMapCache.get(url);
3192
3193    if (sdCache == null) {
3194      StructureDefinition sd = (StructureDefinition) context.getContext().fetchResource(StructureDefinition.class, url);
3195      if (sd == null) {
3196        if (url.equals("http://hl7.org/fhir/StructureDefinition/Base")) {
3197          sd = (StructureDefinition) context.getContext().fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/Element");                
3198        }
3199        if (sd == null) {
3200          throw new FHIRException("Unable to retrieve StructureDefinition with URL " + url);
3201        }
3202      }
3203      sdCache = new HashMap<String, ElementDefinition>();
3204      sdMapCache.put(url, sdCache);
3205      String webroot = sd.getUserString("webroot");
3206      for (ElementDefinition e : sd.getSnapshot().getElement()) {
3207        context.getProfileUtilities().updateURLs(sd.getUrl(), webroot, e);
3208        sdCache.put(e.getId(), e);
3209      }
3210    }
3211    return sdCache.get(id);
3212  }
3213
3214
3215  // Returns the ElementDefinition for the 'parent' of the current element
3216  private ElementDefinition getBaseElement(ElementDefinition e, String url) {
3217    if (e.hasUserData(ProfileUtilities.UD_DERIVATION_POINTER)) {
3218      return getElementById(url, e.getUserString(ProfileUtilities.UD_DERIVATION_POINTER));
3219    }
3220    return null;
3221  }
3222
3223  // Returns the ElementDefinition for the 'root' ancestor of the current element
3224  private ElementDefinition getRootElement(ElementDefinition e) {
3225    if (!e.hasBase())
3226      return null;
3227    String basePath = e.getBase().getPath();
3228    String url = "http://hl7.org/fhir/StructureDefinition/" + (basePath.contains(".") ? basePath.substring(0, basePath.indexOf(".")) : basePath);
3229    try {
3230      return getElementById(url, basePath);
3231    } catch (FHIRException except) {
3232      // Likely a logical model, so this is ok
3233      return null;
3234    }
3235  }
3236  private void checkInScope(List<ElementDefinition> stack, List<ElementDefinition> excluded) {
3237    if (stack.size() > 2) {
3238      ElementDefinition parent = stack.get(stack.size()-2);
3239      ElementDefinition focus = stack.get(stack.size()-1);
3240
3241      if (excluded.contains(parent) || "0".equals(parent.getMax())) {
3242        excluded.add(focus);
3243      }
3244    }
3245  }
3246
3247  private void generateAnchors(List<ElementDefinition> stack, Map<String, ElementDefinition> allAnchors) {
3248    List<String> list = new ArrayList<>();
3249    list.add(stack.get(0).getId()); // initialise
3250    for (int i = 1; i < stack.size(); i++) {
3251      ElementDefinition ed = stack.get(i);
3252      List<String> aliases = new ArrayList<>();
3253      String name = tail(ed.getPath());
3254      if (name.endsWith("[x]")) {
3255        aliases.add(name);
3256        Set<String> tl = new HashSet<String>(); // guard against duplicate type names - can happn in some versions
3257        for (TypeRefComponent tr : ed.getType()) {
3258          String tc = tr.getWorkingCode();
3259          if (!tl.contains(tc)) {
3260            aliases.add(name.replace("[x]", Utilities.capitalize(tc)));
3261            aliases.add(name+":"+name.replace("[x]", Utilities.capitalize(tc)));
3262            tl.add(tc);
3263          }
3264        }
3265      } else if (ed.hasSliceName()) {
3266        aliases.add(name+":"+ed.getSliceName());
3267        // names.add(name); no good generating this?
3268      } else {
3269        aliases.add(name);
3270      }
3271      List<String> generated = new ArrayList<>();
3272      for (String l : list) {
3273        for (String a : aliases) {
3274          generated.add(l+"."+a);
3275        }
3276      }
3277      list.clear();
3278      list.addAll(generated);
3279    }
3280    ElementDefinition ed = stack.get(stack.size()-1);
3281
3282    // now we have all the possible names, but some of them might be inappropriate if we've
3283    // already generated a type slicer. On the other hand, if we've already done that, we're
3284    // going to steal any type specific ones off it.
3285    List<String> removed = new ArrayList<>();
3286    for (String s : list) {
3287      if (!allAnchors.containsKey(s)) {
3288        allAnchors.put(s, ed);
3289      } else if (s.endsWith("[x]")) {
3290        // that belongs on the earlier element
3291        removed.add(s);
3292      } else {
3293        // we delete it from the other
3294        @SuppressWarnings("unchecked")
3295        List<String> other = (List<String>) allAnchors.get(s).getUserData("dict.generator.anchors");
3296        other.remove(s);
3297        allAnchors.put(s, ed);
3298      }
3299    }
3300    list.removeAll(removed);
3301    ed.setUserData("dict.generator.anchors", list);
3302  }
3303
3304  private void addToStack(List<ElementDefinition> stack, ElementDefinition ec) {
3305    while (!stack.isEmpty() && !isParent(stack.get(stack.size()-1), ec)) {
3306      stack.remove(stack.size()-1);
3307    }
3308    stack.add(ec);
3309  }
3310
3311  private boolean isParent(ElementDefinition ed, ElementDefinition ec) {      
3312    return ec.getPath().startsWith(ed.getPath()+".");
3313  }
3314
3315  private List<String> makeAnchors(ElementDefinition ed, String anchorPrefix) {
3316    List<String> list = (List<String>) ed.getUserData("dict.generator.anchors");
3317    List<String>  res = new ArrayList<>();
3318    res.add(anchorPrefix + ed.getId());
3319    for (String s : list) {
3320      if (!s.equals(ed.getId())) {
3321        res.add(anchorPrefix + s);
3322      }
3323    }
3324    return res;
3325  }
3326
3327
3328
3329  private void link(XhtmlNode x, String id, String anchorPrefix) {
3330    var ah = x.ah("#" + anchorPrefix + id);
3331    ah.attribute("title", "link to here");
3332    ah.attribute("class", "self-link");
3333    var svg = ah.svg();
3334    svg.attribute("viewBox", "0 0 1792 1792");
3335    svg.attribute("width", "16");
3336    svg.attribute("height", "16");
3337    svg.attribute("class", "self-link");
3338    svg.path("M1520 1216q0-40-28-68l-208-208q-28-28-68-28-42 0-72 32 3 3 19 18.5t21.5 21.5 15 19 13 25.5 3.5 27.5q0 40-28 68t-68 28q-15 0-27.5-3.5t-25.5-13-19-15-21.5-21.5-18.5-19q-33 31-33 73 0 40 28 68l206 207q27 27 68 27 40 0 68-26l147-146q28-28 28-67zm-703-705q0-40-28-68l-206-207q-28-28-68-28-39 0-68 27l-147 146q-28 28-28 67 0 40 28 68l208 208q27 27 68 27 42 0 72-31-3-3-19-18.5t-21.5-21.5-15-19-13-25.5-3.5-27.5q0-40 28-68t68-28q15 0 27.5 3.5t25.5 13 19 15 21.5 21.5 18.5 19q33-31 33-73zm895 705q0 120-85 203l-147 146q-83 83-203 83-121 0-204-85l-206-207q-83-83-83-203 0-123 88-209l-88-88q-86 88-208 88-120 0-204-84l-208-208q-84-84-84-204t85-203l147-146q83-83 203-83 121 0 204 85l206 207q83 83 83 203 0 123-88 209l88 88q86-88 208-88 120 0 204 84l208 208q84 84 84 204z");     
3339  }
3340
3341  private boolean isProfiledExtension(ElementDefinition ec) {
3342    return ec.getType().size() == 1 && "Extension".equals(ec.getType().get(0).getWorkingCode()) && ec.getType().get(0).hasProfile();
3343  }
3344
3345  private ElementDefinition getExtensionValueDefinition(StructureDefinition extDefn) {
3346    for (ElementDefinition ed : extDefn.getSnapshot().getElement()) {
3347      if (ed.getPath().startsWith("Extension.value"))
3348        return ed;
3349    }
3350    return null;
3351  }
3352      
3353  public XhtmlNode compareMarkdown(String location, PrimitiveType md, PrimitiveType compare, int mode) throws FHIRException, IOException {
3354    if (compare == null || mode == GEN_MODE_DIFF) {
3355      if (md.hasValue()) {
3356        String xhtml = hostMd.processMarkdown(location, md);
3357        if (Utilities.noString(xhtml)) {
3358          return null;
3359        }
3360        XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
3361        try {
3362          renderStatusDiv(md, x).add(new XhtmlParser().parseFragment(xhtml));
3363        } catch (Exception e) {
3364          x.span("color: maroon").tx(e.getLocalizedMessage());          
3365        }
3366        return x;
3367      } else {
3368        return null;
3369      }
3370    } else if (areEqual(compare, md)) {
3371      if (md.hasValue()) {
3372        String xhtml = "<div>"+hostMd.processMarkdown(location, md)+"</div>";
3373        XhtmlNode div = new XhtmlParser().parseFragment(xhtml);
3374        for (XhtmlNode n : div.getChildNodes()) {
3375          if (n.getNodeType() == NodeType.Element) {
3376            n.style(unchangedStyle());
3377          }
3378        }
3379        return div;
3380      } else {
3381        return null;
3382      }
3383    } else {
3384      XhtmlNode ndiv = new XhtmlNode(NodeType.Element, "div");
3385      if (md.hasValue()) {
3386        String xhtml = "<div>"+hostMd.processMarkdown(location, md)+"</div>";
3387        XhtmlNode div = new XhtmlParser().parseFragment(xhtml);
3388        ndiv.copyAllContent(div);
3389      }
3390      if (compare.hasValue()) {
3391        String xhtml = "<div>"+hostMd.processMarkdown(location, compare)+"</div>";
3392        XhtmlNode div = new XhtmlParser().parseFragment(xhtml);
3393        for (XhtmlNode n : div.getChildNodes()) {
3394          if (n.getNodeType() == NodeType.Element) {
3395            n.style(removedStyle());
3396          }
3397        }
3398        ndiv.br();
3399        ndiv.copyAllContent(div);
3400      }
3401      return ndiv;
3402    }
3403  }
3404
3405  private boolean areEqual(PrimitiveType compare, PrimitiveType md) {
3406    if (compare == null && md == null) {
3407      return true;
3408    } else if (compare != null && md != null) {
3409      String one = compare.getValueAsString();
3410      String two = md.getValueAsString();
3411      if (one == null && two == null) {
3412        return true;
3413      } else if (one != null && one.equals(two)) {
3414        return true;
3415      }
3416    }
3417    return false;
3418  }
3419
3420  public XhtmlNode compareString(String newStr, Base source, String nLink, String name, Base parent, String oldStr, String oLink, int mode) {
3421    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
3422    if (mode != GEN_MODE_KEY) {
3423      if (newStr != null) {
3424        renderStatus(source, x).ah(nLink).tx(newStr);
3425      } else if (VersionComparisonAnnotation.hasDeleted(parent, name)) {
3426        PrimitiveType p = (PrimitiveType) VersionComparisonAnnotation.getDeletedItem(parent, name);
3427        renderStatus(p, x).tx(p.primitiveValue());        
3428      } else {
3429        return null;
3430      }
3431    } else if (oldStr==null || oldStr.isEmpty()) {
3432      if (newStr==null || newStr.isEmpty()) {
3433        return null;
3434      } else {
3435        renderStatus(source, x).ah(nLink).tx(newStr);
3436      }
3437    } else if (oldStr!=null && !oldStr.isEmpty() && (newStr==null || newStr.isEmpty())) {
3438      if (mode == GEN_MODE_DIFF) {
3439        return null;
3440      } else {
3441        removed(x).ah(oLink).tx(oldStr);
3442      }
3443    } else if (oldStr.equals(newStr)) {
3444      if (mode==GEN_MODE_DIFF) {
3445        return null;
3446      } else {
3447        unchanged(x).ah(nLink).tx(newStr);
3448      }
3449    } else if (newStr.startsWith(oldStr)) {
3450      unchanged(x).ah(oLink).tx(oldStr);
3451      renderStatus(source, x).ah(nLink).tx(newStr.substring(oldStr.length()));
3452    } else {
3453      // TODO: improve comparision in this fall-through case, by looking for matches in sub-paragraphs?
3454      renderStatus(source, x).ah(nLink).tx(newStr);
3455      removed(x).ah(oLink).tx(oldStr);
3456    }
3457    return x;
3458  }
3459
3460  public boolean compareString(XhtmlNode x, String newStr, Base source, String nLink, String name, Base parent, String oldStr, String oLink, int mode) {
3461    XhtmlNode x1 = compareString(newStr, source, nLink, name, parent, oldStr, oLink, mode);
3462    if (x1 == null) {
3463      return false;
3464    } else {
3465      x.getChildNodes().addAll(x1.getChildNodes());
3466      return true;
3467    }
3468  }
3469
3470  public XhtmlNode unchanged(XhtmlNode x) {
3471    return x.span(unchangedStyle());
3472  }
3473
3474  private String unchangedStyle() {
3475    return "color:DarkGray";
3476  }
3477
3478  public XhtmlNode removed(XhtmlNode x) {
3479    return x.span(removedStyle());
3480  }
3481
3482  private String removedStyle() {
3483    return "color:DarkGray;text-decoration:line-through";
3484  }
3485
3486  private void generateElementInner(XhtmlNode tbl, StructureDefinition sd, ElementDefinition d, int mode, ElementDefinition value, ElementDefinition compare, ElementDefinition compareValue, boolean strikethrough) throws FHIRException, IOException {
3487    boolean root = !d.getPath().contains(".");
3488    boolean slicedExtension = d.hasSliceName() && (d.getPath().endsWith(".extension") || d.getPath().endsWith(".modifierExtension"));
3489//    int slicedExtensionMode = (mode == GEN_MODE_KEY) && slicedExtension ? GEN_MODE_SNAP : mode; // see ProfileUtilities.checkExtensionDoco / Task 3970
3490    if (d.hasSliceName()) {
3491      tableRow(tbl, "Slice Name", "profiling.html#slicing", strikethrough, compareString(d.getSliceName(), d.getSliceNameElement(), null, (compare != null ? compare.getSliceName() : null), d, null, "sliceName", mode));   
3492      tableRow(tbl, "Slice Constraining", "profiling.html#slicing", strikethrough, compareString(encodeValue(d.getSliceIsConstrainingElement()), d.getSliceIsConstrainingElement(), null, (compare != null ? encodeValue(compare.getSliceIsConstrainingElement()) : null), d, null, "sliceName", mode));   
3493    }
3494
3495    tableRow(tbl, "Definition", null, strikethrough, compareMarkdown(sd.getName(), d.getDefinitionElement(), (compare==null) || slicedExtension ? null : compare.getDefinitionElement(), mode));
3496    tableRow(tbl, "Short", null, strikethrough, compareString(d.hasShort() ? d.getShort() : null, d.getShortElement(), null, "short", d, compare!= null && compare.hasShortElement() ? compare.getShort() : null, null, mode));
3497    tableRow(tbl, "Comments", null, strikethrough, compareMarkdown(sd.getName(), d.getCommentElement(), (compare==null) || slicedExtension ? null : compare.getCommentElement(), mode));
3498    tableRow(tbl, "Note", null, strikethrough, businessIdWarning(sd.getName(), tail(d.getPath())));
3499    tableRow(tbl, "Control", "conformance-rules.html#conformance", strikethrough, describeCardinality(d, compare, mode)); 
3500    tableRow(tbl, "Binding", "terminologies.html", strikethrough, describeBinding(sd, d, d.getPath(), compare, mode));
3501    if (d.hasContentReference()) {
3502      tableRow(tbl, "Type", null, strikethrough, "See " + d.getContentReference().substring(1));
3503    } else {
3504      tableRow(tbl, "Type", "datatypes.html", strikethrough, describeTypes(d.getType(), false, d, compare, mode, value, compareValue, sd)); 
3505    }
3506    if (d.hasExtension(ToolingExtensions.EXT_DEF_TYPE)) {
3507      tableRow(tbl, "Default Type", "datatypes.html", strikethrough, ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_DEF_TYPE));          
3508    }
3509    if (d.hasExtension(ToolingExtensions.EXT_TYPE_SPEC)) {
3510      tableRow(tbl, Utilities.pluralize("Type Specifier", d.getExtensionsByUrl(ToolingExtensions.EXT_TYPE_SPEC).size()), "datatypes.html", strikethrough, formatTypeSpecifiers(d));          
3511    }
3512    if (d.getPath().endsWith("[x]") && !d.prohibited()) {
3513      tableRow(tbl, "[x] Note", null, strikethrough).ahWithText("See ", spec("formats.html#choice"), null, "Choice of Data Types", " for further information about how to use [x]");
3514    }
3515    tableRow(tbl, "Is Modifier", "conformance-rules.html#ismodifier", strikethrough, presentModifier(d, mode, compare));
3516    if (d.getMustHaveValue()) {
3517      tableRow(tbl, "Primitive Value", "elementdefinition.html#primitives", strikethrough, "This primitive type must have a value (the value must be present, and cannot be replaced by an extension)");
3518    } else if (d.hasValueAlternatives()) {
3519      tableRow(tbl, "Primitive Value", "elementdefinition.html#primitives", strikethrough, renderCanonicalList("This primitive type may be present, or absent if replaced by one of the following extensions: ", d.getValueAlternatives()));      
3520    } else if (hasPrimitiveTypes(d)) {
3521      tableRow(tbl, "Primitive Value", "elementdefinition.html#primitives", strikethrough, "This primitive element may be present, or absent, or replaced by an extension");            
3522    }
3523    if (ToolingExtensions.hasAllowedUnits(d)) {      
3524      tableRow(tbl, "Allowed Units", "http://hl7.org/fhir/extensions/StructureDefinition-elementdefinition-allowedUnits.html", strikethrough, describeAllowedUnits(d));        
3525    }
3526    tableRow(tbl, "Must Support", "conformance-rules.html#mustSupport", strikethrough, displayBoolean(d.getMustSupport(), d.getMustSupportElement(), "mustSupport", d, compare==null ? null : compare.getMustSupportElement(), mode));
3527    if (d.getMustSupport()) {
3528      if (hasMustSupportTypes(d.getType())) {
3529        tableRow(tbl, "Must Support Types", "datatypes.html", strikethrough, describeTypes(d.getType(), true, d, compare, mode, null, null, sd));
3530      } else if (hasChoices(d.getType())) {
3531        tableRow(tbl, "Must Support Types", "datatypes.html", strikethrough, "No must-support rules about the choice of types/profiles");
3532      }
3533    }
3534    if (root && sd.getKind() == StructureDefinitionKind.LOGICAL) {
3535      tableRow(tbl, "Logical Model", null, strikethrough, ToolingExtensions.readBoolExtension(sd, ToolingExtensions.EXT_LOGICAL_TARGET) ? "This logical model can be the target of a reference" : "This logical model cannot be the target of a reference");
3536    }
3537
3538    if (root && sd.hasExtension(ToolingExtensions.EXT_SD_IMPOSE_PROFILE)) {
3539      tableRow(tbl, "Impose Profile", "http://hl7.org/fhir/extensions/StructureDefinition-structuredefinition-imposeProfile.html", strikethrough, 
3540          renderCanonicalListExt("This profile also requires that the instance also conform this additional profile: ", sd.getExtensionsByUrl(ToolingExtensions.EXT_SD_IMPOSE_PROFILE)));
3541    }
3542    if (root && sd.hasExtension(ToolingExtensions.EXT_SD_COMPLIES_WITH_PROFILE)) {
3543      tableRow(tbl, "Complies with Profile", "http://hl7.org/fhir/extensions/StructureDefinition-structuredefinition-compliesWithProfile.html", strikethrough, 
3544          renderCanonicalListExt("This profile compiles with the profile ", sd.getExtensionsByUrl(ToolingExtensions.EXT_SD_COMPLIES_WITH_PROFILE)));
3545    }
3546    tableRow(tbl, "Obligations", null, strikethrough, describeObligations(d, root, sd));   
3547
3548    if (d.hasExtension(ToolingExtensions.EXT_EXTENSION_STYLE)) {
3549      String es = d.getExtensionString(ToolingExtensions.EXT_EXTENSION_STYLE);
3550      if ("named-elements".equals(es)) {
3551        if (context.hasLink(KnownLinkType.JSON_NAMES)) {
3552          tableRow(tbl, "Extension Style", context.getLink(KnownLinkType.JSON_NAMES), strikethrough, "This element can be extended by named JSON elements");
3553        } else {
3554          tableRow(tbl, "Extension Style", ToolingExtensions.WEB_EXTENSION_STYLE, strikethrough, "This element can be extended by named JSON elements");
3555        }
3556      }
3557    }
3558
3559    if (!d.getPath().contains(".") && ToolingExtensions.hasExtension(sd, ToolingExtensions.EXT_BINDING_STYLE)) {
3560      tableRow(tbl, "Binding Style", ToolingExtensions.WEB_BINDING_STYLE, strikethrough, 
3561          "This type can be bound to a value set using the " + ToolingExtensions.readStringExtension(sd, ToolingExtensions.EXT_BINDING_STYLE)+" binding style");            
3562    }
3563
3564    if (d.hasExtension(ToolingExtensions.EXT_DATE_FORMAT)) {
3565      tableRow(tbl, "Date Format", null, strikethrough, ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_DATE_FORMAT));
3566    }
3567    String ide = ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_ID_EXPECTATION);
3568    if (ide != null) {
3569      if (ide.equals("optional")) {
3570        tableRow(tbl, "ID Expectation", null, strikethrough, "Id may or not be present (this is the default for elements but not resources)");
3571      } else if (ide.equals("required")) {
3572        tableRow(tbl, "ID Expectation", null, strikethrough, "Id is required to be present (this is the default for resources but not elements)");
3573      } else if (ide.equals("required")) {
3574        tableRow(tbl, "ID Expectation", null, strikethrough, "An ID is not allowed in this context");
3575      }
3576    }
3577    // tooling extensions for formats
3578    if (ToolingExtensions.hasExtensions(d, ToolingExtensions.EXT_JSON_EMPTY, ToolingExtensions.EXT_JSON_PROP_KEY, ToolingExtensions.EXT_JSON_NULLABLE, 
3579        ToolingExtensions.EXT_JSON_NAME, ToolingExtensions.EXT_JSON_PRIMITIVE_CHOICE)) {
3580      tableRow(tbl, "JSON Format", null, strikethrough,  describeJson(d));          
3581    }
3582    if (d.hasExtension(ToolingExtensions.EXT_XML_NAMESPACE) || sd.hasExtension(ToolingExtensions.EXT_XML_NAMESPACE) || d.hasExtension(ToolingExtensions.EXT_XML_NAME) || (root && sd.hasExtension(ToolingExtensions.EXT_XML_NO_ORDER)) ||
3583        d.hasRepresentation()) {
3584      tableRow(tbl, "XML Format", null, strikethrough, describeXml(sd, d, root));          
3585    }
3586
3587    if (d.hasExtension(ToolingExtensions.EXT_IMPLIED_PREFIX)) {
3588      tableRow(tbl, "String Format", null, strikethrough).codeWithText("When this element is read ", ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_IMPLIED_PREFIX), "is prefixed to the value before validation");                
3589    }
3590
3591    if (d.hasExtension(ToolingExtensions.EXT_STANDARDS_STATUS)) {
3592      StandardsStatus ss = StandardsStatus.fromCode(d.getExtensionString(ToolingExtensions.EXT_STANDARDS_STATUS));
3593      //      gc.addStyledText("Standards Status = "+ss.toDisplay(), ss.getAbbrev(), "black", ss.getColor(), baseSpecUrl()+, true);
3594      StructureDefinition sdb = context.getContext().fetchResource(StructureDefinition.class, sd.getBaseDefinition());
3595      if (sdb != null) {
3596        StandardsStatus base = determineStandardsStatus(sdb, (ElementDefinition) d.getUserData("derived.pointer"));
3597        if (base != null) {
3598          tableRow(tbl, "Standards Status", "versions.html#std-process", strikethrough, ss.toDisplay()+" (from "+base.toDisplay()+")");
3599        } else {
3600          tableRow(tbl, "Standards Status", "versions.html#std-process", strikethrough, ss.toDisplay());          
3601        }
3602      } else {
3603        tableRow(tbl, "Standards Status", "versions.html#std-process", strikethrough, ss.toDisplay());
3604      }
3605    }
3606    if (mode != GEN_MODE_DIFF && d.hasIsSummary()) {
3607      tableRow(tbl, "Summary", "search.html#summary", strikethrough, Boolean.toString(d.getIsSummary()));
3608    }
3609    tableRow(tbl, "Requirements", null, strikethrough, compareMarkdown(sd.getName(), d.getRequirementsElement(), (compare==null) || slicedExtension ? null : compare.getRequirementsElement(), mode));
3610    tableRow(tbl, "Label", null, strikethrough, compareString(d.getLabel(), d.getLabelElement(), null, "label", d, (compare != null ? compare.getLabel() : null), null, mode));   
3611    tableRow(tbl, "Alternate Names", null, strikethrough, compareSimpleTypeLists(d.getAlias(), ((compare==null) || slicedExtension ? null : compare.getAlias()), mode));
3612    tableRow(tbl, "Definitional Codes", null, strikethrough, compareDataTypeLists(d.getCode(), ((compare==null) || slicedExtension ? null : compare.getCode()), mode));
3613    tableRow(tbl, "Min Value", null, strikethrough, compareString(d.hasMinValue() ? encodeValue(d.getMinValue()) : null, d.getMinValue(), null, "minValue", d, compare!= null && compare.hasMinValue() ? encodeValue(compare.getMinValue()) : null, null, mode));
3614    tableRow(tbl, "Max Value", null, strikethrough, compareString(d.hasMaxValue() ? encodeValue(d.getMaxValue()) : null, d.getMaxValue(), null, "maxValue", d, compare!= null && compare.hasMaxValue() ? encodeValue(compare.getMaxValue()) : null, null, mode));
3615    tableRow(tbl, "Max Length", null, strikethrough, compareString(d.hasMaxLength() ? toStr(d.getMaxLength()) : null, d.getMaxLengthElement(), null, "maxLength", d, compare!= null && compare.hasMaxLengthElement() ? toStr(compare.getMaxLength()) : null, null, mode));
3616    tableRow(tbl, "Value Required", null, strikethrough, compareString(encodeValue(d.getMustHaveValueElement()), d.getMustHaveValueElement(), null, (compare != null ? encodeValue(compare.getMustHaveValueElement()) : null), d, null, "mustHaveValueElement", mode));   
3617    tableRow(tbl, "Value Alternatives", null, strikethrough, compareSimpleTypeLists(d.getValueAlternatives(), ((compare==null) || slicedExtension ? null : compare.getValueAlternatives()), mode));
3618    tableRow(tbl, "Default Value", null, strikethrough, encodeValue(d.getDefaultValue(), "defaultValue", d, compare==null ? null : compare.getDefaultValue(), mode));
3619    tableRow(tbl, "Meaning if Missing", null, strikethrough, d.getMeaningWhenMissing());
3620    tableRow(tbl, "Fixed Value", null, strikethrough, encodeValue(d.getFixed(), "fixed", d, compare==null ? null : compare.getFixed(), mode));
3621    tableRow(tbl, "Pattern Value", null, strikethrough, encodeValue(d.getPattern(), "pattern", d, compare==null ? null : compare.getPattern(), mode));
3622    tableRow(tbl, "Example", null, strikethrough, encodeValues(d.getExample()));
3623    tableRow(tbl, "Invariants", null, strikethrough, invariants(d.getConstraint(), compare==null ? null : compare.getConstraint(), d, mode));
3624    tableRow(tbl, "LOINC Code", null, strikethrough, getMapping(sd, d, LOINC_MAPPING, compare, mode));
3625    tableRow(tbl, "SNOMED-CT Code", null, strikethrough, getMapping(sd, d, SNOMED_MAPPING, compare, mode));
3626    tbl.tx("\r\n");
3627  }
3628  
3629  private XhtmlNode presentModifier(ElementDefinition d, int mode, ElementDefinition compare) throws FHIRException, IOException {
3630    XhtmlNode x1 = compareString(encodeValue(d.getIsModifierElement()), d.getIsModifierElement(), null, "isModifier", d, compare == null ? null : encodeValue(compare.getIsModifierElement()), null, mode);
3631    if (x1 != null) {
3632      XhtmlNode x2 = compareString(encodeValue(d.getIsModifierReasonElement()), d.getIsModifierReasonElement(), null, "isModifierReason", d, compare == null ? null : encodeValue(compare.getIsModifierReasonElement()), null, mode);
3633      if (x2 != null) {
3634        x1.tx(" because ");
3635        x1.copyAllContent(x2);
3636      }
3637    }
3638    return x1;
3639  }  
3640  
3641  private String spec(String name) {
3642    return Utilities.pathURL(VersionUtilities.getSpecUrl(context.getWorker().getVersion()) , name);
3643  }
3644
3645  private XhtmlNode describeXml(StructureDefinition profile, ElementDefinition d, boolean root) {
3646    XhtmlNode ret = new XhtmlNode(NodeType.Element, "div");
3647    for (PropertyRepresentation pr : PropertyRepresentation.values()) {
3648      if (d.hasRepresentation(pr)) {
3649        switch (pr) {
3650        case CDATEXT:
3651          ret.tx("This property is represented as CDA Text in the XML.");
3652          break;
3653        case TYPEATTR:
3654          ret.codeWithText("The type of this property is determined using the ", "xsi:type", "attribute.");
3655          break;
3656        case XHTML:
3657          ret.tx("This property is represented as XHTML Text in the XML.");
3658          break;
3659        case XMLATTR:
3660          ret.tx("In the XML format, this property is represented as an attribute.");
3661          break;
3662        case XMLTEXT:
3663          ret.tx("In the XML format, this property is represented as unadorned text.");
3664          break;
3665        default:
3666        }
3667      }
3668    }
3669    String name = ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_XML_NAMESPACE);
3670    if (name == null && root) {
3671      name = ToolingExtensions.readStringExtension(profile, ToolingExtensions.EXT_XML_NAMESPACE);
3672    }
3673    if (name != null) {
3674      ret.codeWithText("In the XML format, this property has the namespace ", name, ".");
3675    }
3676    name = ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_XML_NAME);
3677    if (name != null) {
3678      ret.codeWithText("In the XML format, this property has the actual name", name, ".");
3679    }
3680    boolean no = root && ToolingExtensions.readBoolExtension(profile, ToolingExtensions.EXT_XML_NO_ORDER);
3681    if (no) {
3682      ret.tx("The children of this property can appear in any order in the XML.");
3683    }
3684    return ret;
3685  }
3686
3687  private XhtmlNode describeJson(ElementDefinition d) {
3688    XhtmlNode ret = new XhtmlNode(NodeType.Element, "div");
3689    var ul = ret.ul();
3690    boolean list = ToolingExtensions.countExtensions(d, ToolingExtensions.EXT_JSON_EMPTY, ToolingExtensions.EXT_JSON_PROP_KEY, ToolingExtensions.EXT_JSON_NULLABLE, ToolingExtensions.EXT_JSON_NAME) > 1;
3691
3692    String code = ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_JSON_EMPTY);
3693    if (code != null) {
3694      switch (code) {
3695      case "present":
3696        ul.li().tx("The JSON Array for this property is present even when there are no items in the instance (e.g. as an empty array)");
3697        break;
3698      case "absent":
3699        ul.li().tx("The JSON Array for this property is not present when there are no items in the instance (e.g. never as an empty array)");
3700        break;
3701      case "either":
3702        ul.li().tx("The JSON Array for this property may be present even when there are no items in the instance (e.g. may be present as an empty array)");
3703        break;
3704      }
3705    }
3706    String jn = ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_JSON_NAME);
3707    if (jn != null) {
3708      if (d.getPath().contains(".")) {
3709        ul.li().codeWithText("This property appears in JSON with the property name ", jn, null);
3710      } else {
3711        ul.li().codeWithText("This type can appear in JSON with the property name ", jn, " (in elements using named extensions)");          
3712      }
3713    }
3714    code = ToolingExtensions.readStringExtension(d, ToolingExtensions.EXT_JSON_PROP_KEY);
3715    if (code != null) {
3716      ul.li().codeWithText("This repeating object is represented as a single JSON object with named properties. The name of the property (key) is the value of the ", code, " child");
3717    }
3718    if (ToolingExtensions.readBoolExtension(d, ToolingExtensions.EXT_JSON_NULLABLE)) {
3719      ul.li().tx("This object can be represented as null in the JSON structure (which counts as 'present' for cardinality purposes)");
3720    }
3721    if (ToolingExtensions.readBoolExtension(d, ToolingExtensions.EXT_JSON_PRIMITIVE_CHOICE)) {
3722      ul.li().tx("The type of this element is inferred from the JSON type in the instance");
3723    }
3724
3725    switch (ul.getChildNodes().size()) {
3726    case 0: return null;
3727    case 1: return ul.getChildNodes().get(0);
3728    default: return ret;
3729    }
3730  }
3731
3732  private XhtmlNode describeObligations(ElementDefinition d, boolean root, StructureDefinition sdx) throws IOException {
3733    XhtmlNode ret = new XhtmlNode(NodeType.Element, "div");
3734    ObligationsRenderer obr = new ObligationsRenderer(corePath, sdx, d.getPath(), context, hostMd, this);
3735    obr.seeObligations(d.getExtensionsByUrl(ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS));
3736    obr.seeRootObligations(d.getId(), sdx.getExtensionsByUrl(ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS));
3737    if (obr.hasObligations() || (root && (sdx.hasExtension(ToolingExtensions.EXT_OBLIGATION_PROFILE_FLAG) || sdx.hasExtension(ToolingExtensions.EXT_OBLIGATION_INHERITS)))) {
3738      XhtmlNode ul = ret.ul();
3739      if (root) {
3740        if (sdx.hasExtension(ToolingExtensions.EXT_OBLIGATION_PROFILE_FLAG)) {
3741          ul.li().tx("This is an obligation profile that only contains obligations and additional bindings");           
3742        } 
3743        for (Extension ext : sdx.getExtensionsByUrl(ToolingExtensions.EXT_OBLIGATION_INHERITS)) {
3744          String iu = ext.getValue().primitiveValue();
3745          XhtmlNode bb = ul.li();
3746          bb.tx("This profile picks up obligations and additional bindings from ");           
3747          StructureDefinition sd = context.getContext().fetchResource(StructureDefinition.class, iu); 
3748          if (sd == null) { 
3749            bb.code().tx(iu);                     
3750          } else if (sd.hasWebPath()) { 
3751            bb.ah(sd.getWebPath()).tx(sd.present());
3752          } else { 
3753            bb.ah(iu).tx(sd.present());
3754          } 
3755        }  
3756        if (ul.isEmpty()) {
3757          ret.remove(ul);
3758        }
3759      }
3760      if (obr.hasObligations()) {
3761        XhtmlNode tbl = ret.table("grid");
3762        obr.renderTable(tbl.getChildNodes(), true);
3763        if (tbl.isEmpty()) {
3764          ret.remove(tbl);
3765        }
3766      }
3767      return ret.hasChildren() ? ret : null;
3768    } else {
3769      return null;
3770    }
3771  }
3772
3773  private XhtmlNode describeAllowedUnits(ElementDefinition d) {
3774    XhtmlNode ret = new XhtmlNode(NodeType.Element, "div");
3775    DataType au = ToolingExtensions.getAllowedUnits(d);
3776    if (au instanceof CanonicalType) {
3777      String url = ((CanonicalType) au).asStringValue();
3778      ValueSet vs = context.getContext().fetchResource(ValueSet.class, url);
3779      ret.tx("Value set ");         
3780      genCT(ret, url, vs);
3781      return ret;
3782    } else if (au instanceof CodeableConcept) {
3783      CodeableConcept cc = (CodeableConcept) au;
3784      if (cc.getCoding().size() != 1) {
3785        ret.tx("One of:");
3786      }
3787      ret.tx(summarise(cc));
3788      return ret;
3789    }
3790    return null;
3791  }
3792
3793  private void genCT(XhtmlNode x, String url, CanonicalResource cr) {
3794    if (cr == null) {
3795      x.code().tx(url);
3796    } else if (!cr.hasWebPath()) {
3797      x.ah(url).tx(cr.present());
3798    } else {
3799      x.ah(cr.getWebPath()).tx(cr.present());
3800    }
3801  }
3802
3803  private boolean hasPrimitiveTypes(ElementDefinition d) {
3804    for (TypeRefComponent tr : d.getType()) {
3805      if (isPrimitive(tr.getCode())) {
3806        return true;
3807      }
3808    }
3809    return false;
3810  }
3811
3812
3813  private XhtmlNode renderCanonicalListExt(String text, List<Extension> list) {
3814    List<CanonicalType> clist = new ArrayList<>();
3815    for (Extension ext : list) {
3816      if (ext.hasValueCanonicalType()) {
3817        clist.add(ext.getValueCanonicalType());
3818      }
3819    }
3820    return renderCanonicalList(text, clist);
3821  }
3822
3823  private XhtmlNode renderCanonicalList(String text, List<CanonicalType> list) {
3824    XhtmlNode ret = new XhtmlNode(NodeType.Element, "div");
3825    ret.tx(text);
3826    var ul = ret.ul();
3827    for (CanonicalType ct : list) {
3828      CanonicalResource cr = (CanonicalResource) context.getContext().fetchResource(Resource.class,  ct.getValue());
3829      genCT(ul.li(), ct.getValue(), cr);      
3830    }
3831    return ret;
3832  }
3833
3834  private StandardsStatus determineStandardsStatus(StructureDefinition sd, ElementDefinition ed) {
3835    if (ed != null && ed.hasExtension(ToolingExtensions.EXT_STANDARDS_STATUS)) {
3836      return StandardsStatus.fromCode(ed.getExtensionString(ToolingExtensions.EXT_STANDARDS_STATUS));
3837    }
3838    while (sd != null) {
3839      if (sd.hasExtension(ToolingExtensions.EXT_STANDARDS_STATUS)) {
3840        return ToolingExtensions.getStandardsStatus(sd);
3841      }
3842      sd = context.getContext().fetchResource(StructureDefinition.class, sd.getBaseDefinition());
3843    }
3844    return null;
3845  }
3846
3847  private boolean hasChoices(List<TypeRefComponent> types) {
3848    for (TypeRefComponent type : types) {
3849      if (type.getProfile().size() > 1 || type.getTargetProfile().size() > 1) {
3850        return true;
3851      }
3852    }
3853    return types.size() > 1;
3854  }
3855
3856  private String sliceOrderString(ElementDefinitionSlicingComponent slicing) {
3857    if (slicing.getOrdered())
3858      return "ordered";
3859    else
3860      return "unordered";
3861  }
3862  
3863  private void generateSlicing(XhtmlNode tbl, StructureDefinition profile, ElementDefinition ed, ElementDefinitionSlicingComponent slicing, ElementDefinition compare, int mode, boolean strikethrough) throws IOException {
3864    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
3865    
3866    x.codeWithText("This element introduces a set of slices on ", ed.getPath(), ". The slices are ");
3867    String newOrdered = sliceOrderString(slicing);
3868    String oldOrdered = (compare==null || !compare.hasSlicing()) ? null : sliceOrderString(compare.getSlicing());
3869    compareString(x, newOrdered, slicing.getOrderedElement(), null, null, null, oldOrdered, null, mode);
3870    x.tx(" and ");
3871    compareString(x, slicing.hasRules() ? slicing.getRules().getDisplay() : null, slicing.getRulesElement(), null, "rules", slicing, compare!=null && compare.hasSlicing() && compare.getSlicing().hasRules() ? compare.getSlicing().getRules().getDisplay() : null, null, mode);
3872    
3873    if (slicing.hasDiscriminator()) {
3874      x.tx(", and can be differentiated using the following discriminators: ");
3875      StatusList<DiscriminatorWithStatus> list = new StatusList<>();
3876      for (ElementDefinitionSlicingDiscriminatorComponent d : slicing.getDiscriminator()) {
3877        list.add(new DiscriminatorWithStatus(d));
3878      }
3879      if (compare != null) {      
3880        for (ElementDefinitionSlicingDiscriminatorComponent d : slicing.getDiscriminator()) {
3881          list.merge(new DiscriminatorWithStatus(d));
3882        }
3883      }
3884      x.tx(", and can be differentiated using the following discriminators: ");
3885      var ul = x.ul();
3886      for (DiscriminatorWithStatus rc : list) {
3887        rc.render(x.li());
3888      }
3889    } else {
3890      x.tx(", and defines no discriminators to differentiate the slices");
3891    }
3892    tableRow(tbl, "Slicing", "profiling.html#slicing", strikethrough, x);
3893    tbl.tx("\r\n");
3894  }
3895
3896  private XhtmlNode tableRow(XhtmlNode x, String name, String defRef, boolean strikethrough) throws IOException {
3897    var tr = x.tr();
3898    if (strikethrough) {
3899      tr.style("text-decoration: line-through");
3900    }
3901    addFirstCell(name, defRef, tr);
3902    return tr.td();
3903  }
3904  
3905
3906  private void tableRow(XhtmlNode x, String name, String defRef, boolean strikethrough, XhtmlNode possibleTd) throws IOException {
3907    if (possibleTd != null && !possibleTd.isEmpty()) {
3908      var tr = x.tr();
3909      if (strikethrough) {
3910        tr.style("text-decoration: line-through");
3911      }
3912      addFirstCell(name, defRef, tr);
3913      tr.td().copyAllContent(possibleTd);
3914    }
3915  }
3916
3917  private void tableRow(XhtmlNode x, String name, String defRef, boolean strikethrough, String text) throws IOException {
3918    if (!Utilities.noString(text)) {
3919      var tr = x.tr();
3920      if (strikethrough) {
3921        tr.style("text-decoration: line-through");
3922      }
3923      addFirstCell(name, defRef, tr);
3924      tr.td().tx(text);
3925    }
3926  }
3927
3928  private void addFirstCell(String name, String defRef, XhtmlNode tr) {
3929    var td = tr.td();
3930    if (name.length() <= 16) {
3931     td.style("white-space: nowrap");
3932    }
3933    if (defRef == null) {
3934      td.tx(name);
3935    } else if (Utilities.isAbsoluteUrl(defRef)) {
3936      td.ah(defRef).tx(name);
3937    } else {
3938      td.ah(corePath+defRef).tx(name);
3939    }
3940  }
3941
3942  private String head(String path) {
3943    if (path.contains("."))
3944      return path.substring(0, path.indexOf("."));
3945    else
3946      return path;
3947  }
3948  private String nottail(String path) {
3949    if (path.contains("."))
3950      return path.substring(0, path.lastIndexOf("."));
3951    else
3952      return path;
3953  }
3954
3955  private XhtmlNode businessIdWarning(String resource, String name) {
3956    if (name.equals("identifier")) {
3957      XhtmlNode ret = new XhtmlNode(NodeType.Element, "div");
3958      ret.tx("This is a business identifier, not a resource identifier (see ");
3959      ret.ah(corePath + "resource.html#identifiers").tx("discussion");
3960      ret.tx(")");
3961      return ret;
3962    } 
3963    if (name.equals("version")) {// && !resource.equals("Device"))
3964      XhtmlNode ret = new XhtmlNode(NodeType.Element, "div");
3965      ret.tx("This is a business versionId, not a resource version id (see ");
3966      ret.ah(corePath + "resource.html#versions").tx("discussion");
3967      ret.tx(")");
3968      return ret;
3969    }
3970    return null;
3971  }
3972
3973  private XhtmlNode describeCardinality(ElementDefinition d, ElementDefinition compare, int mode) throws IOException {
3974    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
3975    if (compare==null || mode==GEN_MODE_DIFF) {
3976      if (!d.hasMax() && !d.hasMin())
3977        return null;
3978      else if (d.getMax() == null) {
3979        renderStatus(d.getMinElement(), x).tx(toStr(d.getMin()));
3980        x.tx("..?");
3981      } else {
3982        renderStatus(d.getMinElement(), x).tx(toStr(d.getMin()));
3983        x.tx( "..");
3984        renderStatus(d.getMaxElement(), x).tx( d.getMax());
3985      }
3986    } else {
3987      if (!(mode==GEN_MODE_DIFF && (d.getMin()==compare.getMin() || d.getMin()==0))) {
3988        compareString(x, toStr(d.getMin()), d.getMinElement(), null, "min", d, toStr(compare.getMin()), null, mode);
3989      }
3990      x.tx("..");
3991      if (!(mode==GEN_MODE_DIFF && (d.getMax().equals(compare.getMax()) || "1".equals(d.getMax())))) {
3992        compareString(x, d.getMax(), d.getMaxElement(), null, "max", d, compare.getMax(), null, mode);
3993      }
3994    }
3995    XhtmlNode t = compareSimpleTypeLists(d.getCondition(), compare == null ? null : compare.getCondition(), mode);
3996    if (t != null) {
3997      x.br();
3998      x.tx("This element is affected by the following invariants: "); 
3999      x.copyAllContent(t);
4000    }    
4001    return x;
4002  }
4003  
4004  private boolean hasMustSupportTypes(List<TypeRefComponent> types) {
4005    for (TypeRefComponent tr : types) {
4006      if (isMustSupport(tr)) {
4007        return true;
4008      }
4009    }
4010    return false;
4011  }
4012
4013  private XhtmlNode describeTypes(List<TypeRefComponent> types, boolean mustSupportOnly, ElementDefinition ed, ElementDefinition compare, int mode, ElementDefinition value, ElementDefinition compareValue, StructureDefinition sd) throws FHIRException, IOException {
4014    if (types.isEmpty())
4015      return null;
4016
4017    List<TypeRefComponent> compareTypes = compare==null ? new ArrayList<>() : compare.getType();
4018    XhtmlNode ret = new XhtmlNode(NodeType.Element, "div");
4019    if ((!mustSupportOnly && types.size() == 1 && compareTypes.size() <=1 && (mode != GEN_MODE_DIFF || !VersionComparisonAnnotation.hasDeleted(ed, "type"))) || (mustSupportOnly && mustSupportCount(types) == 1)) {
4020      if (!mustSupportOnly || isMustSupport(types.get(0))) {
4021        describeType(ret, types.get(0), mustSupportOnly, compareTypes.size()==0 ? null : compareTypes.get(0), mode, sd);
4022      }
4023    } else {
4024      boolean first = true;
4025      if (types.size() > 1) {
4026        ret.tx("Choice of: ");
4027      }
4028      Map<String,TypeRefComponent> map = new HashMap<String, TypeRefComponent>();
4029      for (TypeRefComponent t : compareTypes) {
4030        map.put(t.getCode(), t);
4031      }
4032      for (TypeRefComponent t : types) {
4033        TypeRefComponent compareType = map.get(t.getCode());
4034        if (compareType!=null)
4035          map.remove(t.getCode());
4036        if (!mustSupportOnly || isMustSupport(t)) {
4037          if (first) {
4038            first = false;
4039          } else {
4040            ret.tx(", ");
4041          }
4042          describeType(ret, t, mustSupportOnly, compareType, mode, sd);
4043        }
4044      }
4045      for (TypeRefComponent t : map.values()) {
4046        ret.tx(", ");
4047        describeType(removed(ret), t, mustSupportOnly, null, mode, sd);
4048      }
4049      if (mode == GEN_MODE_DIFF) {
4050        for (Base b : VersionComparisonAnnotation.getDeleted(ed, "type")) {
4051          TypeRefComponent t = (TypeRefComponent) b;
4052          ret.tx(", ");
4053          describeType(ret, t, false, null, mode, sd);
4054        }
4055      }
4056    }
4057    if (value != null) {
4058      XhtmlNode xt = processSecondary(mode, value, compareValue, mode, sd);
4059      if (xt != null) {
4060        ret.copyAllContent(xt);
4061      }    
4062    }
4063    return ret;
4064  }
4065  
4066  private XhtmlNode processSecondary(int mode, ElementDefinition value, ElementDefinition compareValue, int compMode, StructureDefinition sd) throws FHIRException, IOException {
4067    switch (mode) {
4068    case 1:
4069      return null;
4070    case 2:
4071      XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
4072      x.tx(" (Complex Extension)");
4073      return x;
4074    case 3:
4075      x = new XhtmlNode(NodeType.Element, "div");
4076      x.tx(" (Extension Type: ");
4077      x.copyAllContent(describeTypes(value.getType(), false, value, compareValue, compMode, null, null, sd));
4078      x.tx(")");
4079      return x;
4080    default:
4081      return null;
4082    }
4083  }
4084
4085
4086  private int mustSupportCount(List<TypeRefComponent> types) {
4087    int c = 0;
4088    for (TypeRefComponent tr : types) {
4089      if (isMustSupport(tr)) {
4090        c++;
4091      }
4092    }
4093    return c;
4094  }
4095
4096  
4097  private void describeType(XhtmlNode x, TypeRefComponent t, boolean mustSupportOnly, TypeRefComponent compare, int mode, StructureDefinition sd) throws FHIRException, IOException {
4098    if (t.getWorkingCode() == null) {
4099      return;
4100    }
4101    if (t.getWorkingCode().startsWith("=")) {
4102      return;
4103    }
4104
4105    boolean ts = false;
4106    if (t.getWorkingCode().startsWith("xs:")) {
4107      ts = compareString(x, t.getWorkingCode(), t, null, "code", t, compare==null ? null : compare.getWorkingCode(), null, mode);
4108    } else {
4109      ts = compareString(x, t.getWorkingCode(), t, getTypeLink(t, sd), "code", t, compare==null ? null : compare.getWorkingCode(), compare==null ? null : getTypeLink(compare, sd), mode);
4110    }
4111    
4112    if ((!mustSupportOnly && (t.hasProfile() || (compare!=null && compare.hasProfile()))) || isMustSupport(t.getProfile())) {
4113      StatusList<ResolvedCanonical> profiles = analyseProfiles(t.getProfile(), compare == null ? null : compare.getProfile(), mustSupportOnly, mode);      
4114      if (profiles.size() > 0) {
4115        if (!ts) {
4116          getTypeLink(unchanged(x), t, sd);
4117          ts = true;
4118        }
4119        x.tx("(");
4120        boolean first = true;
4121        for (ResolvedCanonical rc : profiles) {
4122          if (first) first = false; else x.tx(", ");
4123          rc.render(x);
4124        }
4125        x.tx(")");
4126      }
4127    }
4128    
4129    if ((!mustSupportOnly && (t.hasTargetProfile() || (compare!=null && compare.hasTargetProfile()))) || isMustSupport(t.getTargetProfile())) {
4130      List<ResolvedCanonical> profiles = analyseProfiles(t.getTargetProfile(), compare == null ? null : compare.getTargetProfile(), mustSupportOnly, mode);      
4131      if (profiles.size() > 0) {
4132        if (!ts) {
4133          getTypeLink(unchanged(x), t, sd);
4134        }
4135        x.tx("("); // todo: double use of "(" is problematic
4136        boolean first = true;
4137        for (ResolvedCanonical rc : profiles) {
4138          if (first) first = false; else x.tx(", ");
4139          rc.render(x);
4140        }
4141        x.tx(")");
4142      }
4143
4144      if (!t.getAggregation().isEmpty() || (compare!=null && !compare.getAggregation().isEmpty())) {
4145        
4146        for (Enumeration<AggregationMode> a :t.getAggregation()) {
4147          a.setUserData("render.link", corePath + "codesystem-resource-aggregation-mode.html#content");
4148        }
4149        if (compare!=null) {
4150          for (Enumeration<AggregationMode> a : compare.getAggregation()) {
4151            a.setUserData("render.link", corePath + "codesystem-resource-aggregation-mode.html#content");
4152          }
4153        }
4154        var xt = compareSimpleTypeLists(t.getAggregation(), compare == null ? null : compare.getAggregation(), mode);
4155        if (xt != null) {
4156          x.copyAllContent(xt);
4157        }
4158      }
4159    }
4160  }
4161
4162  private StatusList<ResolvedCanonical> analyseProfiles(List<CanonicalType> newProfiles, List<CanonicalType> oldProfiles, boolean mustSupportOnly, int mode) {
4163    StatusList<ResolvedCanonical> profiles = new StatusList<ResolvedCanonical>();
4164    for (CanonicalType pt : newProfiles) {
4165      ResolvedCanonical rc = fetchProfile(pt, mustSupportOnly);
4166      profiles.add(rc);
4167    }
4168    if (oldProfiles!=null && mode != GEN_MODE_DIFF) {
4169      for (CanonicalType pt : oldProfiles) {
4170        profiles.merge(fetchProfile(pt, mustSupportOnly));
4171      }
4172    }
4173    return profiles;
4174  }
4175
4176  private ResolvedCanonical fetchProfile(CanonicalType pt, boolean mustSupportOnly) {
4177    if (!pt.hasValue()) {
4178      return null;
4179    }
4180    if (!mustSupportOnly || isMustSupport(pt)) {
4181      StructureDefinition p = context.getContext().fetchResource(StructureDefinition.class, pt.getValue());
4182      return new ResolvedCanonical(pt.getValue(), p);
4183    } else {
4184      return null;
4185    }
4186  }
4187//
4188//  private String getTypeProfile(CanonicalType pt, boolean mustSupportOnly) {
4189//    StringBuilder b = new StringBuilder();
4190//    if (!mustSupportOnly || isMustSupport(pt)) {
4191//      StructureDefinition p = context.getContext().fetchResource(StructureDefinition.class, pt.getValue());
4192//      if (p == null)
4193//        b.append(pt.getValue());
4194//      else {
4195//        String pth = p.getWebPath();
4196//        b.append("<a href=\"" + Utilities.escapeXml(pth) + "\" title=\"" + pt.getValue() + "\">");
4197//        b.append(p.getName());
4198//        b.append("</a>");
4199//      }
4200//    }
4201//    return b.toString();
4202//  }
4203
4204  private void getTypeLink(XhtmlNode x, TypeRefComponent t, StructureDefinition sd) {
4205    String s = context.getPkp().getLinkFor(sd.getWebPath(), t.getWorkingCode());
4206    if (s != null) {
4207      x.ah(s).tx(t.getWorkingCode());
4208    } else {
4209      x.code().tx(t.getWorkingCode());
4210    }
4211  }
4212  
4213
4214  private String getTypeLink(TypeRefComponent t, StructureDefinition sd) {
4215    String s = context.getPkp().getLinkFor(sd.getWebPath(), t.getWorkingCode());
4216    return s;
4217  }
4218
4219  private XhtmlNode displayBoolean(boolean value, BooleanType source, String name, Base parent, BooleanType compare, int mode) {
4220    String newValue = value ? "true" : source.hasValue() ? "false" : null;
4221    String oldValue = compare==null || compare.getValue()==null ? null : (compare.getValue()!=true ? null : "true");
4222    return compareString(newValue, source, null, name, parent, oldValue, null, mode);
4223  }
4224
4225
4226  private XhtmlNode invariants(List<ElementDefinitionConstraintComponent> originalList, List<ElementDefinitionConstraintComponent> compareList, ElementDefinition parent, int mode) throws IOException {
4227    StatusList<InvariantWithStatus> list = new StatusList<>();
4228    for (ElementDefinitionConstraintComponent v : originalList) {
4229      if (!v.isEmpty()) {
4230        list.add(new InvariantWithStatus(v));
4231      }
4232    }
4233    if (compareList != null && mode != GEN_MODE_DIFF) {
4234      for (ElementDefinitionConstraintComponent v : compareList) {
4235        list.merge(new InvariantWithStatus(v));
4236      }
4237    }
4238    if (list.size() == 0) {
4239      return null;
4240    }
4241    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
4242    boolean first = true;
4243    for (InvariantWithStatus t : list) {
4244      if (first) first = false; else x.br();
4245      t.render(x);
4246    }
4247    for (Base b : VersionComparisonAnnotation.getDeleted(parent, "invariant")) {
4248      if (first) first = false; else x.br();
4249      InvariantWithStatus ts = new InvariantWithStatus((ElementDefinitionConstraintComponent) b);
4250      ts.render(x);
4251    }
4252    return x;
4253  }
4254
4255  private XhtmlNode describeBinding(StructureDefinition sd, ElementDefinition d, String path, ElementDefinition compare, int mode) throws FHIRException, IOException {
4256    if (!d.hasBinding())
4257      return null;
4258    else {
4259      ElementDefinitionBindingComponent binding = d.getBinding();
4260      ElementDefinitionBindingComponent compBinding = compare == null ? null : compare.getBinding();
4261      XhtmlNode bindingDesc = null;
4262      if (binding.hasDescription()) {
4263        MarkdownType newBinding = PublicationHacker.fixBindingDescriptions(context.getContext(), binding.getDescriptionElement());
4264        if (mode == GEN_MODE_SNAP || mode == GEN_MODE_MS) {
4265          bindingDesc = new XhtmlNode(NodeType.Element, "div");
4266          bindingDesc.add(new XhtmlParser().parseFragment(hostMd.processMarkdown("Binding.description", newBinding)));
4267        } else {
4268
4269          StringType oldBinding = compBinding != null && compBinding.hasDescription() ? PublicationHacker.fixBindingDescriptions(context.getContext(), compBinding.getDescriptionElement()) : null;
4270          bindingDesc = compareMarkdown("Binding.description", newBinding, oldBinding, mode);
4271        }
4272      }
4273      if (!binding.hasValueSet()) {
4274        return bindingDesc;
4275      }
4276      
4277      XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
4278      var nsp = x.span();
4279      renderBinding(nsp, binding, compBinding, path, sd, mode);      
4280      if (bindingDesc != null) {
4281        if (isSimpleContent(bindingDesc)) {
4282          x.tx(": ");
4283          x.copyAllContent(bindingDesc.getChildNodes().get(0));
4284        } else {
4285          x.br();
4286          x.copyAllContent(bindingDesc);
4287        }
4288      }
4289
4290      AdditionalBindingsRenderer abr = new AdditionalBindingsRenderer(context.getPkp(), corePath, sd, d.getPath(), context, hostMd, this);
4291
4292      if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) {
4293        abr.seeMaxBinding(ToolingExtensions.getExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), compBinding==null ? null : ToolingExtensions.getExtension(compBinding, ToolingExtensions.EXT_MAX_VALUESET), mode!=GEN_MODE_SNAP && mode!=GEN_MODE_MS);
4294      }
4295      if (binding.hasExtension(ToolingExtensions.EXT_MIN_VALUESET)) {
4296        abr.seeMinBinding(ToolingExtensions.getExtension(binding, ToolingExtensions.EXT_MIN_VALUESET), compBinding==null ? null : ToolingExtensions.getExtension(compBinding, ToolingExtensions.EXT_MIN_VALUESET), mode!=GEN_MODE_SNAP && mode!=GEN_MODE_MS);
4297      }
4298      if (binding.hasExtension(ToolingExtensions.EXT_BINDING_ADDITIONAL)) {
4299        abr.seeAdditionalBindings(binding.getExtensionsByUrl(ToolingExtensions.EXT_BINDING_ADDITIONAL), compBinding==null ? null : compBinding.getExtensionsByUrl(ToolingExtensions.EXT_BINDING_ADDITIONAL), mode!=GEN_MODE_SNAP && mode!=GEN_MODE_MS);
4300      }
4301
4302      if (abr.hasBindings()) {
4303        var tbl = x.table("grid");
4304        abr.render(tbl.getChildNodes(), true);
4305      }
4306      return x;
4307    }
4308  }
4309
4310
4311  private boolean isSimpleContent(XhtmlNode bindingDesc) {
4312    return bindingDesc.getChildNodes().size() == 1 && bindingDesc.getChildNodes().get(0).isPara();
4313  }
4314
4315  private void renderBinding(XhtmlNode span, ElementDefinitionBindingComponent binding, ElementDefinitionBindingComponent compare, String path, StructureDefinition sd, int mode) {
4316    compareString(span, conf(binding), binding.getStrengthElement(), null, "strength", binding, compare == null ? null : conf(compare), null, mode);
4317    span.tx(" ");
4318    BindingResolution br = context.getPkp().resolveBinding(sd, binding, path);
4319    compareString(span, br.display, binding.getValueSetElement(), br.url, "valueSet", binding, compare == null ? null : compare.getValueSet(), null, mode);
4320  }
4321
4322  private String stripPara(String s) {
4323    if (s.startsWith("<p>")) {
4324      s = s.substring(3);
4325    }
4326    if (s.trim().endsWith("</p>")) {
4327      s = s.substring(0, s.lastIndexOf("</p>")-1) + s.substring(s.lastIndexOf("</p>") +4);
4328    }
4329    return s;
4330  }
4331
4332  private String conf(ElementDefinitionBindingComponent def) {
4333    if (def.getStrength() == null) {
4334      return "For codes, see ";
4335    }
4336    switch (def.getStrength()) {
4337    case EXAMPLE:
4338      return "For example codes, see ";
4339    case PREFERRED:
4340      return "The codes SHOULD be taken from ";
4341    case EXTENSIBLE:
4342      return "Unless not suitable, these codes SHALL be taken from ";
4343    case REQUIRED:
4344      return "The codes SHALL be taken from ";
4345    default:
4346      return "?sd-conf?";
4347    }
4348  }
4349
4350  private String encodeValues(List<ElementDefinitionExampleComponent> examples) throws FHIRException, IOException {
4351    StringBuilder b = new StringBuilder();
4352    boolean first = false;
4353    for (ElementDefinitionExampleComponent ex : examples) {
4354      if (first)
4355        first = false;
4356      else
4357        b.append("<br/>");
4358      b.append("<b>" + Utilities.escapeXml(ex.getLabel()) + "</b>:" + encodeValue(ex.getValue()) + "\r\n");
4359    }
4360    return b.toString();
4361
4362  }
4363
4364  private XhtmlNode encodeValue(DataType value, String name, Base parent, DataType compare, int mode) throws FHIRException, IOException {
4365    String oldValue = encodeValue(compare);
4366    String newValue = encodeValue(value);
4367    return compareString(newValue, value, null, name, parent, oldValue, null, mode);
4368  }
4369
4370  private String encodeValue(DataType value) throws FHIRException, IOException {
4371    if (value == null || value.isEmpty())
4372      return null;
4373    if (value instanceof PrimitiveType)
4374      return Utilities.escapeXml(((PrimitiveType) value).asStringValue());
4375
4376    ByteArrayOutputStream bs = new ByteArrayOutputStream();
4377    XmlParser parser = new XmlParser();
4378    parser.setOutputStyle(OutputStyle.PRETTY);
4379    parser.compose(bs, null, value);
4380    String[] lines = bs.toString().split("\\r?\\n");
4381    StringBuilder b = new StringBuilder();
4382    for (String s : lines) {
4383      if (!Utilities.noString(s) && !s.startsWith("<?")) { // eliminate the xml header
4384        b.append(Utilities.escapeXml(s).replace(" ", "&nbsp;") + "<br/>");
4385      }
4386    }
4387    return b.toString();
4388
4389  }
4390
4391  private XhtmlNode getMapping(StructureDefinition profile, ElementDefinition d, String uri, ElementDefinition compare, int mode) {
4392    String id = null;
4393    for (StructureDefinitionMappingComponent m : profile.getMapping()) {
4394      if (m.hasUri() && m.getUri().equals(uri))
4395        id = m.getIdentity();
4396    }
4397    if (id == null)
4398      return null;
4399    String newMap = null;
4400    for (ElementDefinitionMappingComponent m : d.getMapping()) {
4401      if (m.getIdentity().equals(id)) {
4402        newMap = m.getMap();
4403        break;
4404      }
4405    }
4406    if (compare==null)
4407      return new XhtmlNode(NodeType.Element, "div").tx(newMap);
4408    String oldMap = null;
4409    for (ElementDefinitionMappingComponent m : compare.getMapping()) {
4410      if (m.getIdentity().equals(id)) {
4411        oldMap = m.getMap();
4412        break;
4413      }
4414    }
4415
4416    return compareString(Utilities.escapeXml(newMap), null, null, "mapping", d, Utilities.escapeXml(oldMap), null, mode);
4417  }
4418
4419  private XhtmlNode compareSimpleTypeLists(List<? extends PrimitiveType> original, List<? extends PrimitiveType> compare, int mode) throws IOException {
4420    return compareSimpleTypeLists(original, compare, mode, ", ");
4421  }
4422
4423 
4424  private XhtmlNode compareSimpleTypeLists(List<? extends PrimitiveType> originalList, List<? extends PrimitiveType> compareList, int mode, String separator) throws IOException {
4425    StatusList<ValueWithStatus> list = new StatusList<>();
4426    for (PrimitiveType v : originalList) {
4427      if (!v.isEmpty()) {
4428        list.add(new ValueWithStatus(v));
4429      }
4430    }
4431    if (compareList != null && mode != GEN_MODE_DIFF) {
4432      for (PrimitiveType v : compareList) {
4433        list.merge(new ValueWithStatus(v));
4434      }      
4435    }
4436    if (list.size() == 0) {
4437      return null;
4438    }
4439    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
4440    boolean first = true;
4441    for (ValueWithStatus t : list) {
4442      if (first) first = false; else x.tx(separator);
4443      t.render(x);
4444    }
4445    return x;
4446  }
4447  
4448
4449  private XhtmlNode compareDataTypeLists(List<? extends DataType> original, List<? extends DataType> compare, int mode) throws IOException {
4450    return compareDataTypeLists(original, compare, mode, ", ");
4451  }
4452
4453 
4454  private XhtmlNode compareDataTypeLists(List<? extends DataType> originalList, List<? extends DataType> compareList, int mode, String separator) throws IOException {
4455    StatusList<DataValueWithStatus> list = new StatusList<>();
4456    for (DataType v : originalList) {
4457      if (!v.isEmpty()) {
4458        list.add(new DataValueWithStatus(v));
4459      }
4460    }
4461    if (compareList != null && mode != GEN_MODE_DIFF) {
4462      for (DataType v : compareList) {
4463        list.merge(new DataValueWithStatus(v));
4464      }      
4465    }
4466    if (list.size() == 0) {
4467      return null;
4468    }
4469    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
4470    boolean first = true;
4471    for (DataValueWithStatus t : list) {
4472      if (first) first = false; else x.tx(separator);
4473      t.render(x);
4474    }
4475    return x;
4476  }
4477  
4478
4479  
4480  private String summarise(CodeableConcept cc) throws FHIRException {
4481    if (cc.getCoding().size() == 1 && cc.getText() == null) {
4482      return summarise(cc.getCoding().get(0));
4483    } else if (cc.hasText()) {
4484      return "\"" + cc.getText() + "\"";
4485    } else if (cc.getCoding().size() > 0) {
4486      CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
4487      for (Coding c : cc.getCoding()) {
4488        b.append(summarise(c));
4489      }
4490      return b.toString();
4491    } else {
4492      throw new FHIRException("Error describing concept - not done yet (no codings, no text)");
4493    }
4494  }
4495
4496  private String summarise(Coding coding) throws FHIRException {
4497    if ("http://snomed.info/sct".equals(coding.getSystem()))
4498      return "" + translate("sd.summary", "SNOMED CT code") + " " + coding.getCode() + (!coding.hasDisplay() ? "" : "(\"" + gt(coding.getDisplayElement()) + "\")");
4499    if ("http://loinc.org".equals(coding.getSystem()))
4500      return "" + translate("sd.summary", "LOINC code") + " " + coding.getCode() + (!coding.hasDisplay() ? "" : "(\"" + gt(coding.getDisplayElement()) + "\")");
4501    if ("http://unitsofmeasure.org/".equals(coding.getSystem()))
4502      return " (" + translate("sd.summary", "UCUM") + ": " + coding.getCode() + ")";
4503    CodeSystem cs = context.getContext().fetchCodeSystem(coding.getSystem());
4504    if (cs == null)
4505      return "<span title=\"" + coding.getSystem() + "\">" + coding.getCode() + "</a>" + (!coding.hasDisplay() ? "" : "(\"" + gt(coding.getDisplayElement()) + "\")");
4506    else
4507      return "<a title=\"" + cs.present() + "\" href=\"" + Utilities.escapeXml(cs.getWebPath()) + "#" + cs.getId() + "-" + coding.getCode() + "\">" + coding.getCode() + "</a>" + (!coding.hasDisplay() ? "" : "(\"" + gt(coding.getDisplayElement()) + "\")");
4508  }
4509
4510}