001package org.hl7.fhir.r4.conformance;
002
003/*
004  Copyright (c) 2011+, HL7, Inc.
005  All rights reserved.
006  
007  Redistribution and use in source and binary forms, with or without modification, 
008  are permitted provided that the following conditions are met:
009    
010   * Redistributions of source code must retain the above copyright notice, this 
011     list of conditions and the following disclaimer.
012   * Redistributions in binary form must reproduce the above copyright notice, 
013     this list of conditions and the following disclaimer in the documentation 
014     and/or other materials provided with the distribution.
015   * Neither the name of HL7 nor the names of its contributors may be used to 
016     endorse or promote products derived from this software without specific 
017     prior written permission.
018  
019  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
020  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
021  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
022  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
023  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
024  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
025  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
027  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
028  POSSIBILITY OF SUCH DAMAGE.
029  
030 */
031
032import java.io.IOException;
033import java.io.OutputStream;
034import java.util.ArrayList;
035import java.util.Collections;
036import java.util.Comparator;
037import java.util.HashMap;
038import java.util.HashSet;
039import java.util.Iterator;
040import java.util.List;
041import java.util.Map;
042import java.util.Set;
043
044import org.apache.commons.lang3.StringUtils;
045import org.hl7.fhir.exceptions.DefinitionException;
046import org.hl7.fhir.exceptions.FHIRException;
047import org.hl7.fhir.exceptions.FHIRFormatError;
048import org.hl7.fhir.r4.conformance.ProfileUtilities.ProfileKnowledgeProvider.BindingResolution;
049import org.hl7.fhir.r4.context.IWorkerContext;
050import org.hl7.fhir.r4.context.IWorkerContext.ValidationResult;
051import org.hl7.fhir.r4.elementmodel.ObjectConverter;
052import org.hl7.fhir.r4.elementmodel.Property;
053import org.hl7.fhir.r4.formats.IParser;
054import org.hl7.fhir.r4.model.Base;
055import org.hl7.fhir.r4.model.BooleanType;
056import org.hl7.fhir.r4.model.CanonicalType;
057import org.hl7.fhir.r4.model.CodeType;
058import org.hl7.fhir.r4.model.CodeableConcept;
059import org.hl7.fhir.r4.model.Coding;
060import org.hl7.fhir.r4.model.Element;
061import org.hl7.fhir.r4.model.ElementDefinition;
062import org.hl7.fhir.r4.model.ElementDefinition.AggregationMode;
063import org.hl7.fhir.r4.model.ElementDefinition.DiscriminatorType;
064import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBaseComponent;
065import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent;
066import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionConstraintComponent;
067import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionExampleComponent;
068import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionMappingComponent;
069import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingComponent;
070import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent;
071import org.hl7.fhir.r4.model.ElementDefinition.PropertyRepresentation;
072import org.hl7.fhir.r4.model.ElementDefinition.SlicingRules;
073import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent;
074import org.hl7.fhir.r4.model.Enumeration;
075import org.hl7.fhir.r4.model.Enumerations.BindingStrength;
076import org.hl7.fhir.r4.model.Extension;
077import org.hl7.fhir.r4.model.IntegerType;
078import org.hl7.fhir.r4.model.PrimitiveType;
079import org.hl7.fhir.r4.model.Quantity;
080import org.hl7.fhir.r4.model.Resource;
081import org.hl7.fhir.r4.model.StringType;
082import org.hl7.fhir.r4.model.StructureDefinition;
083import org.hl7.fhir.r4.model.StructureDefinition.ExtensionContextType;
084import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionContextComponent;
085import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionDifferentialComponent;
086import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind;
087import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionMappingComponent;
088import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionSnapshotComponent;
089import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule;
090import org.hl7.fhir.r4.model.Type;
091import org.hl7.fhir.r4.model.UriType;
092import org.hl7.fhir.r4.model.ValueSet;
093import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent;
094import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent;
095import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
096import org.hl7.fhir.r4.utils.NarrativeGenerator;
097import org.hl7.fhir.r4.utils.ToolingExtensions;
098import org.hl7.fhir.r4.utils.TranslatingUtilities;
099import org.hl7.fhir.r4.utils.formats.CSVWriter;
100import org.hl7.fhir.r4.utils.formats.XLSXWriter;
101import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
102import org.hl7.fhir.utilities.FhirPublication;
103import org.hl7.fhir.utilities.TerminologyServiceOptions;
104import org.hl7.fhir.utilities.Utilities;
105import org.hl7.fhir.utilities.VersionUtilities;
106import org.hl7.fhir.utilities.i18n.RenderingI18nContext;
107import org.hl7.fhir.utilities.validation.ValidationMessage;
108import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
109import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
110import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
111import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
112import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
113import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableGenerationMode;
114import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
115import org.hl7.fhir.utilities.xhtml.XhtmlNode;
116import org.hl7.fhir.utilities.xml.SchematronWriter;
117import org.hl7.fhir.utilities.xml.SchematronWriter.Rule;
118import org.hl7.fhir.utilities.xml.SchematronWriter.SchematronType;
119import org.hl7.fhir.utilities.xml.SchematronWriter.Section;
120
121/**
122 * This class provides a set of utility operations for working with Profiles.
123 * Key functionality: * getChildMap --? * getChildList * generateSnapshot: Given
124 * a base (snapshot) profile structure, and a differential profile, generate a
125 * new snapshot profile * closeDifferential: fill out a differential by
126 * excluding anything not mentioned * generateExtensionsTable: generate the HTML
127 * for a hierarchical table presentation of the extensions * generateTable:
128 * generate the HTML for a hierarchical table presentation of a structure *
129 * generateSpanningTable: generate the HTML for a table presentation of a
130 * network of structures, starting at a nominated point * summarize: describe
131 * the contents of a profile
132 * 
133 * note to maintainers: Do not make modifications to the snapshot generation
134 * without first changing the snapshot generation test cases to demonstrate the
135 * grounds for your change
136 * 
137 * @author Grahame
138 *
139 */
140public class ProfileUtilities extends TranslatingUtilities {
141
142  public class ElementRedirection {
143
144    private String path;
145    private ElementDefinition element;
146
147    public ElementRedirection(ElementDefinition element, String path) {
148      this.path = path;
149      this.element = element;
150    }
151
152    public ElementDefinition getElement() {
153      return element;
154    }
155
156    @Override
157    public String toString() {
158      return element.toString() + " : " + path;
159    }
160
161    public String getPath() {
162      return path;
163    }
164
165  }
166
167  public class TypeSlice {
168    private ElementDefinition defn;
169    private String type;
170
171    public TypeSlice(ElementDefinition defn, String type) {
172      super();
173      this.defn = defn;
174      this.type = type;
175    }
176
177    public ElementDefinition getDefn() {
178      return defn;
179    }
180
181    public String getType() {
182      return type;
183    }
184
185  }
186
187  private static final int MAX_RECURSION_LIMIT = 10;
188
189  public class ExtensionContext {
190
191    private ElementDefinition element;
192    private StructureDefinition defn;
193
194    public ExtensionContext(StructureDefinition ext, ElementDefinition ed) {
195      this.defn = ext;
196      this.element = ed;
197    }
198
199    public ElementDefinition getElement() {
200      return element;
201    }
202
203    public StructureDefinition getDefn() {
204      return defn;
205    }
206
207    public String getUrl() {
208      if (element == defn.getSnapshot().getElement().get(0))
209        return defn.getUrl();
210      else
211        return element.getSliceName();
212    }
213
214    public ElementDefinition getExtensionValueDefinition() {
215      int i = defn.getSnapshot().getElement().indexOf(element) + 1;
216      while (i < defn.getSnapshot().getElement().size()) {
217        ElementDefinition ed = defn.getSnapshot().getElement().get(i);
218        if (ed.getPath().equals(element.getPath()))
219          return null;
220        if (ed.getPath().startsWith(element.getPath() + ".value"))
221          return ed;
222        i++;
223      }
224      return null;
225    }
226  }
227
228  private static final String ROW_COLOR_ERROR = "#ffcccc";
229  private static final String ROW_COLOR_FATAL = "#ff9999";
230  private static final String ROW_COLOR_WARNING = "#ffebcc";
231  private static final String ROW_COLOR_HINT = "#ebf5ff";
232  private static final String ROW_COLOR_NOT_MUST_SUPPORT = "#d6eaf8";
233  public static final int STATUS_OK = 0;
234  public static final int STATUS_HINT = 1;
235  public static final int STATUS_WARNING = 2;
236  public static final int STATUS_ERROR = 3;
237  public static final int STATUS_FATAL = 4;
238
239  private static final String DERIVATION_EQUALS = "derivation.equals";
240  public static final String DERIVATION_POINTER = "derived.pointer";
241  public static final String IS_DERIVED = "derived.fact";
242  public static final String UD_ERROR_STATUS = "error-status";
243  private static final String GENERATED_IN_SNAPSHOT = "profileutilities.snapshot.processed";
244  private final boolean ADD_REFERENCE_TO_TABLE = true;
245
246  private boolean useTableForFixedValues = true;
247  private boolean debug;
248
249  // note that ProfileUtilities are used re-entrantly internally, so nothing with
250  // process state can be here
251  private final IWorkerContext context;
252  private List<ValidationMessage> messages;
253  private List<String> snapshotStack = new ArrayList<String>();
254  private ProfileKnowledgeProvider pkp;
255  private boolean igmode;
256  private boolean exception;
257  private TerminologyServiceOptions terminologyServiceOptions = new TerminologyServiceOptions(FhirPublication.R4);
258  private boolean newSlicingProcessing;
259
260  public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp) {
261    super();
262    this.context = context;
263    this.messages = messages;
264    this.pkp = pkp;
265  }
266
267  private class UnusedTracker {
268    private boolean used;
269  }
270
271  public boolean isIgmode() {
272    return igmode;
273  }
274
275  public void setIgmode(boolean igmode) {
276    this.igmode = igmode;
277  }
278
279  public interface ProfileKnowledgeProvider {
280    public class BindingResolution {
281      public String display;
282      public String url;
283    }
284
285    public boolean isDatatype(String typeSimple);
286
287    public boolean isResource(String typeSimple);
288
289    public boolean hasLinkFor(String typeSimple);
290
291    public String getLinkFor(String corePath, String typeSimple);
292
293    public BindingResolution resolveBinding(StructureDefinition def, ElementDefinitionBindingComponent binding,
294        String path) throws FHIRException;
295
296    public BindingResolution resolveBinding(StructureDefinition def, String url, String path) throws FHIRException;
297
298    public String getLinkForProfile(StructureDefinition profile, String url);
299
300    public boolean prependLinks();
301
302    public String getLinkForUrl(String corePath, String s);
303  }
304
305  public static List<ElementDefinition> getChildMap(StructureDefinition profile, ElementDefinition element)
306      throws DefinitionException {
307    if (element.getContentReference() != null) {
308      for (ElementDefinition e : profile.getSnapshot().getElement()) {
309        if (element.getContentReference().equals("#" + e.getId()))
310          return getChildMap(profile, e);
311      }
312      throw new DefinitionException(
313          "Unable to resolve name reference " + element.getContentReference() + " at path " + element.getPath());
314
315    } else {
316      List<ElementDefinition> res = new ArrayList<ElementDefinition>();
317      List<ElementDefinition> elements = profile.getSnapshot().getElement();
318      String path = element.getPath();
319      for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) {
320        ElementDefinition e = elements.get(index);
321        if (e.getPath().startsWith(path + ".")) {
322          // We only want direct children, not all descendants
323          if (!e.getPath().substring(path.length() + 1).contains("."))
324            res.add(e);
325        } else
326          break;
327      }
328      return res;
329    }
330  }
331
332  public static List<ElementDefinition> getSliceList(StructureDefinition profile, ElementDefinition element)
333      throws DefinitionException {
334    if (!element.hasSlicing())
335      throw new Error("getSliceList should only be called when the element has slicing");
336
337    List<ElementDefinition> res = new ArrayList<ElementDefinition>();
338    List<ElementDefinition> elements = profile.getSnapshot().getElement();
339    String path = element.getPath();
340    for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) {
341      ElementDefinition e = elements.get(index);
342      if (e.getPath().startsWith(path + ".") || e.getPath().equals(path)) {
343        // We want elements with the same path (until we hit an element that doesn't
344        // start with the same path)
345        if (e.getPath().equals(element.getPath()))
346          res.add(e);
347      } else
348        break;
349    }
350    return res;
351  }
352
353  /**
354   * Given a Structure, navigate to the element given by the path and return the
355   * direct children of that element
356   *
357   * @param structure The structure to navigate into
358   * @param path      The path of the element within the structure to get the
359   *                  children for
360   * @return A List containing the element children (all of them are Elements)
361   */
362  public static List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id) {
363    return getChildList(profile, path, id, false);
364  }
365
366  public static List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id,
367      boolean diff) {
368    List<ElementDefinition> res = new ArrayList<ElementDefinition>();
369
370    boolean capturing = id == null;
371    if (id == null && !path.contains("."))
372      capturing = true;
373
374    List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement();
375    for (ElementDefinition e : list) {
376      if (e == null)
377        throw new Error("element = null: " + profile.getUrl());
378      if (e.getId() == null)
379        throw new Error("element id = null: " + e.toString() + " on " + profile.getUrl());
380
381      if (!capturing && id != null && e.getId().equals(id)) {
382        capturing = true;
383      }
384
385      // If our element is a slice, stop capturing children as soon as we see the next
386      // slice
387      if (capturing && e.hasId() && id != null && !e.getId().equals(id) && e.getPath().equals(path))
388        break;
389
390      if (capturing) {
391        String p = e.getPath();
392
393        if (!Utilities.noString(e.getContentReference()) && path.startsWith(p)) {
394          if (path.length() > p.length())
395            return getChildList(profile, e.getContentReference() + "." + path.substring(p.length() + 1), null, diff);
396          else
397            return getChildList(profile, e.getContentReference(), null, diff);
398
399        } else if (p.startsWith(path + ".") && !p.equals(path)) {
400          String tail = p.substring(path.length() + 1);
401          if (!tail.contains(".")) {
402            res.add(e);
403          }
404        }
405      }
406    }
407
408    return res;
409  }
410
411  public static List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element,
412      boolean diff) {
413    return getChildList(structure, element.getPath(), element.getId(), diff);
414  }
415
416  public static List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element) {
417    return getChildList(structure, element.getPath(), element.getId(), false);
418  }
419
420  public void updateMaps(StructureDefinition base, StructureDefinition derived) throws DefinitionException {
421    if (base == null)
422      throw new DefinitionException("no base profile provided");
423    if (derived == null)
424      throw new DefinitionException("no derived structure provided");
425
426    for (StructureDefinitionMappingComponent baseMap : base.getMapping()) {
427      boolean found = false;
428      for (StructureDefinitionMappingComponent derivedMap : derived.getMapping()) {
429        if (derivedMap.getUri() != null && derivedMap.getUri().equals(baseMap.getUri())) {
430          found = true;
431          break;
432        }
433      }
434      if (!found)
435        derived.getMapping().add(baseMap);
436    }
437  }
438
439  /**
440   * Given a base (snapshot) profile structure, and a differential profile,
441   * generate a new snapshot profile
442   *
443   * @param base             - the base structure on which the differential will
444   *                         be applied
445   * @param differential     - the differential to apply to the base
446   * @param url              - where the base has relative urls for profile
447   *                         references, these need to be converted to absolutes
448   *                         by prepending this URL (e.g. the canonical URL)
449   * @param webUrl           - where the base has relative urls in markdown, these
450   *                         need to be converted to absolutes by prepending this
451   *                         URL (this is not the same as the canonical URL)
452   * @param trimDifferential - if this is true, then the snap short generator will
453   *                         remove any material in the element definitions that
454   *                         is not different to the base
455   * @return
456   * @throws FHIRException
457   * @throws DefinitionException
458   * @throws Exception
459   */
460  public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String webUrl,
461      String profileName) throws DefinitionException, FHIRException {
462    if (base == null)
463      throw new DefinitionException("no base profile provided");
464    if (derived == null)
465      throw new DefinitionException("no derived structure provided");
466
467    if (snapshotStack.contains(derived.getUrl()))
468      throw new DefinitionException(
469          "Circular snapshot references detected; cannot generate snapshot (stack = " + snapshotStack.toString() + ")");
470    snapshotStack.add(derived.getUrl());
471
472    if (!Utilities.noString(webUrl) && !webUrl.endsWith("/"))
473      webUrl = webUrl + '/';
474
475    derived.setSnapshot(new StructureDefinitionSnapshotComponent());
476
477    try {
478      // so we have two lists - the base list, and the differential list
479      // the differential list is only allowed to include things that are in the base
480      // list, but
481      // is allowed to include them multiple times - thereby slicing them
482
483      // our approach is to walk through the base list, and see whether the
484      // differential
485      // says anything about them.
486      int baseCursor = 0;
487      int diffCursor = 0; // we need a diff cursor because we can only look ahead, in the bound scoped by
488                          // longer paths
489
490      if (derived.hasDifferential() && !derived.getDifferential().getElementFirstRep().getPath().contains(".")
491          && !derived.getDifferential().getElementFirstRep().getType().isEmpty())
492        throw new Error("type on first differential element!");
493
494      for (ElementDefinition e : derived.getDifferential().getElement())
495        e.clearUserData(GENERATED_IN_SNAPSHOT);
496
497      // we actually delegate the work to a subroutine so we can re-enter it with a
498      // different cursors
499      StructureDefinitionDifferentialComponent diff = cloneDiff(derived.getDifferential()); // we make a copy here
500                                                                                            // because we're sometimes
501                                                                                            // going to hack the
502                                                                                            // differential while
503                                                                                            // processing it. Have to
504                                                                                            // migrate user data back
505                                                                                            // afterwards
506
507      processPaths("", derived.getSnapshot(), base.getSnapshot(), diff, baseCursor, diffCursor,
508          base.getSnapshot().getElement().size() - 1,
509          derived.getDifferential().hasElement() ? derived.getDifferential().getElement().size() - 1 : -1, url, webUrl,
510          derived.present(), null, null, false, base.getUrl(), null, false, new ArrayList<ElementRedirection>(), base);
511      if (!derived.getSnapshot().getElementFirstRep().getType().isEmpty())
512        throw new Error("type on first snapshot element for " + derived.getSnapshot().getElementFirstRep().getPath()
513            + " in " + derived.getUrl() + " from " + base.getUrl());
514      updateMaps(base, derived);
515
516      if (debug) {
517        System.out.println("Differential: ");
518        for (ElementDefinition ed : derived.getDifferential().getElement())
519          System.out.println("  " + ed.getPath() + " : " + typeSummaryWithProfile(ed) + "[" + ed.getMin() + ".."
520              + ed.getMax() + "]" + sliceSummary(ed) + "  id = " + ed.getId() + " " + constraintSummary(ed));
521        System.out.println("Snapshot: ");
522        for (ElementDefinition ed : derived.getSnapshot().getElement())
523          System.out.println("  " + ed.getPath() + " : " + typeSummaryWithProfile(ed) + "[" + ed.getMin() + ".."
524              + ed.getMax() + "]" + sliceSummary(ed) + "  id = " + ed.getId() + " " + constraintSummary(ed));
525      }
526      setIds(derived, false);
527      // Check that all differential elements have a corresponding snapshot element
528      for (ElementDefinition e : diff.getElement()) {
529        if (!e.hasUserData("diff-source"))
530          throw new Error("Unxpected internal condition - no source on diff element");
531        else {
532          if (e.hasUserData(DERIVATION_EQUALS))
533            ((Base) e.getUserData("diff-source")).setUserData(DERIVATION_EQUALS, e.getUserData(DERIVATION_EQUALS));
534          if (e.hasUserData(DERIVATION_POINTER))
535            ((Base) e.getUserData("diff-source")).setUserData(DERIVATION_POINTER, e.getUserData(DERIVATION_POINTER));
536        }
537        if (!e.hasUserData(GENERATED_IN_SNAPSHOT)) {
538          System.out.println("Error in snapshot generation: Differential for " + derived.getUrl() + " with "
539              + (e.hasId() ? "id: " + e.getId() : "path: " + e.getPath())
540              + " has an element that is not marked with a snapshot match");
541          if (exception)
542            throw new DefinitionException("Snapshot for " + derived.getUrl()
543                + " does not contain an element that matches an existing differential element that has "
544                + (e.hasId() ? "id: " + e.getId() : "path: " + e.getPath()));
545          else
546            messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url,
547                "Snapshot for " + derived.getUrl()
548                    + " does not contain an element that matches an existing differential element that has id: "
549                    + e.getId(),
550                ValidationMessage.IssueSeverity.ERROR));
551        }
552      }
553      if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
554        for (ElementDefinition ed : derived.getSnapshot().getElement()) {
555          if (!ed.hasBase()) {
556            ed.getBase().setPath(ed.getPath()).setMin(ed.getMin()).setMax(ed.getMax());
557          }
558        }
559      }
560    } catch (Exception e) {
561      // if we had an exception generating the snapshot, make sure we don't leave any
562      // half generated snapshot behind
563      derived.setSnapshot(null);
564      throw e;
565    }
566  }
567
568  private StructureDefinitionDifferentialComponent cloneDiff(StructureDefinitionDifferentialComponent source) {
569    StructureDefinitionDifferentialComponent diff = new StructureDefinitionDifferentialComponent();
570    for (ElementDefinition sed : source.getElement()) {
571      ElementDefinition ted = sed.copy();
572      diff.getElement().add(ted);
573      ted.setUserData("diff-source", sed);
574    }
575    return diff;
576  }
577
578  private String constraintSummary(ElementDefinition ed) {
579    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
580    if (ed.hasPattern())
581      b.append("pattern=" + ed.getPattern().fhirType());
582    if (ed.hasFixed())
583      b.append("fixed=" + ed.getFixed().fhirType());
584    if (ed.hasConstraint())
585      b.append("constraints=" + ed.getConstraint().size());
586    return b.toString();
587  }
588
589  private String sliceSummary(ElementDefinition ed) {
590    if (!ed.hasSlicing() && !ed.hasSliceName())
591      return "";
592    if (ed.hasSliceName())
593      return " (slicename = " + ed.getSliceName() + ")";
594
595    StringBuilder b = new StringBuilder();
596    boolean first = true;
597    for (ElementDefinitionSlicingDiscriminatorComponent d : ed.getSlicing().getDiscriminator()) {
598      if (first)
599        first = false;
600      else
601        b.append("|");
602      b.append(d.getPath());
603    }
604    return " (slicing by " + b.toString() + ")";
605  }
606
607  private String typeSummary(ElementDefinition ed) {
608    StringBuilder b = new StringBuilder();
609    boolean first = true;
610    for (TypeRefComponent tr : ed.getType()) {
611      if (first)
612        first = false;
613      else
614        b.append("|");
615      b.append(tr.getWorkingCode());
616    }
617    return b.toString();
618  }
619
620  private String typeSummaryWithProfile(ElementDefinition ed) {
621    StringBuilder b = new StringBuilder();
622    boolean first = true;
623    for (TypeRefComponent tr : ed.getType()) {
624      if (first)
625        first = false;
626      else
627        b.append("|");
628      b.append(tr.getWorkingCode());
629      if (tr.hasProfile()) {
630        b.append("(");
631        b.append(tr.getProfile());
632        b.append(")");
633
634      }
635    }
636    return b.toString();
637  }
638
639  private boolean findMatchingElement(String id, List<ElementDefinition> list) {
640    for (ElementDefinition ed : list) {
641      if (ed.getId().equals(id))
642        return true;
643      if (id.endsWith("[x]")) {
644        if (ed.getId().startsWith(id.substring(0, id.length() - 3))
645            && !ed.getId().substring(id.length() - 3).contains("."))
646          return true;
647      }
648    }
649    return false;
650  }
651
652  /**
653   * @param trimDifferential
654   * @param srcSD
655   * @throws DefinitionException, FHIRException
656   * @throws Exception
657   */
658  private ElementDefinition processPaths(String indent, StructureDefinitionSnapshotComponent result,
659      StructureDefinitionSnapshotComponent base, StructureDefinitionDifferentialComponent differential, int baseCursor,
660      int diffCursor, int baseLimit, int diffLimit, String url, String webUrl, String profileName,
661      String contextPathSrc, String contextPathDst, boolean trimDifferential, String contextName, String resultPathBase,
662      boolean slicingDone, List<ElementRedirection> redirector, StructureDefinition srcSD)
663      throws DefinitionException, FHIRException {
664    if (debug)
665      System.out.println(indent + "PP @ " + resultPathBase + " / " + contextPathSrc + " : base = " + baseCursor + " to "
666          + baseLimit + ", diff = " + diffCursor + " to " + diffLimit + " (slicing = " + slicingDone + ", redirector = "
667          + (redirector == null ? "null" : redirector.toString()) + ")");
668    ElementDefinition res = null;
669    List<TypeSlice> typeList = new ArrayList<>();
670    // just repeat processing entries until we run out of our allowed scope (1st
671    // entry, the allowed scope is all the entries)
672    while (baseCursor <= baseLimit) {
673      // get the current focus of the base, and decide what to do
674      ElementDefinition currentBase = base.getElement().get(baseCursor);
675      String cpath = fixedPathSource(contextPathSrc, currentBase.getPath(), redirector);
676      if (debug)
677        System.out.println(indent + " - " + cpath + ": base = " + baseCursor + " ("
678            + descED(base.getElement(), baseCursor) + ") to " + baseLimit + " (" + descED(base.getElement(), baseLimit)
679            + "), diff = " + diffCursor + " (" + descED(differential.getElement(), diffCursor) + ") to " + diffLimit
680            + " (" + descED(differential.getElement(), diffLimit) + ") " + "(slicingDone = " + slicingDone
681            + ") (diffpath= "
682            + (differential.getElement().size() > diffCursor ? differential.getElement().get(diffCursor).getPath()
683                : "n/a")
684            + ")");
685      List<ElementDefinition> diffMatches = getDiffMatches(differential, cpath, diffCursor, diffLimit, profileName); // get
686                                                                                                                     // a
687                                                                                                                     // list
688                                                                                                                     // of
689                                                                                                                     // matching
690                                                                                                                     // elements
691                                                                                                                     // in
692                                                                                                                     // scope
693
694      // in the simple case, source is not sliced.
695      if (!currentBase.hasSlicing()) {
696        if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item
697          // so we just copy it in
698          ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy());
699          outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
700          updateFromBase(outcome, currentBase);
701          markDerived(outcome);
702          if (resultPathBase == null)
703            resultPathBase = outcome.getPath();
704          else if (!outcome.getPath().startsWith(resultPathBase))
705            throw new DefinitionException("Adding wrong path");
706          result.getElement().add(outcome);
707          if (hasInnerDiffMatches(differential, cpath, diffCursor, diffLimit, base.getElement(), true)) {
708            // well, the profile walks into this, so we need to as well
709            // did we implicitly step into a new type?
710            if (baseHasChildren(base, currentBase)) { // not a new type here
711              processPaths(indent + "  ", result, base, differential, baseCursor + 1, diffCursor, baseLimit, diffLimit,
712                  url, webUrl, profileName, contextPathSrc, contextPathDst, trimDifferential, contextName,
713                  resultPathBase, false, redirector, srcSD);
714              baseCursor = indexOfFirstNonChild(base, currentBase, baseCursor + 1, baseLimit);
715            } else {
716              if (outcome.getType().size() == 0) {
717                throw new DefinitionException(diffMatches.get(0).getPath() + " has no children ("
718                    + differential.getElement().get(diffCursor).getPath() + ") and no types in profile " + profileName);
719              }
720              if (outcome.getType().size() > 1) {
721                for (TypeRefComponent t : outcome.getType()) {
722                  if (!t.getWorkingCode().equals("Reference"))
723                    throw new DefinitionException(diffMatches.get(0).getPath() + " has children ("
724                        + differential.getElement().get(diffCursor).getPath() + ") and multiple types ("
725                        + typeCode(outcome.getType()) + ") in profile " + profileName);
726                }
727              }
728              StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
729              if (dt == null)
730                throw new DefinitionException(
731                    "Unknown type " + outcome.getType().get(0) + " at " + diffMatches.get(0).getPath());
732              contextName = dt.getUrl();
733              int start = diffCursor;
734              while (differential.getElement().size() > diffCursor
735                  && pathStartsWith(differential.getElement().get(diffCursor).getPath(), cpath + "."))
736                diffCursor++;
737              processPaths(indent + "  ", result, dt.getSnapshot(), differential,
738                  1 /* starting again on the data type, but skip the root */, start,
739                  dt.getSnapshot().getElement().size() - 1, diffCursor - 1, url, webUrl, profileName, cpath,
740                  outcome.getPath(), trimDifferential, contextName, resultPathBase, false, redirector, srcSD);
741            }
742          }
743          baseCursor++;
744        } else if (diffMatches.size() == 1
745            && (slicingDone || (!isImplicitSlicing(diffMatches.get(0), cpath) && !(diffMatches.get(0).hasSlicing()
746                || (isExtension(diffMatches.get(0)) && diffMatches.get(0).hasSliceName()))))) {// one matching element
747                                                                                               // in the differential
748          ElementDefinition template = null;
749          if (diffMatches.get(0).hasType() && diffMatches.get(0).getType().size() == 1
750              && diffMatches.get(0).getType().get(0).hasProfile()
751              && !"Reference".equals(diffMatches.get(0).getType().get(0).getWorkingCode())) {
752            CanonicalType p = diffMatches.get(0).getType().get(0).getProfile().get(0);
753            StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getValue());
754            if (sd != null) {
755              if (!sd.hasSnapshot()) {
756                StructureDefinition sdb = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
757                if (sdb == null)
758                  throw new DefinitionException("no base for " + sd.getBaseDefinition());
759                generateSnapshot(sdb, sd, sd.getUrl(),
760                    (sdb.hasUserData("path")) ? Utilities.extractBaseUrl(sdb.getUserString("path")) : webUrl,
761                    sd.getName());
762              }
763              ElementDefinition src;
764              if (p.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT)) {
765                src = null;
766                String eid = p.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT);
767                for (ElementDefinition t : sd.getSnapshot().getElement()) {
768                  if (eid.equals(t.getId()))
769                    src = t;
770                }
771                if (src == null)
772                  throw new DefinitionException("Unable to find element " + eid + " in " + p.getValue());
773              } else
774                src = sd.getSnapshot().getElement().get(0);
775              template = src.copy().setPath(currentBase.getPath());
776              template.setSliceName(null);
777              // temporary work around
778              if (!"Extension".equals(diffMatches.get(0).getType().get(0).getCode())) {
779                template.setMin(currentBase.getMin());
780                template.setMax(currentBase.getMax());
781              }
782            }
783          }
784          if (template == null)
785            template = currentBase.copy();
786          else
787            // some of what's in currentBase overrides template
788            template = overWriteWithCurrent(template, currentBase);
789
790          ElementDefinition outcome = updateURLs(url, webUrl, template);
791          outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
792          if (res == null)
793            res = outcome;
794          updateFromBase(outcome, currentBase);
795          if (diffMatches.get(0).hasSliceName())
796            outcome.setSliceName(diffMatches.get(0).getSliceName());
797          updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url, srcSD);
798          removeStatusExtensions(outcome);
799//          if (outcome.getPath().endsWith("[x]") && outcome.getType().size() == 1 && !outcome.getType().get(0).getCode().equals("*") && !diffMatches.get(0).hasSlicing()) // if the base profile allows multiple types, but the profile only allows one, rename it
800//            outcome.setPath(outcome.getPath().substring(0, outcome.getPath().length()-3)+Utilities.capitalize(outcome.getType().get(0).getCode()));
801          outcome.setSlicing(null);
802          if (resultPathBase == null)
803            resultPathBase = outcome.getPath();
804          else if (!outcome.getPath().startsWith(resultPathBase))
805            throw new DefinitionException("Adding wrong path");
806          result.getElement().add(outcome);
807          baseCursor++;
808          diffCursor = differential.getElement().indexOf(diffMatches.get(0)) + 1;
809          if (differential.getElement().size() > diffCursor && outcome.getPath().contains(".")
810              && (isDataType(outcome.getType()) || outcome.hasContentReference())) { // don't want to do this for the
811                                                                                     // root, since that's base, and
812                                                                                     // we're already processing it
813            if (pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath() + ".")
814                && !baseWalksInto(base.getElement(), baseCursor)) {
815              if (outcome.getType().size() > 1) {
816                if (outcome.getPath().endsWith("[x]") && !diffMatches.get(0).getPath().endsWith("[x]")) {
817                  String en = tail(outcome.getPath());
818                  String tn = tail(diffMatches.get(0).getPath());
819                  String t = tn.substring(en.length() - 3);
820                  if (isPrimitive(Utilities.uncapitalize(t)))
821                    t = Utilities.uncapitalize(t);
822                  List<TypeRefComponent> ntr = getByTypeName(outcome.getType(), t); // keep any additional information
823                  if (ntr.isEmpty())
824                    ntr.add(new TypeRefComponent().setCode(t));
825                  outcome.getType().clear();
826                  outcome.getType().addAll(ntr);
827                }
828                if (outcome.getType().size() > 1)
829                  for (TypeRefComponent t : outcome.getType()) {
830                    if (!t.getCode().equals("Reference")) {
831                      boolean nonExtension = false;
832                      for (ElementDefinition ed : diffMatches)
833                        if (ed != diffMatches.get(0) && !ed.getPath().endsWith(".extension"))
834                          nonExtension = true;
835                      if (nonExtension)
836                        throw new DefinitionException(diffMatches.get(0).getPath() + " has children ("
837                            + differential.getElement().get(diffCursor).getPath() + ") and multiple types ("
838                            + typeCode(outcome.getType()) + ") in profile " + profileName);
839                    }
840                  }
841              }
842              int start = diffCursor;
843              while (differential.getElement().size() > diffCursor && pathStartsWith(
844                  differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath() + "."))
845                diffCursor++;
846              if (outcome.hasContentReference()) {
847                ElementDefinition tgt = getElementById(base.getElement(), outcome.getContentReference());
848                if (tgt == null)
849                  throw new DefinitionException("Unable to resolve reference to " + outcome.getContentReference());
850                replaceFromContentReference(outcome, tgt);
851                int nbc = base.getElement().indexOf(tgt) + 1;
852                int nbl = nbc;
853                while (nbl < base.getElement().size()
854                    && base.getElement().get(nbl).getPath().startsWith(tgt.getPath() + "."))
855                  nbl++;
856                processPaths(indent + "  ", result, base, differential, nbc, start - 1, nbl - 1, diffCursor - 1, url,
857                    webUrl, profileName, tgt.getPath(), diffMatches.get(0).getPath(), trimDifferential, contextName,
858                    resultPathBase, false, redirectorStack(redirector, outcome, cpath), srcSD);
859              } else {
860                StructureDefinition dt = outcome.getType().size() == 1 ? getProfileForDataType(outcome.getType().get(0))
861                    : getProfileForDataType("Element");
862                if (dt == null)
863                  throw new DefinitionException(diffMatches.get(0).getPath() + " has children ("
864                      + differential.getElement().get(diffCursor).getPath() + ") for type "
865                      + typeCode(outcome.getType()) + " in profile " + profileName + ", but can't find type");
866                contextName = dt.getUrl();
867                processPaths(indent + "  ", result, dt.getSnapshot(), differential,
868                    1 /* starting again on the data type, but skip the root */, start,
869                    dt.getSnapshot().getElement().size() - 1, diffCursor - 1, url, webUrl,
870                    profileName + pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(),
871                    trimDifferential, contextName, resultPathBase, false, new ArrayList<ElementRedirection>(), srcSD);
872              }
873            }
874          }
875        } else if (diffsConstrainTypes(diffMatches, cpath, typeList)) {
876          int start = 0;
877          int nbl = findEndOfElement(base, baseCursor);
878          int ndc = differential.getElement().indexOf(diffMatches.get(0));
879          ElementDefinition elementToRemove = null;
880          // we come here whether they are sliced in the diff, or whether the short cut is
881          // used.
882          if (typeList.get(0).type != null) {
883            // this is the short cut method, we've just dived in and specified a type slice.
884            // in R3 (and unpatched R4, as a workaround right now...
885            if (!VersionUtilities.isR4Plus(context.getVersion()) || !newSlicingProcessing) { // newSlicingProcessing is
886                                                                                             // a work around for
887                                                                                             // editorial loop
888                                                                                             // dependency
889              // we insert a cloned element with the right types at the start of the
890              // diffMatches
891              ElementDefinition ed = new ElementDefinition();
892              ed.setPath(determineTypeSlicePath(diffMatches.get(0).getPath(), cpath));
893              for (TypeSlice ts : typeList)
894                ed.addType().setCode(ts.type);
895              ed.setSlicing(new ElementDefinitionSlicingComponent());
896              ed.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
897              ed.getSlicing().setRules(SlicingRules.CLOSED);
898              ed.getSlicing().setOrdered(false);
899              diffMatches.add(0, ed);
900              differential.getElement().add(ndc, ed);
901              elementToRemove = ed;
902            } else {
903              // as of R4, this changed; if there's no slice, there's no constraint on the
904              // slice types, only one the type.
905              // so the element we insert specifies no types (= all types) allowed in the
906              // base, not just the listed type.
907              // see also discussion here:
908              // https://chat.fhir.org/#narrow/stream/179177-conformance/topic/Slicing.20a.20non-repeating.20element
909              ElementDefinition ed = new ElementDefinition();
910              ed.setPath(determineTypeSlicePath(diffMatches.get(0).getPath(), cpath));
911              ed.setSlicing(new ElementDefinitionSlicingComponent());
912              ed.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
913              ed.getSlicing().setRules(SlicingRules.CLOSED);
914              ed.getSlicing().setOrdered(false);
915              diffMatches.add(0, ed);
916              differential.getElement().add(ndc, ed);
917              elementToRemove = ed;
918            }
919          }
920          int ndl = findEndOfElement(differential, ndc);
921          // the first element is setting up the slicing
922          if (diffMatches.get(0).getSlicing().hasRules())
923            if (diffMatches.get(0).getSlicing().getRules() != SlicingRules.CLOSED)
924              throw new FHIRException(
925                  "Error at path " + contextPathSrc + ": Type slicing with slicing.rules != closed");
926          if (diffMatches.get(0).getSlicing().hasOrdered())
927            if (diffMatches.get(0).getSlicing().getOrdered())
928              throw new FHIRException("Error at path " + contextPathSrc + ": Type slicing with slicing.ordered = true");
929          if (diffMatches.get(0).getSlicing().hasDiscriminator()) {
930            if (diffMatches.get(0).getSlicing().getDiscriminator().size() != 1)
931              throw new FHIRException(
932                  "Error at path " + contextPathSrc + ": Type slicing with slicing.discriminator.count() > 1");
933            if (!"$this".equals(diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getPath()))
934              throw new FHIRException(
935                  "Error at path " + contextPathSrc + ": Type slicing with slicing.discriminator.path != '$this'");
936            if (diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getType() != DiscriminatorType.TYPE)
937              throw new FHIRException(
938                  "Error at path " + contextPathSrc + ": Type slicing with slicing.discriminator.type != 'type'");
939          }
940          // check the slice names too while we're at it...
941          for (TypeSlice ts : typeList)
942            if (ts.type != null) {
943              String tn = rootName(cpath) + Utilities.capitalize(ts.type);
944              if (!ts.defn.hasSliceName())
945                ts.defn.setSliceName(tn);
946              else if (!ts.defn.getSliceName().equals(tn))
947                throw new FHIRException(
948                    "Error at path " + (!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath)
949                        + ": Slice name must be '" + tn + "' but is '" + ts.defn.getSliceName() + "'");
950              if (!ts.defn.hasType())
951                ts.defn.addType().setCode(ts.type);
952              else if (ts.defn.getType().size() > 1)
953                throw new FHIRException(
954                    "Error at path " + (!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath)
955                        + ": Slice for type '" + tn + "' has more than one type '" + ts.defn.typeSummary() + "'");
956              else if (!ts.defn.getType().get(0).getCode().equals(ts.type))
957                throw new FHIRException(
958                    "Error at path " + (!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath)
959                        + ": Slice for type '" + tn + "' has wrong type '" + ts.defn.typeSummary() + "'");
960            }
961
962          // ok passed the checks.
963          // copy the root diff, and then process any children it has
964          ElementDefinition e = processPaths(indent + "  ", result, base, differential, baseCursor, ndc, nbl, ndl, url,
965              webUrl, profileName + pathTail(diffMatches, 0), contextPathSrc, contextPathDst, trimDifferential,
966              contextName, resultPathBase, true, redirector, srcSD);
967          if (e == null)
968            throw new FHIRException("Did not find type root: " + diffMatches.get(0).getPath());
969          // now set up slicing on the e (cause it was wiped by what we called.
970          e.setSlicing(new ElementDefinitionSlicingComponent());
971          e.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
972          e.getSlicing().setRules(SlicingRules.CLOSED);
973          e.getSlicing().setOrdered(false);
974          start++;
975          // now process the siblings, which should each be type constrained - and may
976          // also have their own children
977          // now we process the base scope repeatedly for each instance of the item in the
978          // differential list
979          for (int i = start; i < diffMatches.size(); i++) {
980            // our processing scope for the differential is the item in the list, and all
981            // the items before the next one in the list
982            ndc = differential.getElement().indexOf(diffMatches.get(i));
983            ndl = findEndOfElement(differential, ndc);
984            processPaths(indent + "  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl,
985                profileName + pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName,
986                resultPathBase, true, redirector, srcSD);
987          }
988          if (elementToRemove != null) {
989            differential.getElement().remove(elementToRemove);
990            ndl--;
991          }
992
993          // ok, done with that - next in the base list
994          baseCursor = nbl + 1;
995          diffCursor = ndl + 1;
996
997        } else {
998          // ok, the differential slices the item. Let's check our pre-conditions to
999          // ensure that this is correct
1000          if (!unbounded(currentBase) && !isSlicedToOneOnly(diffMatches.get(0)))
1001            // you can only slice an element that doesn't repeat if the sum total of your
1002            // slices is limited to 1
1003            // (but you might do that in order to split up constraints by type)
1004            throw new DefinitionException("Attempt to a slice an element that does not repeat: " + currentBase.getPath()
1005                + "/" + currentBase.getPath() + " from " + contextName + " in " + url);
1006          if (!diffMatches.get(0).hasSlicing() && !isExtension(currentBase)) // well, the diff has set up a slice, but
1007                                                                             // hasn't defined it. this is an error
1008            throw new DefinitionException("Differential does not have a slice: " + currentBase.getPath() + "/ (b:"
1009                + baseCursor + " of " + baseLimit + " / " + diffCursor + "/ " + diffLimit + ") in profile " + url);
1010
1011          // well, if it passed those preconditions then we slice the dest.
1012          int start = 0;
1013          int nbl = findEndOfElement(base, baseCursor);
1014//          if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && differential.getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0))+1) {
1015          if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && (nbl > baseCursor || differential
1016              .getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0)) + 1)) { // there's
1017                                                                                                                        // a
1018                                                                                                                        // default
1019                                                                                                                        // set
1020                                                                                                                        // before
1021                                                                                                                        // the
1022                                                                                                                        // slices
1023            int ndc = differential.getElement().indexOf(diffMatches.get(0));
1024            int ndl = findEndOfElement(differential, ndc);
1025            ElementDefinition e = processPaths(indent + "  ", result, base, differential, baseCursor, ndc, nbl, ndl,
1026                url, webUrl, profileName + pathTail(diffMatches, 0), contextPathSrc, contextPathDst, trimDifferential,
1027                contextName, resultPathBase, true, redirector, srcSD);
1028            if (e == null)
1029              throw new FHIRException("Did not find single slice: " + diffMatches.get(0).getPath());
1030            e.setSlicing(diffMatches.get(0).getSlicing());
1031            start++;
1032          } else {
1033            // we're just going to accept the differential slicing at face value
1034            ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy());
1035            outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
1036            updateFromBase(outcome, currentBase);
1037
1038            if (!diffMatches.get(0).hasSlicing())
1039              outcome.setSlicing(makeExtensionSlicing());
1040            else
1041              outcome.setSlicing(diffMatches.get(0).getSlicing().copy());
1042            if (!outcome.getPath().startsWith(resultPathBase))
1043              throw new DefinitionException("Adding wrong path");
1044            result.getElement().add(outcome);
1045
1046            // differential - if the first one in the list has a name, we'll process it.
1047            // Else we'll treat it as the base definition of the slice.
1048            if (!diffMatches.get(0).hasSliceName()) {
1049              updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url, srcSD);
1050              removeStatusExtensions(outcome);
1051              if (!outcome.hasContentReference() && !outcome.hasType()) {
1052                throw new DefinitionException("not done yet");
1053              }
1054              start++;
1055              // result.getElement().remove(result.getElement().size()-1);
1056            } else
1057              checkExtensionDoco(outcome);
1058          }
1059          // now, for each entry in the diff matches, we're going to process the base item
1060          // our processing scope for base is all the children of the current path
1061          int ndc = diffCursor;
1062          int ndl = diffCursor;
1063          for (int i = start; i < diffMatches.size(); i++) {
1064            // our processing scope for the differential is the item in the list, and all
1065            // the items before the next one in the list
1066            ndc = differential.getElement().indexOf(diffMatches.get(i));
1067            ndl = findEndOfElement(differential, ndc);
1068            /*
1069             * if (skipSlicingElement && i == 0) { ndc = ndc + 1; if (ndc > ndl) continue; }
1070             */
1071            // now we process the base scope repeatedly for each instance of the item in the
1072            // differential list
1073            processPaths(indent + "  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl,
1074                profileName + pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName,
1075                resultPathBase, true, redirector, srcSD);
1076          }
1077          // ok, done with that - next in the base list
1078          baseCursor = nbl + 1;
1079          diffCursor = ndl + 1;
1080        }
1081      } else {
1082        // the item is already sliced in the base profile.
1083        // here's the rules
1084        // 1. irrespective of whether the slicing is ordered or not, the definition
1085        // order must be maintained
1086        // 2. slice element names have to match.
1087        // 3. new slices must be introduced at the end
1088        // corallory: you can't re-slice existing slices. is that ok?
1089
1090        // we're going to need this:
1091        String path = currentBase.getPath();
1092        ElementDefinition original = currentBase;
1093
1094        if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item
1095          // copy across the currentbase, and all of its children and siblings
1096          while (baseCursor < base.getElement().size()
1097              && base.getElement().get(baseCursor).getPath().startsWith(path)) {
1098            ElementDefinition outcome = updateURLs(url, webUrl, base.getElement().get(baseCursor).copy());
1099            outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
1100            if (!outcome.getPath().startsWith(resultPathBase))
1101              throw new DefinitionException(
1102                  "Adding wrong path in profile " + profileName + ": " + outcome.getPath() + " vs " + resultPathBase);
1103            result.getElement().add(outcome); // so we just copy it in
1104            baseCursor++;
1105          }
1106        } else {
1107          // first - check that the slicing is ok
1108          boolean closed = currentBase.getSlicing().getRules() == SlicingRules.CLOSED;
1109          int diffpos = 0;
1110          boolean isExtension = cpath.endsWith(".extension") || cpath.endsWith(".modifierExtension");
1111          if (diffMatches.get(0).hasSlicing()) { // it might be null if the differential doesn't want to say anything
1112                                                 // about slicing
1113//            if (!isExtension)
1114//              diffpos++; // if there's a slice on the first, we'll ignore any content it has
1115            ElementDefinitionSlicingComponent dSlice = diffMatches.get(0).getSlicing();
1116            ElementDefinitionSlicingComponent bSlice = currentBase.getSlicing();
1117            if (dSlice.hasOrderedElement() && bSlice.hasOrderedElement()
1118                && !orderMatches(dSlice.getOrderedElement(), bSlice.getOrderedElement()))
1119              throw new DefinitionException(
1120                  "Slicing rules on differential (" + summarizeSlicing(dSlice) + ") do not match those on base ("
1121                      + summarizeSlicing(bSlice) + ") - order @ " + path + " (" + contextName + ")");
1122            if (!discriminatorMatches(dSlice.getDiscriminator(), bSlice.getDiscriminator()))
1123              throw new DefinitionException(
1124                  "Slicing rules on differential (" + summarizeSlicing(dSlice) + ") do not match those on base ("
1125                      + summarizeSlicing(bSlice) + ") - discriminator @ " + path + " (" + contextName + ")");
1126            if (!ruleMatches(dSlice.getRules(), bSlice.getRules()))
1127              throw new DefinitionException(
1128                  "Slicing rules on differential (" + summarizeSlicing(dSlice) + ") do not match those on base ("
1129                      + summarizeSlicing(bSlice) + ") - rule @ " + path + " (" + contextName + ")");
1130          }
1131          ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy());
1132          outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
1133          updateFromBase(outcome, currentBase);
1134          if (diffMatches.get(0).hasSlicing() || !diffMatches.get(0).hasSliceName()) {
1135            updateFromSlicing(outcome.getSlicing(), diffMatches.get(0).getSlicing());
1136            updateFromDefinition(outcome, diffMatches.get(0), profileName, closed, url, srcSD); // if there's no slice,
1137                                                                                                // we don't want to
1138                                                                                                // update the unsliced
1139                                                                                                // description
1140            removeStatusExtensions(outcome);
1141          } else if (!diffMatches.get(0).hasSliceName())
1142            diffMatches.get(0).setUserData(GENERATED_IN_SNAPSHOT, outcome); // because of updateFromDefinition isn't
1143                                                                            // called
1144
1145          result.getElement().add(outcome);
1146
1147          if (!diffMatches.get(0).hasSliceName()) { // it's not real content, just the slice
1148            diffpos++;
1149          }
1150          if (hasInnerDiffMatches(differential, cpath, diffpos, diffLimit, base.getElement(), false)) {
1151            int nbl = findEndOfElement(base, baseCursor);
1152            int ndc = differential.getElement().indexOf(diffMatches.get(0)) + 1;
1153            int ndl = findEndOfElement(differential, ndc);
1154            processPaths(indent + "  ", result, base, differential, baseCursor + 1, ndc, nbl, ndl, url, webUrl,
1155                profileName + pathTail(diffMatches, 0), contextPathSrc, contextPathDst, trimDifferential, contextName,
1156                resultPathBase, false, null, srcSD);
1157//            throw new Error("Not done yet");
1158//          } else if (currentBase.getType().get(0).getCode().equals("BackboneElement") && diffMatches.size() > 0 && diffMatches.get(0).hasSliceName()) {
1159          } else if (currentBase.getType().get(0).getCode().equals("BackboneElement")) {
1160            // We need to copy children of the backbone element before we start messing
1161            // around with slices
1162            int nbl = findEndOfElement(base, baseCursor);
1163            for (int i = baseCursor + 1; i <= nbl; i++) {
1164              outcome = updateURLs(url, webUrl, base.getElement().get(i).copy());
1165              result.getElement().add(outcome);
1166            }
1167          }
1168
1169          // now, we have two lists, base and diff. we're going to work through base,
1170          // looking for matches in diff.
1171          List<ElementDefinition> baseMatches = getSiblings(base.getElement(), currentBase);
1172          for (ElementDefinition baseItem : baseMatches) {
1173            baseCursor = base.getElement().indexOf(baseItem);
1174            outcome = updateURLs(url, webUrl, baseItem.copy());
1175            updateFromBase(outcome, currentBase);
1176            outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
1177            outcome.setSlicing(null);
1178            if (!outcome.getPath().startsWith(resultPathBase))
1179              throw new DefinitionException("Adding wrong path");
1180            if (diffpos < diffMatches.size()
1181                && diffMatches.get(diffpos).getSliceName().equals(outcome.getSliceName())) {
1182              // if there's a diff, we update the outcome with diff
1183              // no? updateFromDefinition(outcome, diffMatches.get(diffpos), profileName,
1184              // closed, url);
1185              // then process any children
1186              int nbl = findEndOfElement(base, baseCursor);
1187              int ndc = differential.getElement().indexOf(diffMatches.get(diffpos));
1188              int ndl = findEndOfElement(differential, ndc);
1189              // now we process the base scope repeatedly for each instance of the item in the
1190              // differential list
1191              processPaths(indent + "  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl,
1192                  profileName + pathTail(diffMatches, diffpos), contextPathSrc, contextPathDst, closed, contextName,
1193                  resultPathBase, true, redirector, srcSD);
1194              // ok, done with that - now set the cursors for if this is the end
1195              baseCursor = nbl;
1196              diffCursor = ndl + 1;
1197              diffpos++;
1198            } else {
1199              result.getElement().add(outcome);
1200              baseCursor++;
1201              // just copy any children on the base
1202              while (baseCursor < base.getElement().size()
1203                  && base.getElement().get(baseCursor).getPath().startsWith(path)
1204                  && !base.getElement().get(baseCursor).getPath().equals(path)) {
1205                outcome = updateURLs(url, webUrl, base.getElement().get(baseCursor).copy());
1206                outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
1207                if (!outcome.getPath().startsWith(resultPathBase))
1208                  throw new DefinitionException("Adding wrong path");
1209                result.getElement().add(outcome);
1210                baseCursor++;
1211              }
1212              // Lloyd - add this for test T15
1213              baseCursor--;
1214            }
1215          }
1216          // finally, we process any remaining entries in diff, which are new (and which
1217          // are only allowed if the base wasn't closed
1218          if (closed && diffpos < diffMatches.size())
1219            throw new DefinitionException(
1220                "The base snapshot marks a slicing as closed, but the differential tries to extend it in " + profileName
1221                    + " at " + path + " (" + cpath + ")");
1222          if (diffpos == diffMatches.size()) {
1223//Lloyd This was causing problems w/ Telus
1224//            diffCursor++;
1225          } else {
1226            while (diffpos < diffMatches.size()) {
1227              ElementDefinition diffItem = diffMatches.get(diffpos);
1228              for (ElementDefinition baseItem : baseMatches)
1229                if (baseItem.getSliceName().equals(diffItem.getSliceName()))
1230                  throw new DefinitionException("Named items are out of order in the slice");
1231              outcome = updateURLs(url, webUrl, currentBase.copy());
1232              // outcome = updateURLs(url, diffItem.copy());
1233              outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
1234              updateFromBase(outcome, currentBase);
1235              outcome.setSlicing(null);
1236              if (!outcome.getPath().startsWith(resultPathBase))
1237                throw new DefinitionException("Adding wrong path");
1238              result.getElement().add(outcome);
1239              updateFromDefinition(outcome, diffItem, profileName, trimDifferential, url, srcSD);
1240              removeStatusExtensions(outcome);
1241              // --- LM Added this
1242              diffCursor = differential.getElement().indexOf(diffItem) + 1;
1243              if (!outcome.getType().isEmpty()
1244                  && (/* outcome.getType().get(0).getCode().equals("Extension") || */differential.getElement()
1245                      .size() > diffCursor)
1246                  && outcome.getPath().contains(".") && isDataType(outcome.getType())) { // don't want to do this for
1247                                                                                         // the root, since that's base,
1248                                                                                         // and we're already processing
1249                                                                                         // it
1250                if (!baseWalksInto(base.getElement(), baseCursor)) {
1251                  if (differential.getElement().size() > diffCursor && pathStartsWith(
1252                      differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath() + ".")) {
1253                    if (outcome.getType().size() > 1)
1254                      for (TypeRefComponent t : outcome.getType()) {
1255                        if (!t.getCode().equals("Reference"))
1256                          throw new DefinitionException(diffMatches.get(0).getPath() + " has children ("
1257                              + differential.getElement().get(diffCursor).getPath() + ") and multiple types ("
1258                              + typeCode(outcome.getType()) + ") in profile " + profileName);
1259                      }
1260                    TypeRefComponent t = outcome.getType().get(0);
1261                    if (t.getCode().equals("BackboneElement")) {
1262                      int baseStart = base.getElement().indexOf(currentBase) + 1;
1263                      int baseMax = baseStart + 1;
1264                      while (baseMax < base.getElement().size()
1265                          && base.getElement().get(baseMax).getPath().startsWith(currentBase.getPath() + "."))
1266                        baseMax++;
1267                      int start = diffCursor;
1268                      while (differential.getElement().size() > diffCursor && pathStartsWith(
1269                          differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath() + "."))
1270                        diffCursor++;
1271                      processPaths(indent + "  ", result, base, differential, baseStart, start - 1, baseMax - 1,
1272                          diffCursor - 1, url, webUrl, profileName + pathTail(diffMatches, 0),
1273                          base.getElement().get(0).getPath(), base.getElement().get(0).getPath(), trimDifferential,
1274                          contextName, resultPathBase, false, redirector, srcSD);
1275
1276                    } else {
1277                      StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
1278                      // if (t.getCode().equals("Extension") && t.hasProfile() &&
1279                      // !t.getProfile().contains(":")) {
1280                      // lloydfix dt =
1281                      // }
1282                      if (dt == null)
1283                        throw new DefinitionException(diffMatches.get(0).getPath() + " has children ("
1284                            + differential.getElement().get(diffCursor).getPath() + ") for type "
1285                            + typeCode(outcome.getType()) + " in profile " + profileName + ", but can't find type");
1286                      contextName = dt.getUrl();
1287                      int start = diffCursor;
1288                      while (differential.getElement().size() > diffCursor && pathStartsWith(
1289                          differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath() + "."))
1290                        diffCursor++;
1291                      processPaths(indent + "  ", result, dt.getSnapshot(), differential,
1292                          1 /* starting again on the data type, but skip the root */, start - 1,
1293                          dt.getSnapshot().getElement().size() - 1, diffCursor - 1, url, webUrl,
1294                          profileName + pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(),
1295                          trimDifferential, contextName, resultPathBase, false, redirector, srcSD);
1296                    }
1297                  } else if (outcome.getType().get(0).getCode().equals("Extension")) {
1298                    // Force URL to appear if we're dealing with an extension. (This is a kludge -
1299                    // may need to drill down in other cases where we're slicing and the type has a
1300                    // profile declaration that could be setting the fixed value)
1301                    StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
1302                    for (ElementDefinition extEd : dt.getSnapshot().getElement()) {
1303                      // We only want the children that aren't the root
1304                      if (extEd.getPath().contains(".")) {
1305                        ElementDefinition extUrlEd = updateURLs(url, webUrl, extEd.copy());
1306                        extUrlEd.setPath(fixedPathDest(outcome.getPath(), extUrlEd.getPath(), redirector, null));
1307                        // updateFromBase(extUrlEd, currentBase);
1308                        markDerived(extUrlEd);
1309                        result.getElement().add(extUrlEd);
1310                      }
1311                    }
1312                  }
1313                }
1314              }
1315              // ---
1316              diffpos++;
1317            }
1318          }
1319          baseCursor++;
1320        }
1321      }
1322    }
1323
1324    int i = 0;
1325    for (ElementDefinition e : result.getElement()) {
1326      i++;
1327      if (e.hasMinElement() && e.getMinElement().getValue() == null)
1328        throw new Error("null min");
1329    }
1330    return res;
1331  }
1332
1333  private void removeStatusExtensions(ElementDefinition outcome) {
1334    outcome.removeExtension(ToolingExtensions.EXT_FMM_LEVEL);
1335    outcome.removeExtension(ToolingExtensions.EXT_STANDARDS_STATUS);
1336    outcome.removeExtension(ToolingExtensions.EXT_NORMATIVE_VERSION);
1337    outcome.removeExtension(ToolingExtensions.EXT_WORKGROUP);
1338  }
1339
1340  private String descED(List<ElementDefinition> list, int index) {
1341    return index >= 0 && index < list.size() ? list.get(index).present() : "X";
1342  }
1343
1344  private boolean baseHasChildren(StructureDefinitionSnapshotComponent base, ElementDefinition ed) {
1345    int index = base.getElement().indexOf(ed);
1346    if (index == -1 || index >= base.getElement().size() - 1)
1347      return false;
1348    String p = base.getElement().get(index + 1).getPath();
1349    return isChildOf(p, ed.getPath());
1350  }
1351
1352  private boolean isChildOf(String sub, String focus) {
1353    if (focus.endsWith("[x]")) {
1354      focus = focus.substring(0, focus.length() - 3);
1355      return sub.startsWith(focus);
1356    } else
1357      return sub.startsWith(focus + ".");
1358  }
1359
1360  private int indexOfFirstNonChild(StructureDefinitionSnapshotComponent base, ElementDefinition currentBase, int i,
1361      int baseLimit) {
1362    return baseLimit + 1;
1363  }
1364
1365  private String rootName(String cpath) {
1366    String t = tail(cpath);
1367    return t.replace("[x]", "");
1368  }
1369
1370  private String determineTypeSlicePath(String path, String cpath) {
1371    String headP = path.substring(0, path.lastIndexOf("."));
1372//    String tailP = path.substring(path.lastIndexOf(".")+1);
1373    String tailC = cpath.substring(cpath.lastIndexOf(".") + 1);
1374    return headP + "." + tailC;
1375  }
1376
1377  private boolean isImplicitSlicing(ElementDefinition ed, String path) {
1378    if (ed == null || ed.getPath() == null || path == null)
1379      return false;
1380    if (path.equals(ed.getPath()))
1381      return false;
1382    boolean ok = path.endsWith("[x]") && ed.getPath().startsWith(path.substring(0, path.length() - 3));
1383    return ok;
1384  }
1385
1386  private boolean diffsConstrainTypes(List<ElementDefinition> diffMatches, String cPath, List<TypeSlice> typeList) {
1387//    if (diffMatches.size() < 2)
1388//      return false;
1389    String p = diffMatches.get(0).getPath();
1390    if (!p.endsWith("[x]") && !cPath.endsWith("[x]"))
1391      return false;
1392    typeList.clear();
1393    String rn = tail(cPath);
1394    rn = rn.substring(0, rn.length() - 3);
1395    for (int i = 0; i < diffMatches.size(); i++) {
1396      ElementDefinition ed = diffMatches.get(i);
1397      String n = tail(ed.getPath());
1398      if (!n.startsWith(rn))
1399        return false;
1400      String s = n.substring(rn.length());
1401      if (!s.contains(".")) {
1402        if (ed.hasSliceName() && ed.getType().size() == 1) {
1403          typeList.add(new TypeSlice(ed, ed.getTypeFirstRep().getWorkingCode()));
1404        } else if (!ed.hasSliceName() && !s.equals("[x]")) {
1405          if (isDataType(s))
1406            typeList.add(new TypeSlice(ed, s));
1407          else if (isConstrainedDataType(s))
1408            typeList.add(new TypeSlice(ed, baseType(s)));
1409          else if (isPrimitive(Utilities.uncapitalize(s)))
1410            typeList.add(new TypeSlice(ed, Utilities.uncapitalize(s)));
1411        } else if (!ed.hasSliceName() && s.equals("[x]"))
1412          typeList.add(new TypeSlice(ed, null));
1413      }
1414    }
1415    return true;
1416  }
1417
1418  private List<ElementRedirection> redirectorStack(List<ElementRedirection> redirector, ElementDefinition outcome,
1419      String path) {
1420    List<ElementRedirection> result = new ArrayList<ElementRedirection>();
1421    result.addAll(redirector);
1422    result.add(new ElementRedirection(outcome, path));
1423    return result;
1424  }
1425
1426  private List<TypeRefComponent> getByTypeName(List<TypeRefComponent> type, String t) {
1427    List<TypeRefComponent> res = new ArrayList<TypeRefComponent>();
1428    for (TypeRefComponent tr : type) {
1429      if (t.equals(tr.getWorkingCode()))
1430        res.add(tr);
1431    }
1432    return res;
1433  }
1434
1435  private void replaceFromContentReference(ElementDefinition outcome, ElementDefinition tgt) {
1436    outcome.setContentReference(null);
1437    outcome.getType().clear(); // though it should be clear anyway
1438    outcome.getType().addAll(tgt.getType());
1439  }
1440
1441  private boolean baseWalksInto(List<ElementDefinition> elements, int cursor) {
1442    if (cursor >= elements.size())
1443      return false;
1444    String path = elements.get(cursor).getPath();
1445    String prevPath = elements.get(cursor - 1).getPath();
1446    return path.startsWith(prevPath + ".");
1447  }
1448
1449  private ElementDefinition overWriteWithCurrent(ElementDefinition profile, ElementDefinition usage)
1450      throws FHIRFormatError {
1451    ElementDefinition res = profile.copy();
1452    if (usage.hasSliceName())
1453      res.setSliceName(usage.getSliceName());
1454    if (usage.hasLabel())
1455      res.setLabel(usage.getLabel());
1456    for (Coding c : usage.getCode())
1457      res.addCode(c);
1458
1459    if (usage.hasDefinition())
1460      res.setDefinition(usage.getDefinition());
1461    if (usage.hasShort())
1462      res.setShort(usage.getShort());
1463    if (usage.hasComment())
1464      res.setComment(usage.getComment());
1465    if (usage.hasRequirements())
1466      res.setRequirements(usage.getRequirements());
1467    for (StringType c : usage.getAlias())
1468      res.addAlias(c.getValue());
1469    if (usage.hasMin())
1470      res.setMin(usage.getMin());
1471    if (usage.hasMax())
1472      res.setMax(usage.getMax());
1473
1474    if (usage.hasFixed())
1475      res.setFixed(usage.getFixed());
1476    if (usage.hasPattern())
1477      res.setPattern(usage.getPattern());
1478    if (usage.hasExample())
1479      res.setExample(usage.getExample());
1480    if (usage.hasMinValue())
1481      res.setMinValue(usage.getMinValue());
1482    if (usage.hasMaxValue())
1483      res.setMaxValue(usage.getMaxValue());
1484    if (usage.hasMaxLength())
1485      res.setMaxLength(usage.getMaxLength());
1486    if (usage.hasMustSupport())
1487      res.setMustSupport(usage.getMustSupport());
1488    if (usage.hasBinding())
1489      res.setBinding(usage.getBinding().copy());
1490    for (ElementDefinitionConstraintComponent c : usage.getConstraint())
1491      res.addConstraint(c);
1492    for (Extension e : usage.getExtension()) {
1493      if (!res.hasExtension(e.getUrl()))
1494        res.addExtension(e.copy());
1495    }
1496
1497    return res;
1498  }
1499
1500  private boolean checkExtensionDoco(ElementDefinition base) {
1501    // see task 3970. For an extension, there's no point copying across all the
1502    // underlying definitional stuff
1503    boolean isExtension = base.getPath().equals("Extension") || base.getPath().endsWith(".extension")
1504        || base.getPath().endsWith(".modifierExtension");
1505    if (isExtension) {
1506      base.setDefinition("An Extension");
1507      base.setShort("Extension");
1508      base.setCommentElement(null);
1509      base.setRequirementsElement(null);
1510      base.getAlias().clear();
1511      base.getMapping().clear();
1512    }
1513    return isExtension;
1514  }
1515
1516  private String pathTail(List<ElementDefinition> diffMatches, int i) {
1517
1518    ElementDefinition d = diffMatches.get(i);
1519    String s = d.getPath().contains(".") ? d.getPath().substring(d.getPath().lastIndexOf(".") + 1) : d.getPath();
1520    return "." + s
1521        + (d.hasType() && d.getType().get(0).hasProfile() ? "[" + d.getType().get(0).getProfile() + "]" : "");
1522  }
1523
1524  private void markDerived(ElementDefinition outcome) {
1525    for (ElementDefinitionConstraintComponent inv : outcome.getConstraint())
1526      inv.setUserData(IS_DERIVED, true);
1527  }
1528
1529  private String summarizeSlicing(ElementDefinitionSlicingComponent slice) {
1530    StringBuilder b = new StringBuilder();
1531    boolean first = true;
1532    for (ElementDefinitionSlicingDiscriminatorComponent d : slice.getDiscriminator()) {
1533      if (first)
1534        first = false;
1535      else
1536        b.append(", ");
1537      b.append(d);
1538    }
1539    b.append("(");
1540    if (slice.hasOrdered())
1541      b.append(slice.getOrderedElement().asStringValue());
1542    b.append("/");
1543    if (slice.hasRules())
1544      b.append(slice.getRules().toCode());
1545    b.append(")");
1546    if (slice.hasDescription()) {
1547      b.append(" \"");
1548      b.append(slice.getDescription());
1549      b.append("\"");
1550    }
1551    return b.toString();
1552  }
1553
1554  private void updateFromBase(ElementDefinition derived, ElementDefinition base) {
1555    if (base.hasBase()) {
1556      if (!derived.hasBase())
1557        derived.setBase(new ElementDefinitionBaseComponent());
1558      derived.getBase().setPath(base.getBase().getPath());
1559      derived.getBase().setMin(base.getBase().getMin());
1560      derived.getBase().setMax(base.getBase().getMax());
1561    } else {
1562      if (!derived.hasBase())
1563        derived.setBase(new ElementDefinitionBaseComponent());
1564      derived.getBase().setPath(base.getPath());
1565      derived.getBase().setMin(base.getMin());
1566      derived.getBase().setMax(base.getMax());
1567    }
1568  }
1569
1570  private boolean pathStartsWith(String p1, String p2) {
1571    return p1.startsWith(p2);
1572  }
1573
1574  private boolean pathMatches(String p1, String p2) {
1575    return p1.equals(p2) || (p2.endsWith("[x]") && p1.startsWith(p2.substring(0, p2.length() - 3))
1576        && !p1.substring(p2.length() - 3).contains("."));
1577  }
1578
1579  private String fixedPathSource(String contextPath, String pathSimple, List<ElementRedirection> redirector) {
1580    if (contextPath == null)
1581      return pathSimple;
1582//    String ptail = pathSimple.substring(contextPath.length() + 1);
1583    if (redirector.size() > 0) {
1584      String ptail = pathSimple.substring(contextPath.length() + 1);
1585      return redirector.get(redirector.size() - 1).getPath() + "." + ptail;
1586//      return contextPath+"."+tail(redirector.getPath())+"."+ptail.substring(ptail.indexOf(".")+1);
1587    } else {
1588      String ptail = pathSimple.substring(pathSimple.indexOf(".") + 1);
1589      return contextPath + "." + ptail;
1590    }
1591  }
1592
1593  private String fixedPathDest(String contextPath, String pathSimple, List<ElementRedirection> redirector,
1594      String redirectSource) {
1595    String s;
1596    if (contextPath == null)
1597      s = pathSimple;
1598    else {
1599      if (redirector.size() > 0) {
1600        String ptail = pathSimple.substring(redirectSource.length() + 1);
1601        // ptail = ptail.substring(ptail.indexOf(".")+1);
1602        s = contextPath + "." + /* tail(redirector.getPath())+"."+ */ptail;
1603      } else {
1604        String ptail = pathSimple.substring(pathSimple.indexOf(".") + 1);
1605        s = contextPath + "." + ptail;
1606      }
1607    }
1608    return s;
1609  }
1610
1611  private StructureDefinition getProfileForDataType(TypeRefComponent type) {
1612    StructureDefinition sd = null;
1613    if (type.hasProfile()) {
1614      sd = context.fetchResource(StructureDefinition.class, type.getProfile().get(0).getValue());
1615      if (sd == null)
1616        System.out.println("Failed to find referenced profile: " + type.getProfile());
1617    }
1618    if (sd == null)
1619      sd = context.fetchTypeDefinition(type.getWorkingCode());
1620    if (sd == null)
1621      System.out.println("XX: failed to find profle for type: " + type.getWorkingCode()); // debug GJM
1622    return sd;
1623  }
1624
1625  private StructureDefinition getProfileForDataType(String type) {
1626    StructureDefinition sd = context.fetchTypeDefinition(type);
1627    if (sd == null)
1628      System.out.println("XX: failed to find profle for type: " + type); // debug GJM
1629    return sd;
1630  }
1631
1632  public static String typeCode(List<TypeRefComponent> types) {
1633    StringBuilder b = new StringBuilder();
1634    boolean first = true;
1635    for (TypeRefComponent type : types) {
1636      if (first)
1637        first = false;
1638      else
1639        b.append(", ");
1640      b.append(type.getWorkingCode());
1641      if (type.hasTargetProfile())
1642        b.append("{" + type.getTargetProfile() + "}");
1643      else if (type.hasProfile())
1644        b.append("{" + type.getProfile() + "}");
1645    }
1646    return b.toString();
1647  }
1648
1649  private boolean isDataType(List<TypeRefComponent> types) {
1650    if (types.isEmpty())
1651      return false;
1652    for (TypeRefComponent type : types) {
1653      String t = type.getWorkingCode();
1654      if (!isDataType(t) && !isPrimitive(t))
1655        return false;
1656    }
1657    return true;
1658  }
1659
1660  /**
1661   * Finds internal references in an Element's Binding and StructureDefinition
1662   * references (in TypeRef) and bases them on the given url
1663   * 
1664   * @param url     - the base url to use to turn internal references into
1665   *                absolute references
1666   * @param element - the Element to update
1667   * @return - the updated Element
1668   */
1669  private ElementDefinition updateURLs(String url, String webUrl, ElementDefinition element) {
1670    if (element != null) {
1671      ElementDefinition defn = element;
1672      if (defn.hasBinding() && defn.getBinding().hasValueSet() && defn.getBinding().getValueSet().startsWith("#"))
1673        defn.getBinding().setValueSet(url + defn.getBinding().getValueSet());
1674      for (TypeRefComponent t : defn.getType()) {
1675        for (UriType u : t.getProfile()) {
1676          if (u.getValue().startsWith("#"))
1677            u.setValue(url + t.getProfile());
1678        }
1679        for (UriType u : t.getTargetProfile()) {
1680          if (u.getValue().startsWith("#"))
1681            u.setValue(url + t.getTargetProfile());
1682        }
1683      }
1684      if (webUrl != null) {
1685        // also, must touch up the markdown
1686        if (element.hasDefinition())
1687          element.setDefinition(processRelativeUrls(element.getDefinition(), webUrl));
1688        if (element.hasComment())
1689          element.setComment(processRelativeUrls(element.getComment(), webUrl));
1690        if (element.hasRequirements())
1691          element.setRequirements(processRelativeUrls(element.getRequirements(), webUrl));
1692        if (element.hasMeaningWhenMissing())
1693          element.setMeaningWhenMissing(processRelativeUrls(element.getMeaningWhenMissing(), webUrl));
1694      }
1695    }
1696    return element;
1697  }
1698
1699  private String processRelativeUrls(String markdown, String webUrl) {
1700    StringBuilder b = new StringBuilder();
1701    int i = 0;
1702    while (i < markdown.length()) {
1703      if (i < markdown.length() - 3 && markdown.substring(i, i + 2).equals("](")) {
1704        int j = i + 2;
1705        while (j < markdown.length() && markdown.charAt(j) != ')')
1706          j++;
1707        if (j < markdown.length()) {
1708          String url = markdown.substring(i + 2, j);
1709          if (!Utilities.isAbsoluteUrl(url)) {
1710            b.append("](");
1711            b.append(webUrl);
1712            i = i + 1;
1713          } else
1714            b.append(markdown.charAt(i));
1715        } else
1716          b.append(markdown.charAt(i));
1717      } else {
1718        b.append(markdown.charAt(i));
1719      }
1720      i++;
1721    }
1722    return b.toString();
1723  }
1724
1725  private List<ElementDefinition> getSiblings(List<ElementDefinition> list, ElementDefinition current) {
1726    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
1727    String path = current.getPath();
1728    int cursor = list.indexOf(current) + 1;
1729    while (cursor < list.size() && list.get(cursor).getPath().length() >= path.length()) {
1730      if (pathMatches(list.get(cursor).getPath(), path))
1731        result.add(list.get(cursor));
1732      cursor++;
1733    }
1734    return result;
1735  }
1736
1737  private void updateFromSlicing(ElementDefinitionSlicingComponent dst, ElementDefinitionSlicingComponent src) {
1738    if (src.hasOrderedElement())
1739      dst.setOrderedElement(src.getOrderedElement().copy());
1740    if (src.hasDiscriminator()) {
1741      // dst.getDiscriminator().addAll(src.getDiscriminator()); Can't use addAll
1742      // because it uses object equality, not string equality
1743      for (ElementDefinitionSlicingDiscriminatorComponent s : src.getDiscriminator()) {
1744        boolean found = false;
1745        for (ElementDefinitionSlicingDiscriminatorComponent d : dst.getDiscriminator()) {
1746          if (matches(d, s)) {
1747            found = true;
1748            break;
1749          }
1750        }
1751        if (!found)
1752          dst.getDiscriminator().add(s);
1753      }
1754    }
1755    if (src.hasRulesElement())
1756      dst.setRulesElement(src.getRulesElement().copy());
1757  }
1758
1759  private boolean orderMatches(BooleanType diff, BooleanType base) {
1760    return (diff == null) || (base == null) || (diff.getValue() == base.getValue());
1761  }
1762
1763  private boolean discriminatorMatches(List<ElementDefinitionSlicingDiscriminatorComponent> diff,
1764      List<ElementDefinitionSlicingDiscriminatorComponent> base) {
1765    if (diff.isEmpty() || base.isEmpty())
1766      return true;
1767    if (diff.size() != base.size())
1768      return false;
1769    for (int i = 0; i < diff.size(); i++)
1770      if (!matches(diff.get(i), base.get(i)))
1771        return false;
1772    return true;
1773  }
1774
1775  private boolean matches(ElementDefinitionSlicingDiscriminatorComponent c1,
1776      ElementDefinitionSlicingDiscriminatorComponent c2) {
1777    return c1.getType().equals(c2.getType()) && c1.getPath().equals(c2.getPath());
1778  }
1779
1780  private boolean ruleMatches(SlicingRules diff, SlicingRules base) {
1781    return (diff == null) || (base == null) || (diff == base) || (base == SlicingRules.OPEN)
1782        || ((diff == SlicingRules.OPENATEND && base == SlicingRules.CLOSED));
1783  }
1784
1785  private boolean isSlicedToOneOnly(ElementDefinition e) {
1786    return (e.hasSlicing() && e.hasMaxElement() && e.getMax().equals("1"));
1787  }
1788
1789  private ElementDefinitionSlicingComponent makeExtensionSlicing() {
1790    ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent();
1791    slice.addDiscriminator().setPath("url").setType(DiscriminatorType.VALUE);
1792    slice.setOrdered(false);
1793    slice.setRules(SlicingRules.OPEN);
1794    return slice;
1795  }
1796
1797  private boolean isExtension(ElementDefinition currentBase) {
1798    return currentBase.getPath().endsWith(".extension") || currentBase.getPath().endsWith(".modifierExtension");
1799  }
1800
1801  private boolean hasInnerDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end,
1802      List<ElementDefinition> base, boolean allowSlices) throws DefinitionException {
1803    end = Math.min(context.getElement().size(), end);
1804    start = Math.max(0, start);
1805
1806    for (int i = start; i <= end; i++) {
1807      String statedPath = context.getElement().get(i).getPath();
1808      if (statedPath.startsWith(path + ".")) {
1809        return true;
1810      } else if (!statedPath.endsWith(path))
1811        break;
1812    }
1813    return false;
1814  }
1815
1816  private List<ElementDefinition> getDiffMatches(StructureDefinitionDifferentialComponent context, String path,
1817      int start, int end, String profileName) throws DefinitionException {
1818    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
1819    for (int i = start; i <= end; i++) {
1820      String statedPath = context.getElement().get(i).getPath();
1821      if (statedPath.equals(path) || (path.endsWith("[x]") && statedPath.length() > path.length() - 2
1822          && statedPath.substring(0, path.length() - 3).equals(path.substring(0, path.length() - 3))
1823          && (statedPath.length() < path.length() || !statedPath.substring(path.length()).contains(".")))) {
1824        /*
1825         * Commenting this out because it raises warnings when profiling inherited
1826         * elements. For example, Error: unknown element 'Bundle.meta.profile' (or it is
1827         * out of order) in profile ... (looking for 'Bundle.entry') Not sure we have
1828         * enough information here to do the check properly. Might be better done when
1829         * we're sorting the profile?
1830         * 
1831         * if (i != start && result.isEmpty() &&
1832         * !path.startsWith(context.getElement().get(start).getPath())) messages.add(new
1833         * ValidationMessage(Source.ProfileValidator, IssueType.VALUE,
1834         * "StructureDefinition.differential.element["+Integer.toString(start)+"]",
1835         * "Error: unknown element '"+context.getElement().get(start).getPath()
1836         * +"' (or it is out of order) in profile '"+url+"' (looking for '"+path+"')",
1837         * IssueSeverity.WARNING));
1838         * 
1839         */
1840        result.add(context.getElement().get(i));
1841      }
1842    }
1843    return result;
1844  }
1845
1846  private int findEndOfElement(StructureDefinitionDifferentialComponent context, int cursor) {
1847    int result = cursor;
1848    String path = context.getElement().get(cursor).getPath() + ".";
1849    while (result < context.getElement().size() - 1 && context.getElement().get(result + 1).getPath().startsWith(path))
1850      result++;
1851    return result;
1852  }
1853
1854  private int findEndOfElement(StructureDefinitionSnapshotComponent context, int cursor) {
1855    int result = cursor;
1856    String path = context.getElement().get(cursor).getPath() + ".";
1857    while (result < context.getElement().size() - 1 && context.getElement().get(result + 1).getPath().startsWith(path))
1858      result++;
1859    return result;
1860  }
1861
1862  private boolean unbounded(ElementDefinition definition) {
1863    StringType max = definition.getMaxElement();
1864    if (max == null)
1865      return false; // this is not valid
1866    if (max.getValue().equals("1"))
1867      return false;
1868    if (max.getValue().equals("0"))
1869      return false;
1870    return true;
1871  }
1872
1873  private void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn,
1874      boolean trimDifferential, String purl, StructureDefinition srcSD) throws DefinitionException, FHIRException {
1875    source.setUserData(GENERATED_IN_SNAPSHOT, dest);
1876    // we start with a clone of the base profile ('dest') and we copy from the
1877    // profile ('source')
1878    // over the top for anything the source has
1879    ElementDefinition base = dest;
1880    ElementDefinition derived = source;
1881    derived.setUserData(DERIVATION_POINTER, base);
1882    boolean isExtension = checkExtensionDoco(base);
1883
1884    // Before applying changes, apply them to what's in the profile
1885    // TODO: follow Chris's rules - Done by Lloyd
1886    StructureDefinition profile = null;
1887    if (base.hasSliceName())
1888      profile = base.getType().size() == 1 && base.getTypeFirstRep().hasProfile()
1889          ? context.fetchResource(StructureDefinition.class, base.getTypeFirstRep().getProfile().get(0).getValue())
1890          : null;
1891    if (profile == null)
1892      profile = source.getType().size() == 1 && source.getTypeFirstRep().hasProfile()
1893          ? context.fetchResource(StructureDefinition.class, source.getTypeFirstRep().getProfile().get(0).getValue())
1894          : null;
1895    if (profile != null) {
1896      ElementDefinition e = profile.getSnapshot().getElement().get(0);
1897      base.setDefinition(e.getDefinition());
1898      base.setShort(e.getShort());
1899      if (e.hasCommentElement())
1900        base.setCommentElement(e.getCommentElement());
1901      if (e.hasRequirementsElement())
1902        base.setRequirementsElement(e.getRequirementsElement());
1903      base.getAlias().clear();
1904      base.getAlias().addAll(e.getAlias());
1905      base.getMapping().clear();
1906      base.getMapping().addAll(e.getMapping());
1907    }
1908    if (derived != null) {
1909      if (derived.hasSliceName()) {
1910        base.setSliceName(derived.getSliceName());
1911      }
1912
1913      if (derived.hasShortElement()) {
1914        if (!Base.compareDeep(derived.getShortElement(), base.getShortElement(), false))
1915          base.setShortElement(derived.getShortElement().copy());
1916        else if (trimDifferential)
1917          derived.setShortElement(null);
1918        else if (derived.hasShortElement())
1919          derived.getShortElement().setUserData(DERIVATION_EQUALS, true);
1920      }
1921
1922      if (derived.hasDefinitionElement()) {
1923        if (derived.getDefinition().startsWith("..."))
1924          base.setDefinition(Utilities.appendDerivedTextToBase(base.getDefinition(), derived.getDefinition()));
1925        else if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false))
1926          base.setDefinitionElement(derived.getDefinitionElement().copy());
1927        else if (trimDifferential)
1928          derived.setDefinitionElement(null);
1929        else if (derived.hasDefinitionElement())
1930          derived.getDefinitionElement().setUserData(DERIVATION_EQUALS, true);
1931      }
1932
1933      if (derived.hasCommentElement()) {
1934        if (derived.getComment().startsWith("..."))
1935          base.setComment(Utilities.appendDerivedTextToBase(base.getComment(), derived.getComment()));
1936        else if (derived.hasCommentElement() != base.hasCommentElement()
1937            || !Base.compareDeep(derived.getCommentElement(), base.getCommentElement(), false))
1938          base.setCommentElement(derived.getCommentElement().copy());
1939        else if (trimDifferential)
1940          base.setCommentElement(derived.getCommentElement().copy());
1941        else if (derived.hasCommentElement())
1942          derived.getCommentElement().setUserData(DERIVATION_EQUALS, true);
1943      }
1944
1945      if (derived.hasLabelElement()) {
1946        if (derived.getLabel().startsWith("..."))
1947          base.setLabel(Utilities.appendDerivedTextToBase(base.getLabel(), derived.getLabel()));
1948        else if (!base.hasLabelElement() || !Base.compareDeep(derived.getLabelElement(), base.getLabelElement(), false))
1949          base.setLabelElement(derived.getLabelElement().copy());
1950        else if (trimDifferential)
1951          base.setLabelElement(derived.getLabelElement().copy());
1952        else if (derived.hasLabelElement())
1953          derived.getLabelElement().setUserData(DERIVATION_EQUALS, true);
1954      }
1955
1956      if (derived.hasRequirementsElement()) {
1957        if (derived.getRequirements().startsWith("..."))
1958          base.setRequirements(Utilities.appendDerivedTextToBase(base.getRequirements(), derived.getRequirements()));
1959        else if (!base.hasRequirementsElement()
1960            || !Base.compareDeep(derived.getRequirementsElement(), base.getRequirementsElement(), false))
1961          base.setRequirementsElement(derived.getRequirementsElement().copy());
1962        else if (trimDifferential)
1963          base.setRequirementsElement(derived.getRequirementsElement().copy());
1964        else if (derived.hasRequirementsElement())
1965          derived.getRequirementsElement().setUserData(DERIVATION_EQUALS, true);
1966      }
1967      // sdf-9
1968      if (derived.hasRequirements() && !base.getPath().contains("."))
1969        derived.setRequirements(null);
1970      if (base.hasRequirements() && !base.getPath().contains("."))
1971        base.setRequirements(null);
1972
1973      if (derived.hasAlias()) {
1974        if (!Base.compareDeep(derived.getAlias(), base.getAlias(), false))
1975          for (StringType s : derived.getAlias()) {
1976            if (!base.hasAlias(s.getValue()))
1977              base.getAlias().add(s.copy());
1978          }
1979        else if (trimDifferential)
1980          derived.getAlias().clear();
1981        else
1982          for (StringType t : derived.getAlias())
1983            t.setUserData(DERIVATION_EQUALS, true);
1984      }
1985
1986      if (derived.hasMinElement()) {
1987        if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) {
1988          if (derived.getMin() < base.getMin() && !derived.hasSliceName()) // in a slice, minimum cardinality rules do
1989                                                                           // not apply
1990            messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE,
1991                pn + "." + source.getPath(),
1992                "Element " + base.getPath() + ": derived min (" + Integer.toString(derived.getMin())
1993                    + ") cannot be less than base min (" + Integer.toString(base.getMin()) + ")",
1994                ValidationMessage.IssueSeverity.ERROR));
1995          base.setMinElement(derived.getMinElement().copy());
1996        } else if (trimDifferential)
1997          derived.setMinElement(null);
1998        else
1999          derived.getMinElement().setUserData(DERIVATION_EQUALS, true);
2000      }
2001
2002      if (derived.hasMaxElement()) {
2003        if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) {
2004          if (isLargerMax(derived.getMax(), base.getMax()))
2005            messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE,
2006                pn + "." + source.getPath(),
2007                "Element " + base.getPath() + ": derived max (" + derived.getMax()
2008                    + ") cannot be greater than base max (" + base.getMax() + ")",
2009                ValidationMessage.IssueSeverity.ERROR));
2010          base.setMaxElement(derived.getMaxElement().copy());
2011        } else if (trimDifferential)
2012          derived.setMaxElement(null);
2013        else
2014          derived.getMaxElement().setUserData(DERIVATION_EQUALS, true);
2015      }
2016
2017      if (derived.hasFixed()) {
2018        if (!Base.compareDeep(derived.getFixed(), base.getFixed(), true)) {
2019          base.setFixed(derived.getFixed().copy());
2020        } else if (trimDifferential)
2021          derived.setFixed(null);
2022        else
2023          derived.getFixed().setUserData(DERIVATION_EQUALS, true);
2024      }
2025
2026      if (derived.hasPattern()) {
2027        if (!Base.compareDeep(derived.getPattern(), base.getPattern(), false)) {
2028          base.setPattern(derived.getPattern().copy());
2029        } else if (trimDifferential)
2030          derived.setPattern(null);
2031        else
2032          derived.getPattern().setUserData(DERIVATION_EQUALS, true);
2033      }
2034
2035      for (ElementDefinitionExampleComponent ex : derived.getExample()) {
2036        boolean found = false;
2037        for (ElementDefinitionExampleComponent exS : base.getExample())
2038          if (Base.compareDeep(ex, exS, false))
2039            found = true;
2040        if (!found)
2041          base.addExample(ex.copy());
2042        else if (trimDifferential)
2043          derived.getExample().remove(ex);
2044        else
2045          ex.setUserData(DERIVATION_EQUALS, true);
2046      }
2047
2048      if (derived.hasMaxLengthElement()) {
2049        if (!Base.compareDeep(derived.getMaxLengthElement(), base.getMaxLengthElement(), false))
2050          base.setMaxLengthElement(derived.getMaxLengthElement().copy());
2051        else if (trimDifferential)
2052          derived.setMaxLengthElement(null);
2053        else
2054          derived.getMaxLengthElement().setUserData(DERIVATION_EQUALS, true);
2055      }
2056
2057      if (derived.hasMaxValue()) {
2058        if (!Base.compareDeep(derived.getMaxValue(), base.getMaxValue(), false))
2059          base.setMaxValue(derived.getMaxValue().copy());
2060        else if (trimDifferential)
2061          derived.setMaxValue(null);
2062        else
2063          derived.getMaxValue().setUserData(DERIVATION_EQUALS, true);
2064      }
2065
2066      if (derived.hasMinValue()) {
2067        if (!Base.compareDeep(derived.getMinValue(), base.getMinValue(), false))
2068          base.setMinValue(derived.getMinValue().copy());
2069        else if (trimDifferential)
2070          derived.setMinValue(null);
2071        else
2072          derived.getMinValue().setUserData(DERIVATION_EQUALS, true);
2073      }
2074
2075      // todo: what to do about conditions?
2076      // condition : id 0..*
2077
2078      if (derived.hasMustSupportElement()) {
2079        if (!(base.hasMustSupportElement()
2080            && Base.compareDeep(derived.getMustSupportElement(), base.getMustSupportElement(), false)))
2081          base.setMustSupportElement(derived.getMustSupportElement().copy());
2082        else if (trimDifferential)
2083          derived.setMustSupportElement(null);
2084        else
2085          derived.getMustSupportElement().setUserData(DERIVATION_EQUALS, true);
2086      }
2087
2088      // profiles cannot change : isModifier, defaultValue, meaningWhenMissing
2089      // but extensions can change isModifier
2090      if (isExtension) {
2091        if (derived.hasIsModifierElement() && !(base.hasIsModifierElement()
2092            && Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false)))
2093          base.setIsModifierElement(derived.getIsModifierElement().copy());
2094        else if (trimDifferential)
2095          derived.setIsModifierElement(null);
2096        else if (derived.hasIsModifierElement())
2097          derived.getIsModifierElement().setUserData(DERIVATION_EQUALS, true);
2098        if (derived.hasIsModifierReasonElement() && !(base.hasIsModifierReasonElement()
2099            && Base.compareDeep(derived.getIsModifierReasonElement(), base.getIsModifierReasonElement(), false)))
2100          base.setIsModifierReasonElement(derived.getIsModifierReasonElement().copy());
2101        else if (trimDifferential)
2102          derived.setIsModifierReasonElement(null);
2103        else if (derived.hasIsModifierReasonElement())
2104          derived.getIsModifierReasonElement().setUserData(DERIVATION_EQUALS, true);
2105      }
2106
2107      if (derived.hasBinding()) {
2108        if (!base.hasBinding() || !Base.compareDeep(derived.getBinding(), base.getBinding(), false)) {
2109          if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED
2110              && derived.getBinding().getStrength() != BindingStrength.REQUIRED)
2111            messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE,
2112                pn + "." + derived.getPath(),
2113                "illegal attempt to change the binding on " + derived.getPath() + " from "
2114                    + base.getBinding().getStrength().toCode() + " to " + derived.getBinding().getStrength().toCode(),
2115                ValidationMessage.IssueSeverity.ERROR));
2116//            throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode());
2117          else if (base.hasBinding() && derived.hasBinding()
2118              && base.getBinding().getStrength() == BindingStrength.REQUIRED && base.getBinding().hasValueSet()
2119              && derived.getBinding().hasValueSet()) {
2120            ValueSet baseVs = context.fetchResource(ValueSet.class, base.getBinding().getValueSet());
2121            ValueSet contextVs = context.fetchResource(ValueSet.class, derived.getBinding().getValueSet());
2122            if (baseVs == null) {
2123              messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE,
2124                  pn + "." + base.getPath(), "Binding " + base.getBinding().getValueSet() + " could not be located",
2125                  ValidationMessage.IssueSeverity.WARNING));
2126            } else if (contextVs == null) {
2127              messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE,
2128                  pn + "." + derived.getPath(),
2129                  "Binding " + derived.getBinding().getValueSet() + " could not be located",
2130                  ValidationMessage.IssueSeverity.WARNING));
2131            } else {
2132              ValueSetExpansionOutcome expBase = context.expandVS(baseVs, true, false);
2133              ValueSetExpansionOutcome expDerived = context.expandVS(contextVs, true, false);
2134              if (expBase.getValueset() == null)
2135                messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE,
2136                    pn + "." + base.getPath(), "Binding " + base.getBinding().getValueSet() + " could not be expanded",
2137                    ValidationMessage.IssueSeverity.WARNING));
2138              else if (expDerived.getValueset() == null)
2139                messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE,
2140                    pn + "." + derived.getPath(),
2141                    "Binding " + derived.getBinding().getValueSet() + " could not be expanded",
2142                    ValidationMessage.IssueSeverity.WARNING));
2143              else if (!isSubset(expBase.getValueset(), expDerived.getValueset()))
2144                messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE,
2145                    pn + "." + derived.getPath(), "Binding " + derived.getBinding().getValueSet()
2146                        + " is not a subset of binding " + base.getBinding().getValueSet(),
2147                    ValidationMessage.IssueSeverity.ERROR));
2148
2149            }
2150          }
2151          base.setBinding(derived.getBinding().copy());
2152        } else if (trimDifferential)
2153          derived.setBinding(null);
2154        else
2155          derived.getBinding().setUserData(DERIVATION_EQUALS, true);
2156      } // else if (base.hasBinding() && doesn't have bindable type )
2157        // base
2158
2159      if (derived.hasIsSummaryElement()) {
2160        if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false)) {
2161          if (base.hasIsSummary())
2162            throw new Error("Error in profile " + pn + " at " + derived.getPath() + ": Base isSummary = "
2163                + base.getIsSummaryElement().asStringValue() + ", derived isSummary = "
2164                + derived.getIsSummaryElement().asStringValue());
2165          base.setIsSummaryElement(derived.getIsSummaryElement().copy());
2166        } else if (trimDifferential)
2167          derived.setIsSummaryElement(null);
2168        else
2169          derived.getIsSummaryElement().setUserData(DERIVATION_EQUALS, true);
2170      }
2171
2172      if (derived.hasType()) {
2173        if (!Base.compareDeep(derived.getType(), base.getType(), false)) {
2174          if (base.hasType()) {
2175            for (TypeRefComponent ts : derived.getType()) {
2176//              if (!ts.hasCode()) { // ommitted in the differential; copy it over....
2177//                if (base.getType().size() > 1) 
2178//                  throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": constrained type code must be present if there are multiple types ("+base.typeSummary()+")");
2179//                if (base.getType().get(0).getCode() != null)
2180//                  ts.setCode(base.getType().get(0).getCode());
2181//              }
2182              boolean ok = false;
2183              CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
2184              String t = ts.getWorkingCode();
2185              for (TypeRefComponent td : base.getType()) {
2186                ;
2187                String tt = td.getWorkingCode();
2188                b.append(tt);
2189                if (td.hasCode()
2190                    && (tt.equals(t) || "Extension".equals(tt) || (t.equals("uri") && tt.equals("string")) || // work
2191                                                                                                              // around
2192                                                                                                              // for old
2193                                                                                                              // badly
2194                                                                                                              // generated
2195                                                                                                              // SDs
2196                        "Element".equals(tt) || "*".equals(tt)
2197                        || (("Resource".equals(tt) || ("DomainResource".equals(tt)) && pkp.isResource(t)))))
2198                  ok = true;
2199              }
2200              if (!ok)
2201                throw new DefinitionException("StructureDefinition " + pn + " at " + derived.getPath()
2202                    + ": illegal constrained type " + t + " from " + b.toString() + " in " + srcSD.getUrl());
2203            }
2204          }
2205          base.getType().clear();
2206          for (TypeRefComponent t : derived.getType()) {
2207            TypeRefComponent tt = t.copy();
2208//            tt.setUserData(DERIVATION_EQUALS, true);
2209            base.getType().add(tt);
2210          }
2211        } else if (trimDifferential)
2212          derived.getType().clear();
2213        else
2214          for (TypeRefComponent t : derived.getType())
2215            t.setUserData(DERIVATION_EQUALS, true);
2216      }
2217
2218      if (derived.hasMapping()) {
2219        // todo: mappings are not cumulative - one replaces another
2220        if (!Base.compareDeep(derived.getMapping(), base.getMapping(), false)) {
2221          for (ElementDefinitionMappingComponent s : derived.getMapping()) {
2222            boolean found = false;
2223            for (ElementDefinitionMappingComponent d : base.getMapping()) {
2224              found = found || (d.getIdentity().equals(s.getIdentity()) && d.getMap().equals(s.getMap()));
2225            }
2226            if (!found)
2227              base.getMapping().add(s);
2228          }
2229        } else if (trimDifferential)
2230          derived.getMapping().clear();
2231        else
2232          for (ElementDefinitionMappingComponent t : derived.getMapping())
2233            t.setUserData(DERIVATION_EQUALS, true);
2234      }
2235
2236      // todo: constraints are cumulative. there is no replacing
2237      for (ElementDefinitionConstraintComponent s : base.getConstraint()) {
2238        s.setUserData(IS_DERIVED, true);
2239        if (!s.hasSource())
2240          s.setSource(base.getId());
2241      }
2242      if (derived.hasConstraint()) {
2243        for (ElementDefinitionConstraintComponent s : derived.getConstraint()) {
2244          ElementDefinitionConstraintComponent inv = s.copy();
2245          base.getConstraint().add(inv);
2246        }
2247      }
2248
2249      // now, check that we still have a bindable type; if not, delete the binding -
2250      // see task 8477
2251      if (dest.hasBinding() && !hasBindableType(dest))
2252        dest.setBinding(null);
2253
2254      // finally, we copy any extensions from source to dest
2255      for (Extension ex : derived.getExtension()) {
2256        StructureDefinition sd = context.fetchResource(StructureDefinition.class, ex.getUrl());
2257        if (sd == null || sd.getSnapshot() == null || sd.getSnapshot().getElementFirstRep().getMax().equals("1"))
2258          ToolingExtensions.removeExtension(dest, ex.getUrl());
2259        dest.addExtension(ex.copy());
2260      }
2261    }
2262  }
2263
2264  private boolean hasBindableType(ElementDefinition ed) {
2265    for (TypeRefComponent tr : ed.getType()) {
2266      if (Utilities.existsInList(tr.getWorkingCode(), "Coding", "CodeableConcept", "Quantity", "uri", "string", "code"))
2267        return true;
2268    }
2269    return false;
2270  }
2271
2272  private boolean isLargerMax(String derived, String base) {
2273    if ("*".equals(base))
2274      return false;
2275    if ("*".equals(derived))
2276      return true;
2277    return Integer.parseInt(derived) > Integer.parseInt(base);
2278  }
2279
2280  private boolean isSubset(ValueSet expBase, ValueSet expDerived) {
2281    return codesInExpansion(expDerived.getExpansion().getContains(), expBase.getExpansion());
2282  }
2283
2284  private boolean codesInExpansion(List<ValueSetExpansionContainsComponent> contains,
2285      ValueSetExpansionComponent expansion) {
2286    for (ValueSetExpansionContainsComponent cc : contains) {
2287      if (!inExpansion(cc, expansion.getContains()))
2288        return false;
2289      if (!codesInExpansion(cc.getContains(), expansion))
2290        return false;
2291    }
2292    return true;
2293  }
2294
2295  private boolean inExpansion(ValueSetExpansionContainsComponent cc,
2296      List<ValueSetExpansionContainsComponent> contains) {
2297    for (ValueSetExpansionContainsComponent cc1 : contains) {
2298      if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode()))
2299        return true;
2300      if (inExpansion(cc, cc1.getContains()))
2301        return true;
2302    }
2303    return false;
2304  }
2305
2306  public void closeDifferential(StructureDefinition base, StructureDefinition derived) throws FHIRException {
2307    for (ElementDefinition edb : base.getSnapshot().getElement()) {
2308      if (isImmediateChild(edb) && !edb.getPath().endsWith(".id")) {
2309        ElementDefinition edm = getMatchInDerived(edb, derived.getDifferential().getElement());
2310        if (edm == null) {
2311          ElementDefinition edd = derived.getDifferential().addElement();
2312          edd.setPath(edb.getPath());
2313          edd.setMax("0");
2314        } else if (edb.hasSlicing()) {
2315          closeChildren(base, edb, derived, edm);
2316        }
2317      }
2318    }
2319    sortDifferential(base, derived, derived.getName(), new ArrayList<String>());
2320  }
2321
2322  private void closeChildren(StructureDefinition base, ElementDefinition edb, StructureDefinition derived,
2323      ElementDefinition edm) {
2324    String path = edb.getPath() + ".";
2325    int baseStart = base.getSnapshot().getElement().indexOf(edb);
2326    int baseEnd = findEnd(base.getSnapshot().getElement(), edb, baseStart + 1);
2327    int diffStart = derived.getDifferential().getElement().indexOf(edm);
2328    int diffEnd = findEnd(derived.getDifferential().getElement(), edm, diffStart + 1);
2329
2330    for (int cBase = baseStart; cBase < baseEnd; cBase++) {
2331      ElementDefinition edBase = base.getSnapshot().getElement().get(cBase);
2332      if (isImmediateChild(edBase, edb)) {
2333        ElementDefinition edMatch = getMatchInDerived(edBase, derived.getDifferential().getElement(), diffStart,
2334            diffEnd);
2335        if (edMatch == null) {
2336          ElementDefinition edd = derived.getDifferential().addElement();
2337          edd.setPath(edBase.getPath());
2338          edd.setMax("0");
2339        } else {
2340          closeChildren(base, edBase, derived, edMatch);
2341        }
2342      }
2343    }
2344  }
2345
2346  private int findEnd(List<ElementDefinition> list, ElementDefinition ed, int cursor) {
2347    String path = ed.getPath() + ".";
2348    while (cursor < list.size() && list.get(cursor).getPath().startsWith(path))
2349      cursor++;
2350    return cursor;
2351  }
2352
2353  private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list) {
2354    for (ElementDefinition t : list)
2355      if (t.getPath().equals(ed.getPath()))
2356        return t;
2357    return null;
2358  }
2359
2360  private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list, int start, int end) {
2361    for (int i = start; i < end; i++) {
2362      ElementDefinition t = list.get(i);
2363      if (t.getPath().equals(ed.getPath()))
2364        return t;
2365    }
2366    return null;
2367  }
2368
2369  private boolean isImmediateChild(ElementDefinition ed) {
2370    String p = ed.getPath();
2371    if (!p.contains("."))
2372      return false;
2373    p = p.substring(p.indexOf(".") + 1);
2374    return !p.contains(".");
2375  }
2376
2377  private boolean isImmediateChild(ElementDefinition candidate, ElementDefinition base) {
2378    String p = candidate.getPath();
2379    if (!p.contains("."))
2380      return false;
2381    if (!p.startsWith(base.getPath() + "."))
2382      return false;
2383    p = p.substring(base.getPath().length() + 1);
2384    return !p.contains(".");
2385  }
2386
2387  public XhtmlNode generateExtensionTable(String defFile, StructureDefinition ed, String imageFolder,
2388      boolean inlineGraphics, boolean full, String corePath, String imagePath, Set<String> outputTracker)
2389      throws IOException, FHIRException {
2390    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), imageFolder, inlineGraphics, true);
2391    TableModel model = gen.initNormalTable(corePath, false, true, ed.getId(), false, TableGenerationMode.XML);
2392
2393    boolean deep = false;
2394    String m = "";
2395    boolean vdeep = false;
2396    if (ed.getSnapshot().getElementFirstRep().getIsModifier())
2397      m = "modifier_";
2398    for (ElementDefinition eld : ed.getSnapshot().getElement()) {
2399      deep = deep || eld.getPath().contains("Extension.extension.");
2400      vdeep = vdeep || eld.getPath().contains("Extension.extension.extension.");
2401    }
2402    Row r = gen.new Row();
2403    model.getRows().add(r);
2404    String en;
2405    if (!full)
2406      en = ed.getName();
2407    else if (ed.getSnapshot().getElement().get(0).getIsModifier())
2408      en = "modifierExtension";
2409    else
2410      en = "extension";
2411
2412    r.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile + "-definitions.html#extension." + ed.getName(),
2413        en, null, null));
2414    r.getCells().add(gen.new Cell());
2415    r.getCells().add(gen.new Cell(null, null,
2416        describeCardinality(ed.getSnapshot().getElement().get(0), null, new UnusedTracker()), null, null));
2417
2418    ElementDefinition ved = null;
2419    if (full || vdeep) {
2420      r.getCells().add(gen.new Cell("", "", "Extension", null, null));
2421
2422      r.setIcon(deep ? "icon_" + m + "extension_complex.png" : "icon_extension_simple.png",
2423          deep ? HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX
2424              : HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
2425      List<ElementDefinition> children = getChildren(ed.getSnapshot().getElement(),
2426          ed.getSnapshot().getElement().get(0));
2427      for (ElementDefinition child : children)
2428        if (!child.getPath().endsWith(".id"))
2429          genElement(defFile == null ? "" : defFile + "-definitions.html#extension.", gen, r.getSubRows(), child,
2430              ed.getSnapshot().getElement(), null, true, defFile, true, full, corePath, imagePath, true, false, false,
2431              false, null);
2432    } else if (deep) {
2433      List<ElementDefinition> children = new ArrayList<ElementDefinition>();
2434      for (ElementDefinition ted : ed.getSnapshot().getElement()) {
2435        if (ted.getPath().equals("Extension.extension"))
2436          children.add(ted);
2437      }
2438
2439      r.getCells().add(gen.new Cell("", "", "Extension", null, null));
2440      r.setIcon("icon_" + m + "extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
2441
2442      for (ElementDefinition c : children) {
2443        ved = getValueFor(ed, c);
2444        ElementDefinition ued = getUrlFor(ed, c);
2445        if (ved != null && ued != null) {
2446          Row r1 = gen.new Row();
2447          r.getSubRows().add(r1);
2448          r1.getCells()
2449              .add(gen.new Cell(null, defFile == null ? "" : defFile + "-definitions.html#extension." + ed.getName(),
2450                  ((UriType) ued.getFixed()).getValue(), null, null));
2451          r1.getCells().add(gen.new Cell());
2452          r1.getCells().add(gen.new Cell(null, null, describeCardinality(c, null, new UnusedTracker()), null, null));
2453          genTypes(gen, r1, ved, defFile, ed, corePath, imagePath);
2454          Cell cell = gen.new Cell();
2455          cell.addMarkdown(c.getDefinition());
2456          r1.getCells().add(cell);
2457          r1.setIcon("icon_" + m + "extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
2458        }
2459      }
2460    } else {
2461      for (ElementDefinition ted : ed.getSnapshot().getElement()) {
2462        if (ted.getPath().startsWith("Extension.value"))
2463          ved = ted;
2464      }
2465
2466      genTypes(gen, r, ved, defFile, ed, corePath, imagePath);
2467
2468      r.setIcon("icon_" + m + "extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
2469    }
2470    Cell c = gen.new Cell("", "", "URL = " + ed.getUrl(), null, null);
2471    Piece cc = gen.new Piece(null, ed.getName() + ": ", null);
2472    c.addPiece(gen.new Piece("br")).addPiece(cc);
2473    c.addMarkdown(ed.getDescription());
2474
2475    if (!full && !(deep || vdeep) && ved != null && ved.hasBinding()) {
2476      c.addPiece(gen.new Piece("br"));
2477      BindingResolution br = pkp.resolveBinding(ed, ved.getBinding(), ved.getPath());
2478      c.getPieces().add(checkForNoChange(ved.getBinding(),
2479          gen.new Piece(null, translate("sd.table", "Binding") + ": ", null).addStyle("font-weight:bold")));
2480      c.getPieces()
2481          .add(checkForNoChange(ved.getBinding(),
2482              gen.new Piece(
2483                  br.url == null ? null
2484                      : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath + br.url,
2485                  br.display, null)));
2486      if (ved.getBinding().hasStrength()) {
2487        c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, " (", null)));
2488        c.getPieces()
2489            .add(checkForNoChange(ved.getBinding(),
2490                gen.new Piece(corePath + "terminologies.html#" + ved.getBinding().getStrength().toCode(),
2491                    egt(ved.getBinding().getStrengthElement()), ved.getBinding().getStrength().getDefinition())));
2492        c.getPieces().add(gen.new Piece(null, ")", null));
2493      }
2494    }
2495    c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, describeExtensionContext(ed), null));
2496    r.getCells().add(c);
2497
2498    try {
2499      return gen.generate(model, corePath, 0, outputTracker);
2500    } catch (org.hl7.fhir.exceptions.FHIRException e) {
2501      throw new FHIRException(e.getMessage(), e);
2502    }
2503  }
2504
2505  private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) {
2506    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
2507    while (i < ed.getSnapshot().getElement().size()
2508        && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath() + ".")) {
2509      if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath() + ".url"))
2510        return ed.getSnapshot().getElement().get(i);
2511      i++;
2512    }
2513    return null;
2514  }
2515
2516  private ElementDefinition getValueFor(StructureDefinition ed, ElementDefinition c) {
2517    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
2518    while (i < ed.getSnapshot().getElement().size()
2519        && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath() + ".")) {
2520      if (ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath() + ".value"))
2521        return ed.getSnapshot().getElement().get(i);
2522      i++;
2523    }
2524    return null;
2525  }
2526
2527  private static final int AGG_NONE = 0;
2528  private static final int AGG_IND = 1;
2529  private static final int AGG_GR = 2;
2530  private static final boolean TABLE_FORMAT_FOR_FIXED_VALUES = false;
2531
2532  private Cell genTypes(HierarchicalTableGenerator gen, Row r, ElementDefinition e, String profileBaseFileName,
2533      StructureDefinition profile, String corePath, String imagePath) {
2534    Cell c = gen.new Cell();
2535    r.getCells().add(c);
2536    List<TypeRefComponent> types = e.getType();
2537    if (!e.hasType()) {
2538      if (e.hasContentReference()) {
2539        return c;
2540      } else {
2541        ElementDefinition d = (ElementDefinition) e.getUserData(DERIVATION_POINTER);
2542        if (d != null && d.hasType()) {
2543          types = new ArrayList<ElementDefinition.TypeRefComponent>();
2544          for (TypeRefComponent tr : d.getType()) {
2545            TypeRefComponent tt = tr.copy();
2546            tt.setUserData(DERIVATION_EQUALS, true);
2547            types.add(tt);
2548          }
2549        } else
2550          return c;
2551      }
2552    }
2553
2554    boolean first = true;
2555
2556    TypeRefComponent tl = null;
2557    for (TypeRefComponent t : types) {
2558      if (first)
2559        first = false;
2560      else
2561        c.addPiece(checkForNoChange(tl, gen.new Piece(null, ", ", null)));
2562      tl = t;
2563      if (t.hasTarget()) {
2564        c.getPieces().add(gen.new Piece(corePath + "references.html", t.getWorkingCode(), null));
2565        c.getPieces().add(gen.new Piece(null, "(", null));
2566        boolean tfirst = true;
2567        for (UriType u : t.getTargetProfile()) {
2568          if (tfirst)
2569            tfirst = false;
2570          else
2571            c.addPiece(gen.new Piece(null, " | ", null));
2572          genTargetLink(gen, profileBaseFileName, corePath, c, t, u.getValue());
2573        }
2574        c.getPieces().add(gen.new Piece(null, ")", null));
2575        if (t.getAggregation().size() > 0) {
2576          c.getPieces().add(gen.new Piece(corePath + "valueset-resource-aggregation-mode.html", " {", null));
2577          boolean firstA = true;
2578          for (Enumeration<AggregationMode> a : t.getAggregation()) {
2579            if (firstA = true)
2580              firstA = false;
2581            else
2582              c.getPieces().add(gen.new Piece(corePath + "valueset-resource-aggregation-mode.html", ", ", null));
2583            c.getPieces().add(gen.new Piece(corePath + "valueset-resource-aggregation-mode.html",
2584                codeForAggregation(a.getValue()), hintForAggregation(a.getValue())));
2585          }
2586          c.getPieces().add(gen.new Piece(corePath + "valueset-resource-aggregation-mode.html", "}", null));
2587        }
2588      } else if (t.hasProfile() && (!t.getWorkingCode().equals("Extension") || isProfiledType(t.getProfile()))) { // a
2589                                                                                                                  // profiled
2590                                                                                                                  // type
2591        String ref;
2592        ref = pkp.getLinkForProfile(profile, t.getProfile().get(0).getValue());
2593        if (ref != null) {
2594          String[] parts = ref.split("\\|");
2595          if (parts[0].startsWith("http:") || parts[0].startsWith("https:")) {
2596//            c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], "<" + parts[1] + ">", t.getCode()))); Lloyd
2597            c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], parts[1], t.getWorkingCode())));
2598          } else {
2599//            c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().startsWith(corePath)? corePath: "")+parts[0], "<" + parts[1] + ">", t.getCode())));
2600            c.addPiece(checkForNoChange(t,
2601                gen.new Piece(
2602                    (t.getProfile().get(0).getValue().startsWith(corePath + "StructureDefinition") ? corePath : "")
2603                        + parts[0],
2604                    parts[1], t.getWorkingCode())));
2605          }
2606        } else
2607          c.addPiece(checkForNoChange(t,
2608              gen.new Piece((t.getProfile().get(0).getValue().startsWith(corePath) ? corePath : "") + ref,
2609                  t.getWorkingCode(), null)));
2610      } else {
2611        String tc = t.getWorkingCode();
2612        if (pkp != null && pkp.hasLinkFor(tc)) {
2613          c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, tc), tc, null)));
2614        } else
2615          c.addPiece(checkForNoChange(t, gen.new Piece(null, tc, null)));
2616      }
2617    }
2618    return c;
2619  }
2620
2621  public void genTargetLink(HierarchicalTableGenerator gen, String profileBaseFileName, String corePath, Cell c,
2622      TypeRefComponent t, String u) {
2623    if (u.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
2624      StructureDefinition sd = context.fetchResource(StructureDefinition.class, u);
2625      if (sd != null) {
2626        String disp = sd.hasTitle() ? sd.getTitle() : sd.getName();
2627        c.addPiece(checkForNoChange(t, gen.new Piece(checkPrepend(corePath, sd.getUserString("path")), disp, null)));
2628      } else {
2629        String rn = u.substring(40);
2630        c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, rn), rn, null)));
2631      }
2632    } else if (Utilities.isAbsoluteUrl(u)) {
2633      StructureDefinition sd = context.fetchResource(StructureDefinition.class, u);
2634      if (sd != null) {
2635        String disp = sd.hasTitle() ? sd.getTitle() : sd.getName();
2636        String ref = pkp.getLinkForProfile(null, sd.getUrl());
2637        if (ref.contains("|"))
2638          ref = ref.substring(0, ref.indexOf("|"));
2639        c.addPiece(checkForNoChange(t, gen.new Piece(ref, disp, null)));
2640      } else
2641        c.addPiece(checkForNoChange(t, gen.new Piece(null, u, null)));
2642    } else if (t.hasTargetProfile() && u.startsWith("#"))
2643      c.addPiece(checkForNoChange(t,
2644          gen.new Piece(corePath + profileBaseFileName + "." + u.substring(1).toLowerCase() + ".html", u, null)));
2645  }
2646
2647  private boolean isProfiledType(List<CanonicalType> theProfile) {
2648    for (CanonicalType next : theProfile) {
2649      if (StringUtils.defaultString(next.getValueAsString()).contains(":")) {
2650        return true;
2651      }
2652    }
2653    return false;
2654  }
2655
2656  private String codeForAggregation(AggregationMode a) {
2657    switch (a) {
2658    case BUNDLED:
2659      return "b";
2660    case CONTAINED:
2661      return "c";
2662    case REFERENCED:
2663      return "r";
2664    default:
2665      return "?";
2666    }
2667  }
2668
2669  private String hintForAggregation(AggregationMode a) {
2670    if (a != null)
2671      return a.getDefinition();
2672    else
2673      return null;
2674  }
2675
2676  private String checkPrepend(String corePath, String path) {
2677    if (pkp.prependLinks() && !(path.startsWith("http:") || path.startsWith("https:")))
2678      return corePath + path;
2679    else
2680      return path;
2681  }
2682
2683  private ElementDefinition getElementByName(List<ElementDefinition> elements, String contentReference) {
2684    for (ElementDefinition ed : elements)
2685      if (ed.hasSliceName() && ("#" + ed.getSliceName()).equals(contentReference))
2686        return ed;
2687    return null;
2688  }
2689
2690  private ElementDefinition getElementById(List<ElementDefinition> elements, String contentReference) {
2691    for (ElementDefinition ed : elements)
2692      if (ed.hasId() && ("#" + ed.getId()).equals(contentReference))
2693        return ed;
2694    return null;
2695  }
2696
2697  public static String describeExtensionContext(StructureDefinition ext) {
2698    StringBuilder b = new StringBuilder();
2699    b.append("Use on ");
2700    for (int i = 0; i < ext.getContext().size(); i++) {
2701      StructureDefinitionContextComponent ec = ext.getContext().get(i);
2702      if (i > 0)
2703        b.append(i < ext.getContext().size() - 1 ? ", " : " or ");
2704      b.append(ec.getType().getDisplay());
2705      b.append(" ");
2706      b.append(ec.getExpression());
2707    }
2708    if (ext.hasContextInvariant()) {
2709      b.append(
2710          ", with <a href=\"structuredefinition-definitions.html#StructureDefinition.contextInvariant\">Context Invariant</a> = ");
2711      boolean first = true;
2712      for (StringType s : ext.getContextInvariant()) {
2713        if (first)
2714          first = false;
2715        else
2716          b.append(", ");
2717        b.append("<code>" + s.getValue() + "</code>");
2718      }
2719    }
2720    return b.toString();
2721  }
2722
2723  private String describeCardinality(ElementDefinition definition, ElementDefinition fallback, UnusedTracker tracker) {
2724    IntegerType min = definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
2725    StringType max = definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
2726    if (min.isEmpty() && fallback != null)
2727      min = fallback.getMinElement();
2728    if (max.isEmpty() && fallback != null)
2729      max = fallback.getMaxElement();
2730
2731    tracker.used = !max.isEmpty() && !max.getValue().equals("0");
2732
2733    if (min.isEmpty() && max.isEmpty())
2734      return null;
2735    else
2736      return (!min.hasValue() ? "" : Integer.toString(min.getValue())) + ".." + (!max.hasValue() ? "" : max.getValue());
2737  }
2738
2739  private void genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef,
2740      UnusedTracker tracker, ElementDefinition fallback) {
2741    IntegerType min = !hasDef ? new IntegerType()
2742        : definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
2743    StringType max = !hasDef ? new StringType()
2744        : definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
2745    if (min.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) {
2746      ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER);
2747      if (base.hasMinElement()) {
2748        min = base.getMinElement().copy();
2749        min.setUserData(DERIVATION_EQUALS, true);
2750      }
2751    }
2752    if (max.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) {
2753      ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER);
2754      if (base.hasMaxElement()) {
2755        max = base.getMaxElement().copy();
2756        max.setUserData(DERIVATION_EQUALS, true);
2757      }
2758    }
2759    if (min.isEmpty() && fallback != null)
2760      min = fallback.getMinElement();
2761    if (max.isEmpty() && fallback != null)
2762      max = fallback.getMaxElement();
2763
2764    if (!max.isEmpty())
2765      tracker.used = !max.getValue().equals("0");
2766
2767    Cell cell = gen.new Cell(null, null, null, null, null);
2768    row.getCells().add(cell);
2769    if (!min.isEmpty() || !max.isEmpty()) {
2770      cell.addPiece(
2771          checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), null)));
2772      cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", null)));
2773      cell.addPiece(checkForNoChange(min, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), null)));
2774    }
2775  }
2776
2777  private Piece checkForNoChange(Element source, Piece piece) {
2778    if (source.hasUserData(DERIVATION_EQUALS)) {
2779      piece.addStyle("opacity: 0.4");
2780    }
2781    return piece;
2782  }
2783
2784  private Piece checkForNoChange(Element src1, Element src2, Piece piece) {
2785    if (src1.hasUserData(DERIVATION_EQUALS) && src2.hasUserData(DERIVATION_EQUALS)) {
2786      piece.addStyle("opacity: 0.5");
2787    }
2788    return piece;
2789  }
2790
2791  public XhtmlNode generateTable(String defFile, StructureDefinition profile, boolean diff, String imageFolder,
2792      boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, String imagePath,
2793      boolean logicalModel, boolean allInvariants, Set<String> outputTracker) throws IOException, FHIRException {
2794    assert (diff != snapshot);// check it's ok to get rid of one of these
2795    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), imageFolder, inlineGraphics, true);
2796    TableModel model = gen.initNormalTable(corePath, false, true, profile.getId() + (diff ? "d" : "s"), false,
2797        TableGenerationMode.XML);
2798    List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement();
2799    List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
2800    profiles.add(profile);
2801    if (list.isEmpty()) {
2802      ElementDefinition root = new ElementDefinition().setPath(profile.getType());
2803      root.setId(profile.getType());
2804      list.add(root);
2805    }
2806    genElement(defFile == null ? null : defFile + "#", gen, model.getRows(), list.get(0), list, profiles, diff,
2807        profileBaseFileName, null, snapshot, corePath, imagePath, true, logicalModel,
2808        profile.getDerivation() == TypeDerivationRule.CONSTRAINT && usesMustSupport(list), allInvariants, null);
2809    try {
2810      return gen.generate(model, imagePath, 0, outputTracker);
2811    } catch (org.hl7.fhir.exceptions.FHIRException e) {
2812      throw new FHIRException("Error generating table for profile " + profile.getUrl() + ": " + e.getMessage(), e);
2813    }
2814  }
2815
2816  public XhtmlNode generateGrid(String defFile, StructureDefinition profile, String imageFolder, boolean inlineGraphics,
2817      String profileBaseFileName, String corePath, String imagePath, Set<String> outputTracker)
2818      throws IOException, FHIRException {
2819    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), imageFolder, inlineGraphics, true);
2820    TableModel model = gen.initGridTable(corePath, profile.getId());
2821    List<ElementDefinition> list = profile.getSnapshot().getElement();
2822    List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
2823    profiles.add(profile);
2824    genGridElement(defFile == null ? null : defFile + "#", gen, model.getRows(), list.get(0), list, profiles, true,
2825        profileBaseFileName, null, corePath, imagePath, true,
2826        profile.getDerivation() == TypeDerivationRule.CONSTRAINT && usesMustSupport(list));
2827    try {
2828      return gen.generate(model, imagePath, 1, outputTracker);
2829    } catch (org.hl7.fhir.exceptions.FHIRException e) {
2830      throw new FHIRException(e.getMessage(), e);
2831    }
2832  }
2833
2834  private boolean usesMustSupport(List<ElementDefinition> list) {
2835    for (ElementDefinition ed : list)
2836      if (ed.hasMustSupport() && ed.getMustSupport())
2837        return true;
2838    return false;
2839  }
2840
2841  private Row genElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element,
2842      List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName,
2843      Boolean extensions, boolean snapshot, String corePath, String imagePath, boolean root, boolean logicalModel,
2844      boolean isConstraintMode, boolean allInvariants, Row slicingRow) throws IOException, FHIRException {
2845    Row originalRow = slicingRow;
2846    StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size() - 1);
2847    String s = tail(element.getPath());
2848    if (element.hasSliceName())
2849      s = s + ":" + element.getSliceName();
2850    Row typesRow = null;
2851
2852    List<ElementDefinition> children = getChildren(all, element);
2853    boolean isExtension = (s.equals("extension") || s.equals("modifierExtension"));
2854//    if (!snapshot && isExtension && extensions != null && extensions != isExtension)
2855//      return;
2856
2857    if (!onlyInformationIsMapping(all, element)) {
2858      Row row = gen.new Row();
2859      row.setAnchor(element.getPath());
2860      row.setColor(getRowColor(element, isConstraintMode));
2861      if (element.hasSlicing())
2862        row.setLineColor(1);
2863      else if (element.hasSliceName())
2864        row.setLineColor(2);
2865      else
2866        row.setLineColor(0);
2867      boolean hasDef = element != null;
2868      boolean ext = false;
2869      if (tail(element.getPath()).equals("extension")) {
2870        if (element.hasType() && element.getType().get(0).hasProfile()
2871            && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue()))
2872          row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
2873        else
2874          row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
2875        ext = true;
2876      } else if (tail(element.getPath()).equals("modifierExtension")) {
2877        if (element.hasType() && element.getType().get(0).hasProfile()
2878            && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue()))
2879          row.setIcon("icon_modifier_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
2880        else
2881          row.setIcon("icon_modifier_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
2882      } else if (!hasDef || element.getType().size() == 0)
2883        row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
2884      else if (hasDef && element.getType().size() > 1) {
2885        if (allAreReference(element.getType()))
2886          row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
2887        else {
2888          row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE);
2889          typesRow = row;
2890        }
2891      } else if (hasDef && element.getType().get(0).getWorkingCode() != null
2892          && element.getType().get(0).getWorkingCode().startsWith("@"))
2893        row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE);
2894      else if (hasDef && isPrimitive(element.getType().get(0).getWorkingCode()))
2895        row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
2896      else if (hasDef && element.getType().get(0).hasTarget())
2897        row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
2898      else if (hasDef && isDataType(element.getType().get(0).getWorkingCode()))
2899        row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
2900      else
2901        row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
2902      String ref = defPath == null ? null : defPath + element.getId();
2903      UnusedTracker used = new UnusedTracker();
2904      used.used = true;
2905      if (logicalModel && element.hasRepresentation(PropertyRepresentation.XMLATTR))
2906        s = "@" + s;
2907      Cell left = gen.new Cell(null, ref, s,
2908          (element.hasSliceName() ? translate("sd.table", "Slice") + " " + element.getSliceName() : "")
2909              + (hasDef && element.hasSliceName() ? ": " : "") + (!hasDef ? null : gt(element.getDefinitionElement())),
2910          null);
2911      row.getCells().add(left);
2912      Cell gc = gen.new Cell();
2913      row.getCells().add(gc);
2914      if (element != null && element.getIsModifier())
2915        checkForNoChange(element.getIsModifierElement(), gc
2916            .addStyledText(translate("sd.table", "This element is a modifier element"), "?!", null, null, null, false));
2917      if (element != null && element.getMustSupport())
2918        checkForNoChange(element.getMustSupportElement(), gc
2919            .addStyledText(translate("sd.table", "This element must be supported"), "S", "white", "red", null, false));
2920      if (element != null && element.getIsSummary())
2921        checkForNoChange(element.getIsSummaryElement(), gc.addStyledText(
2922            translate("sd.table", "This element is included in summaries"), "\u03A3", null, null, null, false));
2923      if (element != null && (!element.getConstraint().isEmpty() || !element.getCondition().isEmpty()))
2924        gc.addStyledText(translate("sd.table", "This element has or is affected by some invariants"), "I", null, null,
2925            null, false);
2926
2927      ExtensionContext extDefn = null;
2928      if (ext) {
2929        if (element != null && element.getType().size() == 1 && element.getType().get(0).hasProfile()) {
2930          String eurl = element.getType().get(0).getProfile().get(0).getValue();
2931          extDefn = locateExtension(StructureDefinition.class, eurl);
2932          if (extDefn == null) {
2933            genCardinality(gen, element, row, hasDef, used, null);
2934            row.getCells().add(gen.new Cell(null, null, "?? " + element.getType().get(0).getProfile(), null, null));
2935            generateDescription(gen, row, element, null, used.used, profile.getUrl(), eurl, profile, corePath,
2936                imagePath, root, logicalModel, allInvariants, snapshot);
2937          } else {
2938            String name = urltail(eurl);
2939            left.getPieces().get(0).setText(name);
2940            // left.getPieces().get(0).setReference((String)
2941            // extDefn.getExtensionStructure().getTag("filename"));
2942            left.getPieces().get(0).setHint(translate("sd.table", "Extension URL") + " = " + extDefn.getUrl());
2943            genCardinality(gen, element, row, hasDef, used, extDefn.getElement());
2944            ElementDefinition valueDefn = extDefn.getExtensionValueDefinition();
2945            if (valueDefn != null && !"0".equals(valueDefn.getMax()))
2946              genTypes(gen, row, valueDefn, profileBaseFileName, profile, corePath, imagePath);
2947            else // if it's complex, we just call it nothing
2948              // genTypes(gen, row, extDefn.getSnapshot().getElement().get(0),
2949              // profileBaseFileName, profile);
2950              row.getCells().add(gen.new Cell(null, null, "(" + translate("sd.table", "Complex") + ")", null, null));
2951            generateDescription(gen, row, element, extDefn.getElement(), used.used, null, extDefn.getUrl(), profile,
2952                corePath, imagePath, root, logicalModel, allInvariants, valueDefn, snapshot);
2953          }
2954        } else {
2955          genCardinality(gen, element, row, hasDef, used, null);
2956          if ("0".equals(element.getMax()))
2957            row.getCells().add(gen.new Cell());
2958          else
2959            genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath);
2960          generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root,
2961              logicalModel, allInvariants, snapshot);
2962        }
2963      } else {
2964        genCardinality(gen, element, row, hasDef, used, null);
2965        if (element.hasSlicing())
2966          row.getCells().add(gen.new Cell(null, corePath + "profiling.html#slicing", "(Slice Definition)", null, null));
2967        else if (hasDef && !"0".equals(element.getMax()) && typesRow == null)
2968          genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath);
2969        else
2970          row.getCells().add(gen.new Cell());
2971        generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root,
2972            logicalModel, allInvariants, snapshot);
2973      }
2974      if (element.hasSlicing()) {
2975        if (standardExtensionSlicing(element)) {
2976          used.used = true; // doesn't matter whether we have a type, we're used if we're setting up slicing
2977                            // ... element.hasType() && element.getType().get(0).hasProfile();
2978          showMissing = false; // ?
2979        } else {
2980          row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE);
2981          slicingRow = row;
2982          for (Cell cell : row.getCells())
2983            for (Piece p : cell.getPieces()) {
2984              p.addStyle("font-style: italic");
2985            }
2986        }
2987      } else if (element.hasSliceName()) {
2988        row.setIcon("icon_slice_item.png", HierarchicalTableGenerator.TEXT_ICON_SLICE_ITEM);
2989      }
2990      if (used.used || showMissing)
2991        rows.add(row);
2992      if (!used.used && !element.hasSlicing()) {
2993        for (Cell cell : row.getCells())
2994          for (Piece p : cell.getPieces()) {
2995            p.setStyle("text-decoration:line-through");
2996            p.setReference(null);
2997          }
2998      } else {
2999        if (slicingRow != originalRow && !children.isEmpty()) {
3000          // we've entered a slice; we're going to create a holder row for the slice
3001          // children
3002          Row hrow = gen.new Row();
3003          hrow.setAnchor(element.getPath());
3004          hrow.setColor(getRowColor(element, isConstraintMode));
3005          hrow.setLineColor(1);
3006          hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
3007          hrow.getCells().add(gen.new Cell(null, null, "(All Slices)", "", null));
3008          hrow.getCells().add(gen.new Cell());
3009          hrow.getCells().add(gen.new Cell());
3010          hrow.getCells().add(gen.new Cell());
3011          hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all slices", "", null));
3012          row.getSubRows().add(hrow);
3013          row = hrow;
3014        }
3015        if (typesRow != null && !children.isEmpty()) {
3016          // we've entered a typing slice; we're going to create a holder row for the all
3017          // types children
3018          Row hrow = gen.new Row();
3019          hrow.setAnchor(element.getPath());
3020          hrow.setColor(getRowColor(element, isConstraintMode));
3021          hrow.setLineColor(1);
3022          hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
3023          hrow.getCells().add(gen.new Cell(null, null, "(All Types)", "", null));
3024          hrow.getCells().add(gen.new Cell());
3025          hrow.getCells().add(gen.new Cell());
3026          hrow.getCells().add(gen.new Cell());
3027          hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all Types", "", null));
3028          row.getSubRows().add(hrow);
3029          row = hrow;
3030        }
3031
3032        Row currRow = row;
3033        for (ElementDefinition child : children) {
3034          if (!child.hasSliceName())
3035            currRow = row;
3036          if (logicalModel || !child.getPath().endsWith(".id") || (child.getPath().endsWith(".id") && (profile != null)
3037              && (profile.getDerivation() == TypeDerivationRule.CONSTRAINT)))
3038            currRow = genElement(defPath, gen, currRow.getSubRows(), child, all, profiles, showMissing,
3039                profileBaseFileName, isExtension, snapshot, corePath, imagePath, false, logicalModel, isConstraintMode,
3040                allInvariants, currRow);
3041        }
3042//        if (!snapshot && (extensions == null || !extensions))
3043//          for (ElementDefinition child : children)
3044//            if (child.getPath().endsWith(".extension") || child.getPath().endsWith(".modifierExtension"))
3045//              genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, true, false, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants);
3046      }
3047      if (typesRow != null) {
3048        makeChoiceRows(typesRow.getSubRows(), element, gen, corePath, profileBaseFileName);
3049      }
3050    }
3051    return slicingRow;
3052  }
3053
3054  private void makeChoiceRows(List<Row> subRows, ElementDefinition element, HierarchicalTableGenerator gen,
3055      String corePath, String profileBaseFileName) {
3056    // create a child for each choice
3057    for (TypeRefComponent tr : element.getType()) {
3058      Row choicerow = gen.new Row();
3059      String t = tr.getWorkingCode();
3060      if (isReference(t)) {
3061        choicerow.getCells()
3062            .add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), null, null));
3063        choicerow.getCells().add(gen.new Cell());
3064        choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
3065        choicerow.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
3066        Cell c = gen.new Cell();
3067        choicerow.getCells().add(c);
3068        if (ADD_REFERENCE_TO_TABLE) {
3069          if (tr.getWorkingCode().equals("canonical"))
3070            c.getPieces().add(gen.new Piece(corePath + "datatypes.html#canonical", "canonical", null));
3071          else
3072            c.getPieces().add(gen.new Piece(corePath + "references.html#Reference", "Reference", null));
3073          c.getPieces().add(gen.new Piece(null, "(", null));
3074        }
3075        boolean first = true;
3076        for (CanonicalType rt : tr.getTargetProfile()) {
3077          if (!first)
3078            c.getPieces().add(gen.new Piece(null, " | ", null));
3079          genTargetLink(gen, profileBaseFileName, corePath, c, tr, rt.getValue());
3080          first = false;
3081        }
3082        if (ADD_REFERENCE_TO_TABLE)
3083          c.getPieces().add(gen.new Piece(null, ")", null));
3084
3085      } else {
3086        StructureDefinition sd = context.fetchTypeDefinition(t);
3087        if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) {
3088          choicerow.getCells().add(gen.new Cell(null, null,
3089              tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), sd.getDescription(), null));
3090          choicerow.getCells().add(gen.new Cell());
3091          choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
3092          choicerow.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
3093          choicerow.getCells().add(gen.new Cell(null, corePath + "datatypes.html#" + t, t, null, null));
3094          // } else if (definitions.getConstraints().contthnsKey(t)) {
3095          // ProfiledType pt = definitions.getConstraints().get(t);
3096          // choicerow.getCells().add(gen.new Cell(null, null, e.getName().replace("[x]",
3097          // Utilities.capitalize(pt.getBaseType())),
3098          // definitions.getTypes().containsKey(t) ?
3099          // definitions.getTypes().get(t).getDefinition() : null, null));
3100          // choicerow.getCells().add(gen.new Cell());
3101          // choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
3102          // choicerow.setIcon("icon_datatype.gif",
3103          // HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
3104          // choicerow.getCells().add(gen.new Cell(null,
3105          // definitions.getSrcFile(t)+".html#"+t.replace("*", "open"), t, null, null));
3106        } else {
3107          choicerow.getCells().add(gen.new Cell(null, null,
3108              tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), sd.getDescription(), null));
3109          choicerow.getCells().add(gen.new Cell());
3110          choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
3111          choicerow.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
3112          choicerow.getCells().add(gen.new Cell(null, pkp.getLinkFor(corePath, t), t, null, null));
3113        }
3114      }
3115      choicerow.getCells().add(gen.new Cell());
3116      subRows.add(choicerow);
3117    }
3118  }
3119
3120  private boolean isReference(String t) {
3121    return t.equals("Reference") || t.equals("canonical");
3122  }
3123
3124  private void genGridElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element,
3125      List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName,
3126      Boolean extensions, String corePath, String imagePath, boolean root, boolean isConstraintMode)
3127      throws IOException, FHIRException {
3128    StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size() - 1);
3129    String s = tail(element.getPath());
3130    List<ElementDefinition> children = getChildren(all, element);
3131    boolean isExtension = (s.equals("extension") || s.equals("modifierExtension"));
3132
3133    if (!onlyInformationIsMapping(all, element)) {
3134      Row row = gen.new Row();
3135      row.setAnchor(element.getPath());
3136      row.setColor(getRowColor(element, isConstraintMode));
3137      if (element.hasSlicing())
3138        row.setLineColor(1);
3139      else if (element.hasSliceName())
3140        row.setLineColor(2);
3141      else
3142        row.setLineColor(0);
3143      boolean hasDef = element != null;
3144      String ref = defPath == null ? null : defPath + element.getId();
3145      UnusedTracker used = new UnusedTracker();
3146      used.used = true;
3147      Cell left = gen.new Cell();
3148      if (element.getType().size() == 1 && element.getType().get(0).isPrimitive())
3149        left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement()))
3150            .addStyle("font-weight:bold"));
3151      else
3152        left.getPieces()
3153            .add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())));
3154      if (element.hasSliceName()) {
3155        left.getPieces().add(gen.new Piece("br"));
3156        String indent = StringUtils.repeat('\u00A0', 1 + 2 * (element.getPath().split("\\.").length));
3157        left.getPieces().add(gen.new Piece(null, indent + "(" + element.getSliceName() + ")", null));
3158      }
3159      row.getCells().add(left);
3160
3161      ExtensionContext extDefn = null;
3162      genCardinality(gen, element, row, hasDef, used, null);
3163      if (hasDef && !"0".equals(element.getMax()))
3164        genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath);
3165      else
3166        row.getCells().add(gen.new Cell());
3167      generateGridDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, null);
3168      /*
3169       * if (element.hasSlicing()) { if (standardExtensionSlicing(element)) {
3170       * used.used = element.hasType() && element.getType().get(0).hasProfile();
3171       * showMissing = false; } else { row.setIcon("icon_slice.png",
3172       * HierarchicalTableGenerator.TEXT_ICON_SLICE);
3173       * row.getCells().get(2).getPieces().clear(); for (Cell cell : row.getCells())
3174       * for (Piece p : cell.getPieces()) { p.addStyle("font-style: italic"); } } }
3175       */
3176      rows.add(row);
3177      for (ElementDefinition child : children)
3178        if (child.getMustSupport())
3179          genGridElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName,
3180              isExtension, corePath, imagePath, false, isConstraintMode);
3181    }
3182  }
3183
3184  private ExtensionContext locateExtension(Class<StructureDefinition> class1, String value) {
3185    if (value.contains("#")) {
3186      StructureDefinition ext = context.fetchResource(StructureDefinition.class,
3187          value.substring(0, value.indexOf("#")));
3188      if (ext == null)
3189        return null;
3190      String tail = value.substring(value.indexOf("#") + 1);
3191      ElementDefinition ed = null;
3192      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
3193        if (tail.equals(ted.getSliceName())) {
3194          ed = ted;
3195          return new ExtensionContext(ext, ed);
3196        }
3197      }
3198      return null;
3199    } else {
3200      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value);
3201      if (ext == null)
3202        return null;
3203      else
3204        return new ExtensionContext(ext, ext.getSnapshot().getElement().get(0));
3205    }
3206  }
3207
3208  private boolean extensionIsComplex(String value) {
3209    if (value.contains("#")) {
3210      StructureDefinition ext = context.fetchResource(StructureDefinition.class,
3211          value.substring(0, value.indexOf("#")));
3212      if (ext == null)
3213        return false;
3214      String tail = value.substring(value.indexOf("#") + 1);
3215      ElementDefinition ed = null;
3216      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
3217        if (tail.equals(ted.getSliceName())) {
3218          ed = ted;
3219          break;
3220        }
3221      }
3222      if (ed == null)
3223        return false;
3224      int i = ext.getSnapshot().getElement().indexOf(ed);
3225      int j = i + 1;
3226      while (j < ext.getSnapshot().getElement().size()
3227          && !ext.getSnapshot().getElement().get(j).getPath().equals(ed.getPath()))
3228        j++;
3229      return j - i > 5;
3230    } else {
3231      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value);
3232      return ext != null && ext.getSnapshot().getElement().size() > 5;
3233    }
3234  }
3235
3236  private String getRowColor(ElementDefinition element, boolean isConstraintMode) {
3237    switch (element.getUserInt(UD_ERROR_STATUS)) {
3238    case STATUS_HINT:
3239      return ROW_COLOR_HINT;
3240    case STATUS_WARNING:
3241      return ROW_COLOR_WARNING;
3242    case STATUS_ERROR:
3243      return ROW_COLOR_ERROR;
3244    case STATUS_FATAL:
3245      return ROW_COLOR_FATAL;
3246    }
3247    if (isConstraintMode && !element.getMustSupport() && !element.getIsModifier() && element.getPath().contains("."))
3248      return null; // ROW_COLOR_NOT_MUST_SUPPORT;
3249    else
3250      return null;
3251  }
3252
3253  private String urltail(String path) {
3254    if (path.contains("#"))
3255      return path.substring(path.lastIndexOf('#') + 1);
3256    if (path.contains("/"))
3257      return path.substring(path.lastIndexOf('/') + 1);
3258    else
3259      return path;
3260
3261  }
3262
3263  private boolean standardExtensionSlicing(ElementDefinition element) {
3264    String t = tail(element.getPath());
3265    return (t.equals("extension") || t.equals("modifierExtension"))
3266        && element.getSlicing().getRules() != SlicingRules.CLOSED && element.getSlicing().getDiscriminator().size() == 1
3267        && element.getSlicing().getDiscriminator().get(0).getPath().equals("url")
3268        && element.getSlicing().getDiscriminator().get(0).getType().equals(DiscriminatorType.VALUE);
3269  }
3270
3271  private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition,
3272      ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile,
3273      String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, boolean snapshot)
3274      throws IOException, FHIRException {
3275    return generateDescription(gen, row, definition, fallback, used, baseURL, url, profile, corePath, imagePath, root,
3276        logicalModel, allInvariants, null, snapshot);
3277  }
3278
3279  private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition,
3280      ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile,
3281      String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants,
3282      ElementDefinition valueDefn, boolean snapshot) throws IOException, FHIRException {
3283    Cell c = gen.new Cell();
3284    row.getCells().add(c);
3285
3286    if (used) {
3287      if (logicalModel && ToolingExtensions.hasExtension(profile,
3288          "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) {
3289        if (root) {
3290          c.getPieces().add(
3291              gen.new Piece(null, translate("sd.table", "XML Namespace") + ": ", null).addStyle("font-weight:bold"));
3292          c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(profile,
3293              "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null));
3294        } else if (!root
3295            && ToolingExtensions.hasExtension(definition,
3296                "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")
3297            && !ToolingExtensions
3298                .readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")
3299                .equals(ToolingExtensions.readStringExtension(profile,
3300                    "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))) {
3301          c.getPieces().add(
3302              gen.new Piece(null, translate("sd.table", "XML Namespace") + ": ", null).addStyle("font-weight:bold"));
3303          c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(definition,
3304              "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null));
3305        }
3306      }
3307
3308      if (definition.hasContentReference()) {
3309        ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference());
3310        if (ed == null)
3311          c.getPieces().add(gen.new Piece(null,
3312              translate("sd.table", "Unknown reference to %s", definition.getContentReference()), null));
3313        else
3314          c.getPieces().add(gen.new Piece("#" + ed.getPath(), translate("sd.table", "See %s", ed.getPath()), null));
3315      }
3316      if (definition.getPath().endsWith("url") && definition.hasFixed()) {
3317        c.getPieces().add(checkForNoChange(definition.getFixed(),
3318            gen.new Piece(null, "\"" + buildJson(definition.getFixed()) + "\"", null).addStyle("color: darkgreen")));
3319      } else {
3320        if (definition != null && definition.hasShort()) {
3321          if (!c.getPieces().isEmpty())
3322            c.addPiece(gen.new Piece("br"));
3323          c.addPiece(checkForNoChange(definition.getShortElement(),
3324              gen.new Piece(null, gt(definition.getShortElement()), null)));
3325        } else if (fallback != null && fallback.hasShort()) {
3326          if (!c.getPieces().isEmpty())
3327            c.addPiece(gen.new Piece("br"));
3328          c.addPiece(
3329              checkForNoChange(fallback.getShortElement(), gen.new Piece(null, gt(fallback.getShortElement()), null)));
3330        }
3331        if (url != null) {
3332          if (!c.getPieces().isEmpty())
3333            c.addPiece(gen.new Piece("br"));
3334          String fullUrl = url.startsWith("#") ? baseURL + url : url;
3335          StructureDefinition ed = context.fetchResource(StructureDefinition.class, url);
3336          String ref = null;
3337          String ref2 = null;
3338          String fixedUrl = null;
3339          if (ed != null) {
3340            String p = ed.getUserString("path");
3341            if (p != null) {
3342              ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p);
3343            }
3344            fixedUrl = getFixedUrl(ed);
3345            if (fixedUrl != null) {// if its null, we guess that it's not a profiled extension?
3346              if (fixedUrl.equals(url))
3347                fixedUrl = null;
3348              else {
3349                StructureDefinition ed2 = context.fetchResource(StructureDefinition.class, fixedUrl);
3350                if (ed2 != null) {
3351                  String p2 = ed2.getUserString("path");
3352                  if (p2 != null) {
3353                    ref2 = p2.startsWith("http:") || igmode ? p2 : Utilities.pathURL(corePath, p2);
3354                  }
3355                }
3356              }
3357            }
3358          }
3359          if (fixedUrl == null) {
3360            c.getPieces()
3361                .add(gen.new Piece(null, translate("sd.table", "URL") + ": ", null).addStyle("font-weight:bold"));
3362            c.getPieces().add(gen.new Piece(ref, fullUrl, null));
3363          } else {
3364            // reference to a profile take on the extension show the base URL
3365            c.getPieces()
3366                .add(gen.new Piece(null, translate("sd.table", "URL") + ": ", null).addStyle("font-weight:bold"));
3367            c.getPieces().add(gen.new Piece(ref2, fixedUrl, null));
3368            c.getPieces().add(
3369                gen.new Piece(null, translate("sd.table", " profiled by ") + " ", null).addStyle("font-weight:bold"));
3370            c.getPieces().add(gen.new Piece(ref, fullUrl, null));
3371
3372          }
3373        }
3374
3375        if (definition.hasSlicing()) {
3376          if (!c.getPieces().isEmpty())
3377            c.addPiece(gen.new Piece("br"));
3378          c.getPieces()
3379              .add(gen.new Piece(null, translate("sd.table", "Slice") + ": ", null).addStyle("font-weight:bold"));
3380          c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null));
3381        }
3382        if (definition != null) {
3383          ElementDefinitionBindingComponent binding = null;
3384          if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty())
3385            binding = valueDefn.getBinding();
3386          else if (definition.hasBinding())
3387            binding = definition.getBinding();
3388          if (binding != null && !binding.isEmpty()) {
3389            if (!c.getPieces().isEmpty())
3390              c.addPiece(gen.new Piece("br"));
3391            BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath());
3392            c.getPieces().add(checkForNoChange(binding,
3393                gen.new Piece(null, translate("sd.table", "Binding") + ": ", null).addStyle("font-weight:bold")));
3394            c.getPieces()
3395                .add(checkForNoChange(binding,
3396                    gen.new Piece(
3397                        br.url == null ? null
3398                            : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath + br.url,
3399                        br.display, null)));
3400            if (binding.hasStrength()) {
3401              c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null)));
3402              c.getPieces()
3403                  .add(checkForNoChange(binding,
3404                      gen.new Piece(corePath + "terminologies.html#" + binding.getStrength().toCode(),
3405                          egt(binding.getStrengthElement()), binding.getStrength().getDefinition())));
3406
3407              c.getPieces().add(gen.new Piece(null, ")", null));
3408            }
3409            if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) {
3410              br = pkp.resolveBinding(profile,
3411                  ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET),
3412                  definition.getPath());
3413              c.addPiece(gen.new Piece("br"));
3414              c.getPieces()
3415                  .add(checkForNoChange(binding,
3416                      gen.new Piece(corePath + "extension-elementdefinition-maxvalueset.html",
3417                          translate("sd.table", "Max Binding") + ": ", "Max Value Set Extension")
3418                              .addStyle("font-weight:bold")));
3419              c.getPieces()
3420                  .add(checkForNoChange(binding,
3421                      gen.new Piece(
3422                          br.url == null ? null
3423                              : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath + br.url,
3424                          br.display, null)));
3425            }
3426            if (binding.hasExtension(ToolingExtensions.EXT_MIN_VALUESET)) {
3427              br = pkp.resolveBinding(profile,
3428                  ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MIN_VALUESET),
3429                  definition.getPath());
3430              c.addPiece(gen.new Piece("br"));
3431              c.getPieces()
3432                  .add(checkForNoChange(binding,
3433                      gen.new Piece(corePath + "extension-elementdefinition-minvalueset.html",
3434                          translate("sd.table", "Min Binding") + ": ", "Min Value Set Extension")
3435                              .addStyle("font-weight:bold")));
3436              c.getPieces()
3437                  .add(checkForNoChange(binding,
3438                      gen.new Piece(
3439                          br.url == null ? null
3440                              : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath + br.url,
3441                          br.display, null)));
3442            }
3443          }
3444          for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
3445            if (!inv.hasSource() || allInvariants) {
3446              if (!c.getPieces().isEmpty())
3447                c.addPiece(gen.new Piece("br"));
3448              c.getPieces().add(
3449                  checkForNoChange(inv, gen.new Piece(null, inv.getKey() + ": ", null).addStyle("font-weight:bold")));
3450              c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, gt(inv.getHumanElement()), null)));
3451            }
3452          }
3453          if ((definition.hasBase() && definition.getBase().getMax().equals("*"))
3454              || (definition.hasMax() && definition.getMax().equals("*"))) {
3455            if (c.getPieces().size() > 0)
3456              c.addPiece(gen.new Piece("br"));
3457            if (definition.hasOrderMeaning()) {
3458              c.getPieces()
3459                  .add(gen.new Piece(null, "This repeating element order: " + definition.getOrderMeaning(), null));
3460            } else {
3461              // don't show this, this it's important: c.getPieces().add(gen.new Piece(null,
3462              // "This repeating element has no defined order", null));
3463            }
3464          }
3465
3466          if (definition.hasFixed()) {
3467            if (!c.getPieces().isEmpty())
3468              c.addPiece(gen.new Piece("br"));
3469            c.getPieces().add(checkForNoChange(definition.getFixed(),
3470                gen.new Piece(null, translate("sd.table", "Fixed Value") + ": ", null).addStyle("font-weight:bold")));
3471            if (!useTableForFixedValues || definition.getFixed().isPrimitive()) {
3472              String s = buildJson(definition.getFixed());
3473              String link = null;
3474              if (Utilities.isAbsoluteUrl(s))
3475                link = pkp.getLinkForUrl(corePath, s);
3476              c.getPieces().add(
3477                  checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen")));
3478            } else {
3479              c.getPieces().add(checkForNoChange(definition.getFixed(),
3480                  gen.new Piece(null, "As shown", null).addStyle("color: darkgreen")));
3481              genFixedValue(gen, row, definition.getFixed(), snapshot, false, corePath);
3482            }
3483            if (isCoded(definition.getFixed()) && !hasDescription(definition.getFixed())) {
3484              Piece p = describeCoded(gen, definition.getFixed());
3485              if (p != null)
3486                c.getPieces().add(p);
3487            }
3488          } else if (definition.hasPattern()) {
3489            if (!c.getPieces().isEmpty())
3490              c.addPiece(gen.new Piece("br"));
3491            c.getPieces()
3492                .add(checkForNoChange(definition.getPattern(),
3493                    gen.new Piece(null, translate("sd.table", "Required Pattern") + ": ", null)
3494                        .addStyle("font-weight:bold")));
3495            if (!useTableForFixedValues || definition.getPattern().isPrimitive())
3496              c.getPieces().add(checkForNoChange(definition.getPattern(),
3497                  gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen")));
3498            else {
3499              c.getPieces().add(checkForNoChange(definition.getPattern(),
3500                  gen.new Piece(null, "At least the following", null).addStyle("color: darkgreen")));
3501              genFixedValue(gen, row, definition.getPattern(), snapshot, true, corePath);
3502            }
3503          } else if (definition.hasExample()) {
3504            for (ElementDefinitionExampleComponent ex : definition.getExample()) {
3505              if (!c.getPieces().isEmpty())
3506                c.addPiece(gen.new Piece("br"));
3507              c.getPieces()
3508                  .add(checkForNoChange(ex,
3509                      gen.new Piece(null, translate("sd.table", "Example")
3510                          + ("".equals("General") ? "" : " " + ex.getLabel() + "'") + ": ", null)
3511                              .addStyle("font-weight:bold")));
3512              c.getPieces().add(checkForNoChange(ex,
3513                  gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen")));
3514            }
3515          }
3516          if (definition.hasMaxLength() && definition.getMaxLength() != 0) {
3517            if (!c.getPieces().isEmpty())
3518              c.addPiece(gen.new Piece("br"));
3519            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(),
3520                gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold")));
3521            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(),
3522                gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen")));
3523          }
3524          if (profile != null) {
3525            for (StructureDefinitionMappingComponent md : profile.getMapping()) {
3526              if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) {
3527                ElementDefinitionMappingComponent map = null;
3528                for (ElementDefinitionMappingComponent m : definition.getMapping())
3529                  if (m.getIdentity().equals(md.getIdentity()))
3530                    map = m;
3531                if (map != null) {
3532                  for (int i = 0; i < definition.getMapping().size(); i++) {
3533                    c.addPiece(gen.new Piece("br"));
3534                    c.getPieces().add(
3535                        gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)
3536                            + ": " + map.getMap(), null));
3537                  }
3538                }
3539              }
3540            }
3541          }
3542        }
3543      }
3544    }
3545    return c;
3546  }
3547
3548  private void genFixedValue(HierarchicalTableGenerator gen, Row erow, Type value, boolean snapshot, boolean pattern,
3549      String corePath) {
3550    String ref = pkp.getLinkFor(corePath, value.fhirType());
3551    ref = ref.substring(0, ref.indexOf(".html")) + "-definitions.html#";
3552    StructureDefinition sd = context.fetchTypeDefinition(value.fhirType());
3553
3554    for (org.hl7.fhir.r4.model.Property t : value.children()) {
3555      if (t.getValues().size() > 0 || snapshot) {
3556        ElementDefinition ed = findElementDefinition(sd, t.getName());
3557        if (t.getValues().size() == 0 || (t.getValues().size() == 1 && t.getValues().get(0).isEmpty())) {
3558          Row row = gen.new Row();
3559          erow.getSubRows().add(row);
3560          Cell c = gen.new Cell();
3561          row.getCells().add(c);
3562          c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref + ed.getPath()
3563              : corePath + "element-definitions.html#" + ed.getBase().getPath()), t.getName(), null));
3564          c = gen.new Cell();
3565          row.getCells().add(c);
3566          c.addPiece(gen.new Piece(null, null, null));
3567          c = gen.new Cell();
3568          row.getCells().add(c);
3569          if (!pattern) {
3570            c.addPiece(gen.new Piece(null, "0..0", null));
3571            row.setIcon("icon_fixed.gif", "Fixed Value" /* HierarchicalTableGenerator.TEXT_ICON_FIXED */);
3572          } else if (isPrimitive(t.getTypeCode())) {
3573            row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
3574            c.addPiece(gen.new Piece(null,
3575                "0.." + (t.getMaxCardinality() == 2147483647 ? "*" : Integer.toString(t.getMaxCardinality())), null));
3576          } else if (isReference(t.getTypeCode())) {
3577            row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
3578            c.addPiece(gen.new Piece(null,
3579                "0.." + (t.getMaxCardinality() == 2147483647 ? "*" : Integer.toString(t.getMaxCardinality())), null));
3580          } else {
3581            row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
3582            c.addPiece(gen.new Piece(null,
3583                "0.." + (t.getMaxCardinality() == 2147483647 ? "*" : Integer.toString(t.getMaxCardinality())), null));
3584          }
3585          c = gen.new Cell();
3586          row.getCells().add(c);
3587          c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, t.getTypeCode()), t.getTypeCode(), null));
3588          c = gen.new Cell();
3589          c.addPiece(gen.new Piece(null, ed.getShort(), null));
3590          row.getCells().add(c);
3591        } else {
3592          for (Base b : t.getValues()) {
3593            Row row = gen.new Row();
3594            erow.getSubRows().add(row);
3595            row.setIcon("icon_fixed.gif", "Fixed Value" /* HierarchicalTableGenerator.TEXT_ICON_FIXED */);
3596
3597            Cell c = gen.new Cell();
3598            row.getCells().add(c);
3599            c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref + ed.getPath()
3600                : corePath + "element-definitions.html#" + ed.getBase().getPath()), t.getName(), null));
3601
3602            c = gen.new Cell();
3603            row.getCells().add(c);
3604            c.addPiece(gen.new Piece(null, null, null));
3605
3606            c = gen.new Cell();
3607            row.getCells().add(c);
3608            if (pattern)
3609              c.addPiece(gen.new Piece(null,
3610                  "1.." + (t.getMaxCardinality() == 2147483647 ? "*" : Integer.toString(t.getMaxCardinality())), null));
3611            else
3612              c.addPiece(gen.new Piece(null, "1..1", null));
3613
3614            c = gen.new Cell();
3615            row.getCells().add(c);
3616            c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, b.fhirType()), b.fhirType(), null));
3617
3618            if (b.isPrimitive()) {
3619              c = gen.new Cell();
3620              row.getCells().add(c);
3621              c.addPiece(gen.new Piece(null, ed.getShort(), null));
3622              c.addPiece(gen.new Piece("br"));
3623              c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold"));
3624              String s = b.primitiveValue();
3625              // ok. let's see if we can find a relevant link for this
3626              String link = null;
3627              if (Utilities.isAbsoluteUrl(s))
3628                link = pkp.getLinkForUrl(corePath, s);
3629              c.getPieces().add(gen.new Piece(link, s, null).addStyle("color: darkgreen"));
3630            } else {
3631              c = gen.new Cell();
3632              row.getCells().add(c);
3633              c.addPiece(gen.new Piece(null, ed.getShort(), null));
3634              c.addPiece(gen.new Piece("br"));
3635              c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold"));
3636              c.getPieces().add(gen.new Piece(null, "(complex)", null).addStyle("color: darkgreen"));
3637              genFixedValue(gen, row, (Type) b, snapshot, pattern, corePath);
3638            }
3639          }
3640        }
3641      }
3642    }
3643  }
3644
3645  private ElementDefinition findElementDefinition(StructureDefinition sd, String name) {
3646    String path = sd.getType() + "." + name;
3647    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
3648      if (ed.getPath().equals(path))
3649        return ed;
3650    }
3651    throw new FHIRException("Unable to find element " + path);
3652  }
3653
3654  private String getFixedUrl(StructureDefinition sd) {
3655    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
3656      if (ed.getPath().equals("Extension.url")) {
3657        if (ed.hasFixed() && ed.getFixed() instanceof UriType)
3658          return ed.getFixed().primitiveValue();
3659      }
3660    }
3661    return null;
3662  }
3663
3664  private Piece describeCoded(HierarchicalTableGenerator gen, Type fixed) {
3665    if (fixed instanceof Coding) {
3666      Coding c = (Coding) fixed;
3667      ValidationResult vr = context.validateCode(terminologyServiceOptions, c.getSystem(), c.getCode(), c.getDisplay());
3668      if (vr.getDisplay() != null)
3669        return gen.new Piece(null, " (" + vr.getDisplay() + ")", null).addStyle("color: darkgreen");
3670    } else if (fixed instanceof CodeableConcept) {
3671      CodeableConcept cc = (CodeableConcept) fixed;
3672      for (Coding c : cc.getCoding()) {
3673        ValidationResult vr = context.validateCode(terminologyServiceOptions, c.getSystem(), c.getCode(),
3674            c.getDisplay());
3675        if (vr.getDisplay() != null)
3676          return gen.new Piece(null, " (" + vr.getDisplay() + ")", null).addStyle("color: darkgreen");
3677      }
3678    }
3679    return null;
3680  }
3681
3682  private boolean hasDescription(Type fixed) {
3683    if (fixed instanceof Coding) {
3684      return ((Coding) fixed).hasDisplay();
3685    } else if (fixed instanceof CodeableConcept) {
3686      CodeableConcept cc = (CodeableConcept) fixed;
3687      if (cc.hasText())
3688        return true;
3689      for (Coding c : cc.getCoding())
3690        if (c.hasDisplay())
3691          return true;
3692    } // (fixed instanceof CodeType) || (fixed instanceof Quantity);
3693    return false;
3694  }
3695
3696  private boolean isCoded(Type fixed) {
3697    return (fixed instanceof Coding) || (fixed instanceof CodeableConcept) || (fixed instanceof CodeType)
3698        || (fixed instanceof Quantity);
3699  }
3700
3701  private Cell generateGridDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition,
3702      ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile,
3703      String corePath, String imagePath, boolean root, ElementDefinition valueDefn) throws IOException, FHIRException {
3704    Cell c = gen.new Cell();
3705    row.getCells().add(c);
3706
3707    if (used) {
3708      if (definition.hasContentReference()) {
3709        ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference());
3710        if (ed == null)
3711          c.getPieces().add(gen.new Piece(null, "Unknown reference to " + definition.getContentReference(), null));
3712        else
3713          c.getPieces().add(gen.new Piece("#" + ed.getPath(), "See " + ed.getPath(), null));
3714      }
3715      if (definition.getPath().endsWith("url") && definition.hasFixed()) {
3716        c.getPieces().add(checkForNoChange(definition.getFixed(),
3717            gen.new Piece(null, "\"" + buildJson(definition.getFixed()) + "\"", null).addStyle("color: darkgreen")));
3718      } else {
3719        if (url != null) {
3720          if (!c.getPieces().isEmpty())
3721            c.addPiece(gen.new Piece("br"));
3722          String fullUrl = url.startsWith("#") ? baseURL + url : url;
3723          StructureDefinition ed = context.fetchResource(StructureDefinition.class, url);
3724          String ref = null;
3725          if (ed != null) {
3726            String p = ed.getUserString("path");
3727            if (p != null) {
3728              ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p);
3729            }
3730          }
3731          c.getPieces().add(gen.new Piece(null, "URL: ", null).addStyle("font-weight:bold"));
3732          c.getPieces().add(gen.new Piece(ref, fullUrl, null));
3733        }
3734
3735        if (definition.hasSlicing()) {
3736          if (!c.getPieces().isEmpty())
3737            c.addPiece(gen.new Piece("br"));
3738          c.getPieces().add(gen.new Piece(null, "Slice: ", null).addStyle("font-weight:bold"));
3739          c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null));
3740        }
3741        if (definition != null) {
3742          ElementDefinitionBindingComponent binding = null;
3743          if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty())
3744            binding = valueDefn.getBinding();
3745          else if (definition.hasBinding())
3746            binding = definition.getBinding();
3747          if (binding != null && !binding.isEmpty()) {
3748            if (!c.getPieces().isEmpty())
3749              c.addPiece(gen.new Piece("br"));
3750            BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath());
3751            c.getPieces()
3752                .add(checkForNoChange(binding, gen.new Piece(null, "Binding: ", null).addStyle("font-weight:bold")));
3753            c.getPieces()
3754                .add(checkForNoChange(binding,
3755                    gen.new Piece(
3756                        br.url == null ? null
3757                            : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath + br.url,
3758                        br.display, null)));
3759            if (binding.hasStrength()) {
3760              c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null)));
3761              c.getPieces()
3762                  .add(checkForNoChange(binding,
3763                      gen.new Piece(corePath + "terminologies.html#" + binding.getStrength().toCode(),
3764                          binding.getStrength().toCode(), binding.getStrength().getDefinition())));
3765              c.getPieces().add(gen.new Piece(null, ")", null));
3766            }
3767          }
3768          for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
3769            if (!c.getPieces().isEmpty())
3770              c.addPiece(gen.new Piece("br"));
3771            c.getPieces().add(
3772                checkForNoChange(inv, gen.new Piece(null, inv.getKey() + ": ", null).addStyle("font-weight:bold")));
3773            c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null)));
3774          }
3775          if (definition.hasFixed()) {
3776            if (!c.getPieces().isEmpty())
3777              c.addPiece(gen.new Piece("br"));
3778            c.getPieces().add(checkForNoChange(definition.getFixed(),
3779                gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight:bold")));
3780            String s = buildJson(definition.getFixed());
3781            String link = null;
3782            if (Utilities.isAbsoluteUrl(s))
3783              link = pkp.getLinkForUrl(corePath, s);
3784            c.getPieces().add(
3785                checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen")));
3786          } else if (definition.hasPattern()) {
3787            if (!c.getPieces().isEmpty())
3788              c.addPiece(gen.new Piece("br"));
3789            c.getPieces().add(checkForNoChange(definition.getPattern(),
3790                gen.new Piece(null, "Required Pattern: ", null).addStyle("font-weight:bold")));
3791            c.getPieces().add(checkForNoChange(definition.getPattern(),
3792                gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen")));
3793          } else if (definition.hasExample()) {
3794            for (ElementDefinitionExampleComponent ex : definition.getExample()) {
3795              if (!c.getPieces().isEmpty())
3796                c.addPiece(gen.new Piece("br"));
3797              c.getPieces().add(checkForNoChange(ex,
3798                  gen.new Piece(null, "Example'" + ("".equals("General") ? "" : " " + ex.getLabel() + "'") + ": ", null)
3799                      .addStyle("font-weight:bold")));
3800              c.getPieces().add(checkForNoChange(ex,
3801                  gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen")));
3802            }
3803          }
3804          if (definition.hasMaxLength() && definition.getMaxLength() != 0) {
3805            if (!c.getPieces().isEmpty())
3806              c.addPiece(gen.new Piece("br"));
3807            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(),
3808                gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold")));
3809            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(),
3810                gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen")));
3811          }
3812          if (profile != null) {
3813            for (StructureDefinitionMappingComponent md : profile.getMapping()) {
3814              if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) {
3815                ElementDefinitionMappingComponent map = null;
3816                for (ElementDefinitionMappingComponent m : definition.getMapping())
3817                  if (m.getIdentity().equals(md.getIdentity()))
3818                    map = m;
3819                if (map != null) {
3820                  for (int i = 0; i < definition.getMapping().size(); i++) {
3821                    c.addPiece(gen.new Piece("br"));
3822                    c.getPieces().add(
3823                        gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)
3824                            + ": " + map.getMap(), null));
3825                  }
3826                }
3827              }
3828            }
3829          }
3830          if (definition.hasDefinition()) {
3831            if (!c.getPieces().isEmpty())
3832              c.addPiece(gen.new Piece("br"));
3833            c.getPieces().add(gen.new Piece(null, "Definition: ", null).addStyle("font-weight:bold"));
3834            c.addPiece(gen.new Piece("br"));
3835            c.addMarkdown(definition.getDefinition());
3836//            c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null)));
3837          }
3838          if (definition.getComment() != null) {
3839            if (!c.getPieces().isEmpty())
3840              c.addPiece(gen.new Piece("br"));
3841            c.getPieces().add(gen.new Piece(null, "Comments: ", null).addStyle("font-weight:bold"));
3842            c.addPiece(gen.new Piece("br"));
3843            c.addMarkdown(definition.getComment());
3844//            c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null)));
3845          }
3846        }
3847      }
3848    }
3849    return c;
3850  }
3851
3852  private String buildJson(Type value) throws IOException {
3853    if (value instanceof PrimitiveType)
3854      return ((PrimitiveType) value).asStringValue();
3855
3856    IParser json = context.newJsonParser();
3857    return json.composeString(value, null);
3858  }
3859
3860  public String describeSlice(ElementDefinitionSlicingComponent slicing) {
3861    return translate("sd.table", "%s, %s by %s",
3862        slicing.getOrdered() ? translate("sd.table", "Ordered") : translate("sd.table", "Unordered"),
3863        describe(slicing.getRules()), commas(slicing.getDiscriminator()));
3864  }
3865
3866  private String commas(List<ElementDefinitionSlicingDiscriminatorComponent> list) {
3867    CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder();
3868    for (ElementDefinitionSlicingDiscriminatorComponent id : list)
3869      c.append(id.getType().toCode() + ":" + id.getPath());
3870    return c.toString();
3871  }
3872
3873  private String describe(SlicingRules rules) {
3874    if (rules == null)
3875      return translate("sd.table", "Unspecified");
3876    switch (rules) {
3877    case CLOSED:
3878      return translate("sd.table", "Closed");
3879    case OPEN:
3880      return translate("sd.table", "Open");
3881    case OPENATEND:
3882      return translate("sd.table", "Open At End");
3883    default:
3884      return "??";
3885    }
3886  }
3887
3888  private boolean onlyInformationIsMapping(List<ElementDefinition> list, ElementDefinition e) {
3889    return (!e.hasSliceName() && !e.hasSlicing() && (onlyInformationIsMapping(e))) && getChildren(list, e).isEmpty();
3890  }
3891
3892  private boolean onlyInformationIsMapping(ElementDefinition d) {
3893    return !d.hasShort() && !d.hasDefinition() && !d.hasRequirements() && !d.getAlias().isEmpty() && !d.hasMinElement()
3894        && !d.hasMax() && !d.getType().isEmpty() && !d.hasContentReference() && !d.hasExample() && !d.hasFixed()
3895        && !d.hasMaxLengthElement() && !d.getCondition().isEmpty() && !d.getConstraint().isEmpty()
3896        && !d.hasMustSupportElement() && !d.hasBinding();
3897  }
3898
3899  private boolean allAreReference(List<TypeRefComponent> types) {
3900    for (TypeRefComponent t : types) {
3901      if (!t.hasTarget())
3902        return false;
3903    }
3904    return true;
3905  }
3906
3907  private List<ElementDefinition> getChildren(List<ElementDefinition> all, ElementDefinition element) {
3908    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
3909    int i = all.indexOf(element) + 1;
3910    while (i < all.size() && all.get(i).getPath().length() > element.getPath().length()) {
3911      if ((all.get(i).getPath().substring(0, element.getPath().length() + 1).equals(element.getPath() + "."))
3912          && !all.get(i).getPath().substring(element.getPath().length() + 1).contains("."))
3913        result.add(all.get(i));
3914      i++;
3915    }
3916    return result;
3917  }
3918
3919  private String tail(String path) {
3920    if (path.contains("."))
3921      return path.substring(path.lastIndexOf('.') + 1);
3922    else
3923      return path;
3924  }
3925
3926  private boolean isDataType(String value) {
3927    StructureDefinition sd = context.fetchTypeDefinition(value);
3928    if (sd == null) // might be running before all SDs are available
3929      return Utilities.existsInList(value, "Address", "Age", "Annotation", "Attachment", "CodeableConcept", "Coding",
3930          "ContactPoint", "Count", "Distance", "Duration", "HumanName", "Identifier", "Money", "Period", "Quantity",
3931          "Range", "Ratio", "Reference", "SampledData", "Signature", "Timing", "ContactDetail", "Contributor",
3932          "DataRequirement", "Expression", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition",
3933          "UsageContext");
3934    else
3935      return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE
3936          && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION;
3937  }
3938
3939  private boolean isConstrainedDataType(String value) {
3940    StructureDefinition sd = context.fetchTypeDefinition(value);
3941    if (sd == null) // might be running before all SDs are available
3942      return Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity");
3943    else
3944      return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.CONSTRAINT;
3945  }
3946
3947  private String baseType(String value) {
3948    StructureDefinition sd = context.fetchTypeDefinition(value);
3949    if (sd != null) // might be running before all SDs are available
3950      return sd.getType();
3951    if (Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity"))
3952      return "Quantity";
3953    throw new Error("Internal error -  type not known " + value);
3954  }
3955
3956  public boolean isPrimitive(String value) {
3957    StructureDefinition sd = context.fetchTypeDefinition(value);
3958    if (sd == null) // might be running before all SDs are available
3959      return Utilities.existsInList(value, "base64Binary", "boolean", "canonical", "code", "date", "dateTime",
3960          "decimal", "id", "instant", "integer", "markdown", "oid", "positiveInt", "string", "time", "unsignedInt",
3961          "uri", "url", "uuid");
3962    else
3963      return sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
3964  }
3965
3966//  private static String listStructures(StructureDefinition p) {
3967//    StringBuilder b = new StringBuilder();
3968//    boolean first = true;
3969//    for (ProfileStructureComponent s : p.getStructure()) {
3970//      if (first)
3971//        first = false;
3972//      else
3973//        b.append(", ");
3974//      if (pkp != null && pkp.hasLinkFor(s.getType()))
3975//        b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>");
3976//      else
3977//        b.append(s.getType());
3978//    }
3979//    return b.toString();
3980//  }
3981
3982  public StructureDefinition getProfile(StructureDefinition source, String url) {
3983    StructureDefinition profile = null;
3984    String code = null;
3985    if (url.startsWith("#")) {
3986      profile = source;
3987      code = url.substring(1);
3988    } else if (context != null) {
3989      String[] parts = url.split("\\#");
3990      profile = context.fetchResource(StructureDefinition.class, parts[0]);
3991      code = parts.length == 1 ? null : parts[1];
3992    }
3993    if (profile == null)
3994      return null;
3995    if (code == null)
3996      return profile;
3997    for (Resource r : profile.getContained()) {
3998      if (r instanceof StructureDefinition && r.getId().equals(code))
3999        return (StructureDefinition) r;
4000    }
4001    return null;
4002  }
4003
4004  public static class ElementDefinitionHolder {
4005    private String name;
4006    private ElementDefinition self;
4007    private int baseIndex = 0;
4008    private List<ElementDefinitionHolder> children;
4009    private boolean placeHolder = false;
4010
4011    public ElementDefinitionHolder(ElementDefinition self, boolean isPlaceholder) {
4012      super();
4013      this.self = self;
4014      this.name = self.getPath();
4015      this.placeHolder = isPlaceholder;
4016      children = new ArrayList<ElementDefinitionHolder>();
4017    }
4018
4019    public ElementDefinitionHolder(ElementDefinition self) {
4020      this(self, false);
4021    }
4022
4023    public ElementDefinition getSelf() {
4024      return self;
4025    }
4026
4027    public List<ElementDefinitionHolder> getChildren() {
4028      return children;
4029    }
4030
4031    public int getBaseIndex() {
4032      return baseIndex;
4033    }
4034
4035    public void setBaseIndex(int baseIndex) {
4036      this.baseIndex = baseIndex;
4037    }
4038
4039    public boolean isPlaceHolder() {
4040      return this.placeHolder;
4041    }
4042
4043    @Override
4044    public String toString() {
4045      if (self.hasSliceName())
4046        return self.getPath() + "(" + self.getSliceName() + ")";
4047      else
4048        return self.getPath();
4049    }
4050  }
4051
4052  public static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> {
4053
4054    private boolean inExtension;
4055    private List<ElementDefinition> snapshot;
4056    private int prefixLength;
4057    private String base;
4058    private String name;
4059    private Set<String> errors = new HashSet<String>();
4060
4061    public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base,
4062        int prefixLength, String name) {
4063      this.inExtension = inExtension;
4064      this.snapshot = snapshot;
4065      this.prefixLength = prefixLength;
4066      this.base = base;
4067      this.name = name;
4068    }
4069
4070    @Override
4071    public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) {
4072      if (o1.getBaseIndex() == 0)
4073        o1.setBaseIndex(find(o1.getSelf().getPath()));
4074      if (o2.getBaseIndex() == 0)
4075        o2.setBaseIndex(find(o2.getSelf().getPath()));
4076      return o1.getBaseIndex() - o2.getBaseIndex();
4077    }
4078
4079    private int find(String path) {
4080      String op = path;
4081      int lc = 0;
4082      String actual = base + path.substring(prefixLength);
4083      for (int i = 0; i < snapshot.size(); i++) {
4084        String p = snapshot.get(i).getPath();
4085        if (p.equals(actual)) {
4086          return i;
4087        }
4088        if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length() - 3)) && !(actual.endsWith("[x]"))
4089            && !actual.substring(p.length() - 3).contains(".")) {
4090          return i;
4091        }
4092        if (path.startsWith(p + ".") && snapshot.get(i).hasContentReference()) {
4093          String ref = snapshot.get(i).getContentReference();
4094          if (ref.substring(1, 2).toUpperCase().equals(ref.substring(1, 2))) {
4095            actual = base + (ref.substring(1) + "." + path.substring(p.length() + 1)).substring(prefixLength);
4096            path = actual;
4097          } else {
4098            // Older versions of FHIR (e.g. 2016May) had reference of the style #parameter
4099            // instead of #Parameters.parameter, so we have to handle that
4100            actual = base
4101                + (path.substring(0, path.indexOf(".") + 1) + ref.substring(1) + "." + path.substring(p.length() + 1))
4102                    .substring(prefixLength);
4103            path = actual;
4104          }
4105
4106          i = 0;
4107          lc++;
4108          if (lc > MAX_RECURSION_LIMIT)
4109            throw new Error("Internal recursion detection: find() loop path recursion > " + MAX_RECURSION_LIMIT
4110                + " - check paths are valid (for path " + path + "/" + op + ")");
4111        }
4112      }
4113      if (prefixLength == 0)
4114        errors.add("Differential contains path " + path + " which is not found in the base");
4115      else
4116        errors.add(
4117            "Differential contains path " + path + " which is actually " + actual + ", which is not found in the base");
4118      return 0;
4119    }
4120
4121    public void checkForErrors(List<String> errorList) {
4122      if (errors.size() > 0) {
4123//        CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
4124//        for (String s : errors)
4125//          b.append("StructureDefinition "+name+": "+s);
4126//        throw new DefinitionException(b.toString());
4127        for (String s : errors)
4128          if (s.startsWith("!"))
4129            errorList.add("!StructureDefinition " + name + ": " + s.substring(1));
4130          else
4131            errorList.add("StructureDefinition " + name + ": " + s);
4132      }
4133    }
4134  }
4135
4136  public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors)
4137      throws FHIRException {
4138    final List<ElementDefinition> diffList = diff.getDifferential().getElement();
4139    int lastCount = diffList.size();
4140    // first, we move the differential elements into a tree
4141    if (diffList.isEmpty())
4142      return;
4143
4144    ElementDefinitionHolder edh = null;
4145    int i = 0;
4146    if (diffList.get(0).getPath().contains(".")) {
4147      String newPath = diffList.get(0).getPath().split("\\.")[0];
4148      ElementDefinition e = new ElementDefinition(new StringType(newPath));
4149      edh = new ElementDefinitionHolder(e, true);
4150    } else {
4151      edh = new ElementDefinitionHolder(diffList.get(0));
4152      i = 1;
4153    }
4154
4155    boolean hasSlicing = false;
4156    List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly
4157    for (ElementDefinition elt : diffList) {
4158      if (elt.hasSlicing() || paths.contains(elt.getPath())) {
4159        hasSlicing = true;
4160        break;
4161      }
4162      paths.add(elt.getPath());
4163    }
4164    if (!hasSlicing) {
4165      // if Differential does not have slicing then safe to pre-sort the list
4166      // so elements and subcomponents are together
4167      Collections.sort(diffList, new ElementNameCompare());
4168    }
4169
4170    processElementsIntoTree(edh, i, diff.getDifferential().getElement());
4171
4172    // now, we sort the siblings throughout the tree
4173    ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name);
4174    sortElements(edh, cmp, errors);
4175
4176    // now, we serialise them back to a list
4177    diffList.clear();
4178    writeElements(edh, diffList);
4179
4180    if (lastCount != diffList.size())
4181      errors.add("Sort failed: counts differ; at least one of the paths in the differential is illegal");
4182  }
4183
4184  private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) {
4185    String path = edh.getSelf().getPath();
4186    final String prefix = path + ".";
4187    while (i < list.size() && list.get(i).getPath().startsWith(prefix)) {
4188      if (list.get(i).getPath().substring(prefix.length() + 1).contains(".")) {
4189        String newPath = prefix + list.get(i).getPath().substring(prefix.length()).split("\\.")[0];
4190        ElementDefinition e = new ElementDefinition(new StringType(newPath));
4191        ElementDefinitionHolder child = new ElementDefinitionHolder(e, true);
4192        edh.getChildren().add(child);
4193        i = processElementsIntoTree(child, i, list);
4194
4195      } else {
4196        ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i));
4197        edh.getChildren().add(child);
4198        i = processElementsIntoTree(child, i + 1, list);
4199      }
4200    }
4201    return i;
4202  }
4203
4204  private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors)
4205      throws FHIRException {
4206    if (edh.getChildren().size() == 1)
4207      // special case - sort needsto allocate base numbers, but there'll be no sort if
4208      // there's only 1 child. So in that case, we just go ahead and allocated base
4209      // number directly
4210      edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath());
4211    else
4212      Collections.sort(edh.getChildren(), cmp);
4213    cmp.checkForErrors(errors);
4214
4215    for (ElementDefinitionHolder child : edh.getChildren()) {
4216      if (child.getChildren().size() > 0) {
4217        ElementDefinitionComparer ccmp = getComparer(cmp, child);
4218        if (ccmp != null)
4219          sortElements(child, ccmp, errors);
4220      }
4221    }
4222  }
4223
4224  public ElementDefinitionComparer getComparer(ElementDefinitionComparer cmp, ElementDefinitionHolder child)
4225      throws FHIRException, Error {
4226    // what we have to check for here is running off the base profile into a data
4227    // type profile
4228    ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex());
4229    ElementDefinitionComparer ccmp;
4230    if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getWorkingCode())
4231        || ed.getType().get(0).getWorkingCode().equals(ed.getPath())) {
4232      ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name);
4233    } else if (ed.getType().get(0).getWorkingCode().equals("Extension") && child.getSelf().getType().size() == 1
4234        && child.getSelf().getType().get(0).hasProfile()) {
4235      StructureDefinition profile = context.fetchResource(StructureDefinition.class,
4236          child.getSelf().getType().get(0).getProfile().get(0).getValue());
4237      if (profile == null)
4238        ccmp = null; // this might happen before everything is loaded. And we don't so much care
4239                     // about sot order in this case
4240      else
4241        ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(),
4242            ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
4243    } else if (ed.getType().size() == 1 && !ed.getType().get(0).getWorkingCode().equals("*")) {
4244      StructureDefinition profile = context.fetchResource(StructureDefinition.class,
4245          sdNs(ed.getType().get(0).getWorkingCode()));
4246      if (profile == null)
4247        throw new FHIRException(
4248            "Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath());
4249      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(),
4250          ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
4251    } else if (child.getSelf().getType().size() == 1) {
4252      StructureDefinition profile = context.fetchResource(StructureDefinition.class,
4253          sdNs(child.getSelf().getType().get(0).getWorkingCode()));
4254      if (profile == null)
4255        throw new FHIRException(
4256            "Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath());
4257      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(),
4258          child.getSelf().getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
4259    } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) {
4260      String edLastNode = ed.getPath().replaceAll("(.*\\.)*(.*)", "$2");
4261      String childLastNode = child.getSelf().getPath().replaceAll("(.*\\.)*(.*)", "$2");
4262      String p = childLastNode.substring(edLastNode.length() - 3);
4263      if (isPrimitive(Utilities.uncapitalize(p)))
4264        p = Utilities.uncapitalize(p);
4265      StructureDefinition sd = context.fetchResource(StructureDefinition.class, sdNs(p));
4266      if (sd == null)
4267        throw new Error("Unable to find profile '" + p + "' at " + ed.getId());
4268      ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(),
4269          cmp.name);
4270    } else if (child.getSelf().hasType() && child.getSelf().getType().get(0).getWorkingCode().equals("Reference")) {
4271      for (TypeRefComponent t : child.getSelf().getType()) {
4272        if (!t.getWorkingCode().equals("Reference")) {
4273          throw new Error(
4274              "Can't have children on an element with a polymorphic type - you must slice and constrain the types first (sortElements: "
4275                  + ed.getPath() + ":" + typeCode(ed.getType()) + ")");
4276        }
4277      }
4278      StructureDefinition profile = context.fetchResource(StructureDefinition.class,
4279          sdNs(ed.getType().get(0).getWorkingCode()));
4280      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(),
4281          ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
4282    } else if (!child.getSelf().hasType() && ed.getType().get(0).getWorkingCode().equals("Reference")) {
4283      for (TypeRefComponent t : ed.getType()) {
4284        if (!t.getWorkingCode().equals("Reference")) {
4285          throw new Error("Not handled yet (sortElements: " + ed.getPath() + ":" + typeCode(ed.getType()) + ")");
4286        }
4287      }
4288      StructureDefinition profile = context.fetchResource(StructureDefinition.class,
4289          sdNs(ed.getType().get(0).getWorkingCode()));
4290      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(),
4291          ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
4292    } else {
4293      // this is allowed if we only profile the extensions
4294      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs("Element"));
4295      if (profile == null)
4296        throw new FHIRException(
4297            "Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath());
4298      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), "Element",
4299          child.getSelf().getPath().length(), cmp.name);
4300//      throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")");
4301    }
4302    return ccmp;
4303  }
4304
4305  private static String sdNs(String type) {
4306    return sdNs(type, null);
4307  }
4308
4309  public static String sdNs(String type, String overrideVersionNs) {
4310    if (Utilities.isAbsoluteUrl(type))
4311      return type;
4312    else if (overrideVersionNs != null)
4313      return Utilities.pathURL(overrideVersionNs, type);
4314    else
4315      return "http://hl7.org/fhir/StructureDefinition/" + type;
4316  }
4317
4318  private boolean isAbstract(String code) {
4319    return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource")
4320        || code.equals("DomainResource");
4321  }
4322
4323  private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) {
4324    if (!edh.isPlaceHolder())
4325      list.add(edh.getSelf());
4326    for (ElementDefinitionHolder child : edh.getChildren()) {
4327      writeElements(child, list);
4328    }
4329  }
4330
4331  /**
4332   * First compare element by path then by name if same
4333   */
4334  private static class ElementNameCompare implements Comparator<ElementDefinition> {
4335
4336    @Override
4337    public int compare(ElementDefinition o1, ElementDefinition o2) {
4338      String path1 = normalizePath(o1);
4339      String path2 = normalizePath(o2);
4340      int cmp = path1.compareTo(path2);
4341      if (cmp == 0) {
4342        String name1 = o1.hasSliceName() ? o1.getSliceName() : "";
4343        String name2 = o2.hasSliceName() ? o2.getSliceName() : "";
4344        cmp = name1.compareTo(name2);
4345      }
4346      return cmp;
4347    }
4348
4349    private static String normalizePath(ElementDefinition e) {
4350      if (!e.hasPath())
4351        return "";
4352      String path = e.getPath();
4353      // if sorting element names make sure onset[x] appears before onsetAge,
4354      // onsetDate, etc.
4355      // so strip off the [x] suffix when comparing the path names.
4356      if (path.endsWith("[x]")) {
4357        path = path.substring(0, path.length() - 3);
4358      }
4359      return path;
4360    }
4361
4362  }
4363
4364  // generate schematrons for the rules in a structure definition
4365  public void generateSchematrons(OutputStream dest, StructureDefinition structure)
4366      throws IOException, DefinitionException {
4367    if (structure.getDerivation() != TypeDerivationRule.CONSTRAINT)
4368      throw new DefinitionException("not the right kind of structure to generate schematrons for");
4369    if (!structure.hasSnapshot())
4370      throw new DefinitionException("needs a snapshot");
4371
4372    StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBaseDefinition());
4373
4374    if (base != null) {
4375      SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName());
4376
4377      ElementDefinition ed = structure.getSnapshot().getElement().get(0);
4378      generateForChildren(sch, "f:" + ed.getPath(), ed, structure, base);
4379      sch.dump();
4380    }
4381  }
4382
4383  // generate a CSV representation of the structure definition
4384  public void generateCsvs(OutputStream dest, StructureDefinition structure, boolean asXml)
4385      throws IOException, DefinitionException, Exception {
4386    if (!structure.hasSnapshot())
4387      throw new DefinitionException("needs a snapshot");
4388
4389    CSVWriter csv = new CSVWriter(dest, structure, asXml);
4390
4391    for (ElementDefinition child : structure.getSnapshot().getElement()) {
4392      csv.processElement(child);
4393    }
4394    csv.dump();
4395  }
4396
4397  // generate an Excel representation of the structure definition
4398  public void generateXlsx(OutputStream dest, StructureDefinition structure, boolean asXml,
4399      boolean hideMustSupportFalse) throws IOException, DefinitionException, Exception {
4400    if (!structure.hasSnapshot())
4401      throw new DefinitionException("needs a snapshot");
4402
4403    XLSXWriter xlsx = new XLSXWriter(dest, structure, asXml, hideMustSupportFalse);
4404
4405    for (ElementDefinition child : structure.getSnapshot().getElement()) {
4406      xlsx.processElement(child);
4407    }
4408    xlsx.dump();
4409    xlsx.close();
4410  }
4411
4412  private class Slicer extends ElementDefinitionSlicingComponent {
4413    String criteria = "";
4414    String name = "";
4415    boolean check;
4416
4417    public Slicer(boolean cantCheck) {
4418      super();
4419      this.check = cantCheck;
4420    }
4421  }
4422
4423  private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing,
4424      StructureDefinition structure) {
4425    // given a child in a structure, it's sliced. figure out the slicing xpath
4426    if (child.getPath().endsWith(".extension")) {
4427      ElementDefinition ued = getUrlFor(structure, child);
4428      if ((ued == null || !ued.hasFixed()) && !(child.hasType() && (child.getType().get(0).hasProfile())))
4429        return new Slicer(false);
4430      else {
4431        Slicer s = new Slicer(true);
4432        String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).getValue()
4433            : ((UriType) ued.getFixed()).asStringValue();
4434        s.name = " with URL = '" + url + "'";
4435        s.criteria = "[@url = '" + url + "']";
4436        return s;
4437      }
4438    } else
4439      return new Slicer(false);
4440  }
4441
4442  private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed,
4443      StructureDefinition structure, StructureDefinition base) throws IOException {
4444    // generateForChild(txt, structure, child);
4445    List<ElementDefinition> children = getChildList(structure, ed);
4446    String sliceName = null;
4447    ElementDefinitionSlicingComponent slicing = null;
4448    for (ElementDefinition child : children) {
4449      String name = tail(child.getPath());
4450      if (child.hasSlicing()) {
4451        sliceName = name;
4452        slicing = child.getSlicing();
4453      } else if (!name.equals(sliceName))
4454        slicing = null;
4455
4456      ElementDefinition based = getByPath(base, child.getPath());
4457      boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin()));
4458      boolean doMax = child.hasMax() && !child.getMax().equals("*")
4459          && (based == null || (!child.getMax().equals(based.getMax())));
4460      Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure);
4461      if (slicer.check) {
4462        if (doMin || doMax) {
4463          Section s = sch.section(xpath);
4464          Rule r = s.rule(xpath);
4465          if (doMin)
4466            r.assrt("count(f:" + name + slicer.criteria + ") >= " + Integer.toString(child.getMin()),
4467                name + slicer.name + ": minimum cardinality of '" + name + "' is " + Integer.toString(child.getMin()));
4468          if (doMax)
4469            r.assrt("count(f:" + name + slicer.criteria + ") <= " + child.getMax(),
4470                name + slicer.name + ": maximum cardinality of '" + name + "' is " + child.getMax());
4471        }
4472      }
4473    }
4474    for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
4475      if (inv.hasXpath()) {
4476        Section s = sch.section(ed.getPath());
4477        Rule r = s.rule(xpath);
4478        r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId() + ": " : "") + inv.getHuman()
4479            + (inv.hasUserData(IS_DERIVED) ? " (inherited)" : ""));
4480      }
4481    }
4482    for (ElementDefinition child : children) {
4483      String name = tail(child.getPath());
4484      generateForChildren(sch, xpath + "/f:" + name, child, structure, base);
4485    }
4486  }
4487
4488  private ElementDefinition getByPath(StructureDefinition base, String path) {
4489    for (ElementDefinition ed : base.getSnapshot().getElement()) {
4490      if (ed.getPath().equals(path))
4491        return ed;
4492      if (ed.getPath().endsWith("[x]") && ed.getPath().length() <= path.length() - 3
4493          && ed.getPath().substring(0, ed.getPath().length() - 3).equals(path.substring(0, ed.getPath().length() - 3)))
4494        return ed;
4495    }
4496    return null;
4497  }
4498
4499  public void setIds(StructureDefinition sd, boolean checkFirst) throws DefinitionException {
4500    if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) {
4501      if (!sd.hasDifferential())
4502        sd.setDifferential(new StructureDefinitionDifferentialComponent());
4503      generateIds(sd.getDifferential().getElement(), sd.getUrl());
4504    }
4505    if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) {
4506      if (!sd.hasSnapshot())
4507        sd.setSnapshot(new StructureDefinitionSnapshotComponent());
4508      generateIds(sd.getSnapshot().getElement(), sd.getUrl());
4509    }
4510  }
4511
4512  private boolean hasMissingIds(List<ElementDefinition> list) {
4513    for (ElementDefinition ed : list) {
4514      if (!ed.hasId())
4515        return true;
4516    }
4517    return false;
4518  }
4519
4520  public class SliceList {
4521
4522    private Map<String, String> slices = new HashMap<>();
4523
4524    public void seeElement(ElementDefinition ed) {
4525      Iterator<Map.Entry<String, String>> iter = slices.entrySet().iterator();
4526      while (iter.hasNext()) {
4527        Map.Entry<String, String> entry = iter.next();
4528        if (entry.getKey().length() > ed.getPath().length() || entry.getKey().equals(ed.getPath()))
4529          iter.remove();
4530      }
4531
4532      if (ed.hasSliceName())
4533        slices.put(ed.getPath(), ed.getSliceName());
4534    }
4535
4536    public String[] analyse(List<String> paths) {
4537      String s = paths.get(0);
4538      String[] res = new String[paths.size()];
4539      res[0] = null;
4540      for (int i = 1; i < paths.size(); i++) {
4541        s = s + "." + paths.get(i);
4542        if (slices.containsKey(s))
4543          res[i] = slices.get(s);
4544        else
4545          res[i] = null;
4546      }
4547      return res;
4548    }
4549
4550  }
4551
4552  private void generateIds(List<ElementDefinition> list, String name) throws DefinitionException {
4553    if (list.isEmpty())
4554      return;
4555
4556    Map<String, String> idMap = new HashMap<String, String>();
4557    Map<String, String> idList = new HashMap<String, String>();
4558
4559    SliceList sliceInfo = new SliceList();
4560    // first pass, update the element ids
4561    for (ElementDefinition ed : list) {
4562      List<String> paths = new ArrayList<String>();
4563      if (!ed.hasPath())
4564        throw new DefinitionException(
4565            "No path on element Definition " + Integer.toString(list.indexOf(ed)) + " in " + name);
4566      sliceInfo.seeElement(ed);
4567      String[] pl = ed.getPath().split("\\.");
4568      for (int i = paths.size(); i < pl.length; i++) // -1 because the last path is in focus
4569        paths.add(pl[i]);
4570      String slices[] = sliceInfo.analyse(paths);
4571
4572      StringBuilder b = new StringBuilder();
4573      b.append(paths.get(0));
4574      for (int i = 1; i < paths.size(); i++) {
4575        b.append(".");
4576        String s = paths.get(i);
4577        String p = slices[i];
4578        b.append(s);
4579        if (p != null) {
4580          b.append(":");
4581          b.append(p);
4582        }
4583      }
4584      String bs = b.toString();
4585      idMap.put(ed.hasId() ? ed.getId() : ed.getPath(), bs);
4586      ed.setId(bs);
4587      if (idList.containsKey(bs)) {
4588        if (exception || messages == null)
4589          throw new DefinitionException(
4590              "Same id '" + bs + "'on multiple elements " + idList.get(bs) + "/" + ed.getPath() + " in " + name);
4591        else
4592          messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE,
4593              name + "." + bs, "Duplicate Element id " + bs, ValidationMessage.IssueSeverity.ERROR));
4594      }
4595      idList.put(bs, ed.getPath());
4596      if (ed.hasContentReference()) {
4597        String s = ed.getContentReference().substring(1);
4598        if (idMap.containsKey(s))
4599          ed.setContentReference("#" + idMap.get(s));
4600
4601      }
4602    }
4603    // second path - fix up any broken path based id references
4604
4605  }
4606
4607//  private String describeExtension(ElementDefinition ed) {
4608//    if (!ed.hasType() || !ed.getTypeFirstRep().hasProfile())
4609//      return "";
4610//    return "$"+urlTail(ed.getTypeFirstRep().getProfile());
4611//  }
4612//
4613
4614  private String urlTail(String profile) {
4615    return profile.contains("/") ? profile.substring(profile.lastIndexOf("/") + 1) : profile;
4616  }
4617
4618  private String checkName(String name) {
4619//    if (name.contains("."))
4620////      throw new Exception("Illegal name "+name+": no '.'");
4621//    if (name.contains(" "))
4622//      throw new Exception("Illegal name "+name+": no spaces");
4623    StringBuilder b = new StringBuilder();
4624    for (char c : name.toCharArray()) {
4625      if (!Utilities.existsInList(c, '.', ' ', ':', '"', '\'', '(', ')', '&', '[', ']'))
4626        b.append(c);
4627    }
4628    return b.toString().toLowerCase();
4629  }
4630
4631  private int charCount(String path, char t) {
4632    int res = 0;
4633    for (char ch : path.toCharArray()) {
4634      if (ch == t)
4635        res++;
4636    }
4637    return res;
4638  }
4639
4640//
4641//private void generateForChild(TextStreamWriter txt,
4642//    StructureDefinition structure, ElementDefinition child) {
4643//  // TODO Auto-generated method stub
4644//
4645//}
4646
4647  private interface ExampleValueAccessor {
4648    Type getExampleValue(ElementDefinition ed);
4649
4650    String getId();
4651  }
4652
4653  private class BaseExampleValueAccessor implements ExampleValueAccessor {
4654    @Override
4655    public Type getExampleValue(ElementDefinition ed) {
4656      if (ed.hasFixed())
4657        return ed.getFixed();
4658      if (ed.hasExample())
4659        return ed.getExample().get(0).getValue();
4660      else
4661        return null;
4662    }
4663
4664    @Override
4665    public String getId() {
4666      return "-genexample";
4667    }
4668  }
4669
4670  private class ExtendedExampleValueAccessor implements ExampleValueAccessor {
4671    private String index;
4672
4673    public ExtendedExampleValueAccessor(String index) {
4674      this.index = index;
4675    }
4676
4677    @Override
4678    public Type getExampleValue(ElementDefinition ed) {
4679      if (ed.hasFixed())
4680        return ed.getFixed();
4681      for (Extension ex : ed.getExtension()) {
4682        String ndx = ToolingExtensions.readStringExtension(ex, "index");
4683        Type value = ToolingExtensions.getExtension(ex, "exValue").getValue();
4684        if (index.equals(ndx) && value != null)
4685          return value;
4686      }
4687      return null;
4688    }
4689
4690    @Override
4691    public String getId() {
4692      return "-genexample-" + index;
4693    }
4694  }
4695
4696  public List<org.hl7.fhir.r4.elementmodel.Element> generateExamples(StructureDefinition sd, boolean evenWhenNoExamples)
4697      throws FHIRException {
4698    List<org.hl7.fhir.r4.elementmodel.Element> examples = new ArrayList<org.hl7.fhir.r4.elementmodel.Element>();
4699    if (sd.hasSnapshot()) {
4700      if (evenWhenNoExamples || hasAnyExampleValues(sd))
4701        examples.add(generateExample(sd, new BaseExampleValueAccessor()));
4702      for (int i = 1; i <= 50; i++) {
4703        if (hasAnyExampleValues(sd, Integer.toString(i)))
4704          examples.add(generateExample(sd, new ExtendedExampleValueAccessor(Integer.toString(i))));
4705      }
4706    }
4707    return examples;
4708  }
4709
4710  private org.hl7.fhir.r4.elementmodel.Element generateExample(StructureDefinition profile,
4711      ExampleValueAccessor accessor) throws FHIRException {
4712    ElementDefinition ed = profile.getSnapshot().getElementFirstRep();
4713    org.hl7.fhir.r4.elementmodel.Element r = new org.hl7.fhir.r4.elementmodel.Element(ed.getPath(),
4714        new Property(context, ed, profile));
4715    List<ElementDefinition> children = getChildMap(profile, ed);
4716    for (ElementDefinition child : children) {
4717      if (child.getPath().endsWith(".id")) {
4718        org.hl7.fhir.r4.elementmodel.Element id = new org.hl7.fhir.r4.elementmodel.Element("id",
4719            new Property(context, child, profile));
4720        id.setValue(profile.getId() + accessor.getId());
4721        r.getChildren().add(id);
4722      } else {
4723        org.hl7.fhir.r4.elementmodel.Element e = createExampleElement(profile, child, accessor);
4724        if (e != null)
4725          r.getChildren().add(e);
4726      }
4727    }
4728    return r;
4729  }
4730
4731  private org.hl7.fhir.r4.elementmodel.Element createExampleElement(StructureDefinition profile, ElementDefinition ed,
4732      ExampleValueAccessor accessor) throws FHIRException {
4733    Type v = accessor.getExampleValue(ed);
4734    if (v != null) {
4735      return new ObjectConverter(context).convert(new Property(context, ed, profile), v);
4736    } else {
4737      org.hl7.fhir.r4.elementmodel.Element res = new org.hl7.fhir.r4.elementmodel.Element(tail(ed.getPath()),
4738          new Property(context, ed, profile));
4739      boolean hasValue = false;
4740      List<ElementDefinition> children = getChildMap(profile, ed);
4741      for (ElementDefinition child : children) {
4742        if (!child.hasContentReference()) {
4743          org.hl7.fhir.r4.elementmodel.Element e = createExampleElement(profile, child, accessor);
4744          if (e != null) {
4745            hasValue = true;
4746            res.getChildren().add(e);
4747          }
4748        }
4749      }
4750      if (hasValue)
4751        return res;
4752      else
4753        return null;
4754    }
4755  }
4756
4757  private boolean hasAnyExampleValues(StructureDefinition sd, String index) {
4758    for (ElementDefinition ed : sd.getSnapshot().getElement())
4759      for (Extension ex : ed.getExtension()) {
4760        String ndx = ToolingExtensions.readStringExtension(ex, "index");
4761        Extension exv = ToolingExtensions.getExtension(ex, "exValue");
4762        if (exv != null) {
4763          Type value = exv.getValue();
4764          if (index.equals(ndx) && value != null)
4765            return true;
4766        }
4767      }
4768    return false;
4769  }
4770
4771  private boolean hasAnyExampleValues(StructureDefinition sd) {
4772    for (ElementDefinition ed : sd.getSnapshot().getElement())
4773      if (ed.hasExample())
4774        return true;
4775    return false;
4776  }
4777
4778  public void populateLogicalSnapshot(StructureDefinition sd) throws FHIRException {
4779    sd.getSnapshot().getElement().add(sd.getDifferential().getElementFirstRep().copy());
4780
4781    if (sd.hasBaseDefinition()) {
4782      StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
4783      if (base == null)
4784        throw new FHIRException(
4785            "Unable to find base definition for logical model: " + sd.getBaseDefinition() + " from " + sd.getUrl());
4786      copyElements(sd, base.getSnapshot().getElement());
4787    }
4788    copyElements(sd, sd.getDifferential().getElement());
4789  }
4790
4791  private void copyElements(StructureDefinition sd, List<ElementDefinition> list) {
4792    for (ElementDefinition ed : list) {
4793      if (ed.getPath().contains(".")) {
4794        ElementDefinition n = ed.copy();
4795        n.setPath(sd.getSnapshot().getElementFirstRep().getPath() + "."
4796            + ed.getPath().substring(ed.getPath().indexOf(".") + 1));
4797        sd.getSnapshot().addElement(n);
4798      }
4799    }
4800  }
4801
4802  public void cleanUpDifferential(StructureDefinition sd) {
4803    if (sd.getDifferential().getElement().size() > 1)
4804      cleanUpDifferential(sd, 1);
4805  }
4806
4807  private void cleanUpDifferential(StructureDefinition sd, int start) {
4808    int level = Utilities.charCount(sd.getDifferential().getElement().get(start).getPath(), '.');
4809    int c = start;
4810    int len = sd.getDifferential().getElement().size();
4811    HashSet<String> paths = new HashSet<String>();
4812    while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') == level) {
4813      ElementDefinition ed = sd.getDifferential().getElement().get(c);
4814      if (!paths.contains(ed.getPath())) {
4815        paths.add(ed.getPath());
4816        int ic = c + 1;
4817        while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level)
4818          ic++;
4819        ElementDefinition slicer = null;
4820        List<ElementDefinition> slices = new ArrayList<ElementDefinition>();
4821        slices.add(ed);
4822        while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') == level) {
4823          ElementDefinition edi = sd.getDifferential().getElement().get(ic);
4824          if (ed.getPath().equals(edi.getPath())) {
4825            if (slicer == null) {
4826              slicer = new ElementDefinition();
4827              slicer.setPath(edi.getPath());
4828              slicer.getSlicing().setRules(SlicingRules.OPEN);
4829              sd.getDifferential().getElement().add(c, slicer);
4830              c++;
4831              ic++;
4832            }
4833            slices.add(edi);
4834          }
4835          ic++;
4836          while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level)
4837            ic++;
4838        }
4839        // now we're at the end, we're going to figure out the slicing discriminator
4840        if (slicer != null)
4841          determineSlicing(slicer, slices);
4842      }
4843      c++;
4844      if (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) {
4845        cleanUpDifferential(sd, c);
4846        c++;
4847        while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level)
4848          c++;
4849      }
4850    }
4851  }
4852
4853  private void determineSlicing(ElementDefinition slicer, List<ElementDefinition> slices) {
4854    // first, name them
4855    int i = 0;
4856    for (ElementDefinition ed : slices) {
4857      if (ed.hasUserData("slice-name")) {
4858        ed.setSliceName(ed.getUserString("slice-name"));
4859      } else {
4860        i++;
4861        ed.setSliceName("slice-" + Integer.toString(i));
4862      }
4863    }
4864    // now, the hard bit, how are they differentiated?
4865    // right now, we hard code this...
4866    if (slicer.getPath().endsWith(".extension") || slicer.getPath().endsWith(".modifierExtension"))
4867      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url");
4868    else if (slicer.getPath().equals("DiagnosticReport.result"))
4869      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("reference.code");
4870    else if (slicer.getPath().equals("Observation.related"))
4871      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("target.reference.code");
4872    else if (slicer.getPath().equals("Bundle.entry"))
4873      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("resource.@profile");
4874    else
4875      throw new Error("No slicing for " + slicer.getPath());
4876  }
4877
4878  public class SpanEntry {
4879    private List<SpanEntry> children = new ArrayList<SpanEntry>();
4880    private boolean profile;
4881    private String id;
4882    private String name;
4883    private String resType;
4884    private String cardinality;
4885    private String description;
4886    private String profileLink;
4887    private String resLink;
4888    private String type;
4889
4890    public String getName() {
4891      return name;
4892    }
4893
4894    public void setName(String name) {
4895      this.name = name;
4896    }
4897
4898    public String getResType() {
4899      return resType;
4900    }
4901
4902    public void setResType(String resType) {
4903      this.resType = resType;
4904    }
4905
4906    public String getCardinality() {
4907      return cardinality;
4908    }
4909
4910    public void setCardinality(String cardinality) {
4911      this.cardinality = cardinality;
4912    }
4913
4914    public String getDescription() {
4915      return description;
4916    }
4917
4918    public void setDescription(String description) {
4919      this.description = description;
4920    }
4921
4922    public String getProfileLink() {
4923      return profileLink;
4924    }
4925
4926    public void setProfileLink(String profileLink) {
4927      this.profileLink = profileLink;
4928    }
4929
4930    public String getResLink() {
4931      return resLink;
4932    }
4933
4934    public void setResLink(String resLink) {
4935      this.resLink = resLink;
4936    }
4937
4938    public String getId() {
4939      return id;
4940    }
4941
4942    public void setId(String id) {
4943      this.id = id;
4944    }
4945
4946    public boolean isProfile() {
4947      return profile;
4948    }
4949
4950    public void setProfile(boolean profile) {
4951      this.profile = profile;
4952    }
4953
4954    public List<SpanEntry> getChildren() {
4955      return children;
4956    }
4957
4958    public String getType() {
4959      return type;
4960    }
4961
4962    public void setType(String type) {
4963      this.type = type;
4964    }
4965
4966  }
4967
4968  public XhtmlNode generateSpanningTable(StructureDefinition profile, String imageFolder, boolean onlyConstraints,
4969      String constraintPrefix, Set<String> outputTracker) throws IOException, FHIRException {
4970    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(new RenderingI18nContext(), imageFolder, false, true);
4971    TableModel model = initSpanningTable(gen, "", false, profile.getId());
4972    Set<String> processed = new HashSet<String>();
4973    SpanEntry span = buildSpanningTable("(focus)", "", profile, processed, onlyConstraints, constraintPrefix);
4974
4975    genSpanEntry(gen, model.getRows(), span);
4976    return gen.generate(model, "", 0, outputTracker);
4977  }
4978
4979  private SpanEntry buildSpanningTable(String name, String cardinality, StructureDefinition profile,
4980      Set<String> processed, boolean onlyConstraints, String constraintPrefix) throws IOException {
4981    SpanEntry res = buildSpanEntryFromProfile(name, cardinality, profile);
4982    boolean wantProcess = !processed.contains(profile.getUrl());
4983    processed.add(profile.getUrl());
4984    if (wantProcess && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
4985      for (ElementDefinition ed : profile.getSnapshot().getElement()) {
4986        if (!"0".equals(ed.getMax()) && ed.getType().size() > 0) {
4987          String card = getCardinality(ed, profile.getSnapshot().getElement());
4988          if (!card.endsWith(".0")) {
4989            List<String> refProfiles = listReferenceProfiles(ed);
4990            if (refProfiles.size() > 0) {
4991              String uri = refProfiles.get(0);
4992              if (uri != null) {
4993                StructureDefinition sd = context.fetchResource(StructureDefinition.class, uri);
4994                if (sd != null && (!onlyConstraints || (sd.getDerivation() == TypeDerivationRule.CONSTRAINT
4995                    && (constraintPrefix == null || sd.getUrl().startsWith(constraintPrefix))))) {
4996                  res.getChildren().add(
4997                      buildSpanningTable(nameForElement(ed), card, sd, processed, onlyConstraints, constraintPrefix));
4998                }
4999              }
5000            }
5001          }
5002        }
5003      }
5004    }
5005    return res;
5006  }
5007
5008  private String getCardinality(ElementDefinition ed, List<ElementDefinition> list) {
5009    int min = ed.getMin();
5010    int max = !ed.hasMax() || ed.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(ed.getMax());
5011    while (ed != null && ed.getPath().contains(".")) {
5012      ed = findParent(ed, list);
5013      if (ed.getMax().equals("0"))
5014        max = 0;
5015      else if (!ed.getMax().equals("1") && !ed.hasSlicing())
5016        max = Integer.MAX_VALUE;
5017      if (ed.getMin() == 0)
5018        min = 0;
5019    }
5020    return Integer.toString(min) + ".." + (max == Integer.MAX_VALUE ? "*" : Integer.toString(max));
5021  }
5022
5023  private ElementDefinition findParent(ElementDefinition ed, List<ElementDefinition> list) {
5024    int i = list.indexOf(ed) - 1;
5025    while (i >= 0 && !ed.getPath().startsWith(list.get(i).getPath() + "."))
5026      i--;
5027    if (i == -1)
5028      return null;
5029    else
5030      return list.get(i);
5031  }
5032
5033  private List<String> listReferenceProfiles(ElementDefinition ed) {
5034    List<String> res = new ArrayList<String>();
5035    for (TypeRefComponent tr : ed.getType()) {
5036      // code is null if we're dealing with "value" and profile is null if we just
5037      // have Reference()
5038      if (tr.hasTarget() && tr.hasTargetProfile())
5039        for (UriType u : tr.getTargetProfile())
5040          res.add(u.getValue());
5041    }
5042    return res;
5043  }
5044
5045  private String nameForElement(ElementDefinition ed) {
5046    return ed.getPath().substring(ed.getPath().indexOf(".") + 1);
5047  }
5048
5049  private SpanEntry buildSpanEntryFromProfile(String name, String cardinality, StructureDefinition profile)
5050      throws IOException {
5051    SpanEntry res = new SpanEntry();
5052    res.setName(name);
5053    res.setCardinality(cardinality);
5054    res.setProfileLink(profile.getUserString("path"));
5055    res.setResType(profile.getType());
5056    StructureDefinition base = context.fetchResource(StructureDefinition.class, res.getResType());
5057    if (base != null)
5058      res.setResLink(base.getUserString("path"));
5059    res.setId(profile.getId());
5060    res.setProfile(profile.getDerivation() == TypeDerivationRule.CONSTRAINT);
5061    StringBuilder b = new StringBuilder();
5062    b.append(res.getResType());
5063    boolean first = true;
5064    boolean open = false;
5065    if (profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
5066      res.setDescription(profile.getName());
5067      for (ElementDefinition ed : profile.getSnapshot().getElement()) {
5068        if (isKeyProperty(ed.getBase().getPath()) && ed.hasFixed()) {
5069          if (first) {
5070            open = true;
5071            first = false;
5072            b.append("[");
5073          } else {
5074            b.append(", ");
5075          }
5076          b.append(tail(ed.getBase().getPath()));
5077          b.append("=");
5078          b.append(summarize(ed.getFixed()));
5079        }
5080      }
5081      if (open)
5082        b.append("]");
5083    } else
5084      res.setDescription("Base FHIR " + profile.getName());
5085    res.setType(b.toString());
5086    return res;
5087  }
5088
5089  private String summarize(Type value) throws IOException {
5090    if (value instanceof Coding)
5091      return summarizeCoding((Coding) value);
5092    else if (value instanceof CodeableConcept)
5093      return summarizeCodeableConcept((CodeableConcept) value);
5094    else
5095      return buildJson(value);
5096  }
5097
5098  private String summarizeCoding(Coding value) {
5099    String uri = value.getSystem();
5100    String system = NarrativeGenerator.describeSystem(uri);
5101    if (Utilities.isURL(system)) {
5102      if (system.equals("http://cap.org/protocols"))
5103        system = "CAP Code";
5104    }
5105    return system + " " + value.getCode();
5106  }
5107
5108  private String summarizeCodeableConcept(CodeableConcept value) {
5109    if (value.hasCoding())
5110      return summarizeCoding(value.getCodingFirstRep());
5111    else
5112      return value.getText();
5113  }
5114
5115  private boolean isKeyProperty(String path) {
5116    return Utilities.existsInList(path, "Observation.code");
5117  }
5118
5119  public TableModel initSpanningTable(HierarchicalTableGenerator gen, String prefix, boolean isLogical, String id) {
5120    TableModel model = gen.new TableModel(id, false);
5121
5122    model.setDocoImg(prefix + "help16.png");
5123    model.setDocoRef(prefix + "formats.html#table"); // todo: change to graph definition
5124    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Property", "A profiled resource", null, 0));
5125    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Card.",
5126        "Minimum and Maximum # of times the the element can appear in the instance", null, 0));
5127    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Content", "What goes here", null, 0));
5128    model.getTitles()
5129        .add(gen.new Title(null, model.getDocoRef(), "Description", "Description of the profile", null, 0));
5130    return model;
5131  }
5132
5133  private void genSpanEntry(HierarchicalTableGenerator gen, List<Row> rows, SpanEntry span) throws IOException {
5134    Row row = gen.new Row();
5135    rows.add(row);
5136    row.setAnchor(span.getId());
5137    // row.setColor(..?);
5138    if (span.isProfile())
5139      row.setIcon("icon_profile.png", HierarchicalTableGenerator.TEXT_ICON_PROFILE);
5140    else
5141      row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
5142
5143    row.getCells().add(gen.new Cell(null, null, span.getName(), null, null));
5144    row.getCells().add(gen.new Cell(null, null, span.getCardinality(), null, null));
5145    row.getCells().add(gen.new Cell(null, span.getProfileLink(), span.getType(), null, null));
5146    row.getCells().add(gen.new Cell(null, null, span.getDescription(), null, null));
5147
5148    for (SpanEntry child : span.getChildren())
5149      genSpanEntry(gen, row.getSubRows(), child);
5150  }
5151
5152  public static ElementDefinitionSlicingDiscriminatorComponent interpretR2Discriminator(String discriminator,
5153      boolean isExists) {
5154    if (discriminator.endsWith("@pattern"))
5155      return makeDiscriminator(DiscriminatorType.PATTERN,
5156          discriminator.length() == 8 ? "" : discriminator.substring(0, discriminator.length() - 9));
5157    if (discriminator.endsWith("@profile"))
5158      return makeDiscriminator(DiscriminatorType.PROFILE,
5159          discriminator.length() == 8 ? "" : discriminator.substring(0, discriminator.length() - 9));
5160    if (discriminator.endsWith("@type"))
5161      return makeDiscriminator(DiscriminatorType.TYPE,
5162          discriminator.length() == 5 ? "" : discriminator.substring(0, discriminator.length() - 6));
5163    if (discriminator.endsWith("@exists"))
5164      return makeDiscriminator(DiscriminatorType.EXISTS,
5165          discriminator.length() == 7 ? "" : discriminator.substring(0, discriminator.length() - 8));
5166    if (isExists)
5167      return makeDiscriminator(DiscriminatorType.EXISTS, discriminator);
5168    return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(discriminator);
5169  }
5170
5171  private static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType dType, String str) {
5172    return new ElementDefinitionSlicingDiscriminatorComponent().setType(dType)
5173        .setPath(Utilities.noString(str) ? "$this" : str);
5174  }
5175
5176  public static String buildR2Discriminator(ElementDefinitionSlicingDiscriminatorComponent t) throws FHIRException {
5177    switch (t.getType()) {
5178    case PROFILE:
5179      return t.getPath() + "/@profile";
5180    case TYPE:
5181      return t.getPath() + "/@type";
5182    case VALUE:
5183      return t.getPath();
5184    case PATTERN:
5185      return t.getPath();
5186    case EXISTS:
5187      return t.getPath(); // determination of value vs. exists is based on whether there's only 2 slices -
5188                          // one with minOccurs=1 and other with maxOccur=0
5189    default:
5190      throw new FHIRException("Unable to represent " + t.getType().toCode() + ":" + t.getPath() + " in R2");
5191    }
5192  }
5193
5194  public static StructureDefinition makeExtensionForVersionedURL(IWorkerContext context, String url) {
5195    String epath = url.substring(54);
5196    if (!epath.contains("."))
5197      return null;
5198    String type = epath.substring(0, epath.indexOf("."));
5199    StructureDefinition sd = context.fetchTypeDefinition(type);
5200    if (sd == null)
5201      return null;
5202    ElementDefinition ed = null;
5203    for (ElementDefinition t : sd.getSnapshot().getElement()) {
5204      if (t.getPath().equals(epath)) {
5205        ed = t;
5206        break;
5207      }
5208    }
5209    if (ed == null)
5210      return null;
5211    if ("Element".equals(ed.typeSummary()) || "BackboneElement".equals(ed.typeSummary())) {
5212      return null;
5213    } else {
5214      StructureDefinition template = context.fetchResource(StructureDefinition.class,
5215          "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities");
5216      StructureDefinition ext = template.copy();
5217      ext.setUrl(url);
5218      ext.setId("extension-" + epath);
5219      ext.setName("Extension-" + epath);
5220      ext.setTitle("Extension for r4 " + epath);
5221      ext.setStatus(sd.getStatus());
5222      ext.setDate(sd.getDate());
5223      ext.getContact().clear();
5224      ext.getContact().addAll(sd.getContact());
5225      ext.setFhirVersion(sd.getFhirVersion());
5226      ext.setDescription(ed.getDefinition());
5227      ext.getContext().clear();
5228      ext.addContext().setType(ExtensionContextType.ELEMENT).setExpression(epath.substring(0, epath.lastIndexOf(".")));
5229      ext.getDifferential().getElement().clear();
5230      ext.getSnapshot().getElement().get(3).setFixed(new UriType(url));
5231      ext.getSnapshot().getElement().set(4, ed.copy());
5232      ext.getSnapshot().getElement().get(4).setPath("Extension.value" + Utilities.capitalize(ed.typeSummary()));
5233      return ext;
5234    }
5235
5236  }
5237
5238  public boolean isThrowException() {
5239    return exception;
5240  }
5241
5242  public void setThrowException(boolean exception) {
5243    this.exception = exception;
5244  }
5245
5246  public TerminologyServiceOptions getTerminologyServiceOptions() {
5247    return terminologyServiceOptions;
5248  }
5249
5250  public void setTerminologyServiceOptions(TerminologyServiceOptions terminologyServiceOptions) {
5251    this.terminologyServiceOptions = terminologyServiceOptions;
5252  }
5253
5254  public boolean isNewSlicingProcessing() {
5255    return newSlicingProcessing;
5256  }
5257
5258  public void setNewSlicingProcessing(boolean newSlicingProcessing) {
5259    this.newSlicingProcessing = newSlicingProcessing;
5260  }
5261
5262  public boolean isDebug() {
5263    return debug;
5264  }
5265
5266  public void setDebug(boolean debug) {
5267    this.debug = debug;
5268  }
5269  
5270
5271  public static boolean isExtensionDefinition(StructureDefinition sd) {
5272    return sd.getDerivation() == TypeDerivationRule.CONSTRAINT && sd.getType().equals("Extension");
5273  }
5274
5275  public static boolean isSimpleExtension(StructureDefinition sd) {
5276    if (!isExtensionDefinition(sd)) {
5277      return false;
5278    }
5279    ElementDefinition value = sd.getSnapshot().getElementByPath("Extension.value");
5280    return value != null && !value.isProhibited();
5281  }
5282
5283  public static boolean isComplexExtension(StructureDefinition sd) {
5284    if (!isExtensionDefinition(sd)) {
5285      return false;
5286    }
5287    ElementDefinition value = sd.getSnapshot().getElementByPath("Extension.value");
5288    return value == null || value.isProhibited();
5289  }
5290
5291  public static boolean isModifierExtension(StructureDefinition sd) {
5292    ElementDefinition defn = sd.getSnapshot().getElementByPath("Extension");
5293    return defn.getIsModifier();
5294  }
5295
5296}