001package org.hl7.fhir.r5.conformance.profile;
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
032
033import java.io.IOException;
034import java.io.OutputStream;
035import java.util.ArrayList;
036import java.util.Arrays;
037import java.util.Collections;
038import java.util.Comparator;
039import java.util.Date;
040import java.util.HashMap;
041import java.util.HashSet;
042import java.util.Iterator;
043import java.util.List;
044import java.util.Map;
045import java.util.Set;
046
047import org.hl7.fhir.exceptions.DefinitionException;
048import org.hl7.fhir.exceptions.FHIRException;
049import org.hl7.fhir.exceptions.FHIRFormatError;
050import org.hl7.fhir.r5.conformance.ElementRedirection;
051import org.hl7.fhir.r5.conformance.profile.MappingAssistant.MappingMergeModeOption;
052import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.AllowUnknownProfile;
053import org.hl7.fhir.r5.conformance.profile.ProfileUtilities.ElementDefinitionCounter;
054import org.hl7.fhir.r5.context.IWorkerContext;
055import org.hl7.fhir.r5.elementmodel.ObjectConverter;
056import org.hl7.fhir.r5.elementmodel.Property;
057import org.hl7.fhir.r5.fhirpath.ExpressionNode;
058import org.hl7.fhir.r5.fhirpath.FHIRPathEngine;
059import org.hl7.fhir.r5.fhirpath.ExpressionNode.Kind;
060import org.hl7.fhir.r5.fhirpath.ExpressionNode.Operation;
061import org.hl7.fhir.r5.model.Base;
062import org.hl7.fhir.r5.model.BooleanType;
063import org.hl7.fhir.r5.model.CanonicalType;
064import org.hl7.fhir.r5.model.Coding;
065import org.hl7.fhir.r5.model.DataType;
066import org.hl7.fhir.r5.model.Element;
067import org.hl7.fhir.r5.model.ElementDefinition;
068import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType;
069import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBaseComponent;
070import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingAdditionalComponent;
071import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
072import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent;
073import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionExampleComponent;
074import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent;
075import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingComponent;
076import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent;
077import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules;
078import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
079import org.hl7.fhir.r5.model.Enumerations.BindingStrength;
080import org.hl7.fhir.r5.model.Enumerations.FHIRVersion;
081import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
082import org.hl7.fhir.r5.model.Extension;
083import org.hl7.fhir.r5.model.IdType;
084import org.hl7.fhir.r5.model.MarkdownType;
085import org.hl7.fhir.r5.model.Resource;
086import org.hl7.fhir.r5.model.StringType;
087import org.hl7.fhir.r5.model.StructureDefinition;
088import org.hl7.fhir.r5.model.StructureDefinition.ExtensionContextType;
089import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionContextComponent;
090import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionDifferentialComponent;
091import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
092import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionMappingComponent;
093import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionSnapshotComponent;
094import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
095import org.hl7.fhir.r5.model.UriType;
096import org.hl7.fhir.r5.model.UsageContext;
097import org.hl7.fhir.r5.model.ValueSet;
098import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionComponent;
099import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent;
100import org.hl7.fhir.r5.terminologies.expansion.ValueSetExpansionOutcome;
101import org.hl7.fhir.r5.terminologies.utilities.ValidationResult;
102import org.hl7.fhir.r5.utils.ToolingExtensions;
103import org.hl7.fhir.r5.utils.XVerExtensionManager;
104import org.hl7.fhir.r5.utils.XVerExtensionManager.XVerExtensionStatus;
105import org.hl7.fhir.r5.utils.formats.CSVWriter;
106import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
107import org.hl7.fhir.utilities.FhirPublication;
108import org.hl7.fhir.utilities.Utilities;
109import org.hl7.fhir.utilities.VersionUtilities;
110import org.hl7.fhir.utilities.i18n.I18nConstants;
111import org.hl7.fhir.utilities.validation.ValidationMessage;
112import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
113import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
114import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
115import org.hl7.fhir.utilities.validation.ValidationOptions;
116import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
117import org.hl7.fhir.utilities.xml.SchematronWriter;
118import org.hl7.fhir.utilities.xml.SchematronWriter.Rule;
119import org.hl7.fhir.utilities.xml.SchematronWriter.SchematronType;
120import org.hl7.fhir.utilities.xml.SchematronWriter.Section;
121
122/** 
123 * This class provides a set of utility operations for working with Profiles.
124 * Key functionality:
125 *  * getChildMap --?
126 *  * getChildList
127 *  * generateSnapshot: Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
128 *  * closeDifferential: fill out a differential by excluding anything not mentioned
129 *  * generateExtensionsTable: generate the HTML for a hierarchical table presentation of the extensions
130 *  * generateTable: generate  the HTML for a hierarchical table presentation of a structure
131 *  * generateSpanningTable: generate the HTML for a table presentation of a network of structures, starting at a nominated point
132 *  * summarize: describe the contents of a profile
133 *  
134 * note to maintainers: Do not make modifications to the snapshot generation without first changing the snapshot generation test cases to demonstrate the grounds for your change
135 *  
136 * @author Grahame
137 *
138 */
139public class ProfileUtilities {
140
141  private static boolean suppressIgnorableExceptions;
142
143  
144  public class ElementDefinitionCounter {
145    int countMin = 0;
146    int countMax = 0;
147    int index = 0;
148    ElementDefinition focus;
149    Set<String> names = new HashSet<>();
150
151    public ElementDefinitionCounter(ElementDefinition ed, int i) {
152      focus = ed;
153      index = i;
154    }
155
156    public int checkMin() {
157      if (countMin > focus.getMin()) {
158        return countMin;
159      } else {     
160        return -1;
161      }
162    }
163
164    public int checkMax() {
165      if (countMax > max(focus.getMax())) {
166        return countMax;
167      } else {     
168        return -1;
169      }
170    }
171
172    private int max(String max) {
173      if ("*".equals(max)) {
174        return Integer.MAX_VALUE;
175      } else {
176        return Integer.parseInt(max);
177      }
178    }
179
180    public boolean count(ElementDefinition ed, String name) {
181      countMin = countMin + ed.getMin();
182      if (countMax < Integer.MAX_VALUE) {
183        int m = max(ed.getMax());
184        if (m == Integer.MAX_VALUE) {
185          countMax = m;
186        } else {
187          countMax = countMax + m;
188        }
189      }
190      boolean ok = !names.contains(name);
191      names.add(name);
192      return ok;
193    }
194
195    public ElementDefinition getFocus() {
196      return focus;
197    }
198
199    public boolean checkMinMax() {
200      return countMin <= countMax;
201    }
202
203    public int getIndex() {
204      return index;
205    }
206    
207  }
208
209  public enum AllowUnknownProfile {
210    NONE, // exception if there's any unknown profiles (the default)
211    NON_EXTNEIONS, // don't raise an exception except on Extension (because more is going on there
212    ALL_TYPES // allow any unknow profile
213  }
214
215  /**
216   * These extensions are stripped in inherited profiles (and may be replaced by 
217   */
218  
219  public static final List<String> NON_INHERITED_ED_URLS = Arrays.asList(
220      "http://hl7.org/fhir/tools/StructureDefinition/binding-definition",
221      "http://hl7.org/fhir/tools/StructureDefinition/no-binding",
222      "http://hl7.org/fhir/StructureDefinition/elementdefinition-isCommonBinding",
223      "http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status",  
224      "http://hl7.org/fhir/StructureDefinition/structuredefinition-category",
225      "http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm",
226      "http://hl7.org/fhir/StructureDefinition/structuredefinition-implements",
227      "http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name",
228      "http://hl7.org/fhir/StructureDefinition/structuredefinition-security-category",
229      "http://hl7.org/fhir/StructureDefinition/structuredefinition-wg",
230      "http://hl7.org/fhir/StructureDefinition/structuredefinition-normative-version",
231      "http://hl7.org/fhir/tools/StructureDefinition/obligation-profile",
232      "http://hl7.org/fhir/StructureDefinition/structuredefinition-standards-status-reason",
233      ToolingExtensions.EXT_SUMMARY/*,
234      ToolingExtensions.EXT_OBLIGATION_CORE,
235      ToolingExtensions.EXT_OBLIGATION_TOOLS*/);
236
237  public static final List<String> DEFAULT_INHERITED_ED_URLS = Arrays.asList(
238      "http://hl7.org/fhir/StructureDefinition/questionnaire-optionRestriction",
239      "http://hl7.org/fhir/StructureDefinition/questionnaire-referenceProfile",
240      "http://hl7.org/fhir/StructureDefinition/questionnaire-referenceResource",
241      "http://hl7.org/fhir/StructureDefinition/questionnaire-unitOption",
242
243      "http://hl7.org/fhir/StructureDefinition/mimeType");
244
245  /**
246   * These extensions are ignored when found in differentials
247   */  
248  public static final List<String> NON_OVERRIDING_ED_URLS = Arrays.asList(
249      "http://hl7.org/fhir/StructureDefinition/elementdefinition-translatable",
250      "http://hl7.org/fhir/tools/StructureDefinition/elementdefinition-json-name",
251      "http://hl7.org/fhir/tools/StructureDefinition/implied-string-prefix",
252      "http://hl7.org/fhir/tools/StructureDefinition/json-empty-behavior",
253      "http://hl7.org/fhir/tools/StructureDefinition/json-nullable",
254      "http://hl7.org/fhir/tools/StructureDefinition/json-primitive-choice",
255      "http://hl7.org/fhir/tools/StructureDefinition/json-property-key",
256      "http://hl7.org/fhir/tools/StructureDefinition/type-specifier",
257      "http://hl7.org/fhir/tools/StructureDefinition/xml-choice-group",
258      ToolingExtensions.EXT_XML_NAMESPACE, ToolingExtensions.EXT_XML_NAMESPACE_DEPRECATED,
259      ToolingExtensions.EXT_XML_NAME, ToolingExtensions.EXT_XML_NAME_DEPRECATED,
260      "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype"
261      );
262
263  /**
264   * When these extensions are found, they override whatever is set on the ancestor element 
265   */  
266  public static final List<String> OVERRIDING_ED_URLS = Arrays.asList(
267      "http://hl7.org/fhir/tools/StructureDefinition/elementdefinition-date-format",
268      ToolingExtensions.EXT_DATE_RULES,
269      "http://hl7.org/fhir/StructureDefinition/designNote",
270      "http://hl7.org/fhir/StructureDefinition/elementdefinition-allowedUnits",
271      "http://hl7.org/fhir/StructureDefinition/elementdefinition-question",
272      "http://hl7.org/fhir/StructureDefinition/entryFormat",
273      "http://hl7.org/fhir/StructureDefinition/maxDecimalPlaces",
274      "http://hl7.org/fhir/StructureDefinition/maxSize",
275      "http://hl7.org/fhir/StructureDefinition/minLength",
276      "http://hl7.org/fhir/StructureDefinition/questionnaire-choiceOrientation",
277      "http://hl7.org/fhir/StructureDefinition/questionnaire-displayCategory",
278      "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden",
279      "http://hl7.org/fhir/StructureDefinition/questionnaire-itemControl",
280      "http://hl7.org/fhir/StructureDefinition/questionnaire-signatureRequired",
281      "http://hl7.org/fhir/StructureDefinition/questionnaire-sliderStepValue",
282      "http://hl7.org/fhir/StructureDefinition/questionnaire-supportLink",
283      "http://hl7.org/fhir/StructureDefinition/questionnaire-unit",
284      "http://hl7.org/fhir/StructureDefinition/questionnaire-unitValueSet",
285      "http://hl7.org/fhir/StructureDefinition/questionnaire-usageMode",
286      "http://hl7.org/fhir/StructureDefinition/structuredefinition-display-hint",
287      "http://hl7.org/fhir/StructureDefinition/structuredefinition-explicit-type-name"
288      );
289  
290  public IWorkerContext getContext() {
291    return this.context;
292  }
293
294  public static class SourcedChildDefinitions {
295    private StructureDefinition source;
296    private List<ElementDefinition> list;
297    public SourcedChildDefinitions(StructureDefinition source, List<ElementDefinition> list) {
298      super();
299      this.source = source;
300      this.list = list;
301    }
302    public StructureDefinition getSource() {
303      return source;
304    }
305    public List<ElementDefinition> getList() {
306      return list;
307    }
308  }
309
310  public class ElementDefinitionResolution {
311
312    private StructureDefinition source;
313    private ElementDefinition element;
314
315    public ElementDefinitionResolution(StructureDefinition source, ElementDefinition element) {
316      this.source = source;
317      this.element = element;
318    }
319
320    public StructureDefinition getSource() {
321      return source;
322    }
323
324    public ElementDefinition getElement() {
325      return element;
326    }
327
328  }
329
330  public static class ElementChoiceGroup {
331    private Row row;
332    private String name;
333    private boolean mandatory;
334    private List<String> elements = new ArrayList<>();
335    
336    public ElementChoiceGroup(String name, boolean mandatory) {
337      super();
338      this.name = name;
339      this.mandatory = mandatory;
340    }
341    public Row getRow() {
342      return row;
343    }
344    public List<String> getElements() {
345      return elements;
346    }
347    public void setRow(Row row) {
348      this.row = row;      
349    }
350    public String getName() {
351      return name;
352    }
353    public boolean isMandatory() {
354      return mandatory;
355    }
356    public void setMandatory(boolean mandatory) {
357      this.mandatory = mandatory;
358    }
359    
360  }
361  
362  private static final int MAX_RECURSION_LIMIT = 10;
363  
364  public static class ExtensionContext {
365
366    private ElementDefinition element;
367    private StructureDefinition defn;
368
369    public ExtensionContext(StructureDefinition ext, ElementDefinition ed) {
370      this.defn = ext;
371      this.element = ed;
372    }
373
374    public ElementDefinition getElement() {
375      return element;
376    }
377
378    public StructureDefinition getDefn() {
379      return defn;
380    }
381
382    public String getUrl() {
383      if (element == defn.getSnapshot().getElement().get(0))
384        return defn.getUrl();
385      else
386        return element.getSliceName();
387    }
388
389    public ElementDefinition getExtensionValueDefinition() {
390      int i = defn.getSnapshot().getElement().indexOf(element)+1;
391      while (i < defn.getSnapshot().getElement().size()) {
392        ElementDefinition ed = defn.getSnapshot().getElement().get(i);
393        if (ed.getPath().equals(element.getPath()))
394          return null;
395        if (ed.getPath().startsWith(element.getPath()+".value") && !ed.hasSlicing())
396          return ed;
397        i++;
398      }
399      return null;
400    }
401  }
402  
403  public static final String UD_BASE_MODEL = "base.model";
404  public static final String UD_BASE_PATH = "base.path";
405  public static final String UD_DERIVATION_EQUALS = "derivation.equals";
406  public static final String UD_DERIVATION_POINTER = "derived.pointer";
407  public static final String UD_IS_DERIVED = "derived.fact";
408  public static final String UD_GENERATED_IN_SNAPSHOT = "profileutilities.snapshot.processed";  
409  private static final boolean COPY_BINDING_EXTENSIONS = false;
410  private static final boolean DONT_DO_THIS = false;
411  
412  private boolean debug;
413  // note that ProfileUtilities are used re-entrantly internally, so nothing with process state can be here
414  private final IWorkerContext context;
415  private FHIRPathEngine fpe;
416  private List<ValidationMessage> messages;
417  private List<String> snapshotStack = new ArrayList<String>();
418  private ProfileKnowledgeProvider pkp;
419//  private boolean igmode;
420  private boolean exception;
421  private ValidationOptions terminologyServiceOptions = new ValidationOptions(FhirPublication.R5);
422  private boolean newSlicingProcessing;
423  private String defWebRoot;
424  private boolean autoFixSliceNames;
425  private XVerExtensionManager xver;
426  private boolean wantFixDifferentialFirstElementType;
427  private Set<String> masterSourceFileNames;
428  private Set<String> localFileNames;
429  private Map<String, SourcedChildDefinitions> childMapCache = new HashMap<>();
430  private AllowUnknownProfile allowUnknownProfile = AllowUnknownProfile.ALL_TYPES;
431  private MappingMergeModeOption mappingMergeMode = MappingMergeModeOption.APPEND;
432  private boolean forPublication;
433  private List<StructureDefinition> obligationProfiles = new ArrayList<>();
434 
435  public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp, FHIRPathEngine fpe) {
436    super();
437    this.context = context;
438    this.messages = messages;
439    this.pkp = pkp;
440
441    this.fpe = fpe;
442    if (context != null && this.fpe == null) {
443      this.fpe = new FHIRPathEngine(context, this);
444    }
445  }
446
447  public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp) {
448    super();
449    this.context = context;
450    this.messages = messages;
451    this.pkp = pkp;
452    if (context != null) {
453      this.fpe = new FHIRPathEngine(context, this);
454    }
455  }
456  
457  public boolean isWantFixDifferentialFirstElementType() {
458    return wantFixDifferentialFirstElementType;
459  }
460
461  public void setWantFixDifferentialFirstElementType(boolean wantFixDifferentialFirstElementType) {
462    this.wantFixDifferentialFirstElementType = wantFixDifferentialFirstElementType;
463  }
464
465  public boolean isAutoFixSliceNames() {
466    return autoFixSliceNames;
467  }
468
469  public ProfileUtilities setAutoFixSliceNames(boolean autoFixSliceNames) {
470    this.autoFixSliceNames = autoFixSliceNames;
471    return this;
472  }
473
474  public SourcedChildDefinitions getChildMap(StructureDefinition profile, ElementDefinition element) throws DefinitionException {
475    String cacheKey = "cm."+profile.getVersionedUrl()+"#"+(element.hasId() ? element.getId() : element.getPath());
476    if (childMapCache.containsKey(cacheKey)) {
477      return childMapCache.get(cacheKey);
478    }
479    StructureDefinition src = profile;
480    if (element.getContentReference() != null) {
481      List<ElementDefinition> list = null;
482      String id = null;
483      if (element.getContentReference().startsWith("#")) {
484        // internal reference
485        id = element.getContentReference().substring(1);
486        list = profile.getSnapshot().getElement();
487      } else if (element.getContentReference().contains("#")) {
488        // external reference
489        String ref = element.getContentReference();
490        StructureDefinition sd = context.fetchResource(StructureDefinition.class, ref.substring(0, ref.indexOf("#")), profile);
491        if (sd == null) {
492          throw new DefinitionException("unable to process contentReference '"+element.getContentReference()+"' on element '"+element.getId()+"'");
493        }
494        src = sd;
495        list = sd.getSnapshot().getElement();
496        id = ref.substring(ref.indexOf("#")+1);        
497      } else {
498        throw new DefinitionException("unable to process contentReference '"+element.getContentReference()+"' on element '"+element.getId()+"'");
499      }
500        
501      for (ElementDefinition e : list) {
502        if (id.equals(e.getId()))
503          return getChildMap(src, e);
504      }
505      throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_NAME_REFERENCE__AT_PATH_, element.getContentReference(), element.getPath()));
506
507    } else {
508      List<ElementDefinition> res = new ArrayList<ElementDefinition>();
509      List<ElementDefinition> elements = profile.getSnapshot().getElement();
510      String path = element.getPath();
511      for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) {
512        ElementDefinition e = elements.get(index);
513        if (e.getPath().startsWith(path + ".")) {
514          // We only want direct children, not all descendants
515          if (!e.getPath().substring(path.length()+1).contains("."))
516            res.add(e);
517        } else
518          break;
519      }
520      SourcedChildDefinitions result  = new SourcedChildDefinitions(src, res);
521      childMapCache.put(cacheKey, result);
522      return result;
523    }
524  }
525
526
527  public List<ElementDefinition> getSliceList(StructureDefinition profile, ElementDefinition element) throws DefinitionException {
528    if (!element.hasSlicing())
529      throw new Error(context.formatMessage(I18nConstants.GETSLICELIST_SHOULD_ONLY_BE_CALLED_WHEN_THE_ELEMENT_HAS_SLICING));
530
531    List<ElementDefinition> res = new ArrayList<ElementDefinition>();
532    List<ElementDefinition> elements = profile.getSnapshot().getElement();
533    String path = element.getPath();
534    for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) {
535      ElementDefinition e = elements.get(index);
536      if (e.getPath().startsWith(path + ".") || e.getPath().equals(path)) {
537        // We want elements with the same path (until we hit an element that doesn't start with the same path)
538        if (e.getPath().equals(element.getPath()))
539          res.add(e);
540      } else
541        break;
542    }
543    return res;
544  }
545
546
547  /**
548   * Given a Structure, navigate to the element given by the path and return the direct children of that element
549   *
550   * @param profile The structure to navigate into
551   * @param path The path of the element within the structure to get the children for
552   * @return A List containing the element children (all of them are Elements)
553   */
554  public List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id) {
555    return getChildList(profile, path, id, false);
556  }
557  
558  public List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id, boolean diff) {
559    return getChildList(profile, path, id, diff, false);
560  }
561  
562  public List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id, boolean diff, boolean refs) {
563    List<ElementDefinition> res = new ArrayList<ElementDefinition>();
564
565    boolean capturing = id==null;
566    if (id==null && !path.contains("."))
567      capturing = true;
568  
569    List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement();
570    for (ElementDefinition e : list) {
571      if (e == null)
572        throw new Error(context.formatMessage(I18nConstants.ELEMENT__NULL_, profile.getUrl()));
573      if (e.getId() == null)
574        throw new Error(context.formatMessage(I18nConstants.ELEMENT_ID__NULL__ON_, e.toString(), profile.getUrl()));
575      
576      if (!capturing && id!=null && e.getId().equals(id)) {
577        capturing = true;
578      }
579      
580      // If our element is a slice, stop capturing children as soon as we see the next slice
581      if (capturing && e.hasId() && id!= null && !e.getId().equals(id) && e.getPath().equals(path))
582        break;
583      
584      if (capturing) {
585        String p = e.getPath();
586  
587        if (refs && !Utilities.noString(e.getContentReference()) && path.startsWith(p)) {
588          if (path.length() > p.length()) {
589            return getChildList(profile, e.getContentReference()+"."+path.substring(p.length()+1), null, diff);
590          } else if (e.getContentReference().startsWith("#")) {
591            return getChildList(profile, e.getContentReference().substring(1), null, diff);            
592          } else if (e.getContentReference().contains("#")) {
593            String url = e.getContentReference().substring(0, e.getContentReference().indexOf("#"));
594            StructureDefinition sd = context.fetchResource(StructureDefinition.class, url, profile);
595            if (sd == null) {
596              throw new DefinitionException("Unable to find Structure "+url);
597            }
598            return getChildList(sd, e.getContentReference().substring(e.getContentReference().indexOf("#")+1), null, diff);            
599          } else {
600            return getChildList(profile, e.getContentReference(), null, diff);
601          }
602          
603        } else if (p.startsWith(path+".") && !p.equals(path)) {
604          String tail = p.substring(path.length()+1);
605          if (!tail.contains(".")) {
606            res.add(e);
607          }
608        }
609      }
610    }
611
612    return res;
613  }
614
615  public List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element, boolean diff, boolean refs) {
616    return getChildList(structure, element.getPath(), element.getId(), diff, refs);
617  }
618
619  public List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element, boolean diff) {
620    return getChildList(structure, element.getPath(), element.getId(), diff);
621  }
622
623  public List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element) {
624    if (element.hasContentReference()) {
625      ElementDefinition target = element;
626      for (ElementDefinition t : structure.getSnapshot().getElement()) {
627        if (t.getId().equals(element.getContentReference().substring(1))) {
628          target = t;
629        }
630      }      
631      return getChildList(structure, target.getPath(), target.getId(), false);
632    } else {
633      return getChildList(structure, element.getPath(), element.getId(), false);
634    }
635        }
636
637  /**
638   * Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
639   *
640   * @param base - the base structure on which the differential will be applied
641   * @param derived - the differential to apply to the base
642   * @param url - where the base has relative urls for profile references, these need to be converted to absolutes by prepending this URL (e.g. the canonical URL)
643   * @param webUrl - where the base has relative urls in markdown, these need to be converted to absolutes by prepending this URL (this is not the same as the canonical URL)
644   * @return
645   * @throws FHIRException 
646   * @throws DefinitionException 
647   * @throws Exception
648   */
649  public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String webUrl, String profileName) throws DefinitionException, FHIRException {
650    if (base == null) {
651      throw new DefinitionException(context.formatMessage(I18nConstants.NO_BASE_PROFILE_PROVIDED));
652    }
653    if (derived == null) {
654      throw new DefinitionException(context.formatMessage(I18nConstants.NO_DERIVED_STRUCTURE_PROVIDED));
655    }
656    checkNotGenerating(base, "Base for generating a snapshot for the profile "+derived.getUrl());
657    checkNotGenerating(derived, "Focus for generating a snapshot");
658
659    if (!base.hasType()) {
660      throw new DefinitionException(context.formatMessage(I18nConstants.BASE_PROFILE__HAS_NO_TYPE, base.getUrl()));
661    }
662    if (!derived.hasType()) {
663      throw new DefinitionException(context.formatMessage(I18nConstants.DERIVED_PROFILE__HAS_NO_TYPE, derived.getUrl()));
664    }
665    if (!derived.hasDerivation()) {
666      throw new DefinitionException(context.formatMessage(I18nConstants.DERIVED_PROFILE__HAS_NO_DERIVATION_VALUE_AND_SO_CANT_BE_PROCESSED, derived.getUrl()));
667    }
668    if (!base.getType().equals(derived.getType()) && derived.getDerivation() == TypeDerivationRule.CONSTRAINT) {
669      throw new DefinitionException(context.formatMessage(I18nConstants.BASE__DERIVED_PROFILES_HAVE_DIFFERENT_TYPES____VS___, base.getUrl(), base.getType(), derived.getUrl(), derived.getType()));
670    }
671    if (!base.hasSnapshot()) {
672      StructureDefinition sdb = context.fetchResource(StructureDefinition.class, base.getBaseDefinition());
673      if (sdb == null)
674        throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_BASE__FOR_, base.getBaseDefinition(), base.getUrl()));
675      checkNotGenerating(sdb, "an extension base");
676      generateSnapshot(sdb, base, base.getUrl(), (sdb.hasWebPath()) ? Utilities.extractBaseUrl(sdb.getWebPath()) : webUrl, base.getName());
677    }
678    fixTypeOfResourceId(base);
679    if (base.hasExtension(ToolingExtensions.EXT_TYPE_PARAMETER)) {
680      checkTypeParameters(base, derived);
681    }
682    
683    if (snapshotStack.contains(derived.getUrl())) {
684      throw new DefinitionException(context.formatMessage(I18nConstants.CIRCULAR_SNAPSHOT_REFERENCES_DETECTED_CANNOT_GENERATE_SNAPSHOT_STACK__, snapshotStack.toString()));
685    }
686    derived.setUserData("profileutils.snapshot.generating", true);
687    snapshotStack.add(derived.getUrl());
688    try {
689
690      if (!Utilities.noString(webUrl) && !webUrl.endsWith("/"))
691        webUrl = webUrl + '/';
692
693      if (defWebRoot == null)
694        defWebRoot = webUrl;
695      derived.setSnapshot(new StructureDefinitionSnapshotComponent());
696
697      try {
698        checkDifferential(derived.getDifferential().getElement(), derived.getTypeName(), derived.getUrl());
699        checkDifferentialBaseType(derived);
700
701        copyInheritedExtensions(base, derived, webUrl);
702
703        findInheritedObligationProfiles(derived);
704        // so we have two lists - the base list, and the differential list
705        // the differential list is only allowed to include things that are in the base list, but
706        // is allowed to include them multiple times - thereby slicing them
707
708        // our approach is to walk through the base list, and see whether the differential
709        // says anything about them.
710        // we need a diff cursor because we can only look ahead, in the bound scoped by longer paths
711
712
713        for (ElementDefinition e : derived.getDifferential().getElement()) 
714          e.clearUserData(UD_GENERATED_IN_SNAPSHOT);
715
716        // we actually delegate the work to a subroutine so we can re-enter it with a different cursors
717        StructureDefinitionDifferentialComponent diff = cloneDiff(derived.getDifferential()); // we make a copy here because we're sometimes going to hack the differential while processing it. Have to migrate user data back afterwards
718
719        StructureDefinitionSnapshotComponent baseSnapshot  = base.getSnapshot();
720        if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
721          String derivedType = derived.getTypeName();
722
723          baseSnapshot = cloneSnapshot(baseSnapshot, base.getTypeName(), derivedType);
724        }
725        //      if (derived.getId().equals("2.16.840.1.113883.10.20.22.2.1.1")) {
726        //        debug = true;
727        //      }
728
729        MappingAssistant mappingDetails = new MappingAssistant(mappingMergeMode, base, derived, context.getVersion());
730        
731        ProfilePathProcessor.processPaths(this, base, derived, url, webUrl, diff, baseSnapshot, mappingDetails);
732
733        checkGroupConstraints(derived);
734        if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
735          int i = 0;
736          for (ElementDefinition e : diff.getElement()) {
737            if (!e.hasUserData(UD_GENERATED_IN_SNAPSHOT) && e.getPath().contains(".")) {
738              ElementDefinition existing = getElementInCurrentContext(e.getPath(), derived.getSnapshot().getElement());
739              if (existing != null) {
740                updateFromDefinition(existing, e, profileName, false, url, base, derived, "StructureDefinition.differential.element["+i+"]", mappingDetails);
741              } else {
742                ElementDefinition outcome = updateURLs(url, webUrl, e.copy());
743                e.setUserData(UD_GENERATED_IN_SNAPSHOT, outcome);
744                derived.getSnapshot().addElement(outcome);
745                if (walksInto(diff.getElement(), e)) {
746                  if (e.getType().size() > 1) {
747                    throw new DefinitionException("Unsupported scenario: specialization walks into multiple types at "+e.getId()); 
748                  } else {
749                    addInheritedElementsForSpecialization(derived.getSnapshot(), outcome, outcome.getTypeFirstRep().getWorkingCode(), outcome.getPath(), url, webUrl);
750                  }
751                }
752              }
753            }
754            i++;
755          }
756        }
757
758        if (derived.getKind() != StructureDefinitionKind.LOGICAL && !derived.getSnapshot().getElementFirstRep().getType().isEmpty())
759          throw new Error(context.formatMessage(I18nConstants.TYPE_ON_FIRST_SNAPSHOT_ELEMENT_FOR__IN__FROM_, derived.getSnapshot().getElementFirstRep().getPath(), derived.getUrl(), base.getUrl()));
760        mappingDetails.update();
761
762        setIds(derived, false);
763        if (debug) {
764          System.out.println("Differential: ");
765          for (ElementDefinition ed : derived.getDifferential().getElement())
766            System.out.println("  "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  "+constraintSummary(ed));
767          System.out.println("Snapshot: ");
768          for (ElementDefinition ed : derived.getSnapshot().getElement())
769            System.out.println("  "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  "+constraintSummary(ed));
770          System.out.println("diff: ");
771          for (ElementDefinition ed : diff.getElement())
772            System.out.println("  "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  "+constraintSummary(ed)+" [gen = "+(ed.hasUserData(UD_GENERATED_IN_SNAPSHOT) ? ed.getUserData(UD_GENERATED_IN_SNAPSHOT) : "--")+"]");
773        }
774        CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
775        //Check that all differential elements have a corresponding snapshot element
776        int ce = 0;
777        int i = 0;
778        for (ElementDefinition e : diff.getElement()) {
779          if (!e.hasUserData("diff-source"))
780            throw new Error(context.formatMessage(I18nConstants.UNXPECTED_INTERNAL_CONDITION__NO_SOURCE_ON_DIFF_ELEMENT));
781          else {
782            if (e.hasUserData(UD_DERIVATION_EQUALS))
783              ((Base) e.getUserData("diff-source")).setUserData(UD_DERIVATION_EQUALS, e.getUserData(UD_DERIVATION_EQUALS));
784            if (e.hasUserData(UD_DERIVATION_POINTER))
785              ((Base) e.getUserData("diff-source")).setUserData(UD_DERIVATION_POINTER, e.getUserData(UD_DERIVATION_POINTER));
786          }
787          if (!e.hasUserData(UD_GENERATED_IN_SNAPSHOT)) {
788            b.append(e.hasId() ? "id: "+e.getId() : "path: "+e.getPath());
789            ce++;
790            if (e.hasId()) {
791              String msg = "No match found for "+e.getId()+" in the generated snapshot: check that the path and definitions are legal in the differential (including order)";
792              messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, "StructureDefinition.differential.element["+i+"]", msg, ValidationMessage.IssueSeverity.ERROR));
793            }
794          }
795          i++;
796        }
797        if (!Utilities.noString(b.toString())) {
798          String msg = "The profile "+derived.getUrl()+" has "+ce+" "+Utilities.pluralize("element", ce)+" in the differential ("+b.toString()+") that don't have a matching element in the snapshot: check that the path and definitions are legal in the differential (including order)";
799          if (debug) {
800            System.err.println("Error in snapshot generation: "+msg);
801            if (!debug) {
802              System.out.println("Differential: ");
803              for (ElementDefinition ed : derived.getDifferential().getElement())
804                System.out.println("  "+ed.getId()+" = "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  "+constraintSummary(ed));
805              System.out.println("Snapshot: ");
806              for (ElementDefinition ed : derived.getSnapshot().getElement())
807                System.out.println("  "+ed.getId()+" = "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  "+constraintSummary(ed));
808            }
809          }
810          handleError(url, msg);
811        }
812        // hack around a problem in R4 definitions (somewhere?)
813        for (ElementDefinition ed : derived.getSnapshot().getElement()) {
814          for (ElementDefinitionMappingComponent mm : ed.getMapping()) {
815            if (mm.hasMap()) {
816              mm.setMap(mm.getMap().trim());
817            }
818          }
819          for (ElementDefinitionConstraintComponent s : ed.getConstraint()) {
820            if (s.hasSource()) {
821              String ref = s.getSource();
822              if (!Utilities.isAbsoluteUrl(ref)) {
823                if (ref.contains(".")) {
824                  s.setSource("http://hl7.org/fhir/StructureDefinition/"+ref.substring(0, ref.indexOf("."))+"#"+ref);
825                } else {
826                  s.setSource("http://hl7.org/fhir/StructureDefinition/"+ref);
827                }
828              }  
829            }
830          }
831        }
832        if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
833          for (ElementDefinition ed : derived.getSnapshot().getElement()) {
834            if (!ed.hasBase()) {
835              ed.getBase().setPath(ed.getPath()).setMin(ed.getMin()).setMax(ed.getMax());
836            }
837          }
838        }
839        // check slicing is ok while we're at it. and while we're doing this. update the minimum count if we need to
840        String tn = derived.getType();
841        if (tn.contains("/")) {
842          tn = tn.substring(tn.lastIndexOf("/")+1);
843        }
844        Map<String, ElementDefinitionCounter> slices = new HashMap<>();
845        i = 0;
846        for (ElementDefinition ed : derived.getSnapshot().getElement()) {
847          if (ed.hasSlicing()) {
848            slices.put(ed.getPath(), new ElementDefinitionCounter(ed, i));            
849          } else {
850            Set<String> toRemove = new HashSet<>();
851            for (String s : slices.keySet()) {
852              if (Utilities.charCount(s, '.') >= Utilities.charCount(ed.getPath(), '.') && !s.equals(ed.getPath())) {
853                toRemove.add(s);
854              }
855            }
856            for (String s : toRemove) {
857              ElementDefinitionCounter slice = slices.get(s);
858              int count = slice.checkMin();
859              boolean repeats = !"1".equals(slice.getFocus().getBase().getMax()); // type slicing if repeats = 1
860              if (count > -1 && repeats) {
861                if (slice.getFocus().hasUserData("auto-added-slicing")) {
862                  slice.getFocus().setMin(count);
863                } else {
864                  String msg = "The slice definition for "+slice.getFocus().getId()+" has a minimum of "+slice.getFocus().getMin()+" but the slices add up to a minimum of "+count; 
865                  messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 
866                      "StructureDefinition.snapshot.element["+slice.getIndex()+"]", msg, forPublication ? ValidationMessage.IssueSeverity.ERROR : ValidationMessage.IssueSeverity.INFORMATION).setIgnorableError(true));
867                }
868              }
869              count = slice.checkMax();
870              if (count > -1 && repeats) {
871                String msg = "The slice definition for "+slice.getFocus().getId()+" has a maximum of "+slice.getFocus().getMax()+" but the slices add up to a maximum of "+count+". Check that this is what is intended"; 
872                messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 
873                    "StructureDefinition.snapshot.element["+slice.getIndex()+"]", msg, ValidationMessage.IssueSeverity.INFORMATION));                                            
874              }
875              if (!slice.checkMinMax()) {
876                String msg = "The slice definition for "+slice.getFocus().getId()+" has a maximum of "+slice.getFocus().getMax()+" which is less than the minimum of "+slice.getFocus().getMin(); 
877                messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 
878                    "StructureDefinition.snapshot.element["+slice.getIndex()+"]", msg, ValidationMessage.IssueSeverity.WARNING));                                                            
879              }
880              slices.remove(s);
881            }            
882          }
883          if (ed.getPath().contains(".") && !ed.getPath().startsWith(tn+".")) {
884            throw new Error("The element "+ed.getId()+" in the profile '"+derived.getVersionedUrl()+" doesn't have the right path (should start with "+tn+".");
885          }
886          if (ed.hasSliceName() && !slices.containsKey(ed.getPath())) {
887            String msg = "The element "+ed.getId()+" launches straight into slicing without the slicing being set up properly first";
888            messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 
889                "StructureDefinition.snapshot.element["+i+"]", msg, ValidationMessage.IssueSeverity.ERROR).setIgnorableError(true));            
890          }
891          if (ed.hasSliceName() && slices.containsKey(ed.getPath())) {
892            if (!slices.get(ed.getPath()).count(ed, ed.getSliceName())) {
893              String msg = "Duplicate slice name "+ed.getSliceName()+" on "+ed.getId()+" (["+i+"])";
894              messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 
895                  "StructureDefinition.snapshot.element["+i+"]", msg, ValidationMessage.IssueSeverity.ERROR).setIgnorableError(true));            
896            }
897          }
898          i++;
899        }
900        
901        i = 0;
902        // last, check for wrong profiles or target profiles
903        for (ElementDefinition ed : derived.getSnapshot().getElement()) {
904          for (TypeRefComponent t : ed.getType()) {
905            for (UriType u : t.getProfile()) {
906              StructureDefinition sd = context.fetchResource(StructureDefinition.class, u.getValue(), derived);
907              if (sd == null) {
908                if (makeXVer().matchingUrl(u.getValue()) && xver.status(u.getValue()) == XVerExtensionStatus.Valid) {
909                  sd = xver.makeDefinition(u.getValue());              
910                }
911              }
912              if (sd == null) {
913                if (messages != null) {
914                  messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, 
915                      "StructureDefinition.snapshot.element["+i+"]", "The type of profile "+u.getValue()+" cannot be checked as the profile is not known", IssueSeverity.WARNING));
916                }
917              } else {
918                String wt = t.getWorkingCode();
919                if (ed.getPath().equals("Bundle.entry.response.outcome")) {
920                  wt = "OperationOutcome";
921                }
922                String tt = sd.getType();
923                boolean elementProfile = u.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT);
924                if (elementProfile) {
925                  ElementDefinition edt = sd.getSnapshot().getElementById(u.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT));
926                  if (edt == null) {
927                    handleError(url, "The profile "+u.getValue()+" has type "+sd.getType()+" which is not consistent with the stated type "+wt);
928                  } else {
929                    tt = edt.typeSummary();
930                  }
931                }
932                if (!tt.equals(wt)) {
933                  boolean ok = !elementProfile && isCompatibleType(wt, sd);
934                  if (!ok) {
935                    handleError(url, "The profile "+u.getValue()+" has type "+sd.getType()+" which is not consistent with the stated type "+wt);
936                  }
937                }
938              }
939            }
940          }
941          i++;
942        }
943      } catch (Exception e) {
944        // if we had an exception generating the snapshot, make sure we don't leave any half generated snapshot behind
945        derived.setSnapshot(null);
946        derived.clearUserData("profileutils.snapshot.generating");
947        throw e;
948      }
949    } finally {
950      derived.clearUserData("profileutils.snapshot.generating");
951      snapshotStack.remove(derived.getUrl());
952    }
953    derived.setUserData("profileutils.snapshot.generated", true); // used by the publisher
954  }
955
956
957  private void checkTypeParameters(StructureDefinition base, StructureDefinition derived) {
958    String bt = ToolingExtensions.readStringExtension(base, ToolingExtensions.EXT_TYPE_PARAMETER);
959    if (!derived.hasExtension(ToolingExtensions.EXT_TYPE_PARAMETER)) {
960      throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_MISSING, base.getVersionedUrl(), bt, derived.getVersionedUrl()));
961    }
962    String dt = ToolingExtensions.readStringExtension(derived, ToolingExtensions.EXT_TYPE_PARAMETER);
963    StructureDefinition bsd = context.fetchTypeDefinition(bt);
964    StructureDefinition dsd = context.fetchTypeDefinition(dt);
965    if (bsd == null) {
966      throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_UNKNOWN, base.getVersionedUrl(), bt));
967    }
968    if (dsd == null) {
969      throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_UNKNOWN, derived.getVersionedUrl(), dt));
970    }
971    StructureDefinition t = dsd;
972    while (t != bsd && t != null) {
973      t = context.fetchResource(StructureDefinition.class, t.getBaseDefinition());
974    }
975    if (t == null) {
976      throw new DefinitionException(context.formatMessage(I18nConstants.SD_TYPE_PARAMETER_INVALID, base.getVersionedUrl(), bt, derived.getVersionedUrl(), dt));
977    }
978  }
979
980  private XVerExtensionManager makeXVer() {
981    if (xver == null) {
982      xver = new XVerExtensionManager(context);
983    }
984    return xver;
985  }
986
987  private ElementDefinition getElementInCurrentContext(String path, List<ElementDefinition> list) {
988    for (int i = list.size() -1; i >= 0; i--) {
989      ElementDefinition t = list.get(i);
990      if (t.getPath().equals(path)) {
991        return t;
992      } else if (!path.startsWith(head(t.getPath()))) {
993        return null;
994      }
995    }
996    return null;
997  }
998
999  private String head(String path) {
1000    return path.contains(".") ? path.substring(0, path.lastIndexOf(".")+1) : path;
1001  }
1002
1003  private void findInheritedObligationProfiles(StructureDefinition derived) {
1004    for (Extension ext : derived.getExtensionsByUrl(ToolingExtensions.EXT_OBLIGATION_INHERITS)) {
1005      StructureDefinition op = context.fetchResource(StructureDefinition.class, ext.getValueCanonicalType().primitiveValue());
1006      if (op != null && ToolingExtensions.readBoolExtension(op, ToolingExtensions.EXT_OBLIGATION_PROFILE_FLAG)) {
1007        if (derived.getBaseDefinition().equals(op.getBaseDefinition())) {
1008          obligationProfiles.add(op);
1009        }
1010      }
1011    }
1012  }
1013
1014  private void handleError(String url, String msg) {
1015    if (exception)
1016      throw new DefinitionException(msg);
1017    else
1018      messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url, msg, ValidationMessage.IssueSeverity.ERROR));
1019  }
1020
1021
1022
1023
1024  private void copyInheritedExtensions(StructureDefinition base, StructureDefinition derived, String webUrl) {
1025    for (Extension ext : base.getExtension()) {
1026      if (!Utilities.existsInList(ext.getUrl(), NON_INHERITED_ED_URLS) && !derived.hasExtension(ext.getUrl())) {
1027        Extension next = ext.copy();
1028        if (ext.hasValueMarkdownType()) {
1029          MarkdownType md = ext.getValueMarkdownType();
1030          md.setValue(processRelativeUrls(md.getValue(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false));
1031        }
1032        derived.getExtension().add(next);
1033
1034      }
1035    }
1036    
1037  }
1038
1039  private void addInheritedElementsForSpecialization(StructureDefinitionSnapshotComponent snapshot, ElementDefinition focus, String type, String path, String url, String weburl) {
1040     StructureDefinition sd = context.fetchTypeDefinition(type);
1041     if (sd != null) {
1042       // don't do this. should already be in snapshot ... addInheritedElementsForSpecialization(snapshot, focus, sd.getBaseDefinition(), path, url, weburl);
1043       for (ElementDefinition ed : sd.getSnapshot().getElement()) {
1044         if (ed.getPath().contains(".")) {
1045           ElementDefinition outcome = updateURLs(url, weburl, ed.copy());
1046           outcome.setPath(outcome.getPath().replace(sd.getTypeName(), path));
1047           snapshot.getElement().add(outcome);
1048         } else {
1049           focus.getConstraint().addAll(ed.getConstraint());
1050           for (Extension ext : ed.getExtension()) {
1051             if (!Utilities.existsInList(ext.getUrl(), NON_INHERITED_ED_URLS) && !focus.hasExtension(ext.getUrl())) {
1052               focus.getExtension().add(ext.copy());
1053             }
1054           }
1055         }
1056       }
1057     }
1058  }
1059
1060  private boolean walksInto(List<ElementDefinition> list, ElementDefinition ed) {
1061    int i = list.indexOf(ed);
1062    return (i < list.size() - 1) && list.get(i + 1).getPath().startsWith(ed.getPath()+".");
1063  }
1064
1065  private void fixTypeOfResourceId(StructureDefinition base) {
1066    if (base.getKind() == StructureDefinitionKind.RESOURCE && (base.getFhirVersion() == null || VersionUtilities.isR4Plus(base.getFhirVersion().toCode()))) {
1067      fixTypeOfResourceId(base.getSnapshot().getElement());
1068      fixTypeOfResourceId(base.getDifferential().getElement());      
1069    }
1070  }
1071
1072  private void fixTypeOfResourceId(List<ElementDefinition> list) {
1073    for (ElementDefinition ed : list) {
1074      if (ed.hasBase() && ed.getBase().getPath().equals("Resource.id")) {
1075        for (TypeRefComponent tr : ed.getType()) {
1076          tr.setCode("http://hl7.org/fhirpath/System.String");
1077          tr.removeExtension(ToolingExtensions.EXT_FHIR_TYPE);
1078          ToolingExtensions.addUrlExtension(tr, ToolingExtensions.EXT_FHIR_TYPE, "id");
1079        }
1080      }
1081    }    
1082  }
1083
1084  /**
1085   * Check if derived has the correct base type
1086   *
1087   * Clear first element of differential under certain conditions.
1088   *
1089   * @param derived
1090   * @throws Error
1091   */
1092  private void checkDifferentialBaseType(StructureDefinition derived) throws Error {
1093    if (derived.hasDifferential() && !derived.getDifferential().getElementFirstRep().getPath().contains(".") && !derived.getDifferential().getElementFirstRep().getType().isEmpty()) {
1094      if (wantFixDifferentialFirstElementType && typeMatchesAncestor(derived.getDifferential().getElementFirstRep().getType(), derived.getBaseDefinition(), derived)) {
1095        derived.getDifferential().getElementFirstRep().getType().clear();
1096      } else if (derived.getKind() != StructureDefinitionKind.LOGICAL) {
1097        throw new Error(context.formatMessage(I18nConstants.TYPE_ON_FIRST_DIFFERENTIAL_ELEMENT));
1098      }
1099    }
1100  }
1101
1102  private boolean typeMatchesAncestor(List<TypeRefComponent> type, String baseDefinition, Resource src) {
1103    StructureDefinition sd = context.fetchResource(StructureDefinition.class, baseDefinition, src);
1104    return sd != null && type.size() == 1 && sd.getType().equals(type.get(0).getCode()); 
1105  }
1106
1107
1108  private void checkGroupConstraints(StructureDefinition derived) {
1109    List<ElementDefinition> toRemove = new ArrayList<>();
1110//    List<ElementDefinition> processed = new ArrayList<>();
1111    for (ElementDefinition element : derived.getSnapshot().getElement()) {
1112      if (!toRemove.contains(element) && !element.hasSlicing() && !"0".equals(element.getMax())) {
1113        checkForChildrenInGroup(derived, toRemove, element);
1114      }
1115    }
1116    derived.getSnapshot().getElement().removeAll(toRemove);
1117  }
1118
1119  private void checkForChildrenInGroup(StructureDefinition derived, List<ElementDefinition> toRemove, ElementDefinition element) throws Error {
1120    List<ElementDefinition> children = getChildren(derived, element);
1121    List<ElementChoiceGroup> groups = readChoices(element, children);
1122    for (ElementChoiceGroup group : groups) {
1123//      System.out.println(children);
1124      String mandated = null;
1125      Set<String> names = new HashSet<>();
1126      for (ElementDefinition ed : children) {
1127        String name = tail(ed.getPath());
1128        if (names.contains(name)) {
1129          throw new Error("huh?");
1130        } else {
1131          names.add(name);
1132        }
1133        if (group.getElements().contains(name)) {
1134          if (ed.getMin() == 1) {
1135            if (mandated == null) {
1136              mandated = name;
1137            } else {
1138              throw new Error("Error: there are two mandatory elements in "+derived.getUrl()+" when there can only be one: "+mandated+" and "+name);
1139            }
1140          }
1141        }
1142      }
1143      if (mandated != null) {
1144        for (ElementDefinition ed : children) {
1145          String name = tail(ed.getPath());
1146          if (group.getElements().contains(name) && !mandated.equals(name)) {
1147            ed.setMax("0");
1148            addAllChildren(derived, ed, toRemove);
1149          }
1150        }
1151      }
1152    }
1153  }
1154
1155  private List<ElementDefinition> getChildren(StructureDefinition derived, ElementDefinition element) {
1156    List<ElementDefinition> elements = derived.getSnapshot().getElement();
1157    int index = elements.indexOf(element) + 1;
1158    String path = element.getPath()+".";
1159    List<ElementDefinition> list = new ArrayList<>();
1160    while (index < elements.size()) {
1161      ElementDefinition e = elements.get(index);
1162      String p = e.getPath();
1163      if (p.startsWith(path) && !e.hasSliceName()) {
1164        if (!p.substring(path.length()).contains(".")) {
1165          list.add(e);
1166        }
1167        index++;
1168      } else  {
1169        break;
1170      }
1171    }
1172    return list;
1173  }
1174
1175  private void addAllChildren(StructureDefinition derived, ElementDefinition element, List<ElementDefinition> toRemove) {
1176    List<ElementDefinition> children = getChildList(derived, element);
1177    for (ElementDefinition child : children) {
1178      toRemove.add(child);
1179      addAllChildren(derived, child, toRemove);
1180    }
1181  }
1182
1183  /**
1184   * Check that a differential is valid.
1185   * @param elements
1186   * @param type
1187   * @param url
1188   */
1189  private void checkDifferential(List<ElementDefinition> elements, String type, String url) {
1190    boolean first = true;
1191    String t = urlTail(type);
1192    for (ElementDefinition ed : elements) {
1193      if (!ed.hasPath()) {
1194        throw new FHIRException(context.formatMessage(I18nConstants.NO_PATH_ON_ELEMENT_IN_DIFFERENTIAL_IN_, url));
1195      }
1196      String p = ed.getPath();
1197      if (p == null) {
1198        throw new FHIRException(context.formatMessage(I18nConstants.NO_PATH_VALUE_ON_ELEMENT_IN_DIFFERENTIAL_IN_, url));
1199      }
1200      if (!((first && t.equals(p)) || p.startsWith(t+"."))) {
1201        throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__MUST_START_WITH_, p, url, t, (first ? " (or be '"+t+"')" : "")));
1202      }
1203      if (p.contains(".")) {
1204        // Element names (the parts of a path delineated by the '.' character) SHALL NOT contain whitespace (i.e. Unicode characters marked as whitespace)
1205        // Element names SHALL NOT contain the characters ,:;'"/|?!@#$%^&*()[]{}
1206        // Element names SHOULD not contain non-ASCII characters
1207        // Element names SHALL NOT exceed 64 characters in length
1208        String[] pl = p.split("\\.");
1209        for (String pp : pl) {
1210          if (pp.length() < 1) {
1211            throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__NAME_PORTION_MISING_, p, url));
1212          }
1213          if (pp.length() > 64) {
1214            throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__NAME_PORTION_EXCEEDS_64_CHARS_IN_LENGTH, p, url));
1215          }
1216          for (char ch : pp.toCharArray()) {
1217            if (Utilities.isWhitespace(ch)) {
1218              throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__NO_UNICODE_WHITESPACE, p, url));
1219            }
1220            if (Utilities.existsInList(ch, ',', ':', ';', '\'', '"', '/', '|', '?', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '{', '}')) {
1221              throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__ILLEGAL_CHARACTER_, p, url, ch));
1222            }
1223            if (ch < ' ' || ch > 'z') {
1224              throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__ILLEGAL_CHARACTER_, p, url, ch));
1225            }
1226          }
1227          if (pp.contains("[") || pp.contains("]")) {
1228            if (!pp.endsWith("[x]") || (pp.substring(0, pp.length()-3).contains("[") || (pp.substring(0, pp.length()-3).contains("]")))) {
1229              throw new FHIRException(context.formatMessage(I18nConstants.ILLEGAL_PATH__IN_DIFFERENTIAL_IN__ILLEGAL_CHARACTERS_, p, url));
1230            }
1231          }
1232        }
1233      }
1234    }    
1235  }
1236
1237
1238  private boolean isCompatibleType(String base, StructureDefinition sdt) {
1239    StructureDefinition sdb = context.fetchTypeDefinition(base);
1240    if (sdb.getType().equals(sdt.getType())) {
1241      return true;
1242    }
1243    StructureDefinition sd = context.fetchTypeDefinition(sdt.getType());
1244    while (sd != null) {
1245      if (sd.getType().equals(sdb.getType())) {
1246        return true;
1247      }
1248      if (sd.getUrl().equals(sdb.getUrl())) {
1249        return true;
1250      }
1251      sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd); 
1252    }
1253    return false;
1254  }
1255
1256
1257  private StructureDefinitionDifferentialComponent cloneDiff(StructureDefinitionDifferentialComponent source) {
1258    StructureDefinitionDifferentialComponent diff = new StructureDefinitionDifferentialComponent();
1259    for (ElementDefinition sed : source.getElement()) {
1260      ElementDefinition ted = sed.copy();
1261      diff.getElement().add(ted);
1262      ted.setUserData("diff-source", sed);
1263    }
1264    return diff;
1265  }
1266
1267  private StructureDefinitionSnapshotComponent cloneSnapshot(StructureDefinitionSnapshotComponent source, String baseType, String derivedType) {
1268        StructureDefinitionSnapshotComponent diff = new StructureDefinitionSnapshotComponent();
1269    for (ElementDefinition sed : source.getElement()) {
1270      ElementDefinition ted = sed.copy();
1271      ted.setId(ted.getId().replaceFirst(baseType,derivedType));
1272      ted.setPath(ted.getPath().replaceFirst(baseType,derivedType));
1273      diff.getElement().add(ted);
1274    }
1275    return diff;
1276  }
1277
1278  private String constraintSummary(ElementDefinition ed) {
1279    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1280    if (ed.hasPattern())
1281      b.append("pattern="+ed.getPattern().fhirType());
1282    if (ed.hasFixed())
1283      b.append("fixed="+ed.getFixed().fhirType());
1284    if (ed.hasConstraint())
1285      b.append("constraints="+ed.getConstraint().size());
1286    return b.toString();
1287  }
1288
1289
1290  private String sliceSummary(ElementDefinition ed) {
1291    if (!ed.hasSlicing() && !ed.hasSliceName())
1292      return "";
1293    if (ed.hasSliceName())
1294      return " (slicename = "+ed.getSliceName()+")";
1295    
1296    StringBuilder b = new StringBuilder();
1297    boolean first = true;
1298    for (ElementDefinitionSlicingDiscriminatorComponent d : ed.getSlicing().getDiscriminator()) {
1299      if (first) 
1300        first = false;
1301      else
1302        b.append("|");
1303      b.append(d.getPath());
1304    }
1305    return " (slicing by "+b.toString()+")";
1306  }
1307
1308
1309//  private String typeSummary(ElementDefinition ed) {
1310//    StringBuilder b = new StringBuilder();
1311//    boolean first = true;
1312//    for (TypeRefComponent tr : ed.getType()) {
1313//      if (first) 
1314//        first = false;
1315//      else
1316//        b.append("|");
1317//      b.append(tr.getWorkingCode());
1318//    }
1319//    return b.toString();
1320//  }
1321
1322  private String typeSummaryWithProfile(ElementDefinition ed) {
1323    StringBuilder b = new StringBuilder();
1324    boolean first = true;
1325    for (TypeRefComponent tr : ed.getType()) {
1326      if (first) 
1327        first = false;
1328      else
1329        b.append("|");
1330      b.append(tr.getWorkingCode());
1331      if (tr.hasProfile()) {
1332        b.append("(");
1333        b.append(tr.getProfile());
1334        b.append(")");
1335        
1336      }
1337    }
1338    return b.toString();
1339  }
1340
1341
1342//  private boolean findMatchingElement(String id, List<ElementDefinition> list) {
1343//    for (ElementDefinition ed : list) {
1344//      if (ed.getId().equals(id))
1345//        return true;
1346//      if (id.endsWith("[x]")) {
1347//        if (ed.getId().startsWith(id.substring(0, id.length()-3)) && !ed.getId().substring(id.length()-3).contains("."))
1348//          return true;
1349//      }
1350//    }
1351//    return false;
1352//  }
1353
1354  protected ElementDefinition getById(List<ElementDefinition> list, String baseId) {
1355    for (ElementDefinition t : list) {
1356      if (baseId.equals(t.getId())) {
1357        return t;
1358      }
1359    }
1360    return null;
1361  }
1362
1363  protected void updateConstraintSources(ElementDefinition ed, String url) {
1364    for (ElementDefinitionConstraintComponent c : ed.getConstraint()) {
1365      if (!c.hasSource()) {
1366        c.setSource(url);
1367      }
1368    }
1369    
1370  }
1371
1372  protected Set<String> getListOfTypes(ElementDefinition e) {
1373    Set<String> result = new HashSet<>();
1374    for (TypeRefComponent t : e.getType()) {
1375      result.add(t.getCode());
1376    }
1377    return result;
1378  }
1379
1380  StructureDefinition getTypeForElement(StructureDefinitionDifferentialComponent differential, int diffCursor, String profileName,
1381      List<ElementDefinition> diffMatches, ElementDefinition outcome, String webUrl, Resource srcSD) {
1382    if (outcome.getType().size() == 0) {
1383      if (outcome.hasContentReference()) { 
1384        throw new Error(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_CONTENT_REFERENCE_IN_THIS_CONTEXT, outcome.getContentReference(), outcome.getId(), outcome.getPath()));
1385      } else {
1386        throw new DefinitionException(context.formatMessage(I18nConstants._HAS_NO_CHILDREN__AND_NO_TYPES_IN_PROFILE_, diffMatches.get(0).getPath(), differential.getElement().get(diffCursor).getPath(), profileName));
1387      }
1388    }
1389    if (outcome.getType().size() > 1) {
1390      for (TypeRefComponent t : outcome.getType()) {
1391        if (!t.getWorkingCode().equals("Reference"))
1392          throw new DefinitionException(context.formatMessage(I18nConstants._HAS_CHILDREN__AND_MULTIPLE_TYPES__IN_PROFILE_, diffMatches.get(0).getPath(), differential.getElement().get(diffCursor).getPath(), typeCode(outcome.getType()), profileName));
1393      }
1394    }
1395    StructureDefinition dt = getProfileForDataType(outcome.getType().get(0), webUrl, srcSD);
1396    if (dt == null)
1397      throw new DefinitionException(context.formatMessage(I18nConstants.UNKNOWN_TYPE__AT_, outcome.getType().get(0), diffMatches.get(0).getPath()));
1398    return dt;
1399  }
1400
1401  protected String sliceNames(List<ElementDefinition> diffMatches) {
1402    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1403    for (ElementDefinition ed : diffMatches) {
1404      if (ed.hasSliceName()) {
1405        b.append(ed.getSliceName());
1406      }
1407    }
1408    return b.toString();
1409  }
1410
1411  protected boolean isMatchingType(StructureDefinition sd, List<TypeRefComponent> types, String inner) {
1412    while (sd != null) {
1413      for (TypeRefComponent tr : types) {
1414        if (sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition") && sd.getType().equals(tr.getCode())) {
1415          return true;
1416        }
1417        if (inner == null && sd.getUrl().equals(tr.getCode())) {
1418          return true;
1419        }
1420        if (inner != null) {
1421          ElementDefinition ed = null;
1422          for (ElementDefinition t : sd.getSnapshot().getElement()) {
1423            if (inner.equals(t.getId())) {
1424              ed = t;
1425            }
1426          }
1427          if (ed != null) {
1428            return isMatchingType(ed.getType(), types);
1429          }
1430        }
1431      }
1432      sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd);    
1433    }
1434    return false;
1435  }
1436
1437  private boolean isMatchingType(List<TypeRefComponent> test, List<TypeRefComponent> desired) {
1438    for (TypeRefComponent t : test) {
1439      for (TypeRefComponent d : desired) {
1440        if (t.getCode().equals(d.getCode())) {
1441          return true;          
1442        }
1443      }
1444    }
1445    return false;
1446  }
1447
1448  protected boolean isValidType(TypeRefComponent t, ElementDefinition base) {
1449    for (TypeRefComponent tr : base.getType()) {
1450      if (tr.getCode().equals(t.getCode())) {
1451        return true;
1452      }
1453      if (tr.getWorkingCode().equals(t.getCode())) {
1454        System.err.println("Type error: use of a simple type \""+t.getCode()+"\" wrongly constraining "+base.getPath());
1455        return true;
1456      }
1457    }
1458    return false;
1459  }
1460
1461  protected boolean isGenerating(StructureDefinition sd) {
1462    return sd.hasUserData("profileutils.snapshot.generating");
1463  }
1464
1465
1466  protected void checkNotGenerating(StructureDefinition sd, String role) {
1467    if (sd.hasUserData("profileutils.snapshot.generating")) {
1468      throw new FHIRException(context.formatMessage(I18nConstants.ATTEMPT_TO_USE_A_SNAPSHOT_ON_PROFILE__AS__BEFORE_IT_IS_GENERATED, sd.getUrl(), role));
1469    }
1470  }
1471
1472  protected boolean isBaseResource(List<TypeRefComponent> types) {
1473    if (types.isEmpty())
1474      return false;
1475    for (TypeRefComponent type : types) {
1476      String t = type.getWorkingCode();
1477      if ("Resource".equals(t))
1478        return false;
1479    }
1480    return true;
1481    
1482  }
1483
1484  String determineFixedType(List<ElementDefinition> diffMatches, String fixedType, int i) {
1485    if (diffMatches.get(i).getType().size() == 0 && diffMatches.get(i).hasSliceName()) {
1486      String n = tail(diffMatches.get(i).getPath()).replace("[x]", "");
1487      String t = diffMatches.get(i).getSliceName().substring(n.length());
1488      if (isDataType(t)) {
1489        fixedType = t;
1490      } else if (isPrimitive(Utilities.uncapitalize(t))) {
1491        fixedType = Utilities.uncapitalize(t);
1492      } else {
1493        throw new FHIRException(context.formatMessage(I18nConstants.UNEXPECTED_CONDITION_IN_DIFFERENTIAL_TYPESLICETYPELISTSIZE__10_AND_IMPLICIT_SLICE_NAME_DOES_NOT_CONTAIN_A_VALID_TYPE__AT_, t, diffMatches.get(i).getPath(), diffMatches.get(i).getSliceName()));
1494      }                
1495    } else if (diffMatches.get(i).getType().size() == 1) {
1496      fixedType = diffMatches.get(i).getType().get(0).getCode();
1497    } else {
1498      throw new FHIRException(context.formatMessage(I18nConstants.UNEXPECTED_CONDITION_IN_DIFFERENTIAL_TYPESLICETYPELISTSIZE__1_AT_, diffMatches.get(i).getPath(), diffMatches.get(i).getSliceName()));
1499    }
1500    return fixedType;
1501  }
1502
1503
1504  protected BaseTypeSlice chooseMatchingBaseSlice(List<BaseTypeSlice> baseSlices, String type) {
1505    for (BaseTypeSlice bs : baseSlices) {
1506      if (bs.getType().equals(type)) {
1507        return bs;
1508      }
1509    }
1510    return null;
1511  }
1512
1513
1514  protected List<BaseTypeSlice> findBaseSlices(StructureDefinitionSnapshotComponent list, int start) {
1515    List<BaseTypeSlice> res = new ArrayList<>();
1516    ElementDefinition base = list.getElement().get(start);
1517    int i = start + 1;
1518    while (i <  list.getElement().size() && list.getElement().get(i).getPath().startsWith(base.getPath()+".")) {
1519      i++;      
1520    };
1521    while (i <  list.getElement().size() && list.getElement().get(i).getPath().equals(base.getPath()) && list.getElement().get(i).hasSliceName()) {
1522      int s = i;
1523      i++;
1524      while (i <  list.getElement().size() && list.getElement().get(i).getPath().startsWith(base.getPath()+".")) {
1525        i++;      
1526      };
1527      res.add(new BaseTypeSlice(list.getElement().get(s), list.getElement().get(s).getTypeFirstRep().getCode(), s, i-1));
1528    }
1529    return res;
1530  }
1531
1532
1533  protected String getWebUrl(StructureDefinition dt, String webUrl) {
1534    if (dt.hasWebPath()) {
1535      // this is a hack, but it works for now, since we don't have deep folders
1536      String url = dt.getWebPath();
1537      int i = url.lastIndexOf("/");
1538      if (i < 1) {
1539        return defWebRoot;
1540      } else {
1541        return url.substring(0, i+1);
1542      }
1543    } else {  
1544      return webUrl;
1545    }
1546  }
1547
1548  protected void removeStatusExtensions(ElementDefinition outcome) {
1549    outcome.removeExtension(ToolingExtensions.EXT_FMM_LEVEL);
1550    outcome.removeExtension(ToolingExtensions.EXT_FMM_SUPPORT);
1551    outcome.removeExtension(ToolingExtensions.EXT_FMM_DERIVED);
1552    outcome.removeExtension(ToolingExtensions.EXT_STANDARDS_STATUS);
1553    outcome.removeExtension(ToolingExtensions.EXT_NORMATIVE_VERSION);
1554    outcome.removeExtension(ToolingExtensions.EXT_WORKGROUP);    
1555    outcome.removeExtension(ToolingExtensions.EXT_FMM_SUPPORT);
1556    outcome.removeExtension(ToolingExtensions.EXT_FMM_DERIVED);
1557  }
1558
1559  protected String descED(List<ElementDefinition> list, int index) {
1560    return index >=0 && index < list.size() ? list.get(index).present() : "X";
1561  }
1562
1563  
1564 
1565  protected String rootName(String cpath) {
1566    String t = tail(cpath);
1567    return t.replace("[x]", "");
1568  }
1569
1570
1571  protected String determineTypeSlicePath(String path, String cpath) {
1572    String headP = path.substring(0, path.lastIndexOf("."));
1573//    String tailP = path.substring(path.lastIndexOf(".")+1);
1574    String tailC = cpath.substring(cpath.lastIndexOf(".")+1);
1575    return headP+"."+tailC;
1576  }
1577
1578
1579  protected boolean isImplicitSlicing(ElementDefinition ed, String path) {
1580    if (ed == null || ed.getPath() == null || path == null)
1581      return false;
1582    if (path.equals(ed.getPath()))
1583      return false;
1584    boolean ok = path.endsWith("[x]") && ed.getPath().startsWith(path.substring(0, path.length()-3));
1585    return ok;
1586  }
1587
1588
1589  protected boolean diffsConstrainTypes(List<ElementDefinition> diffMatches, String cPath, List<TypeSlice> typeList) {
1590//    if (diffMatches.size() < 2)
1591    //      return false;
1592    String p = diffMatches.get(0).getPath();
1593    if (!p.endsWith("[x]") && !cPath.endsWith("[x]"))
1594      return false;
1595    typeList.clear();
1596    String rn = tail(cPath);
1597    rn = rn.substring(0, rn.length()-3);
1598    for (int i = 0; i < diffMatches.size(); i++) {
1599      ElementDefinition ed = diffMatches.get(i);
1600      String n = tail(ed.getPath());
1601      if (!n.startsWith(rn))
1602        return false;
1603      String s = n.substring(rn.length());
1604      if (!s.contains(".")) {
1605        if (ed.hasSliceName() && ed.getType().size() == 1) {
1606          typeList.add(new TypeSlice(ed, ed.getTypeFirstRep().getWorkingCode()));
1607        } else if (ed.hasSliceName() && ed.getType().size() == 0) {
1608          if (isDataType(s)) {
1609            typeList.add(new TypeSlice(ed, s));
1610          } else if (isPrimitive(Utilities.uncapitalize(s))) {
1611            typeList.add(new TypeSlice(ed, Utilities.uncapitalize(s)));
1612          } else {
1613            String tn = ed.getSliceName().substring(n.length());
1614            if (isDataType(tn)) {
1615              typeList.add(new TypeSlice(ed, tn));
1616            } else if (isPrimitive(Utilities.uncapitalize(tn))) {
1617              typeList.add(new TypeSlice(ed, Utilities.uncapitalize(tn)));
1618            }
1619          }
1620        } else if (!ed.hasSliceName() && !s.equals("[x]")) {
1621          if (isDataType(s))
1622            typeList.add(new TypeSlice(ed, s));
1623          else if (isConstrainedDataType(s))
1624            typeList.add(new TypeSlice(ed, baseType(s)));
1625          else if (isPrimitive(Utilities.uncapitalize(s)))
1626            typeList.add(new TypeSlice(ed, Utilities.uncapitalize(s)));
1627        } else if (!ed.hasSliceName() && s.equals("[x]"))
1628            typeList.add(new TypeSlice(ed, null));
1629          }
1630        }
1631    return true;
1632  }
1633
1634
1635  protected List<ElementRedirection> redirectorStack(List<ElementRedirection> redirector, ElementDefinition outcome, String path) {
1636    List<ElementRedirection> result = new ArrayList<ElementRedirection>();
1637    result.addAll(redirector);
1638    result.add(new ElementRedirection(outcome, path));
1639    return result;
1640  }
1641
1642
1643  protected List<TypeRefComponent> getByTypeName(List<TypeRefComponent> type, String t) {
1644    List<TypeRefComponent> res = new ArrayList<TypeRefComponent>();
1645    for (TypeRefComponent tr : type) {
1646      if (t.equals(tr.getWorkingCode()))
1647          res.add(tr);
1648    }
1649    return res;
1650  }
1651
1652
1653  protected void replaceFromContentReference(ElementDefinition outcome, ElementDefinition tgt) {
1654    outcome.setContentReference(null);
1655    outcome.getType().clear(); // though it should be clear anyway
1656    outcome.getType().addAll(tgt.getType());    
1657  }
1658
1659
1660  protected boolean baseWalksInto(List<ElementDefinition> elements, int cursor) {
1661    if (cursor >= elements.size())
1662      return false;
1663    String path = elements.get(cursor).getPath();
1664    String prevPath = elements.get(cursor - 1).getPath();
1665    return path.startsWith(prevPath + ".");
1666  }
1667
1668
1669  protected  ElementDefinition fillOutFromBase(ElementDefinition profile, ElementDefinition usage) throws FHIRFormatError {
1670    ElementDefinition res = profile.copy();
1671    if (!res.hasSliceName())
1672      res.setSliceName(usage.getSliceName());
1673    if (!res.hasLabel())
1674      res.setLabel(usage.getLabel());
1675    for (Coding c : usage.getCode())
1676      if (!res.hasCode(c))
1677        res.addCode(c);
1678    
1679    if (!res.hasDefinition())
1680      res.setDefinition(usage.getDefinition());
1681    if (!res.hasShort() && usage.hasShort())
1682      res.setShort(usage.getShort());
1683    if (!res.hasComment() && usage.hasComment())
1684      res.setComment(usage.getComment());
1685    if (!res.hasRequirements() && usage.hasRequirements())
1686      res.setRequirements(usage.getRequirements());
1687    for (StringType c : usage.getAlias())
1688      if (!res.hasAlias(c.getValue()))
1689        res.addAlias(c.getValue());
1690    if (!res.hasMin() && usage.hasMin())
1691      res.setMin(usage.getMin());
1692    if (!res.hasMax() && usage.hasMax())
1693      res.setMax(usage.getMax());
1694     
1695    if (!res.hasFixed() && usage.hasFixed())
1696      res.setFixed(usage.getFixed());
1697    if (!res.hasPattern() && usage.hasPattern())
1698      res.setPattern(usage.getPattern());
1699    if (!res.hasExample() && usage.hasExample())
1700      res.setExample(usage.getExample());
1701    if (!res.hasMinValue() && usage.hasMinValue())
1702      res.setMinValue(usage.getMinValue());
1703    if (!res.hasMaxValue() && usage.hasMaxValue())
1704      res.setMaxValue(usage.getMaxValue());     
1705    if (!res.hasMaxLength() && usage.hasMaxLength())
1706      res.setMaxLength(usage.getMaxLength());
1707    if (!res.hasMustSupport() && usage.hasMustSupport())
1708      res.setMustSupport(usage.getMustSupport());
1709    if (!res.hasMustHaveValue() && usage.hasMustHaveValue())
1710      res.setMustHaveValue(usage.getMustHaveValue());
1711    if (!res.hasBinding() && usage.hasBinding())
1712      res.setBinding(usage.getBinding().copy());
1713    for (ElementDefinitionConstraintComponent c : usage.getConstraint())
1714      if (!res.hasConstraint(c.getKey()))
1715        res.addConstraint(c);
1716    for (Extension e : usage.getExtension()) {
1717      if (!res.hasExtension(e.getUrl()))
1718        res.addExtension(e.copy());
1719    }
1720    
1721    return res;
1722  }
1723
1724
1725  protected boolean checkExtensionDoco(ElementDefinition base) {
1726    // see task 3970. For an extension, there's no point copying across all the underlying definitional stuff
1727    boolean isExtension = (base.getPath().equals("Extension") || base.getPath().endsWith(".extension") || base.getPath().endsWith(".modifierExtension")) &&
1728          (!base.hasBase() || !"II.extension".equals(base.getBase().getPath()));
1729    if (isExtension) {
1730      base.setDefinition("An Extension");
1731      base.setShort("Extension");
1732      base.setCommentElement(null);
1733      base.setRequirementsElement(null);
1734      base.getAlias().clear();
1735      base.getMapping().clear();
1736    }
1737    return isExtension;
1738  }
1739
1740
1741  protected String pathTail(List<ElementDefinition> diffMatches, int i) {
1742    
1743    ElementDefinition d = diffMatches.get(i);
1744    String s = d.getPath().contains(".") ? d.getPath().substring(d.getPath().lastIndexOf(".")+1) : d.getPath();
1745    return "."+s + (d.hasType() && d.getType().get(0).hasProfile() ? "["+d.getType().get(0).getProfile()+"]" : "");
1746  }
1747
1748
1749  protected void markDerived(ElementDefinition outcome) {
1750    for (ElementDefinitionConstraintComponent inv : outcome.getConstraint())
1751      inv.setUserData(UD_IS_DERIVED, true);
1752  }
1753
1754
1755  static String summarizeSlicing(ElementDefinitionSlicingComponent slice) {
1756    StringBuilder b = new StringBuilder();
1757    boolean first = true;
1758    for (ElementDefinitionSlicingDiscriminatorComponent d : slice.getDiscriminator()) {
1759      if (first)
1760        first = false;
1761      else
1762        b.append(", ");
1763      b.append(d.getType().toCode()+":"+d.getPath());
1764    }
1765    b.append(" (");
1766    if (slice.hasOrdered())
1767      b.append(slice.getOrdered() ? "ordered" : "unordered");
1768    b.append("/");
1769    if (slice.hasRules())
1770      b.append(slice.getRules().toCode());
1771    b.append(")");
1772    if (slice.hasDescription()) {
1773      b.append(" \"");
1774      b.append(slice.getDescription());
1775      b.append("\"");
1776    }
1777    return b.toString();
1778  }
1779
1780
1781  protected void updateFromBase(ElementDefinition derived, ElementDefinition base, String baseProfileUrl) {
1782    derived.setUserData(UD_BASE_MODEL, baseProfileUrl);
1783    derived.setUserData(UD_BASE_PATH, base.getPath());
1784    if (base.hasBase()) {
1785      if (!derived.hasBase())
1786        derived.setBase(new ElementDefinitionBaseComponent());
1787      derived.getBase().setPath(base.getBase().getPath());
1788      derived.getBase().setMin(base.getBase().getMin());
1789      derived.getBase().setMax(base.getBase().getMax());
1790    } else {
1791      if (!derived.hasBase())
1792        derived.setBase(new ElementDefinitionBaseComponent());
1793      derived.getBase().setPath(base.getPath());
1794      derived.getBase().setMin(base.getMin());
1795      derived.getBase().setMax(base.getMax());
1796    }
1797  }
1798
1799
1800  protected boolean pathStartsWith(String p1, String p2) {
1801    return p1.startsWith(p2) || (p2.endsWith("[x].") && p1.startsWith(p2.substring(0, p2.length()-4)));
1802  }
1803
1804  private boolean pathMatches(String p1, String p2) {
1805    return p1.equals(p2) || (p2.endsWith("[x]") && p1.startsWith(p2.substring(0, p2.length()-3)) && !p1.substring(p2.length()-3).contains("."));
1806  }
1807
1808
1809  protected String fixedPathSource(String contextPath, String pathSimple, List<ElementRedirection> redirector) {
1810    if (contextPath == null)
1811      return pathSimple;
1812//    String ptail = pathSimple.substring(contextPath.length() + 1);
1813    if (redirector != null && redirector.size() > 0) {
1814      String ptail = null;
1815      if (contextPath.length() >= pathSimple.length()) {
1816        ptail = pathSimple.substring(pathSimple.indexOf(".")+1);
1817      } else {
1818        ptail = pathSimple.substring(contextPath.length()+1);
1819      }
1820      return redirector.get(redirector.size()-1).getPath()+"."+ptail;
1821//      return contextPath+"."+tail(redirector.getPath())+"."+ptail.substring(ptail.indexOf(".")+1);
1822    } else {
1823      String ptail = pathSimple.substring(pathSimple.indexOf(".")+1);
1824      return contextPath+"."+ptail;
1825    }
1826  }
1827  
1828  protected String fixedPathDest(String contextPath, String pathSimple, List<ElementRedirection> redirector, String redirectSource) {
1829    String s;
1830    if (contextPath == null)
1831      s = pathSimple;
1832    else {
1833      if (redirector != null && redirector.size() > 0) {
1834        String ptail = null;
1835        if (redirectSource.length() >= pathSimple.length()) {
1836          ptail = pathSimple.substring(pathSimple.indexOf(".")+1);
1837        } else {
1838          ptail = pathSimple.substring(redirectSource.length()+1);
1839        }
1840  //      ptail = ptail.substring(ptail.indexOf(".")+1);
1841        s = contextPath+"."+/*tail(redirector.getPath())+"."+*/ptail;
1842      } else {
1843        String ptail = pathSimple.substring(pathSimple.indexOf(".")+1);
1844        s = contextPath+"."+ptail;
1845      }
1846    }
1847    return s;
1848  }  
1849
1850  protected StructureDefinition getProfileForDataType(TypeRefComponent type, String webUrl, Resource src)  {
1851    StructureDefinition sd = null;
1852    if (type.hasProfile()) {
1853      sd = context.fetchResource(StructureDefinition.class, type.getProfile().get(0).getValue(), src);
1854      if (sd == null) {
1855        if (makeXVer().matchingUrl(type.getProfile().get(0).getValue()) && xver.status(type.getProfile().get(0).getValue()) == XVerExtensionStatus.Valid) {
1856          sd = xver.makeDefinition(type.getProfile().get(0).getValue());              
1857          generateSnapshot(context.fetchTypeDefinition("Extension"), sd, sd.getUrl(), webUrl, sd.getName());
1858        }
1859      }
1860      if (sd == null) {
1861        if (debug) {
1862          System.err.println("Failed to find referenced profile: " + type.getProfile());
1863        }
1864      }
1865        
1866    }
1867    if (sd == null)
1868      sd = context.fetchTypeDefinition(type.getWorkingCode());
1869    if (sd == null)
1870      System.err.println("XX: failed to find profle for type: " + type.getWorkingCode()); // debug GJM
1871    return sd;
1872  }
1873
1874  protected StructureDefinition getProfileForDataType(String type)  {
1875    StructureDefinition sd = context.fetchTypeDefinition(type);
1876    if (sd == null)
1877      System.err.println("XX: failed to find profle for type: " + type); // debug GJM
1878    return sd;
1879  }
1880
1881  static String typeCode(List<TypeRefComponent> types) {
1882    StringBuilder b = new StringBuilder();
1883    boolean first = true;
1884    for (TypeRefComponent type : types) {
1885      if (first) first = false; else b.append(", ");
1886      b.append(type.getWorkingCode());
1887      if (type.hasTargetProfile())
1888        b.append("{"+type.getTargetProfile()+"}");
1889      else if (type.hasProfile())
1890        b.append("{"+type.getProfile()+"}");
1891    }
1892    return b.toString();
1893  }
1894
1895
1896  protected boolean isDataType(List<TypeRefComponent> types) {
1897    if (types.isEmpty())
1898      return false;
1899    for (TypeRefComponent type : types) {
1900      String t = type.getWorkingCode();
1901      if (!isDataType(t) && !isPrimitive(t))
1902        return false;
1903    }
1904    return true;
1905  }
1906
1907
1908  /**
1909   * Finds internal references in an Element's Binding and StructureDefinition references (in TypeRef) and bases them on the given url
1910   * @param url - the base url to use to turn internal references into absolute references
1911   * @param element - the Element to update
1912   * @return - the updated Element
1913   */
1914  public ElementDefinition updateURLs(String url, String webUrl, ElementDefinition element) {
1915    if (element != null) {
1916      ElementDefinition defn = element;
1917      if (defn.hasBinding() && defn.getBinding().hasValueSet() && defn.getBinding().getValueSet().startsWith("#"))
1918        defn.getBinding().setValueSet(url+defn.getBinding().getValueSet());
1919      for (TypeRefComponent t : defn.getType()) {
1920        for (UriType u : t.getProfile()) {
1921          if (u.getValue().startsWith("#"))
1922            u.setValue(url+t.getProfile());
1923        }
1924        for (UriType u : t.getTargetProfile()) {
1925          if (u.getValue().startsWith("#"))
1926            u.setValue(url+t.getTargetProfile());
1927        }
1928      }
1929      if (webUrl != null) {
1930        // also, must touch up the markdown
1931        if (element.hasDefinition()) {
1932          element.setDefinition(processRelativeUrls(element.getDefinition(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false));
1933        }
1934        if (element.hasComment()) {
1935          element.setComment(processRelativeUrls(element.getComment(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false));
1936        }
1937        if (element.hasRequirements()) {
1938          element.setRequirements(processRelativeUrls(element.getRequirements(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false));
1939        }
1940        if (element.hasMeaningWhenMissing()) {
1941          element.setMeaningWhenMissing(processRelativeUrls(element.getMeaningWhenMissing(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false));
1942        }
1943        if (element.hasBinding() && element.getBinding().hasDescription()) {
1944          element.getBinding().setDescription(processRelativeUrls(element.getBinding().getDescription(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false));
1945        }
1946        for (Extension ext : element.getExtension()) {
1947          if (ext.hasValueMarkdownType()) {
1948            MarkdownType md = ext.getValueMarkdownType();
1949            md.setValue(processRelativeUrls(md.getValue(), webUrl, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, false));
1950          }
1951        }
1952      }
1953    }
1954    return element;
1955  }
1956
1957  
1958  public static String processRelativeUrls(String markdown, String webUrl, String basePath, List<String> resourceNames, Set<String> baseFilenames, Set<String> localFilenames, boolean processRelatives) {
1959    if (markdown == null) {
1960      return "";
1961    }
1962    Set<String> anchorRefs = new HashSet<>();
1963    markdown = markdown+" ";
1964    
1965    StringBuilder b = new StringBuilder();
1966    int i = 0;
1967    int left = -1;
1968    boolean processingLink = false;
1969    int linkLeft = -1;
1970    while (i < markdown.length()) {
1971      if (markdown.charAt(i) == '[') {
1972        if (left == -1) {
1973          left = i;
1974        } else {
1975          left = Integer.MAX_VALUE;
1976        }
1977      }
1978      if (markdown.charAt(i) == ']') {
1979        if (left != -1 && left != Integer.MAX_VALUE && markdown.length() > i && markdown.charAt(i+1) != '(') {
1980          String n = markdown.substring(left+1, i);
1981          if (anchorRefs.contains(n) && markdown.length() > i && markdown.charAt(i+1) == ':') {
1982            processingLink = true;            
1983          } else {
1984            anchorRefs.add(n);
1985          }
1986        }
1987        left = -1;
1988      }
1989      if (processingLink) {
1990        char ch = markdown.charAt(i);
1991        if (linkLeft == -1) {
1992          if (ch != ']' && ch != ':' && !Character.isWhitespace(ch)) {
1993            linkLeft = i;
1994          } else {
1995            b.append(ch);
1996          }
1997        } else {
1998          if (Character.isWhitespace(ch)) {
1999            // found the end of the processible link:
2000            String url = markdown.substring(linkLeft, i);
2001            if (isLikelySourceURLReference(url, resourceNames, baseFilenames, localFilenames, webUrl)) {
2002              b.append(basePath);
2003              if (!Utilities.noString(basePath) && !basePath.endsWith("/")) {
2004                b.append("/");
2005              }
2006            }
2007            b.append(url);
2008            b.append(ch);
2009            linkLeft = -1;
2010          }
2011        }
2012      } else {
2013        if (i < markdown.length()-3 && markdown.substring(i, i+2).equals("](")) {
2014
2015          int j = i + 2;
2016          while (j < markdown.length() && markdown.charAt(j) != ')')
2017            j++;
2018          if (j < markdown.length()) {
2019            String url = markdown.substring(i+2, j);
2020            if (!Utilities.isAbsoluteUrl(url) && !url.startsWith("..")) {
2021              // 
2022              // In principle, relative URLs are supposed to be converted to absolute URLs in snapshots. 
2023              // that's what this code is doing. 
2024              // 
2025              // But that hasn't always happened and there's packages out there where the snapshots 
2026              // contain relative references that actually are references to the main specification 
2027              // 
2028              // This code is trying to guess which relative references are actually to the
2029              // base specification.
2030              // 
2031              if (isLikelySourceURLReference(url, resourceNames, baseFilenames, localFilenames, webUrl)) {
2032                b.append("](");
2033                b.append(basePath);
2034                if (!Utilities.noString(basePath) && !basePath.endsWith("/")) {
2035                  b.append("/");
2036                }
2037                i = i + 1;
2038              } else {
2039                b.append("](");
2040                // disabled 7-Dec 2021 GDG - we don't want to fool with relative URLs at all? 
2041                // re-enabled 11-Feb 2022 GDG - we do want to do this. At least, $assemble in davinci-dtr, where the markdown comes from the SDC IG, and an SDC local reference must be changed to point to SDC. in this case, it's called when generating snapshots
2042                // added processRelatives parameter to deal with this (well, to try)
2043                if (processRelatives && webUrl != null && !issLocalFileName(url, localFilenames)) {
2044                  //                System.out.println("Making "+url+" relative to '"+webUrl+"'");
2045                  b.append(webUrl);
2046                } else {
2047                  //                System.out.println("Not making "+url+" relative to '"+webUrl+"'");
2048                }
2049                i = i + 1;
2050              }
2051            } else
2052              b.append(markdown.charAt(i));
2053          } else 
2054            b.append(markdown.charAt(i));
2055        } else {
2056          b.append(markdown.charAt(i));
2057        }
2058      }
2059      i++;
2060    }
2061    String s = b.toString();
2062    return Utilities.rightTrim(s);
2063  }
2064
2065  private static boolean issLocalFileName(String url, Set<String> localFilenames) {
2066    if (localFilenames != null) {
2067      for (String n : localFilenames) {
2068        if (url.startsWith(n.toLowerCase())) {
2069          return true;
2070        }
2071      }
2072    }
2073    return false; 
2074  }
2075
2076
2077  private static boolean isLikelySourceURLReference(String url, List<String> resourceNames, Set<String> baseFilenames, Set<String> localFilenames, String baseUrl) {
2078    if (url == null) {
2079      return false;
2080    }
2081    if (baseUrl != null && !baseUrl.startsWith("http://hl7.org/fhir/R")) {
2082      if (resourceNames != null) {
2083        for (String n : resourceNames) {
2084          if (n != null && url.startsWith(n.toLowerCase()+".html")) {
2085            return true;
2086          }
2087          if (n != null && url.startsWith(n.toLowerCase()+"-definitions.html")) {
2088            return true;
2089          }
2090        }
2091      }
2092      if (localFilenames != null) {
2093        for (String n : localFilenames) {
2094          if (n != null && url.startsWith(n.toLowerCase())) {
2095            return false;
2096          }
2097        }
2098      }
2099      if (baseFilenames != null) {
2100        for (String n : baseFilenames) {
2101          if (n != null && url.startsWith(n.toLowerCase())) {
2102            return true;
2103          }
2104        }
2105      }
2106    }
2107    return 
2108        url.startsWith("extensibility.html") || 
2109        url.startsWith("terminologies.html") || 
2110        url.startsWith("observation.html") || 
2111        url.startsWith("codesystem.html") || 
2112        url.startsWith("fhirpath.html") || 
2113        url.startsWith("datatypes.html") || 
2114        url.startsWith("operations.html") || 
2115        url.startsWith("resource.html") || 
2116        url.startsWith("elementdefinition.html") ||
2117        url.startsWith("element-definitions.html") ||
2118        url.startsWith("snomedct.html") ||
2119        url.startsWith("loinc.html") ||
2120        url.startsWith("http.html") ||
2121        url.startsWith("references") ||
2122        url.startsWith("license.html") ||
2123        url.startsWith("narrative.html") || 
2124        url.startsWith("search.html") ||
2125        url.startsWith("security.html") ||
2126        url.startsWith("versions.html") ||
2127        url.startsWith("patient-operation-match.html") ||
2128        (url.startsWith("extension-") && url.contains(".html")) || 
2129        url.startsWith("resource-definitions.html");
2130  }
2131
2132  protected List<ElementDefinition> getSiblings(List<ElementDefinition> list, ElementDefinition current) {
2133    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
2134    String path = current.getPath();
2135    int cursor = list.indexOf(current)+1;
2136    while (cursor < list.size() && list.get(cursor).getPath().length() >= path.length()) {
2137      if (pathMatches(list.get(cursor).getPath(), path))
2138        result.add(list.get(cursor));
2139      cursor++;
2140    }
2141    return result;
2142  }
2143
2144  protected void updateFromSlicing(ElementDefinitionSlicingComponent dst, ElementDefinitionSlicingComponent src) {
2145    if (src.hasOrderedElement())
2146      dst.setOrderedElement(src.getOrderedElement().copy());
2147    if (src.hasDiscriminator()) {
2148      //    dst.getDiscriminator().addAll(src.getDiscriminator());  Can't use addAll because it uses object equality, not string equality
2149      for (ElementDefinitionSlicingDiscriminatorComponent s : src.getDiscriminator()) {
2150        boolean found = false;
2151        for (ElementDefinitionSlicingDiscriminatorComponent d : dst.getDiscriminator()) {
2152          if (matches(d, s)) {
2153            found = true;
2154            break;
2155          }
2156        }
2157        if (!found)
2158          dst.getDiscriminator().add(s);
2159      }
2160    }
2161    if (src.hasRulesElement())
2162      dst.setRulesElement(src.getRulesElement().copy());
2163  }
2164
2165  protected boolean orderMatches(BooleanType diff, BooleanType base) {
2166    return (diff == null) || (base == null) || (diff.getValue() == base.getValue());
2167  }
2168
2169  protected boolean discriminatorMatches(List<ElementDefinitionSlicingDiscriminatorComponent> diff, List<ElementDefinitionSlicingDiscriminatorComponent> base) {
2170    if (diff.isEmpty() || base.isEmpty())
2171        return true;
2172    if (diff.size() != base.size())
2173        return false;
2174    for (int i = 0; i < diff.size(); i++)
2175        if (!matches(diff.get(i), base.get(i)))
2176                return false;
2177    return true;
2178  }
2179
2180  private boolean matches(ElementDefinitionSlicingDiscriminatorComponent c1, ElementDefinitionSlicingDiscriminatorComponent c2) {
2181    return c1.getType().equals(c2.getType()) && c1.getPath().equals(c2.getPath());
2182  }
2183
2184
2185  protected boolean ruleMatches(SlicingRules diff, SlicingRules base) {
2186    return (diff == null) || (base == null) || (diff == base) || (base == SlicingRules.OPEN) ||
2187        ((diff == SlicingRules.OPENATEND && base == SlicingRules.CLOSED));
2188  }
2189
2190  protected boolean isSlicedToOneOnly(ElementDefinition e) {
2191    return (e.hasSlicing() && e.hasMaxElement() && e.getMax().equals("1"));
2192  }
2193
2194  protected ElementDefinitionSlicingComponent makeExtensionSlicing() {
2195        ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent();
2196    slice.addDiscriminator().setPath("url").setType(DiscriminatorType.VALUE);
2197    slice.setOrdered(false);
2198    slice.setRules(SlicingRules.OPEN);
2199    return slice;
2200  }
2201
2202  protected boolean isExtension(ElementDefinition currentBase) {
2203    return currentBase.getPath().endsWith(".extension") || currentBase.getPath().endsWith(".modifierExtension");
2204  }
2205
2206  boolean hasInnerDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, List<ElementDefinition> base, boolean allowSlices) throws DefinitionException {
2207    end = Math.min(context.getElement().size(), end);
2208    start = Math.max(0,  start);
2209  
2210    for (int i = start; i <= end; i++) {
2211      ElementDefinition ed = context.getElement().get(i);
2212      String statedPath = ed.getPath();
2213      if (!allowSlices && statedPath.equals(path) && ed.hasSliceName()) {
2214        return false;
2215      } else if (statedPath.startsWith(path+".")) {
2216        return true;
2217      } else if (path.endsWith("[x]") && statedPath.startsWith(path.substring(0, path.length() -3))) {
2218        return true;
2219      } else if (i != start && !allowSlices && !statedPath.startsWith(path+".")) {
2220        return false;
2221      } else if (i != start && allowSlices && !statedPath.startsWith(path)) {
2222        return false;
2223      } else {
2224        // not sure why we get here, but returning false at this point makes a bunch of tests fail
2225      }
2226    }
2227    return false;
2228  }
2229
2230  protected List<ElementDefinition> getDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, String profileName) throws DefinitionException {
2231    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
2232    String[] p = path.split("\\.");
2233    for (int i = start; i <= end; i++) {
2234      String statedPath = context.getElement().get(i).getPath();
2235      String[] sp = statedPath.split("\\.");
2236      boolean ok = sp.length == p.length;
2237      for (int j = 0; j < p.length; j++) {
2238        ok = ok && sp.length > j && (p[j].equals(sp[j]) || isSameBase(p[j], sp[j]));
2239      }
2240// don't need this debug check - everything is ok
2241//      if (ok != (statedPath.equals(path) || (path.endsWith("[x]") && statedPath.length() > path.length() - 2 &&
2242//            statedPath.substring(0, path.length()-3).equals(path.substring(0, path.length()-3)) &&
2243//            (statedPath.length() < path.length() || !statedPath.substring(path.length()).contains("."))))) {
2244//        System.out.println("mismatch in paths: "+statedPath +" vs " +path);
2245//      }
2246      if (ok) {
2247        /*
2248         * Commenting this out because it raises warnings when profiling inherited elements.  For example,
2249         * Error: unknown element 'Bundle.meta.profile' (or it is out of order) in profile ... (looking for 'Bundle.entry')
2250         * Not sure we have enough information here to do the check properly.  Might be better done when we're sorting the profile?
2251
2252        if (i != start && result.isEmpty() && !path.startsWith(context.getElement().get(start).getPath()))
2253          messages.add(new ValidationMessage(Source.ProfileValidator, IssueType.VALUE, "StructureDefinition.differential.element["+Integer.toString(start)+"]", "Error: unknown element '"+context.getElement().get(start).getPath()+"' (or it is out of order) in profile '"+url+"' (looking for '"+path+"')", IssueSeverity.WARNING));
2254
2255         */
2256        result.add(context.getElement().get(i));
2257      }
2258    }
2259    return result;
2260  }
2261
2262
2263  private boolean isSameBase(String p, String sp) {
2264    return (p.endsWith("[x]") && sp.startsWith(p.substring(0, p.length()-3))) || (sp.endsWith("[x]") && p.startsWith(sp.substring(0, sp.length()-3))) ;
2265  }
2266
2267  protected int findEndOfElement(StructureDefinitionDifferentialComponent context, int cursor) {
2268            int result = cursor;
2269            if (cursor >= context.getElement().size())
2270              return result;
2271            String path = context.getElement().get(cursor).getPath()+".";
2272            while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path))
2273              result++;
2274            return result;
2275          }
2276
2277  protected int findEndOfElement(StructureDefinitionSnapshotComponent context, int cursor) {
2278            int result = cursor;
2279            String path = context.getElement().get(cursor).getPath()+".";
2280            while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path))
2281              result++;
2282            return result;
2283          }
2284
2285  protected boolean unbounded(ElementDefinition definition) {
2286    StringType max = definition.getMaxElement();
2287    if (max == null)
2288      return false; // this is not valid
2289    if (max.getValue().equals("1"))
2290      return false;
2291    if (max.getValue().equals("0"))
2292      return false;
2293    return true;
2294  }
2295
2296
2297  public void updateFromObligationProfiles(ElementDefinition base) {
2298    List<ElementDefinition> obligationProfileElements = new ArrayList<>();
2299    for (StructureDefinition sd : obligationProfiles) {
2300      ElementDefinition ed = sd.getSnapshot().getElementById(base.getId());
2301      if (ed != null) {
2302        obligationProfileElements.add(ed);
2303      }
2304    }
2305    for (ElementDefinition ed : obligationProfileElements) {
2306      for (Extension ext : ed.getExtension()) {
2307        if (Utilities.existsInList(ext.getUrl(), ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS)) {
2308          base.getExtension().add(ext.copy());
2309        }      
2310      }
2311    }
2312    boolean hasMustSupport = false;
2313    for (ElementDefinition ed : obligationProfileElements) {
2314      hasMustSupport = hasMustSupport || ed.hasMustSupportElement();
2315    }
2316    if (hasMustSupport) {
2317      for (ElementDefinition ed : obligationProfileElements) {
2318        mergeExtensions(base.getMustSupportElement(), ed.getMustSupportElement());
2319        if (ed.getMustSupport()) {
2320          base.setMustSupport(true);
2321        }
2322      }
2323    }
2324    boolean hasBinding = false;
2325    for (ElementDefinition ed : obligationProfileElements) {
2326      hasBinding = hasBinding || ed.hasBinding();
2327    }
2328    if (hasBinding) {
2329      ElementDefinitionBindingComponent binding = base.getBinding();
2330      for (ElementDefinition ed : obligationProfileElements) {
2331        for (Extension ext : ed.getBinding().getExtension()) {
2332          if (ToolingExtensions.EXT_BINDING_ADDITIONAL.equals(ext.getUrl())) {
2333            String p = ext.getExtensionString("purpose");
2334            if (!Utilities.existsInList(p, "maximum", "required", "extensible")) {
2335              if (!binding.hasExtension(ext)) {
2336                binding.getExtension().add(ext.copy());
2337              }
2338            }
2339          }
2340        }
2341        for (ElementDefinitionBindingAdditionalComponent ab : ed.getBinding().getAdditional()) {
2342          if (!Utilities.existsInList(ab.getPurpose().toCode(), "maximum", "required", "extensible")) {
2343            if (!binding.hasAdditional(ab)) {
2344              binding.getAdditional().add(ab.copy());
2345            }
2346          }
2347        }
2348      }
2349    }
2350  }
2351
2352  
2353  protected void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, boolean trimDifferential, String purl, StructureDefinition srcSD, StructureDefinition derivedSrc, String path, MappingAssistant mappings) throws DefinitionException, FHIRException {
2354    source.setUserData(UD_GENERATED_IN_SNAPSHOT, dest);
2355    // we start with a clone of the base profile ('dest') and we copy from the profile ('source')
2356    // over the top for anything the source has
2357    ElementDefinition base = dest;
2358    ElementDefinition derived = source;
2359    derived.setUserData(UD_DERIVATION_POINTER, base);
2360    boolean isExtension = checkExtensionDoco(base);
2361    List<ElementDefinition> obligationProfileElements = new ArrayList<>();
2362    for (StructureDefinition sd : obligationProfiles) {
2363      ElementDefinition ed = sd.getSnapshot().getElementById(base.getId());
2364      if (ed != null) {
2365        obligationProfileElements.add(ed);
2366      }
2367    }
2368
2369    // hack workaround for problem in R5 snapshots
2370    List<Extension> elist = dest.getExtensionsByUrl(ToolingExtensions.EXT_TRANSLATABLE);
2371    if (elist.size() == 2) {
2372      dest.getExtension().remove(elist.get(1));
2373    }
2374    
2375    updateExtensionsFromDefinition(dest, source);
2376
2377    for (ElementDefinition ed : obligationProfileElements) {
2378      for (Extension ext : ed.getExtension()) {
2379        if (Utilities.existsInList(ext.getUrl(), ToolingExtensions.EXT_OBLIGATION_CORE, ToolingExtensions.EXT_OBLIGATION_TOOLS)) {
2380          dest.getExtension().add(new Extension(ToolingExtensions.EXT_OBLIGATION_CORE, ext.getValue().copy()));
2381        }      
2382      }
2383    }
2384    // Before applying changes, apply them to what's in the profile
2385    StructureDefinition profile = null;
2386    boolean msg = true;
2387    if (base.hasSliceName()) {
2388      profile = base.getType().size() == 1 && base.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, base.getTypeFirstRep().getProfile().get(0).getValue(), srcSD) : null;
2389    }
2390    if (profile == null && source.getTypeFirstRep().hasProfile()) {
2391      String pu = source.getTypeFirstRep().getProfile().get(0).getValue();
2392      profile = context.fetchResource(StructureDefinition.class, pu, derivedSrc);
2393      if (profile == null) {
2394        if (makeXVer().matchingUrl(pu)) {
2395          switch (xver.status(pu)) {
2396            case BadVersion:
2397              throw new FHIRException("Reference to invalid version in extension url " + pu);
2398            case Invalid:
2399              throw new FHIRException("Reference to invalid extension " + pu);
2400            case Unknown:
2401              throw new FHIRException("Reference to unknown extension " + pu);
2402            case Valid:
2403              profile = xver.makeDefinition(pu);
2404              generateSnapshot(context.fetchTypeDefinition("Extension"), profile, profile.getUrl(), context.getSpecUrl(), profile.getName());
2405          }
2406        }
2407        
2408      }
2409      if (profile != null && !"Extension".equals(profile.getType()) && profile.getKind() != StructureDefinitionKind.RESOURCE && profile.getKind() != StructureDefinitionKind.LOGICAL) {
2410        // this is a problem - we're kind of hacking things here. The problem is that we sometimes want the details from the profile to override the 
2411        // inherited attributes, and sometimes not
2412        profile = null;
2413        msg = false;
2414      }
2415    }
2416    if (profile != null) {
2417      if (profile.getSnapshot().getElement().isEmpty()) {
2418        throw new DefinitionException(context.formatMessage(I18nConstants.SNAPSHOT_IS_EMPTY, profile.getVersionedUrl()));
2419      }
2420      ElementDefinition e = profile.getSnapshot().getElement().get(0);
2421      String webroot = profile.getUserString("webroot");
2422
2423      if (e.hasDefinition()) {
2424        base.setDefinition(processRelativeUrls(e.getDefinition(), webroot, context.getSpecUrl(), context.getResourceNames(), masterSourceFileNames, localFileNames, true));
2425      }
2426      base.setShort(e.getShort());
2427      if (e.hasCommentElement())
2428        base.setCommentElement(e.getCommentElement());
2429      if (e.hasRequirementsElement())
2430        base.setRequirementsElement(e.getRequirementsElement());
2431      base.getAlias().clear();
2432      base.getAlias().addAll(e.getAlias());
2433      base.getMapping().clear();
2434      base.getMapping().addAll(e.getMapping());
2435    } else if (source.getType().size() == 1 && source.getTypeFirstRep().hasProfile() && !source.getTypeFirstRep().getProfile().get(0).hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT)) {
2436      // todo: should we change down the profile_element if there's one?
2437      String type = source.getTypeFirstRep().getWorkingCode();
2438      if (msg) {
2439        if ("Extension".equals(type)) {
2440          System.out.println("Can't find Extension definition for "+source.getTypeFirstRep().getProfile().get(0).asStringValue()+" but trying to go on");          
2441          if (allowUnknownProfile != AllowUnknownProfile.ALL_TYPES) {
2442            throw new DefinitionException("Unable to find Extension definition for "+source.getTypeFirstRep().getProfile().get(0).asStringValue());          
2443          }
2444        } else {
2445          System.out.println("Can't find "+type+" profile "+source.getTypeFirstRep().getProfile().get(0).asStringValue()+" but trying to go on");          
2446          if (allowUnknownProfile == AllowUnknownProfile.NONE) {
2447            throw new DefinitionException("Unable to find "+type+" profile "+source.getTypeFirstRep().getProfile().get(0).asStringValue());          
2448          }
2449        }
2450      }
2451    }
2452    if (derived != null) {
2453      if (derived.hasSliceName()) {
2454        base.setSliceName(derived.getSliceName());
2455      }
2456      
2457      if (derived.hasShortElement()) {
2458        if (!Base.compareDeep(derived.getShortElement(), base.getShortElement(), false))
2459          base.setShortElement(derived.getShortElement().copy());
2460        else if (trimDifferential)
2461          derived.setShortElement(null);
2462        else if (derived.hasShortElement())
2463          derived.getShortElement().setUserData(UD_DERIVATION_EQUALS, true);
2464      }
2465
2466      if (derived.hasDefinitionElement()) {
2467        if (derived.getDefinition().startsWith("..."))
2468          base.setDefinition(Utilities.appendDerivedTextToBase(base.getDefinition(), derived.getDefinition()));
2469        else if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false))
2470          base.setDefinitionElement(derived.getDefinitionElement().copy());
2471        else if (trimDifferential)
2472          derived.setDefinitionElement(null);
2473        else if (derived.hasDefinitionElement())
2474          derived.getDefinitionElement().setUserData(UD_DERIVATION_EQUALS, true);
2475      }
2476
2477      if (derived.hasCommentElement()) {
2478        if (derived.getComment().startsWith("..."))
2479          base.setComment(Utilities.appendDerivedTextToBase(base.getComment(), derived.getComment()));
2480        else if (derived.hasCommentElement()!= base.hasCommentElement() || !Base.compareDeep(derived.getCommentElement(), base.getCommentElement(), false))
2481          base.setCommentElement(derived.getCommentElement().copy());
2482        else if (trimDifferential)
2483          base.setCommentElement(derived.getCommentElement().copy());
2484        else if (derived.hasCommentElement())
2485          derived.getCommentElement().setUserData(UD_DERIVATION_EQUALS, true);
2486      }
2487
2488      if (derived.hasLabelElement()) {
2489        if (derived.getLabel().startsWith("..."))
2490          base.setLabel(Utilities.appendDerivedTextToBase(base.getLabel(), derived.getLabel()));
2491        else if (!base.hasLabelElement() || !Base.compareDeep(derived.getLabelElement(), base.getLabelElement(), false))
2492          base.setLabelElement(derived.getLabelElement().copy());
2493        else if (trimDifferential)
2494          base.setLabelElement(derived.getLabelElement().copy());
2495        else if (derived.hasLabelElement())
2496          derived.getLabelElement().setUserData(UD_DERIVATION_EQUALS, true);
2497      }
2498
2499      if (derived.hasRequirementsElement()) {
2500        if (derived.getRequirements().startsWith("..."))
2501          base.setRequirements(Utilities.appendDerivedTextToBase(base.getRequirements(), derived.getRequirements()));
2502        else if (!base.hasRequirementsElement() || !Base.compareDeep(derived.getRequirementsElement(), base.getRequirementsElement(), false))
2503          base.setRequirementsElement(derived.getRequirementsElement().copy());
2504        else if (trimDifferential)
2505          base.setRequirementsElement(derived.getRequirementsElement().copy());
2506        else if (derived.hasRequirementsElement())
2507          derived.getRequirementsElement().setUserData(UD_DERIVATION_EQUALS, true);
2508      }
2509      // sdf-9
2510      if (derived.hasRequirements() && !base.getPath().contains("."))
2511        derived.setRequirements(null);
2512      if (base.hasRequirements() && !base.getPath().contains("."))
2513        base.setRequirements(null);
2514
2515      if (derived.hasAlias()) {
2516        if (!Base.compareDeep(derived.getAlias(), base.getAlias(), false))
2517          for (StringType s : derived.getAlias()) {
2518            if (!base.hasAlias(s.getValue()))
2519              base.getAlias().add(s.copy());
2520          }
2521        else if (trimDifferential)
2522          derived.getAlias().clear();
2523        else
2524          for (StringType t : derived.getAlias())
2525            t.setUserData(UD_DERIVATION_EQUALS, true);
2526      }
2527
2528      if (derived.hasMinElement()) {
2529        if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) {
2530          if (derived.getMin() < base.getMin() && !derived.hasSliceName()) // in a slice, minimum cardinality rules do not apply
2531            messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived min ("+Integer.toString(derived.getMin())+") cannot be less than the base min ("+Integer.toString(base.getMin())+") in "+srcSD.getVersionedUrl(), ValidationMessage.IssueSeverity.ERROR));
2532          base.setMinElement(derived.getMinElement().copy());
2533        } else if (trimDifferential)
2534          derived.setMinElement(null);
2535        else
2536          derived.getMinElement().setUserData(UD_DERIVATION_EQUALS, true);
2537      }
2538
2539      if (derived.hasMaxElement()) {
2540        if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) {
2541          if (isLargerMax(derived.getMax(), base.getMax()))
2542            messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived max ("+derived.getMax()+") cannot be greater than the base max ("+base.getMax()+")", ValidationMessage.IssueSeverity.ERROR));
2543          base.setMaxElement(derived.getMaxElement().copy());
2544        } else if (trimDifferential)
2545          derived.setMaxElement(null);
2546        else
2547          derived.getMaxElement().setUserData(UD_DERIVATION_EQUALS, true);
2548      }
2549
2550      if (derived.hasFixed()) {
2551        if (!Base.compareDeep(derived.getFixed(), base.getFixed(), true)) {
2552          base.setFixed(derived.getFixed().copy());
2553        } else if (trimDifferential)
2554          derived.setFixed(null);
2555        else
2556          derived.getFixed().setUserData(UD_DERIVATION_EQUALS, true);
2557      }
2558
2559      if (derived.hasPattern()) {
2560        if (!Base.compareDeep(derived.getPattern(), base.getPattern(), false)) {
2561          base.setPattern(derived.getPattern().copy());
2562        } else
2563          if (trimDifferential)
2564            derived.setPattern(null);
2565          else
2566            derived.getPattern().setUserData(UD_DERIVATION_EQUALS, true);
2567      }
2568
2569      List<ElementDefinitionExampleComponent> toDelB = new ArrayList<>();
2570      List<ElementDefinitionExampleComponent> toDelD = new ArrayList<>();
2571      for (ElementDefinitionExampleComponent ex : derived.getExample()) {
2572        boolean delete = ex.hasExtension(ToolingExtensions.EXT_ED_SUPPRESS);
2573        if (delete && "$all".equals(ex.getLabel())) {
2574          toDelB.addAll(base.getExample());
2575        } else {
2576          boolean found = false;
2577          for (ElementDefinitionExampleComponent exS : base.getExample()) {
2578            if (Base.compareDeep(ex.getLabel(), exS.getLabel(), false) && Base.compareDeep(ex.getValue(), exS.getValue(), false)) {
2579              if (delete) {
2580                toDelB.add(exS);
2581              } else {
2582                found = true;
2583              }
2584            }
2585          }
2586          if (delete) {
2587            toDelD.add(ex);
2588          } else if (!found) {
2589            base.addExample(ex.copy());
2590          } else if (trimDifferential) {
2591            derived.getExample().remove(ex);
2592          } else {
2593            ex.setUserData(UD_DERIVATION_EQUALS, true);
2594          }
2595        }
2596      }
2597      base.getExample().removeAll(toDelB);
2598      derived.getExample().removeAll(toDelD);
2599
2600      if (derived.hasMaxLengthElement()) {
2601        if (!Base.compareDeep(derived.getMaxLengthElement(), base.getMaxLengthElement(), false))
2602          base.setMaxLengthElement(derived.getMaxLengthElement().copy());
2603        else if (trimDifferential)
2604          derived.setMaxLengthElement(null);
2605        else
2606          derived.getMaxLengthElement().setUserData(UD_DERIVATION_EQUALS, true);
2607      }
2608  
2609      if (derived.hasMaxValue()) {
2610        if (!Base.compareDeep(derived.getMaxValue(), base.getMaxValue(), false))
2611          base.setMaxValue(derived.getMaxValue().copy());
2612        else if (trimDifferential)
2613          derived.setMaxValue(null);
2614        else
2615          derived.getMaxValue().setUserData(UD_DERIVATION_EQUALS, true);
2616      }
2617  
2618      if (derived.hasMinValue()) {
2619        if (!Base.compareDeep(derived.getMinValue(), base.getMinValue(), false))
2620          base.setMinValue(derived.getMinValue().copy());
2621        else if (trimDifferential)
2622          derived.setMinValue(null);
2623        else
2624          derived.getMinValue().setUserData(UD_DERIVATION_EQUALS, true);
2625      }
2626
2627      // todo: what to do about conditions?
2628      // condition : id 0..*
2629
2630      boolean hasMustSupport = derived.hasMustSupportElement();
2631      for (ElementDefinition ed : obligationProfileElements) {
2632        hasMustSupport = hasMustSupport || ed.hasMustSupportElement();
2633      }
2634      if (hasMustSupport) {
2635        BooleanType mse = derived.getMustSupportElement().copy();
2636        for (ElementDefinition ed : obligationProfileElements) {
2637          mergeExtensions(mse, ed.getMustSupportElement());
2638          if (ed.getMustSupport()) {
2639            mse.setValue(true);
2640          }
2641        }
2642        if (!(base.hasMustSupportElement() && Base.compareDeep(base.getMustSupportElement(), mse, false))) {
2643          if (base.hasMustSupport() && base.getMustSupport() && !derived.getMustSupport()) {
2644            messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Illegal constraint [must-support = false] when [must-support = true] in the base profile", ValidationMessage.IssueSeverity.ERROR));
2645          }
2646          base.setMustSupportElement(mse);
2647        } else if (trimDifferential)
2648          derived.setMustSupportElement(null);
2649        else
2650          derived.getMustSupportElement().setUserData(UD_DERIVATION_EQUALS, true);
2651      }
2652      
2653      if (derived.hasMustHaveValueElement()) {
2654        if (!(base.hasMustHaveValueElement() && Base.compareDeep(derived.getMustHaveValueElement(), base.getMustHaveValueElement(), false))) {
2655          if (base.hasMustHaveValue() && base.getMustHaveValue() && !derived.getMustHaveValue()) {
2656            messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Illegal constraint [must-have-value = false] when [must-have-value = true] in the base profile", ValidationMessage.IssueSeverity.ERROR));
2657          }
2658          base.setMustHaveValueElement(derived.getMustHaveValueElement().copy());
2659        } else if (trimDifferential)
2660          derived.setMustHaveValueElement(null);
2661        else
2662          derived.getMustHaveValueElement().setUserData(UD_DERIVATION_EQUALS, true);
2663      }
2664      if (derived.hasValueAlternatives()) {
2665        if (!Base.compareDeep(derived.getValueAlternatives(), base.getValueAlternatives(), false))
2666          for (CanonicalType s : derived.getValueAlternatives()) {
2667            if (!base.hasValueAlternatives(s.getValue()))
2668              base.getValueAlternatives().add(s.copy());
2669          }
2670        else if (trimDifferential)
2671          derived.getValueAlternatives().clear();
2672        else
2673          for (CanonicalType t : derived.getValueAlternatives())
2674            t.setUserData(UD_DERIVATION_EQUALS, true);
2675      }
2676
2677      // profiles cannot change : isModifier, defaultValue, meaningWhenMissing
2678      // but extensions can change isModifier
2679      if (isExtension) {
2680        if (derived.hasIsModifierElement() && !(base.hasIsModifierElement() && Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false)))
2681          base.setIsModifierElement(derived.getIsModifierElement().copy());
2682        else if (trimDifferential)
2683          derived.setIsModifierElement(null);
2684        else if (derived.hasIsModifierElement())
2685          derived.getIsModifierElement().setUserData(UD_DERIVATION_EQUALS, true);
2686        if (derived.hasIsModifierReasonElement() && !(base.hasIsModifierReasonElement() && Base.compareDeep(derived.getIsModifierReasonElement(), base.getIsModifierReasonElement(), false)))
2687          base.setIsModifierReasonElement(derived.getIsModifierReasonElement().copy());
2688        else if (trimDifferential)
2689          derived.setIsModifierReasonElement(null);
2690        else if (derived.hasIsModifierReasonElement())
2691          derived.getIsModifierReasonElement().setUserData(UD_DERIVATION_EQUALS, true);
2692      }
2693
2694      boolean hasBinding = derived.hasBinding();
2695      for (ElementDefinition ed : obligationProfileElements) {
2696        hasBinding = hasBinding || ed.hasBinding();
2697      }
2698      if (hasBinding) {
2699        updateExtensionsFromDefinition(dest.getBinding(), source.getBinding());
2700        ElementDefinitionBindingComponent binding = derived.getBinding();
2701        for (ElementDefinition ed : obligationProfileElements) {
2702          for (Extension ext : ed.getBinding().getExtension()) {
2703            if (ToolingExtensions.EXT_BINDING_ADDITIONAL.equals(ext.getUrl())) {
2704              String p = ext.getExtensionString("purpose");
2705              if (!Utilities.existsInList(p, "maximum", "required", "extensible")) {
2706                if (!binding.hasExtension(ext)) {
2707                  binding.getExtension().add(ext.copy());
2708                }
2709              }
2710            }
2711          }
2712          for (ElementDefinitionBindingAdditionalComponent ab : ed.getBinding().getAdditional()) {
2713            if (!Utilities.existsInList(ab.getPurpose().toCode(), "maximum", "required", "extensible")) {
2714              if (binding.hasAdditional(ab)) {
2715                binding.getAdditional().add(ab.copy());
2716              }
2717            }
2718          }
2719        }
2720        
2721        if (!base.hasBinding() || !Base.compareDeep(derived.getBinding(), base.getBinding(), false)) {
2722          if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && derived.getBinding().getStrength() != BindingStrength.REQUIRED)
2723            messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "illegal attempt to change the binding on "+derived.getPath()+" from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode(), ValidationMessage.IssueSeverity.ERROR));
2724//            throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode());
2725          else if (base.hasBinding() && derived.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && base.getBinding().hasValueSet() && derived.getBinding().hasValueSet()) {
2726            ValueSet baseVs = context.findTxResource(ValueSet.class, base.getBinding().getValueSet(), srcSD);
2727            ValueSet contextVs = context.findTxResource(ValueSet.class, derived.getBinding().getValueSet(), derivedSrc);
2728            if (baseVs == null) {
2729              messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING));
2730            } else if (contextVs == null) {
2731              messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING));
2732            } else {
2733              ValueSetExpansionOutcome expBase = context.expandVS(baseVs, true, false);
2734              ValueSetExpansionOutcome expDerived = context.expandVS(contextVs, true, false);
2735              if (expBase.getValueset() == null)
2736                messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING));
2737              else if (expDerived.getValueset() == null)
2738                messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING));
2739              else if (ToolingExtensions.hasExtension(expBase.getValueset().getExpansion(), ToolingExtensions.EXT_EXP_TOOCOSTLY)) {
2740                if (ToolingExtensions.hasExtension(expDerived.getValueset().getExpansion(), ToolingExtensions.EXT_EXP_TOOCOSTLY) || expDerived.getValueset().getExpansion().getContains().size() > 100) {
2741                  messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Unable to check if "+derived.getBinding().getValueSet()+" is a proper subset of " +base.getBinding().getValueSet()+" - base value set is too large to check", ValidationMessage.IssueSeverity.WARNING));
2742                } else {
2743                  boolean ok = true;
2744                  for (ValueSetExpansionContainsComponent cc : expDerived.getValueset().getExpansion().getContains()) {
2745                    ValidationResult vr = context.validateCode(null, cc.getSystem(), cc.getVersion(), cc.getCode(), null, baseVs);
2746                    if (!vr.isOk()) {
2747                      ok = false;
2748                      break;                      
2749                    }
2750                  }
2751                  if (!ok) {
2752                    messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" is not a subset of binding "+base.getBinding().getValueSet(), ValidationMessage.IssueSeverity.ERROR));
2753                  }
2754                }
2755              } else if (!isSubset(expBase.getValueset(), expDerived.getValueset()))
2756                messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" is not a subset of binding "+base.getBinding().getValueSet(), ValidationMessage.IssueSeverity.ERROR));
2757            }
2758          }
2759          ElementDefinitionBindingComponent d = derived.getBinding();
2760          ElementDefinitionBindingComponent nb = base.getBinding().copy();
2761          if (!COPY_BINDING_EXTENSIONS) {
2762            nb.getExtension().clear();
2763          }
2764          nb.setDescription(null);
2765          nb.getExtension().addAll(d.getExtension());
2766          if (d.hasStrength()) {
2767            nb.setStrength(d.getStrength());
2768          }
2769          if (d.hasDescription()) {
2770            nb.setDescription(d.getDescription());
2771          }
2772          if (d.hasValueSet()) {
2773            nb.setValueSet(d.getValueSet());
2774          }
2775          for (ElementDefinitionBindingAdditionalComponent ab : d.getAdditional()) {
2776            ElementDefinitionBindingAdditionalComponent eab = getMatchingAdditionalBinding(nb, ab);
2777            if (eab != null) {
2778              mergeAdditionalBinding(eab, ab);
2779            } else {
2780              nb.getAdditional().add(ab);
2781            }
2782          }
2783          base.setBinding(nb); 
2784        } else if (trimDifferential)
2785          derived.setBinding(null);
2786        else
2787          derived.getBinding().setUserData(UD_DERIVATION_EQUALS, true);
2788      } else if (base.hasBinding()) {
2789         base.getBinding().getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), ProfileUtilities.NON_INHERITED_ED_URLS));
2790      }
2791
2792      if (derived.hasIsSummaryElement()) {
2793        if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false)) {
2794          if (base.hasIsSummary() && !context.getVersion().equals("1.4.0")) // work around a known issue with some 1.4.0 cosntraints
2795            throw new Error(context.formatMessage(I18nConstants.ERROR_IN_PROFILE__AT__BASE_ISSUMMARY___DERIVED_ISSUMMARY__, purl, derived.getPath(), base.getIsSummaryElement().asStringValue(), derived.getIsSummaryElement().asStringValue()));
2796          base.setIsSummaryElement(derived.getIsSummaryElement().copy());
2797        } else if (trimDifferential)
2798          derived.setIsSummaryElement(null);
2799        else
2800          derived.getIsSummaryElement().setUserData(UD_DERIVATION_EQUALS, true);
2801      }
2802
2803      if (derived.hasType()) {
2804        if (!Base.compareDeep(derived.getType(), base.getType(), false)) {
2805          if (base.hasType()) {
2806            for (TypeRefComponent ts : derived.getType()) {
2807              checkTypeDerivation(purl, derivedSrc, base, derived, ts, path);
2808            }
2809          }
2810          base.getType().clear();
2811          for (TypeRefComponent t : derived.getType()) {
2812            TypeRefComponent tt = t.copy();
2813//            tt.setUserData(DERIVATION_EQUALS, true);
2814            base.getType().add(tt);
2815          }
2816        }
2817        else if (trimDifferential)
2818          derived.getType().clear();
2819        else
2820          for (TypeRefComponent t : derived.getType())
2821            t.setUserData(UD_DERIVATION_EQUALS, true);
2822      }
2823
2824      mappings.merge(derived, base); // note reversal of names to be correct in .merge()
2825
2826      // todo: constraints are cumulative. there is no replacing
2827      for (ElementDefinitionConstraintComponent s : base.getConstraint()) { 
2828        s.setUserData(UD_IS_DERIVED, true);
2829        if (!s.hasSource()) {
2830          s.setSource(srcSD.getUrl());
2831        } 
2832      }
2833      if (derived.hasConstraint()) {
2834        for (ElementDefinitionConstraintComponent s : derived.getConstraint()) {
2835          if (!base.hasConstraint(s.getKey())) {
2836            ElementDefinitionConstraintComponent inv = s.copy();
2837            base.getConstraint().add(inv);
2838          }
2839        }
2840      }
2841      for (IdType id : derived.getCondition()) {
2842        if (!base.hasCondition(id)) {
2843          base.getCondition().add(id);
2844        }
2845      }
2846      
2847      // now, check that we still have a bindable type; if not, delete the binding - see task 8477
2848      if (dest.hasBinding() && !hasBindableType(dest)) {
2849        dest.setBinding(null);
2850      }
2851        
2852//      // finally, we copy any extensions from source to dest
2853      //no, we already did.
2854//      for (Extension ex : derived.getExtension()) {
2855//        !
2856//        StructureDefinition sd  = context.fetchResource(StructureDefinition.class, ex.getUrl(), derivedSrc);
2857//        if (sd == null || sd.getSnapshot() == null || sd.getSnapshot().getElementFirstRep().getMax().equals("1")) {
2858//          ToolingExtensions.removeExtension(dest, ex.getUrl());
2859//        }
2860//        dest.addExtension(ex.copy());
2861//      }
2862    }
2863    if (dest.hasFixed()) {
2864      checkTypeOk(dest, dest.getFixed().fhirType(), srcSD, "fixed");
2865    }
2866    if (dest.hasPattern()) {
2867      checkTypeOk(dest, dest.getPattern().fhirType(), srcSD, "pattern");
2868    }
2869    //updateURLs(url, webUrl, dest);
2870  }
2871
2872  private void updateExtensionsFromDefinition(Element dest, Element source) {
2873    dest.getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), NON_INHERITED_ED_URLS) || (Utilities.existsInList(ext.getUrl(), DEFAULT_INHERITED_ED_URLS) && source.hasExtension(ext.getUrl())));
2874
2875    for (Extension ext : source.getExtension()) {
2876      if (!dest.hasExtension(ext.getUrl())) {
2877        dest.getExtension().add(ext.copy());
2878      } else if (Utilities.existsInList(ext.getUrl(), NON_OVERRIDING_ED_URLS)) {
2879        // do nothing
2880      } else if (Utilities.existsInList(ext.getUrl(), OVERRIDING_ED_URLS)) {
2881        dest.getExtensionByUrl(ext.getUrl()).setValue(ext.getValue());
2882      } else {
2883        dest.getExtension().add(ext.copy());  
2884      }
2885    }
2886  }
2887
2888  private void mergeAdditionalBinding(ElementDefinitionBindingAdditionalComponent dest, ElementDefinitionBindingAdditionalComponent source) {
2889    for (UsageContext t : source.getUsage()) {
2890      if (!hasUsage(dest, t)) {
2891        dest.addUsage(t);
2892      }
2893    }
2894    if (source.getAny()) {
2895      source.setAny(true);
2896    }
2897    if (source.hasShortDoco()) {
2898      dest.setShortDoco(source.getShortDoco());
2899    }
2900    if (source.hasDocumentation()) {
2901      dest.setDocumentation(source.getDocumentation());
2902    }
2903    
2904  }
2905
2906  private boolean hasUsage(ElementDefinitionBindingAdditionalComponent dest, UsageContext tgt) {
2907    for (UsageContext t : dest.getUsage()) {
2908      if (t.getCode() != null && t.getCode().matches(tgt.getCode()) && t.getValue() != null && t.getValue().equals(tgt.getValue())) {
2909        return true;
2910      }
2911    }
2912    return false;
2913  }
2914
2915  private ElementDefinitionBindingAdditionalComponent getMatchingAdditionalBinding(ElementDefinitionBindingComponent nb,ElementDefinitionBindingAdditionalComponent ab) {
2916    for (ElementDefinitionBindingAdditionalComponent t : nb.getAdditional()) {
2917      if (t.getValueSet() != null && t.getValueSet().equals(ab.getValueSet()) && t.getPurpose() == ab.getPurpose()) {
2918        return t;
2919      }
2920    }
2921    return null;
2922  }
2923
2924  private void mergeExtensions(Element tgt, Element src) {
2925     tgt.getExtension().addAll(src.getExtension());
2926  }
2927
2928  private void checkTypeDerivation(String purl, StructureDefinition srcSD, ElementDefinition base, ElementDefinition derived, TypeRefComponent ts, String path) {
2929    boolean ok = false;
2930    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
2931    String t = ts.getWorkingCode();
2932    String tDesc = ts.toString();
2933    for (TypeRefComponent td : base.getType()) {;
2934      boolean matchType = false;
2935      String tt = td.getWorkingCode();
2936      b.append(td.toString());
2937      if (td.hasCode() && (tt.equals(t))) {
2938        matchType = true;
2939      }
2940      if (!matchType) {
2941        StructureDefinition sdt = context.fetchTypeDefinition(tt);
2942        if (sdt != null && (sdt.getAbstract() || sdt.getKind() == StructureDefinitionKind.LOGICAL)) {
2943          StructureDefinition sdb = context.fetchTypeDefinition(t);
2944          while (sdb != null && !matchType) {
2945            matchType = sdb.getType().equals(sdt.getType());
2946            sdb = context.fetchResource(StructureDefinition.class, sdb.getBaseDefinition(), sdb);
2947          }
2948        }
2949      }
2950     // work around for old badly generated SDs
2951//      if (DONT_DO_THIS && Utilities.existsInList(tt, "Extension", "uri", "string", "Element")) {
2952//        matchType = true;
2953//      }
2954//      if (DONT_DO_THIS && Utilities.existsInList(tt, "Resource","DomainResource") && pkp.isResource(t)) {
2955//        matchType = true;
2956//      }
2957      if (matchType) {
2958        ts.copyExtensions(td, "http://hl7.org/fhir/StructureDefinition/elementdefinition-type-must-support", "http://hl7.org/fhir/StructureDefinition/elementdefinition-pattern", "http://hl7.org/fhir/StructureDefinition/obligation");
2959        if (ts.hasTargetProfile()) {
2960          // check that any derived target has a reference chain back to one of the base target profiles
2961          for (UriType u : ts.getTargetProfile()) {
2962            String url = u.getValue();
2963            boolean tgtOk = !td.hasTargetProfile() || sdConformsToTargets(path, derived.getPath(), url, td);            
2964            if (tgtOk) {
2965              ok = true;
2966            } else {
2967              if (messages == null) {
2968                throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT__THE_TARGET_PROFILE__IS_NOT__VALID_CONSTRAINT_ON_THE_BASE_, purl, derived.getPath(), url, td.getTargetProfile()));
2969              } else {
2970                messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, derived.getPath(), "The target profile " + u.getValue() + " is not a valid constraint on the base (" + td.getTargetProfile() + ") at " + derived.getPath(), IssueSeverity.ERROR));
2971              }
2972            }
2973          }
2974        } else {
2975          ok = true;
2976        }
2977      }
2978    }
2979    if (!ok && !isSuppressIgnorableExceptions()) {
2980      throw new DefinitionException(context.formatMessage(I18nConstants.STRUCTUREDEFINITION__AT__ILLEGAL_CONSTRAINED_TYPE__FROM__IN_, purl, derived.getPath(), tDesc, b.toString(), srcSD.getUrl()));
2981    }
2982  }
2983
2984
2985  private boolean sdConformsToTargets(String path, String dPath, String url, TypeRefComponent td) {
2986    if (td.hasTargetProfile(url)) {
2987      return true;
2988    }
2989    StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
2990    if (sd == null) {
2991      if (messages != null) {
2992        messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.BUSINESSRULE, path, "Cannot check whether the target profile " + url + " on "+dPath+" is valid constraint on the base because it is not known", IssueSeverity.WARNING));
2993      }
2994      return true;
2995    } else {
2996      if (sd.hasBaseDefinition() && sdConformsToTargets(path, dPath, sd.getBaseDefinition(), td)) {
2997        return true;
2998      }
2999      for (Extension ext : sd.getExtensionsByUrl(ToolingExtensions.EXT_SD_IMPOSE_PROFILE)) {
3000        if (sdConformsToTargets(path, dPath, ext.getValueCanonicalType().asStringValue(), td)) {
3001          return true;
3002        }
3003      }
3004    }
3005    return false;
3006  }
3007
3008  private void checkTypeOk(ElementDefinition dest, String ft, StructureDefinition sd, String fieldName) {
3009    boolean ok = false;
3010    Set<String> types = new HashSet<>();
3011    if (dest.getPath().contains(".")) {
3012      for (TypeRefComponent t : dest.getType()) {
3013        if (t.hasCode()) {
3014          types.add(t.getWorkingCode());
3015        }
3016        ok = ft.equals(t.getWorkingCode());
3017      }
3018    } else {
3019      types.add(sd.getType());
3020      ok = ft.equals(sd.getType());
3021
3022    }
3023    if (!ok) {
3024      messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.CONFLICT, dest.getId(), "The "+fieldName+" value has type '"+ft+"' which is not valid (valid "+Utilities.pluralize("type", dest.getType().size())+": "+types.toString()+")", IssueSeverity.ERROR));
3025    }
3026  }
3027
3028  private boolean hasBindableType(ElementDefinition ed) {
3029    for (TypeRefComponent tr : ed.getType()) {
3030      if (Utilities.existsInList(tr.getWorkingCode(), "Coding", "CodeableConcept", "Quantity", "uri", "string", "code", "CodeableReference")) {
3031        return true;
3032      }
3033      StructureDefinition sd = context.fetchTypeDefinition(tr.getCode());
3034      if (sd != null && sd.hasExtension(ToolingExtensions.EXT_BINDING_STYLE)) {
3035        return true;
3036      }
3037    }
3038    return false;
3039  }
3040
3041
3042  private boolean isLargerMax(String derived, String base) {
3043    if ("*".equals(base)) {
3044      return false;
3045    }
3046    if ("*".equals(derived)) {
3047      return true;
3048    }
3049    return Integer.parseInt(derived) > Integer.parseInt(base);
3050  }
3051
3052
3053  private boolean isSubset(ValueSet expBase, ValueSet expDerived) {
3054    return codesInExpansion(expDerived.getExpansion().getContains(), expBase.getExpansion());
3055  }
3056
3057
3058  private boolean codesInExpansion(List<ValueSetExpansionContainsComponent> contains, ValueSetExpansionComponent expansion) {
3059    for (ValueSetExpansionContainsComponent cc : contains) {
3060      if (!inExpansion(cc, expansion.getContains())) {
3061        return false;
3062      }
3063      if (!codesInExpansion(cc.getContains(), expansion)) {
3064        return false;
3065      }
3066    }
3067    return true;
3068  }
3069
3070
3071  private boolean inExpansion(ValueSetExpansionContainsComponent cc, List<ValueSetExpansionContainsComponent> contains) {
3072    for (ValueSetExpansionContainsComponent cc1 : contains) {
3073      if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode())) {
3074        return true;
3075      }
3076      if (inExpansion(cc,  cc1.getContains())) {
3077        return true;
3078      }
3079    }
3080    return false;
3081  }
3082  
3083  public void closeDifferential(StructureDefinition base, StructureDefinition derived) throws FHIRException {
3084    for (ElementDefinition edb : base.getSnapshot().getElement()) {
3085      if (isImmediateChild(edb) && !edb.getPath().endsWith(".id")) {
3086        ElementDefinition edm = getMatchInDerived(edb, derived.getDifferential().getElement());
3087        if (edm == null) {
3088          ElementDefinition edd = derived.getDifferential().addElement();
3089          edd.setPath(edb.getPath());
3090          edd.setMax("0");
3091        } else if (edb.hasSlicing()) {
3092          closeChildren(base, edb, derived, edm);
3093        }
3094      }
3095    }
3096    sortDifferential(base, derived, derived.getName(), new ArrayList<String>(), false);
3097  }
3098
3099  private void closeChildren(StructureDefinition base, ElementDefinition edb, StructureDefinition derived, ElementDefinition edm) {
3100//    String path = edb.getPath()+".";
3101    int baseStart = base.getSnapshot().getElement().indexOf(edb);
3102    int baseEnd = findEnd(base.getSnapshot().getElement(), edb, baseStart+1);
3103    int diffStart = derived.getDifferential().getElement().indexOf(edm);
3104    int diffEnd = findEnd(derived.getDifferential().getElement(), edm, diffStart+1);
3105    
3106    for (int cBase = baseStart; cBase < baseEnd; cBase++) {
3107      ElementDefinition edBase = base.getSnapshot().getElement().get(cBase);
3108      if (isImmediateChild(edBase, edb)) {
3109        ElementDefinition edMatch = getMatchInDerived(edBase, derived.getDifferential().getElement(), diffStart, diffEnd);
3110        if (edMatch == null) {
3111          ElementDefinition edd = derived.getDifferential().addElement();
3112          edd.setPath(edBase.getPath());
3113          edd.setMax("0");
3114        } else {
3115          closeChildren(base, edBase, derived, edMatch);
3116        }        
3117      }
3118    }
3119  }
3120
3121  private int findEnd(List<ElementDefinition> list, ElementDefinition ed, int cursor) {
3122    String path = ed.getPath()+".";
3123    while (cursor < list.size() && list.get(cursor).getPath().startsWith(path)) {
3124      cursor++;
3125    }
3126    return cursor;
3127  }
3128
3129
3130  private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list) {
3131    for (ElementDefinition t : list) {
3132      if (t.getPath().equals(ed.getPath())) {
3133        return t;
3134      }
3135    }
3136    return null;
3137  }
3138
3139  private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list, int start, int end) {
3140    for (int i = start; i < end; i++) {
3141      ElementDefinition t = list.get(i);
3142      if (t.getPath().equals(ed.getPath())) {
3143        return t;
3144      }
3145    }
3146    return null;
3147  }
3148
3149
3150  private boolean isImmediateChild(ElementDefinition ed) {
3151    String p = ed.getPath();
3152    if (!p.contains(".")) {
3153      return false;
3154    }
3155    p = p.substring(p.indexOf(".")+1);
3156    return !p.contains(".");
3157  }
3158
3159  private boolean isImmediateChild(ElementDefinition candidate, ElementDefinition base) {
3160    String p = candidate.getPath();
3161    if (!p.contains("."))
3162      return false;
3163    if (!p.startsWith(base.getPath()+"."))
3164      return false;
3165    p = p.substring(base.getPath().length()+1);
3166    return !p.contains(".");
3167  }
3168
3169
3170
3171  private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) {
3172    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
3173    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
3174      if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url"))
3175        return ed.getSnapshot().getElement().get(i);
3176      i++;
3177    }
3178    return null;
3179  }
3180
3181
3182
3183  protected ElementDefinitionResolution getElementById(StructureDefinition source, List<ElementDefinition> elements, String contentReference) {
3184    if (!contentReference.startsWith("#") && contentReference.contains("#")) {
3185      String url = contentReference.substring(0, contentReference.indexOf("#"));
3186      contentReference = contentReference.substring(contentReference.indexOf("#"));
3187      if (!url.equals(source.getUrl())){
3188        source = context.fetchResource(StructureDefinition.class, url, source);
3189        if (source == null) {
3190          return null;
3191        }
3192        elements = source.getSnapshot().getElement();
3193      }      
3194    }
3195    for (ElementDefinition ed : elements)
3196      if (ed.hasId() && ("#"+ed.getId()).equals(contentReference))
3197        return new ElementDefinitionResolution(source, ed);
3198    return null;
3199  }
3200
3201
3202  public static String describeExtensionContext(StructureDefinition ext) {
3203    StringBuilder b = new StringBuilder();
3204    b.append("Use on ");
3205    for (int i = 0; i < ext.getContext().size(); i++) {
3206      StructureDefinitionContextComponent ec = ext.getContext().get(i);
3207      if (i > 0) 
3208        b.append(i < ext.getContext().size() - 1 ? ", " : " or ");
3209      b.append(ec.getType().getDisplay());
3210      b.append(" ");
3211      b.append(ec.getExpression());
3212    }
3213    if (ext.hasContextInvariant()) {
3214      b.append(", with <a href=\"structuredefinition-definitions.html#StructureDefinition.contextInvariant\">Context Invariant</a> = ");
3215      boolean first = true;
3216      for (StringType s : ext.getContextInvariant()) {
3217        if (first)
3218          first = false;
3219        else
3220          b.append(", ");
3221        b.append("<code>"+s.getValue()+"</code>");
3222      }
3223    }
3224    return b.toString(); 
3225  }
3226
3227
3228
3229 
3230//  public XhtmlNode generateTable(String defFile, StructureDefinition profile, boolean diff, String imageFolder, boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, String imagePath,
3231//                                 boolean logicalModel, boolean allInvariants, Set<String> outputTracker, boolean mustSupport, RenderingContext rc) throws IOException, FHIRException {
3232//    return generateTable(defFile, profile, diff, imageFolder, inlineGraphics, profileBaseFileName, snapshot, corePath, imagePath, logicalModel, allInvariants, outputTracker, mustSupport, rc, "");
3233//  }
3234
3235 
3236
3237
3238 
3239  protected String tail(String path) {
3240    if (path == null) {
3241      return "";
3242    } else if (path.contains("."))
3243      return path.substring(path.lastIndexOf('.')+1);
3244    else
3245      return path;
3246  }
3247
3248  private boolean isDataType(String value) {
3249    StructureDefinition sd = context.fetchTypeDefinition(value);
3250    if (sd == null) // might be running before all SDs are available
3251      return Utilities.existsInList(value, "Address", "Age", "Annotation", "Attachment", "CodeableConcept", "Coding", "ContactPoint", "Count", "Distance", "Duration", "HumanName", "Identifier", "Money", "Period", "Quantity", "Range", "Ratio", "Reference", "SampledData", "Signature", "Timing", 
3252            "ContactDetail", "Contributor", "DataRequirement", "Expression", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition", "UsageContext");
3253    else 
3254      return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION;
3255  }
3256
3257  private boolean isConstrainedDataType(String value) {
3258    StructureDefinition sd = context.fetchTypeDefinition(value);
3259    if (sd == null) // might be running before all SDs are available
3260      return Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity");
3261    else 
3262      return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.CONSTRAINT;
3263  }
3264
3265  private String baseType(String value) {
3266    StructureDefinition sd = context.fetchTypeDefinition(value);
3267    if (sd != null) // might be running before all SDs are available
3268      return sd.getTypeName();
3269    if (Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity"))
3270      return "Quantity";
3271    throw new Error(context.formatMessage(I18nConstants.INTERNAL_ERROR___TYPE_NOT_KNOWN_, value));
3272  }
3273
3274
3275  protected boolean isPrimitive(String value) {
3276    StructureDefinition sd = context.fetchTypeDefinition(value);
3277    if (sd == null) // might be running before all SDs are available
3278      return Utilities.existsInList(value, "base64Binary", "boolean", "canonical", "code", "date", "dateTime", "decimal", "id", "instant", "integer", "integer64", "markdown", "oid", "positiveInt", "string", "time", "unsignedInt", "uri", "url", "uuid");
3279    else 
3280      return sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
3281  }
3282
3283//  private static String listStructures(StructureDefinition p) {
3284//    StringBuilder b = new StringBuilder();
3285//    boolean first = true;
3286//    for (ProfileStructureComponent s : p.getStructure()) {
3287//      if (first)
3288//        first = false;
3289//      else
3290//        b.append(", ");
3291//      if (pkp != null && pkp.hasLinkFor(s.getType()))
3292//        b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>");
3293//      else
3294//        b.append(s.getType());
3295//    }
3296//    return b.toString();
3297//  }
3298
3299
3300  public StructureDefinition getProfile(StructureDefinition source, String url) {
3301        StructureDefinition profile = null;
3302        String code = null;
3303        if (url.startsWith("#")) {
3304                profile = source;
3305                code = url.substring(1);
3306        } else if (context != null) {
3307                String[] parts = url.split("\\#");
3308                profile = context.fetchResource(StructureDefinition.class, parts[0], source);
3309      code = parts.length == 1 ? null : parts[1];
3310        }         
3311        if (profile == null)
3312                return null;
3313        if (code == null)
3314                return profile;
3315        for (Resource r : profile.getContained()) {
3316                if (r instanceof StructureDefinition && r.getId().equals(code))
3317                        return (StructureDefinition) r;
3318        }
3319        return null;
3320  }
3321
3322
3323
3324  private static class ElementDefinitionHolder {
3325    private String name;
3326    private ElementDefinition self;
3327    private int baseIndex = 0;
3328    private List<ElementDefinitionHolder> children;
3329    private boolean placeHolder = false;
3330
3331    public ElementDefinitionHolder(ElementDefinition self, boolean isPlaceholder) {
3332      super();
3333      this.self = self;
3334      this.name = self.getPath();
3335      this.placeHolder = isPlaceholder;
3336      children = new ArrayList<ElementDefinitionHolder>();      
3337    }
3338
3339    public ElementDefinitionHolder(ElementDefinition self) {
3340      this(self, false);
3341    }
3342
3343    public ElementDefinition getSelf() {
3344      return self;
3345    }
3346
3347    public List<ElementDefinitionHolder> getChildren() {
3348      return children;
3349    }
3350
3351    public int getBaseIndex() {
3352      return baseIndex;
3353    }
3354
3355    public void setBaseIndex(int baseIndex) {
3356      this.baseIndex = baseIndex;
3357    }
3358
3359    public boolean isPlaceHolder() {
3360      return this.placeHolder;
3361    }
3362
3363    @Override
3364    public String toString() {
3365      if (self.hasSliceName())
3366        return self.getPath()+"("+self.getSliceName()+")";
3367      else
3368        return self.getPath();
3369    }
3370  }
3371
3372  private static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> {
3373
3374    private boolean inExtension;
3375    private List<ElementDefinition> snapshot;
3376    private int prefixLength;
3377    private String base;
3378    private String name;
3379    private String baseName;
3380    private Set<String> errors = new HashSet<String>();
3381
3382    public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, int prefixLength, String name, String baseName) {
3383      this.inExtension = inExtension;
3384      this.snapshot = snapshot;
3385      this.prefixLength = prefixLength;
3386      this.base = base;
3387      if (Utilities.isAbsoluteUrl(base)) {
3388        this.base = urlTail(base);
3389      }
3390      this.name = name;
3391      this.baseName = baseName;
3392    }
3393
3394    @Override
3395    public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) {
3396      if (o1.getBaseIndex() == 0) {
3397        o1.setBaseIndex(find(o1.getSelf().getPath(), true));
3398      }
3399      if (o2.getBaseIndex() == 0) {
3400        o2.setBaseIndex(find(o2.getSelf().getPath(), true));
3401      }
3402      return o1.getBaseIndex() - o2.getBaseIndex();
3403    }
3404
3405    private int find(String path, boolean mandatory) {
3406      String op = path;
3407      int lc = 0;
3408      String actual = base+path.substring(prefixLength);
3409      for (int i = 0; i < snapshot.size(); i++) {
3410        String p = snapshot.get(i).getPath();
3411        if (p.equals(actual)) {
3412          return i;
3413        }
3414        if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length()-3)) && !(actual.endsWith("[x]")) && !actual.substring(p.length()-3).contains(".")) {
3415          return i;
3416        }
3417        if (actual.endsWith("[x]") && p.startsWith(actual.substring(0, actual.length()-3)) && !p.substring(actual.length()-3).contains(".")) {
3418          return i;
3419        }
3420        if (path.startsWith(p+".") && snapshot.get(i).hasContentReference()) {
3421          String ref = snapshot.get(i).getContentReference();
3422          if (ref.substring(1, 2).toUpperCase().equals(ref.substring(1,2))) {
3423            actual = base+(ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength);
3424            path = actual;
3425          } else if (ref.startsWith("http:")) {
3426            actual = base+(ref.substring(ref.indexOf("#")+1)+"."+path.substring(p.length()+1)).substring(prefixLength);
3427            path = actual;            
3428          } else {
3429            // Older versions of FHIR (e.g. 2016May) had reference of the style #parameter instead of #Parameters.parameter, so we have to handle that
3430            actual = base+(path.substring(0,  path.indexOf(".")+1) + ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength);
3431            path = actual;
3432          }
3433            
3434          i = 0;
3435          lc++;
3436          if (lc > MAX_RECURSION_LIMIT)
3437            throw new Error("Internal recursion detection: find() loop path recursion > "+MAX_RECURSION_LIMIT+" - check paths are valid (for path "+path+"/"+op+")");
3438        }
3439      }
3440      if (mandatory) {
3441        if (prefixLength == 0)
3442          errors.add("Differential contains path "+path+" which is not found in the base "+baseName);
3443        else
3444          errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the in base "+ baseName);
3445      }
3446      return 0;
3447    }
3448
3449    public void checkForErrors(List<String> errorList) {
3450      if (errors.size() > 0) {
3451//        CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
3452//        for (String s : errors)
3453//          b.append("StructureDefinition "+name+": "+s);
3454//        throw new DefinitionException(b.toString());
3455        for (String s : errors)
3456          if (s.startsWith("!"))
3457            errorList.add("!StructureDefinition "+name+": "+s.substring(1));
3458          else
3459            errorList.add("StructureDefinition "+name+": "+s);
3460      }
3461    }
3462  }
3463
3464
3465  public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors, boolean errorIfChanges) throws FHIRException  {
3466    int index = 0;
3467    for (ElementDefinition ed : diff.getDifferential().getElement()) {
3468      ed.setUserData("ed.index", Integer.toString(index));
3469      index++;
3470    }
3471    List<ElementDefinition> original = new ArrayList<>();
3472    original.addAll(diff.getDifferential().getElement());
3473    final List<ElementDefinition> diffList = diff.getDifferential().getElement();
3474    int lastCount = diffList.size();
3475    // first, we move the differential elements into a tree
3476    if (diffList.isEmpty())
3477      return;
3478    
3479    ElementDefinitionHolder edh = null;
3480    int i = 0;
3481    if (diffList.get(0).getPath().contains(".")) {
3482      String newPath = diffList.get(0).getPath().split("\\.")[0];
3483      ElementDefinition e = new ElementDefinition(newPath);
3484      edh = new ElementDefinitionHolder(e, true);
3485    } else {
3486      edh = new ElementDefinitionHolder(diffList.get(0));
3487      i = 1;
3488    }
3489
3490    boolean hasSlicing = false;
3491    List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly
3492    for(ElementDefinition elt : diffList) {
3493      if (elt.hasSlicing() || paths.contains(elt.getPath())) {
3494        hasSlicing = true;
3495        break;
3496      }
3497      paths.add(elt.getPath());
3498    }
3499
3500    processElementsIntoTree(edh, i, diff.getDifferential().getElement());
3501
3502    // now, we sort the siblings throughout the tree
3503    ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name, base.getType());
3504    sortElements(edh, cmp, errors);
3505
3506    // now, we serialise them back to a list
3507    List<ElementDefinition> newDiff = new ArrayList<>();
3508    writeElements(edh, newDiff);
3509    if (errorIfChanges) {
3510      compareDiffs(original, newDiff, errors);
3511    }
3512    diffList.clear();
3513    diffList.addAll(newDiff);
3514    
3515    if (lastCount != diffList.size())
3516      errors.add("Sort failed: counts differ; at least one of the paths in the differential is illegal");
3517  }
3518
3519  private void compareDiffs(List<ElementDefinition> diffList, List<ElementDefinition> newDiff, List<String> errors) {
3520    if (diffList.size() != newDiff.size()) {
3521      errors.add("The diff list size changed when sorting - was "+diffList.size()+" is now "+newDiff.size()+
3522          " ["+CommaSeparatedStringBuilder.buildObjects(diffList)+"]/["+CommaSeparatedStringBuilder.buildObjects(newDiff)+"]");
3523    } else {
3524      for (int i = 0; i < Integer.min(diffList.size(), newDiff.size()); i++) {
3525        ElementDefinition e = diffList.get(i);
3526        ElementDefinition n = newDiff.get(i);
3527        if (!n.getPath().equals(e.getPath())) {
3528          errors.add("The element "+(e.hasId() ? e.getId() : e.getPath())+" @diff["+e.getUserString("ed.index")+"] is out of order (and maybe others after it)");
3529          return;
3530        }   
3531      }
3532    }
3533  }
3534
3535
3536  private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) {
3537    String path = edh.getSelf().getPath();
3538    final String prefix = path + ".";
3539    while (i < list.size() && list.get(i).getPath().startsWith(prefix)) {
3540      if (list.get(i).getPath().substring(prefix.length()+1).contains(".")) {
3541        String newPath = prefix + list.get(i).getPath().substring(prefix.length()).split("\\.")[0];
3542        ElementDefinition e = new ElementDefinition(newPath);
3543        ElementDefinitionHolder child = new ElementDefinitionHolder(e, true);
3544        edh.getChildren().add(child);
3545        i = processElementsIntoTree(child, i, list);
3546        
3547      } else {
3548        ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i));
3549        edh.getChildren().add(child);
3550        i = processElementsIntoTree(child, i+1, list);
3551      }
3552    }
3553    return i;
3554  }
3555
3556  private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) throws FHIRException {
3557    if (edh.getChildren().size() == 1)
3558      // special case - sort needsto allocate base numbers, but there'll be no sort if there's only 1 child. So in that case, we just go ahead and allocated base number directly
3559      edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath(), false);
3560    else
3561      Collections.sort(edh.getChildren(), cmp);
3562    if (debug) {
3563      cmp.checkForErrors(errors);
3564    }
3565
3566    for (ElementDefinitionHolder child : edh.getChildren()) {
3567      if (child.getChildren().size() > 0) {
3568        ElementDefinitionComparer ccmp = getComparer(cmp, child);
3569        if (ccmp != null) {
3570          sortElements(child, ccmp, errors);
3571        }
3572      }
3573    }
3574  }
3575
3576
3577  public ElementDefinitionComparer getComparer(ElementDefinitionComparer cmp, ElementDefinitionHolder child) throws FHIRException, Error {
3578    // what we have to check for here is running off the base profile into a data type profile
3579    ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex());
3580    ElementDefinitionComparer ccmp;
3581    if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getWorkingCode()) || ed.getType().get(0).getWorkingCode().equals(ed.getPath())) {
3582      if (ed.hasType() && "Resource".equals(ed.getType().get(0).getWorkingCode()) && (child.getSelf().hasType() && child.getSelf().getType().get(0).hasProfile())) {
3583        if (child.getSelf().getType().get(0).getProfile().size() > 1) {
3584          throw new FHIRException(context.formatMessage(I18nConstants.UNHANDLED_SITUATION_RESOURCE_IS_PROFILED_TO_MORE_THAN_ONE_OPTION__CANNOT_SORT_PROFILE));
3585        }
3586        StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue());
3587        while (profile != null && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
3588          profile = context.fetchResource(StructureDefinition.class, profile.getBaseDefinition());          
3589        }
3590        if (profile==null) {
3591          ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case
3592        } else {
3593          ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), profile.getType(), child.getSelf().getPath().length(), cmp.name, profile.present());
3594        }
3595      } else {
3596        ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name, cmp.name);
3597      }
3598    } else if (ed.getType().get(0).getWorkingCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) {
3599      StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue());
3600      if (profile==null)
3601        ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case
3602      else
3603        ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode()), child.getSelf().getPath().length(), cmp.name, profile.present());
3604    } else if (ed.getType().size() == 1 && !ed.getType().get(0).getWorkingCode().equals("*")) {
3605      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
3606      if (profile==null)
3607        throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath()));
3608      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode()), child.getSelf().getPath().length(), cmp.name, profile.present());
3609    } else if (child.getSelf().getType().size() == 1) {
3610      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(child.getSelf().getType().get(0).getWorkingCode()));
3611      if (profile==null)
3612        throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath()));
3613      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), child.getSelf().getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present());
3614    } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) {
3615      String edLastNode = ed.getPath().replaceAll("(.*\\.)*(.*)", "$2");
3616      String childLastNode = child.getSelf().getPath().replaceAll("(.*\\.)*(.*)", "$2");
3617      String p = childLastNode.substring(edLastNode.length()-3);
3618      if (isPrimitive(Utilities.uncapitalize(p)))
3619        p = Utilities.uncapitalize(p);
3620      StructureDefinition sd = context.fetchResource(StructureDefinition.class, sdNs(p));
3621      if (sd == null)
3622        throw new Error(context.formatMessage(I18nConstants.UNABLE_TO_FIND_PROFILE__AT_, p, ed.getId()));
3623      ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name, sd.present());
3624    } else if (child.getSelf().hasType() && child.getSelf().getType().get(0).getWorkingCode().equals("Reference")) {
3625      for (TypeRefComponent t: child.getSelf().getType()) {
3626        if (!t.getWorkingCode().equals("Reference")) {
3627          throw new Error(context.formatMessage(I18nConstants.CANT_HAVE_CHILDREN_ON_AN_ELEMENT_WITH_A_POLYMORPHIC_TYPE__YOU_MUST_SLICE_AND_CONSTRAIN_THE_TYPES_FIRST_SORTELEMENTS_, ed.getPath(), typeCode(ed.getType())));
3628        }
3629      }
3630      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
3631      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present());
3632    } else if (!child.getSelf().hasType() && ed.getType().get(0).getWorkingCode().equals("Reference")) {
3633      for (TypeRefComponent t: ed.getType()) {
3634        if (!t.getWorkingCode().equals("Reference")) {
3635          throw new Error(context.formatMessage(I18nConstants.NOT_HANDLED_YET_SORTELEMENTS_, ed.getPath(), typeCode(ed.getType())));
3636        }
3637      }
3638      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
3639      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present());
3640    } else {
3641      // this is allowed if we only profile the extensions
3642      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs("Element"));
3643      if (profile==null)
3644        throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath()));
3645      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), "Element", child.getSelf().getPath().length(), cmp.name, profile.present());
3646//      throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")");
3647    }
3648    return ccmp;
3649  }
3650
3651  private String resolveType(String code) {
3652    if (Utilities.isAbsoluteUrl(code)) {
3653      StructureDefinition sd = context.fetchResource(StructureDefinition.class, code);
3654      if (sd != null) {
3655        return sd.getType();
3656      }
3657    }
3658    return code;
3659  }
3660
3661  private static String sdNs(String type) {
3662    return sdNs(type, null);
3663  }
3664  
3665  public static String sdNs(String type, String overrideVersionNs) {
3666    if (Utilities.isAbsoluteUrl(type))
3667      return type;
3668    else if (overrideVersionNs != null)
3669      return Utilities.pathURL(overrideVersionNs, type);
3670    else
3671      return "http://hl7.org/fhir/StructureDefinition/"+type;
3672  }
3673
3674
3675  private boolean isAbstract(String code) {
3676    return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") || code.equals("DomainResource");
3677  }
3678
3679
3680  private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) {
3681    if (!edh.isPlaceHolder())
3682      list.add(edh.getSelf());
3683    for (ElementDefinitionHolder child : edh.getChildren()) {
3684      writeElements(child, list);
3685    }
3686  }
3687
3688  /**
3689   * First compare element by path then by name if same
3690   */
3691  private static class ElementNameCompare implements Comparator<ElementDefinition> {
3692
3693    @Override
3694    public int compare(ElementDefinition o1, ElementDefinition o2) {
3695      String path1 = normalizePath(o1);
3696      String path2 = normalizePath(o2);
3697      int cmp = path1.compareTo(path2);
3698      if (cmp == 0) {
3699        String name1 = o1.hasSliceName() ? o1.getSliceName() : "";
3700        String name2 = o2.hasSliceName() ? o2.getSliceName() : "";
3701        cmp = name1.compareTo(name2);
3702      }
3703      return cmp;
3704    }
3705
3706    private static String normalizePath(ElementDefinition e) {
3707      if (!e.hasPath()) return "";
3708      String path = e.getPath();
3709      // if sorting element names make sure onset[x] appears before onsetAge, onsetDate, etc.
3710      // so strip off the [x] suffix when comparing the path names.
3711      if (path.endsWith("[x]")) {
3712        path = path.substring(0, path.length()-3);
3713      }
3714      return path;
3715    }
3716
3717  }
3718
3719
3720  // generate schematrons for the rules in a structure definition
3721  public void generateSchematrons(OutputStream dest, StructureDefinition structure) throws IOException, DefinitionException {
3722    if (structure.getDerivation() != TypeDerivationRule.CONSTRAINT)
3723      throw new DefinitionException(context.formatMessage(I18nConstants.NOT_THE_RIGHT_KIND_OF_STRUCTURE_TO_GENERATE_SCHEMATRONS_FOR));
3724    if (!structure.hasSnapshot())
3725      throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT));
3726
3727        StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBaseDefinition(), structure);
3728
3729        if (base != null) {
3730          SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName());
3731
3732          ElementDefinition ed = structure.getSnapshot().getElement().get(0);
3733          generateForChildren(sch, "f:"+ed.getPath(), ed, structure, base);
3734          sch.dump();
3735        }
3736  }
3737
3738  // generate a CSV representation of the structure definition
3739  public void generateCsv(OutputStream dest, StructureDefinition structure, boolean asXml) throws IOException, DefinitionException, Exception {
3740    if (!structure.hasSnapshot())
3741      throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT));
3742
3743    CSVWriter csv = new CSVWriter(dest, structure, asXml);
3744
3745    for (ElementDefinition child : structure.getSnapshot().getElement()) {
3746      csv.processElement(null, child);
3747    }
3748    csv.dump();
3749  }
3750  
3751  // generate a CSV representation of the structure definition
3752  public void addToCSV(CSVWriter csv, StructureDefinition structure) throws IOException, DefinitionException, Exception {
3753    if (!structure.hasSnapshot())
3754      throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT));
3755
3756    for (ElementDefinition child : structure.getSnapshot().getElement()) {
3757      csv.processElement(structure, child);
3758    }
3759  }
3760  
3761  
3762  private class Slicer extends ElementDefinitionSlicingComponent {
3763    String criteria = "";
3764    String name = "";   
3765    boolean check;
3766    public Slicer(boolean cantCheck) {
3767      super();
3768      this.check = cantCheck;
3769    }
3770  }
3771  
3772  private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, StructureDefinition structure) {
3773    // given a child in a structure, it's sliced. figure out the slicing xpath
3774    if (child.getPath().endsWith(".extension")) {
3775      ElementDefinition ued = getUrlFor(structure, child);
3776      if ((ued == null || !ued.hasFixed()) && !(child.hasType() && (child.getType().get(0).hasProfile())))
3777        return new Slicer(false);
3778      else {
3779      Slicer s = new Slicer(true);
3780      String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).getValue() : ((UriType) ued.getFixed()).asStringValue();
3781      s.name = " with URL = '"+url+"'";
3782      s.criteria = "[@url = '"+url+"']";
3783      return s;
3784      }
3785    } else
3786      return new Slicer(false);
3787  }
3788
3789  private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, StructureDefinition structure, StructureDefinition base) throws IOException {
3790    //    generateForChild(txt, structure, child);
3791    List<ElementDefinition> children = getChildList(structure, ed);
3792    String sliceName = null;
3793    ElementDefinitionSlicingComponent slicing = null;
3794    for (ElementDefinition child : children) {
3795      String name = tail(child.getPath());
3796      if (child.hasSlicing()) {
3797        sliceName = name;
3798        slicing = child.getSlicing();        
3799      } else if (!name.equals(sliceName))
3800        slicing = null;
3801
3802      ElementDefinition based = getByPath(base, child.getPath());
3803      boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin()));
3804      boolean doMax = child.hasMax() && !child.getMax().equals("*") && (based == null || (!child.getMax().equals(based.getMax())));
3805      Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure);
3806      if (slicer.check) {
3807        if (doMin || doMax) {
3808          Section s = sch.section(xpath);
3809          Rule r = s.rule(xpath);
3810          if (doMin) 
3811            r.assrt("count(f:"+name+slicer.criteria+") >= "+Integer.toString(child.getMin()), name+slicer.name+": minimum cardinality of '"+name+"' is "+Integer.toString(child.getMin()));
3812          if (doMax) 
3813            r.assrt("count(f:"+name+slicer.criteria+") <= "+child.getMax(), name+slicer.name+": maximum cardinality of '"+name+"' is "+child.getMax());
3814        }
3815      }
3816    }
3817/// xpath has been removed
3818//    for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
3819//      if (inv.hasXpath()) {
3820//        Section s = sch.section(ed.getPath());
3821//        Rule r = s.rule(xpath);
3822//        r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId()+": " : "")+inv.getHuman()+(inv.hasUserData(IS_DERIVED) ? " (inherited)" : ""));
3823//      }
3824//    }
3825    if (!ed.hasContentReference()) {
3826      for (ElementDefinition child : children) {
3827        String name = tail(child.getPath());
3828        generateForChildren(sch, xpath+"/f:"+name, child, structure, base);
3829      }
3830    }
3831  }
3832
3833
3834
3835
3836  private ElementDefinition getByPath(StructureDefinition base, String path) {
3837                for (ElementDefinition ed : base.getSnapshot().getElement()) {
3838                        if (ed.getPath().equals(path))
3839                                return ed;
3840                        if (ed.getPath().endsWith("[x]") && ed.getPath().length() <= path.length()-3 &&  ed.getPath().substring(0, ed.getPath().length()-3).equals(path.substring(0, ed.getPath().length()-3)))
3841                                return ed;
3842    }
3843          return null;
3844  }
3845
3846
3847  public void setIds(StructureDefinition sd, boolean checkFirst) throws DefinitionException  {
3848    if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) {
3849      if (!sd.hasDifferential())
3850        sd.setDifferential(new StructureDefinitionDifferentialComponent());
3851      generateIds(sd.getDifferential().getElement(), sd.getUrl(), sd.getType(), sd);
3852    }
3853    if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) {
3854      if (!sd.hasSnapshot())
3855        sd.setSnapshot(new StructureDefinitionSnapshotComponent());
3856      generateIds(sd.getSnapshot().getElement(), sd.getUrl(), sd.getType(), sd);
3857    }
3858  }
3859
3860
3861  private boolean hasMissingIds(List<ElementDefinition> list) {
3862    for (ElementDefinition ed : list) {
3863      if (!ed.hasId())
3864        return true;
3865    }    
3866    return false;
3867  }
3868
3869  private class SliceList {
3870
3871    private Map<String, String> slices = new HashMap<>();
3872    
3873    public void seeElement(ElementDefinition ed) {
3874      Iterator<Map.Entry<String,String>> iter = slices.entrySet().iterator();
3875      while (iter.hasNext()) {
3876        Map.Entry<String,String> entry = iter.next();
3877        if (entry.getKey().length() > ed.getPath().length() || entry.getKey().equals(ed.getPath()))
3878          iter.remove();
3879      }
3880      
3881      if (ed.hasSliceName()) 
3882        slices.put(ed.getPath(), ed.getSliceName());
3883    }
3884
3885    public String[] analyse(List<String> paths) {
3886      String s = paths.get(0);
3887      String[] res = new String[paths.size()];
3888      res[0] = null;
3889      for (int i = 1; i < paths.size(); i++) {
3890        s = s + "."+paths.get(i);
3891        if (slices.containsKey(s)) 
3892          res[i] = slices.get(s);
3893        else
3894          res[i] = null;
3895      }
3896      return res;
3897    }
3898
3899  }
3900
3901  protected void generateIds(List<ElementDefinition> list, String name, String type, StructureDefinition srcSD) throws DefinitionException  {
3902    if (list.isEmpty())
3903      return;
3904    
3905    Map<String, String> idList = new HashMap<String, String>();
3906    Map<String, String> replacedIds = new HashMap<String, String>();
3907    
3908    SliceList sliceInfo = new SliceList();
3909    // first pass, update the element ids
3910    for (ElementDefinition ed : list) {
3911      List<String> paths = new ArrayList<String>();
3912      if (!ed.hasPath())
3913        throw new DefinitionException(context.formatMessage(I18nConstants.NO_PATH_ON_ELEMENT_DEFINITION__IN_, Integer.toString(list.indexOf(ed)), name));
3914      sliceInfo.seeElement(ed);
3915      String[] pl = ed.getPath().split("\\.");
3916      for (int i = paths.size(); i < pl.length; i++) // -1 because the last path is in focus
3917        paths.add(pl[i]);
3918      String slices[] = sliceInfo.analyse(paths);
3919      
3920      StringBuilder b = new StringBuilder();
3921      b.append(paths.get(0));
3922      for (int i = 1; i < paths.size(); i++) {
3923        b.append(".");
3924        String s = paths.get(i);
3925        String p = slices[i];
3926        b.append(fixChars(s));
3927        if (p != null) {
3928          b.append(":");
3929          b.append(p);
3930        }
3931      }
3932      String bs = b.toString();
3933      if (ed.hasId()) {
3934        replacedIds.put(ed.getId(), ed.getPath());
3935      }
3936      ed.setId(bs);
3937      if (idList.containsKey(bs)) {
3938        if (exception || messages == null) {
3939          throw new DefinitionException(context.formatMessage(I18nConstants.SAME_ID_ON_MULTIPLE_ELEMENTS__IN_, bs, idList.get(bs), ed.getPath(), name));
3940        } else
3941          messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, name+"."+bs, "Duplicate Element id "+bs, ValidationMessage.IssueSeverity.ERROR));
3942      }
3943      idList.put(bs, ed.getPath());
3944      if (ed.hasContentReference() && ed.getContentReference().startsWith("#")) {
3945        String s = ed.getContentReference();
3946        String typeURL = getUrlForSource(type, srcSD);
3947        if (replacedIds.containsKey(s.substring(1))) {
3948          ed.setContentReference(typeURL+"#"+replacedIds.get(s.substring(1)));
3949        } else {
3950          ed.setContentReference(typeURL+s);
3951        }
3952      }
3953    }  
3954    // second path - fix up any broken path based id references
3955    
3956  }
3957
3958
3959  private String getUrlForSource(String type, StructureDefinition srcSD) {
3960    if (srcSD.getKind() == StructureDefinitionKind.LOGICAL) {
3961      return srcSD.getUrl();
3962    } else {
3963      return "http://hl7.org/fhir/StructureDefinition/"+type;
3964    }
3965  }
3966
3967  private Object fixChars(String s) {
3968    return s.replace("_", "-");
3969  }
3970
3971
3972//  private String describeExtension(ElementDefinition ed) {
3973//    if (!ed.hasType() || !ed.getTypeFirstRep().hasProfile())
3974//      return "";
3975//    return "$"+urlTail(ed.getTypeFirstRep().getProfile());
3976//  }
3977//
3978
3979  private static String urlTail(String profile) {
3980    return profile.contains("/") ? profile.substring(profile.lastIndexOf("/")+1) : profile;
3981  }
3982//
3983//
3984//  private String checkName(String name) {
3985////    if (name.contains("."))
3986//////      throw new Exception("Illegal name "+name+": no '.'");
3987////    if (name.contains(" "))
3988////      throw new Exception("Illegal name "+name+": no spaces");
3989//    StringBuilder b = new StringBuilder();
3990//    for (char c : name.toCharArray()) {
3991//      if (!Utilities.existsInList(c, '.', ' ', ':', '"', '\'', '(', ')', '&', '[', ']'))
3992//        b.append(c);
3993//    }
3994//    return b.toString().toLowerCase();
3995//  }
3996//
3997//
3998//  private int charCount(String path, char t) {
3999//    int res = 0;
4000//    for (char ch : path.toCharArray()) {
4001//      if (ch == t)
4002//        res++;
4003//    }
4004//    return res;
4005//  }
4006
4007//
4008//private void generateForChild(TextStreamWriter txt,
4009//    StructureDefinition structure, ElementDefinition child) {
4010//  // TODO Auto-generated method stub
4011//
4012//}
4013
4014  private interface ExampleValueAccessor {
4015    DataType getExampleValue(ElementDefinition ed);
4016    String getId();
4017  }
4018
4019  private class BaseExampleValueAccessor implements ExampleValueAccessor {
4020    @Override
4021    public DataType getExampleValue(ElementDefinition ed) {
4022      if (ed.hasFixed())
4023        return ed.getFixed();
4024      if (ed.hasExample())
4025        return ed.getExample().get(0).getValue();
4026      else
4027        return null;
4028    }
4029
4030    @Override
4031    public String getId() {
4032      return "-genexample";
4033    }
4034  }
4035  
4036  private class ExtendedExampleValueAccessor implements ExampleValueAccessor {
4037    private String index;
4038
4039    public ExtendedExampleValueAccessor(String index) {
4040      this.index = index;
4041    }
4042    @Override
4043    public DataType getExampleValue(ElementDefinition ed) {
4044      if (ed.hasFixed())
4045        return ed.getFixed();
4046      for (Extension ex : ed.getExtension()) {
4047       String ndx = ToolingExtensions.readStringExtension(ex, "index");
4048       DataType value = ToolingExtensions.getExtension(ex, "exValue").getValue();
4049       if (index.equals(ndx) && value != null)
4050         return value;
4051      }
4052      return null;
4053    }
4054    @Override
4055    public String getId() {
4056      return "-genexample-"+index;
4057    }
4058  }
4059  
4060  public List<org.hl7.fhir.r5.elementmodel.Element> generateExamples(StructureDefinition sd, boolean evenWhenNoExamples) throws FHIRException {
4061    List<org.hl7.fhir.r5.elementmodel.Element> examples = new ArrayList<org.hl7.fhir.r5.elementmodel.Element>();
4062    if (sd.hasSnapshot()) {
4063      if (evenWhenNoExamples || hasAnyExampleValues(sd)) 
4064        examples.add(generateExample(sd, new BaseExampleValueAccessor()));
4065      for (int i = 1; i <= 50; i++) {
4066        if (hasAnyExampleValues(sd, Integer.toString(i))) 
4067          examples.add(generateExample(sd, new ExtendedExampleValueAccessor(Integer.toString(i))));
4068      }
4069    }
4070    return examples;
4071  }
4072
4073  private org.hl7.fhir.r5.elementmodel.Element generateExample(StructureDefinition profile, ExampleValueAccessor accessor) throws FHIRException {
4074    ElementDefinition ed = profile.getSnapshot().getElementFirstRep();
4075    org.hl7.fhir.r5.elementmodel.Element r = new org.hl7.fhir.r5.elementmodel.Element(ed.getPath(), new Property(context, ed, profile));
4076    SourcedChildDefinitions children = getChildMap(profile, ed);
4077    for (ElementDefinition child : children.getList()) {
4078      if (child.getPath().endsWith(".id")) {
4079        org.hl7.fhir.r5.elementmodel.Element id = new org.hl7.fhir.r5.elementmodel.Element("id", new Property(context, child, profile));
4080        id.setValue(profile.getId()+accessor.getId());
4081        r.getChildren().add(id);
4082      } else { 
4083        org.hl7.fhir.r5.elementmodel.Element e = createExampleElement(profile, child, accessor);
4084        if (e != null)
4085          r.getChildren().add(e);
4086      }
4087    }
4088    return r;
4089  }
4090
4091  private org.hl7.fhir.r5.elementmodel.Element createExampleElement(StructureDefinition profile, ElementDefinition ed, ExampleValueAccessor accessor) throws FHIRException {
4092    DataType v = accessor.getExampleValue(ed);
4093    if (v != null) {
4094      return new ObjectConverter(context).convert(new Property(context, ed, profile), v);
4095    } else {
4096      org.hl7.fhir.r5.elementmodel.Element res = new org.hl7.fhir.r5.elementmodel.Element(tail(ed.getPath()), new Property(context, ed, profile));
4097      boolean hasValue = false;
4098      SourcedChildDefinitions children = getChildMap(profile, ed);
4099      for (ElementDefinition child : children.getList()) {
4100        if (!child.hasContentReference()) {
4101        org.hl7.fhir.r5.elementmodel.Element e = createExampleElement(profile, child, accessor);
4102        if (e != null) {
4103          hasValue = true;
4104          res.getChildren().add(e);
4105        }
4106      }
4107      }
4108      if (hasValue)
4109        return res;
4110      else
4111        return null;
4112    }
4113  }
4114
4115  private boolean hasAnyExampleValues(StructureDefinition sd, String index) {
4116    for (ElementDefinition ed : sd.getSnapshot().getElement())
4117      for (Extension ex : ed.getExtension()) {
4118        String ndx = ToolingExtensions.readStringExtension(ex, "index");
4119        Extension exv = ToolingExtensions.getExtension(ex, "exValue");
4120        if (exv != null) {
4121          DataType value = exv.getValue();
4122        if (index.equals(ndx) && value != null)
4123          return true;
4124        }
4125       }
4126    return false;
4127  }
4128
4129
4130  private boolean hasAnyExampleValues(StructureDefinition sd) {
4131    for (ElementDefinition ed : sd.getSnapshot().getElement())
4132      if (ed.hasExample())
4133        return true;
4134    return false;
4135  }
4136
4137
4138  public void populateLogicalSnapshot(StructureDefinition sd) throws FHIRException {
4139    sd.getSnapshot().getElement().add(sd.getDifferential().getElementFirstRep().copy());
4140    
4141    if (sd.hasBaseDefinition()) {
4142    StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd);
4143    if (base == null)
4144        throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_BASE_DEFINITION_FOR_LOGICAL_MODEL__FROM_, sd.getBaseDefinition(), sd.getUrl()));
4145    copyElements(sd, base.getSnapshot().getElement());
4146    }
4147    copyElements(sd, sd.getDifferential().getElement());
4148  }
4149
4150
4151  private void copyElements(StructureDefinition sd, List<ElementDefinition> list) {
4152    for (ElementDefinition ed : list) {
4153      if (ed.getPath().contains(".")) {
4154        ElementDefinition n = ed.copy();
4155        n.setPath(sd.getSnapshot().getElementFirstRep().getPath()+"."+ed.getPath().substring(ed.getPath().indexOf(".")+1));
4156        sd.getSnapshot().addElement(n);
4157      }
4158    }
4159  }
4160
4161    
4162  public void cleanUpDifferential(StructureDefinition sd) {
4163    if (sd.getDifferential().getElement().size() > 1)
4164      cleanUpDifferential(sd, 1);
4165  }
4166  
4167  private void cleanUpDifferential(StructureDefinition sd, int start) {
4168    int level = Utilities.charCount(sd.getDifferential().getElement().get(start).getPath(), '.');
4169    int c = start;
4170    int len = sd.getDifferential().getElement().size();
4171    HashSet<String> paths = new HashSet<String>();
4172    while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') == level) {
4173      ElementDefinition ed = sd.getDifferential().getElement().get(c);
4174      if (!paths.contains(ed.getPath())) {
4175        paths.add(ed.getPath());
4176        int ic = c+1; 
4177        while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 
4178          ic++;
4179        ElementDefinition slicer = null;
4180        List<ElementDefinition> slices = new ArrayList<ElementDefinition>();
4181        slices.add(ed);
4182        while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') == level) {
4183          ElementDefinition edi = sd.getDifferential().getElement().get(ic);
4184          if (ed.getPath().equals(edi.getPath())) {
4185            if (slicer == null) {
4186              slicer = new ElementDefinition();
4187              slicer.setPath(edi.getPath());
4188              slicer.getSlicing().setRules(SlicingRules.OPEN);
4189              sd.getDifferential().getElement().add(c, slicer);
4190              c++;
4191              ic++;
4192            }
4193            slices.add(edi);
4194          }
4195          ic++;
4196          while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 
4197            ic++;
4198        }
4199        // now we're at the end, we're going to figure out the slicing discriminator
4200        if (slicer != null)
4201          determineSlicing(slicer, slices);
4202      }
4203      c++;
4204      if (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) {
4205        cleanUpDifferential(sd, c);
4206        c++;
4207        while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) 
4208          c++;
4209      }
4210  }
4211  }
4212
4213
4214  private void determineSlicing(ElementDefinition slicer, List<ElementDefinition> slices) {
4215    // first, name them
4216    int i = 0;
4217    for (ElementDefinition ed : slices) {
4218      if (ed.hasUserData("slice-name")) {
4219        ed.setSliceName(ed.getUserString("slice-name"));
4220      } else {
4221        i++;
4222        ed.setSliceName("slice-"+Integer.toString(i));
4223      }
4224    }
4225    // now, the hard bit, how are they differentiated? 
4226    // right now, we hard code this...
4227    if (slicer.getPath().endsWith(".extension") || slicer.getPath().endsWith(".modifierExtension"))
4228      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url");
4229    else if (slicer.getPath().equals("DiagnosticReport.result"))
4230      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("reference.code");
4231    else if (slicer.getPath().equals("Observation.related"))
4232      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("target.reference.code");
4233    else if (slicer.getPath().equals("Bundle.entry"))
4234      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("resource.@profile");
4235    else  
4236      throw new Error("No slicing for "+slicer.getPath());
4237  }
4238
4239
4240  public static ElementDefinitionSlicingDiscriminatorComponent interpretR2Discriminator(String discriminator, boolean isExists) {
4241    if (discriminator.endsWith("@pattern"))
4242      return makeDiscriminator(DiscriminatorType.PATTERN, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 
4243    if (discriminator.endsWith("@profile"))
4244      return makeDiscriminator(DiscriminatorType.PROFILE, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 
4245    if (discriminator.endsWith("@type")) 
4246      return makeDiscriminator(DiscriminatorType.TYPE, discriminator.length() == 5 ? "" : discriminator.substring(0,discriminator.length()-6));
4247    if (discriminator.endsWith("@exists"))
4248      return makeDiscriminator(DiscriminatorType.EXISTS, discriminator.length() == 7 ? "" : discriminator.substring(0,discriminator.length()-8)); 
4249    if (isExists)
4250      return makeDiscriminator(DiscriminatorType.EXISTS, discriminator); 
4251    return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(discriminator);
4252  }
4253
4254
4255  private static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType dType, String str) {
4256    return new ElementDefinitionSlicingDiscriminatorComponent().setType(dType).setPath(Utilities.noString(str)? "$this" : str);
4257  }
4258
4259
4260  public static String buildR2Discriminator(ElementDefinitionSlicingDiscriminatorComponent t) throws FHIRException {
4261    switch (t.getType()) {
4262    case PROFILE: return t.getPath()+"/@profile";
4263    case PATTERN: return t.getPath()+"/@pattern";
4264    case TYPE: return t.getPath()+"/@type";
4265    case VALUE: return t.getPath();
4266    case EXISTS: return t.getPath(); // determination of value vs. exists is based on whether there's only 2 slices - one with minOccurs=1 and other with maxOccur=0
4267    default: throw new FHIRException("Unable to represent "+t.getType().toCode()+":"+t.getPath()+" in R2");    
4268    }
4269  }
4270
4271
4272  public static StructureDefinition makeExtensionForVersionedURL(IWorkerContext context, String url) {
4273    String epath = url.substring(54);
4274    if (!epath.contains("."))
4275      return null;
4276    String type = epath.substring(0, epath.indexOf("."));
4277    StructureDefinition sd = context.fetchTypeDefinition(type);
4278    if (sd == null)
4279      return null;
4280    ElementDefinition ed = null;
4281    for (ElementDefinition t : sd.getSnapshot().getElement()) {
4282      if (t.getPath().equals(epath)) {
4283        ed = t;
4284        break;
4285      }
4286    }
4287    if (ed == null)
4288      return null;
4289    if ("Element".equals(ed.typeSummary()) || "BackboneElement".equals(ed.typeSummary())) {
4290      return null;
4291    } else {
4292      StructureDefinition template = context.fetchResource(StructureDefinition.class, "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities");
4293      StructureDefinition ext = template.copy();
4294      ext.setUrl(url);
4295      ext.setId("extension-"+epath);
4296      ext.setName("Extension-"+epath);
4297      ext.setTitle("Extension for r4 "+epath);
4298      ext.setStatus(sd.getStatus());
4299      ext.setDate(sd.getDate());
4300      ext.getContact().clear();
4301      ext.getContact().addAll(sd.getContact());
4302      ext.setFhirVersion(sd.getFhirVersion());
4303      ext.setDescription(ed.getDefinition());
4304      ext.getContext().clear();
4305      ext.addContext().setType(ExtensionContextType.ELEMENT).setExpression(epath.substring(0, epath.lastIndexOf(".")));
4306      ext.getDifferential().getElement().clear();
4307      ext.getSnapshot().getElement().get(3).setFixed(new UriType(url));
4308      ext.getSnapshot().getElement().set(4, ed.copy());
4309      ext.getSnapshot().getElement().get(4).setPath("Extension.value"+Utilities.capitalize(ed.typeSummary()));
4310      return ext;      
4311    }
4312
4313  }
4314
4315
4316  public boolean isThrowException() {
4317    return exception;
4318  }
4319
4320
4321  public void setThrowException(boolean exception) {
4322    this.exception = exception;
4323  }
4324
4325
4326  public ValidationOptions getTerminologyServiceOptions() {
4327    return terminologyServiceOptions;
4328  }
4329
4330
4331  public void setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) {
4332    this.terminologyServiceOptions = terminologyServiceOptions;
4333  }
4334
4335
4336  public boolean isNewSlicingProcessing() {
4337    return newSlicingProcessing;
4338  }
4339
4340
4341  public ProfileUtilities setNewSlicingProcessing(boolean newSlicingProcessing) {
4342    this.newSlicingProcessing = newSlicingProcessing;
4343    return this;
4344  }
4345
4346
4347  public boolean isDebug() {
4348    return debug;
4349  }
4350
4351
4352  public void setDebug(boolean debug) {
4353    this.debug = debug;
4354  }
4355
4356
4357  public String getDefWebRoot() {
4358    return defWebRoot;
4359  }
4360
4361
4362  public void setDefWebRoot(String defWebRoot) {
4363    this.defWebRoot = defWebRoot;
4364    if (!this.defWebRoot.endsWith("/"))
4365      this.defWebRoot = this.defWebRoot + '/';
4366  }
4367
4368
4369  public static StructureDefinition makeBaseDefinition(FHIRVersion fhirVersion) {
4370    return makeBaseDefinition(fhirVersion.toCode());
4371  }
4372  public static StructureDefinition makeBaseDefinition(String fhirVersion) {
4373    StructureDefinition base = new StructureDefinition();
4374    base.setId("Base");
4375    base.setUrl("http://hl7.org/fhir/StructureDefinition/Base");
4376    base.setVersion(fhirVersion);
4377    base.setName("Base"); 
4378    base.setStatus(PublicationStatus.ACTIVE);
4379    base.setDate(new Date());
4380    base.setFhirVersion(FHIRVersion.fromCode(fhirVersion));
4381    base.setKind(StructureDefinitionKind.COMPLEXTYPE); 
4382    base.setAbstract(true); 
4383    base.setType("Base");
4384    base.setWebPath("http://build.fhir.org/types.html#Base");
4385    ElementDefinition e = base.getSnapshot().getElementFirstRep();
4386    e.setId("Base");
4387    e.setPath("Base"); 
4388    e.setMin(0); 
4389    e.setMax("*"); 
4390    e.getBase().setPath("Base");
4391    e.getBase().setMin(0); 
4392    e.getBase().setMax("*"); 
4393    e.setIsModifier(false); 
4394    e = base.getDifferential().getElementFirstRep();
4395    e.setId("Base");
4396    e.setPath("Base"); 
4397    e.setMin(0); 
4398    e.setMax("*"); 
4399    return base;
4400  }
4401
4402  public XVerExtensionManager getXver() {
4403    return xver;
4404  }
4405
4406  public ProfileUtilities setXver(XVerExtensionManager xver) {
4407    this.xver = xver;
4408    return this;
4409  }
4410
4411
4412  private List<ElementChoiceGroup> readChoices(ElementDefinition ed, List<ElementDefinition> children) {
4413    List<ElementChoiceGroup> result = new ArrayList<>();
4414    for (ElementDefinitionConstraintComponent c : ed.getConstraint()) {
4415      ElementChoiceGroup grp = processConstraint(children, c);
4416      if (grp != null) {
4417        result.add(grp);
4418      }
4419    }
4420    return result;
4421  }
4422
4423  public ElementChoiceGroup processConstraint(List<ElementDefinition> children, ElementDefinitionConstraintComponent c) {
4424    if (!c.hasExpression()) {
4425      return null;
4426    }
4427    ExpressionNode expr = null;
4428    try {
4429      expr = fpe.parse(c.getExpression());
4430    } catch (Exception e) {
4431      return null;
4432    }
4433    if (expr.getKind() != Kind.Group || expr.getOpNext() == null || !(expr.getOperation() == Operation.Equals || expr.getOperation() == Operation.LessOrEqual)) {
4434      return null;      
4435    }
4436    ExpressionNode n1 = expr.getGroup();
4437    ExpressionNode n2 = expr.getOpNext();
4438    if (n2.getKind() != Kind.Constant || n2.getInner() != null || n2.getOpNext() != null || !"1".equals(n2.getConstant().primitiveValue())) {
4439      return null;
4440    }
4441    ElementChoiceGroup grp = new ElementChoiceGroup(c.getKey(), expr.getOperation() == Operation.Equals);
4442    while (n1 != null) {
4443      if (n1.getKind() != Kind.Name || n1.getInner() != null) {
4444        return null;
4445      }
4446      grp.elements.add(n1.getName());
4447      if (n1.getOperation() == null || n1.getOperation() == Operation.Union) {
4448        n1 = n1.getOpNext();
4449      } else {
4450        return null;
4451      }
4452    }
4453    int total = 0;
4454    for (String n : grp.elements) {
4455      boolean found = false;
4456      for (ElementDefinition child : children) {
4457        String name = tail(child.getPath());
4458        if (n.equals(name)) {
4459          found = true;
4460          if (!"0".equals(child.getMax())) {
4461            total++;
4462          }
4463        }
4464      }
4465      if (!found) {
4466        return null;
4467      }
4468    }
4469    if (total <= 1) {
4470      return null;
4471    }
4472    return grp;
4473  }
4474
4475  public Set<String> getMasterSourceFileNames() {
4476    return masterSourceFileNames;
4477  }
4478
4479  public void setMasterSourceFileNames(Set<String> masterSourceFileNames) {
4480    this.masterSourceFileNames = masterSourceFileNames;
4481  }
4482
4483  
4484  public Set<String> getLocalFileNames() {
4485    return localFileNames;
4486  }
4487
4488  public void setLocalFileNames(Set<String> localFileNames) {
4489    this.localFileNames = localFileNames;
4490  }
4491
4492  public ProfileKnowledgeProvider getPkp() {
4493    return pkp;
4494  }
4495
4496
4497  public static final String UD_ERROR_STATUS = "error-status";
4498  public static final int STATUS_OK = 0;
4499  public static final int STATUS_HINT = 1;
4500  public static final int STATUS_WARNING = 2;
4501  public static final int STATUS_ERROR = 3;
4502  public static final int STATUS_FATAL = 4;
4503  private static final String ROW_COLOR_ERROR = "#ffcccc";
4504  private static final String ROW_COLOR_FATAL = "#ff9999";
4505  private static final String ROW_COLOR_WARNING = "#ffebcc";
4506  private static final String ROW_COLOR_HINT = "#ebf5ff";
4507  private static final String ROW_COLOR_NOT_MUST_SUPPORT = "#d6eaf8";
4508
4509  public String getRowColor(ElementDefinition element, boolean isConstraintMode) {
4510    switch (element.getUserInt(UD_ERROR_STATUS)) {
4511    case STATUS_HINT: return ROW_COLOR_HINT;
4512    case STATUS_WARNING: return ROW_COLOR_WARNING;
4513    case STATUS_ERROR: return ROW_COLOR_ERROR;
4514    case STATUS_FATAL: return ROW_COLOR_FATAL;
4515    }
4516    if (isConstraintMode && !element.getMustSupport() && !element.getIsModifier() && element.getPath().contains("."))
4517      return null; // ROW_COLOR_NOT_MUST_SUPPORT;
4518    else
4519      return null;
4520  }
4521
4522  public static boolean isExtensionDefinition(StructureDefinition sd) {
4523    return sd.getDerivation() == TypeDerivationRule.CONSTRAINT && sd.getType().equals("Extension");
4524  }
4525
4526  public AllowUnknownProfile getAllowUnknownProfile() {
4527    return allowUnknownProfile;
4528  }
4529
4530  public void setAllowUnknownProfile(AllowUnknownProfile allowUnknownProfile) {
4531    this.allowUnknownProfile = allowUnknownProfile;
4532  }
4533
4534  public static boolean isSimpleExtension(StructureDefinition sd) {
4535    if (!isExtensionDefinition(sd)) {
4536      return false;
4537    }
4538    ElementDefinition value = sd.getSnapshot().getElementByPath("Extension.value");
4539    return value != null && !value.isProhibited();
4540  }
4541
4542  public static boolean isComplexExtension(StructureDefinition sd) {
4543    if (!isExtensionDefinition(sd)) {
4544      return false;
4545    }
4546    ElementDefinition value = sd.getSnapshot().getElementByPath("Extension.value");
4547    return value == null || value.isProhibited();
4548  }
4549
4550  public static boolean isModifierExtension(StructureDefinition sd) {
4551    ElementDefinition defn = sd.getSnapshot().getElementByPath("Extension");
4552    return defn != null && defn.getIsModifier();
4553  }
4554
4555  public boolean isForPublication() {
4556    return forPublication;
4557  }
4558
4559  public void setForPublication(boolean forPublication) {
4560    this.forPublication = forPublication;
4561  }
4562
4563  public List<ValidationMessage> getMessages() {
4564    return messages;
4565  }
4566
4567  public static boolean isResourceBoundary(ElementDefinition ed) {
4568    return ed.getType().size() == 1 && "Resource".equals(ed.getTypeFirstRep().getCode());
4569  }
4570
4571  public static boolean isSuppressIgnorableExceptions() {
4572    return suppressIgnorableExceptions;
4573  }
4574
4575  public static void setSuppressIgnorableExceptions(boolean suppressIgnorableExceptions) {
4576    ProfileUtilities.suppressIgnorableExceptions = suppressIgnorableExceptions;
4577  }
4578
4579  public void setMessages(List<ValidationMessage> messages) {
4580    this.messages = messages; 
4581  }
4582
4583  private Map<String, List<Property>> propertyCache = new HashMap<>();
4584  
4585  public Map<String, List<Property>> getCachedPropertyList() {
4586    return propertyCache;
4587  }
4588
4589  public void checkExtensions(ElementDefinition outcome) {
4590    outcome.getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), ProfileUtilities.NON_INHERITED_ED_URLS));
4591    if (outcome.hasBinding()) {
4592      outcome.getBinding().getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), ProfileUtilities.NON_INHERITED_ED_URLS));      
4593    }
4594  }
4595
4596}