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    List<ElementDefinition> original = new ArrayList<>();
3467    original.addAll(diff.getDifferential().getElement());
3468    final List<ElementDefinition> diffList = diff.getDifferential().getElement();
3469    int lastCount = diffList.size();
3470    // first, we move the differential elements into a tree
3471    if (diffList.isEmpty())
3472      return;
3473    
3474    ElementDefinitionHolder edh = null;
3475    int i = 0;
3476    if (diffList.get(0).getPath().contains(".")) {
3477      String newPath = diffList.get(0).getPath().split("\\.")[0];
3478      ElementDefinition e = new ElementDefinition(newPath);
3479      edh = new ElementDefinitionHolder(e, true);
3480    } else {
3481      edh = new ElementDefinitionHolder(diffList.get(0));
3482      i = 1;
3483    }
3484
3485    boolean hasSlicing = false;
3486    List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly
3487    for(ElementDefinition elt : diffList) {
3488      if (elt.hasSlicing() || paths.contains(elt.getPath())) {
3489        hasSlicing = true;
3490        break;
3491      }
3492      paths.add(elt.getPath());
3493    }
3494
3495    processElementsIntoTree(edh, i, diff.getDifferential().getElement());
3496
3497    // now, we sort the siblings throughout the tree
3498    ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name, base.getType());
3499    sortElements(edh, cmp, errors);
3500
3501    // now, we serialise them back to a list
3502    List<ElementDefinition> newDiff = new ArrayList<>();
3503    writeElements(edh, newDiff);
3504    if (errorIfChanges) {
3505      compareDiffs(original, newDiff, errors);
3506    }
3507    diffList.clear();
3508    diffList.addAll(newDiff);
3509    
3510    if (lastCount != diffList.size())
3511      errors.add("Sort failed: counts differ; at least one of the paths in the differential is illegal");
3512  }
3513
3514  private void compareDiffs(List<ElementDefinition> diffList, List<ElementDefinition> newDiff, List<String> errors) {
3515    if (diffList.size() != newDiff.size()) {
3516      errors.add("The diff list size changed when sorting - was "+diffList.size()+" is now "+newDiff.size()+
3517          " ["+CommaSeparatedStringBuilder.buildObjects(diffList)+"]/["+CommaSeparatedStringBuilder.buildObjects(newDiff)+"]");
3518    } else {
3519      for (int i = 0; i < Integer.min(diffList.size(), newDiff.size()); i++) {
3520        ElementDefinition e = diffList.get(i);
3521        ElementDefinition n = newDiff.get(i);
3522        if (!n.getPath().equals(e.getPath())) {
3523          errors.add("The element "+e.getPath()+" is out of order (and maybe others after it)");
3524          return;
3525        }   
3526      }
3527    }
3528  }
3529
3530
3531  private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) {
3532    String path = edh.getSelf().getPath();
3533    final String prefix = path + ".";
3534    while (i < list.size() && list.get(i).getPath().startsWith(prefix)) {
3535      if (list.get(i).getPath().substring(prefix.length()+1).contains(".")) {
3536        String newPath = prefix + list.get(i).getPath().substring(prefix.length()).split("\\.")[0];
3537        ElementDefinition e = new ElementDefinition(newPath);
3538        ElementDefinitionHolder child = new ElementDefinitionHolder(e, true);
3539        edh.getChildren().add(child);
3540        i = processElementsIntoTree(child, i, list);
3541        
3542      } else {
3543        ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i));
3544        edh.getChildren().add(child);
3545        i = processElementsIntoTree(child, i+1, list);
3546      }
3547    }
3548    return i;
3549  }
3550
3551  private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) throws FHIRException {
3552    if (edh.getChildren().size() == 1)
3553      // 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
3554      edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath(), false);
3555    else
3556      Collections.sort(edh.getChildren(), cmp);
3557    if (debug) {
3558      cmp.checkForErrors(errors);
3559    }
3560
3561    for (ElementDefinitionHolder child : edh.getChildren()) {
3562      if (child.getChildren().size() > 0) {
3563        ElementDefinitionComparer ccmp = getComparer(cmp, child);
3564        if (ccmp != null) {
3565          sortElements(child, ccmp, errors);
3566        }
3567      }
3568    }
3569  }
3570
3571
3572  public ElementDefinitionComparer getComparer(ElementDefinitionComparer cmp, ElementDefinitionHolder child) throws FHIRException, Error {
3573    // what we have to check for here is running off the base profile into a data type profile
3574    ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex());
3575    ElementDefinitionComparer ccmp;
3576    if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getWorkingCode()) || ed.getType().get(0).getWorkingCode().equals(ed.getPath())) {
3577      if (ed.hasType() && "Resource".equals(ed.getType().get(0).getWorkingCode()) && (child.getSelf().hasType() && child.getSelf().getType().get(0).hasProfile())) {
3578        if (child.getSelf().getType().get(0).getProfile().size() > 1) {
3579          throw new FHIRException(context.formatMessage(I18nConstants.UNHANDLED_SITUATION_RESOURCE_IS_PROFILED_TO_MORE_THAN_ONE_OPTION__CANNOT_SORT_PROFILE));
3580        }
3581        StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue());
3582        while (profile != null && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
3583          profile = context.fetchResource(StructureDefinition.class, profile.getBaseDefinition());          
3584        }
3585        if (profile==null) {
3586          ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case
3587        } else {
3588          ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), profile.getType(), child.getSelf().getPath().length(), cmp.name, profile.present());
3589        }
3590      } else {
3591        ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name, cmp.name);
3592      }
3593    } else if (ed.getType().get(0).getWorkingCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) {
3594      StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue());
3595      if (profile==null)
3596        ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case
3597      else
3598        ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode()), child.getSelf().getPath().length(), cmp.name, profile.present());
3599    } else if (ed.getType().size() == 1 && !ed.getType().get(0).getWorkingCode().equals("*")) {
3600      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
3601      if (profile==null)
3602        throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath()));
3603      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode()), child.getSelf().getPath().length(), cmp.name, profile.present());
3604    } else if (child.getSelf().getType().size() == 1) {
3605      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(child.getSelf().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(), child.getSelf().getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present());
3609    } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) {
3610      String edLastNode = ed.getPath().replaceAll("(.*\\.)*(.*)", "$2");
3611      String childLastNode = child.getSelf().getPath().replaceAll("(.*\\.)*(.*)", "$2");
3612      String p = childLastNode.substring(edLastNode.length()-3);
3613      if (isPrimitive(Utilities.uncapitalize(p)))
3614        p = Utilities.uncapitalize(p);
3615      StructureDefinition sd = context.fetchResource(StructureDefinition.class, sdNs(p));
3616      if (sd == null)
3617        throw new Error(context.formatMessage(I18nConstants.UNABLE_TO_FIND_PROFILE__AT_, p, ed.getId()));
3618      ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name, sd.present());
3619    } else if (child.getSelf().hasType() && child.getSelf().getType().get(0).getWorkingCode().equals("Reference")) {
3620      for (TypeRefComponent t: child.getSelf().getType()) {
3621        if (!t.getWorkingCode().equals("Reference")) {
3622          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())));
3623        }
3624      }
3625      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
3626      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present());
3627    } else if (!child.getSelf().hasType() && ed.getType().get(0).getWorkingCode().equals("Reference")) {
3628      for (TypeRefComponent t: ed.getType()) {
3629        if (!t.getWorkingCode().equals("Reference")) {
3630          throw new Error(context.formatMessage(I18nConstants.NOT_HANDLED_YET_SORTELEMENTS_, ed.getPath(), typeCode(ed.getType())));
3631        }
3632      }
3633      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
3634      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name, profile.present());
3635    } else {
3636      // this is allowed if we only profile the extensions
3637      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs("Element"));
3638      if (profile==null)
3639        throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath()));
3640      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), "Element", child.getSelf().getPath().length(), cmp.name, profile.present());
3641//      throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")");
3642    }
3643    return ccmp;
3644  }
3645
3646  private String resolveType(String code) {
3647    if (Utilities.isAbsoluteUrl(code)) {
3648      StructureDefinition sd = context.fetchResource(StructureDefinition.class, code);
3649      if (sd != null) {
3650        return sd.getType();
3651      }
3652    }
3653    return code;
3654  }
3655
3656  private static String sdNs(String type) {
3657    return sdNs(type, null);
3658  }
3659  
3660  public static String sdNs(String type, String overrideVersionNs) {
3661    if (Utilities.isAbsoluteUrl(type))
3662      return type;
3663    else if (overrideVersionNs != null)
3664      return Utilities.pathURL(overrideVersionNs, type);
3665    else
3666      return "http://hl7.org/fhir/StructureDefinition/"+type;
3667  }
3668
3669
3670  private boolean isAbstract(String code) {
3671    return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") || code.equals("DomainResource");
3672  }
3673
3674
3675  private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) {
3676    if (!edh.isPlaceHolder())
3677      list.add(edh.getSelf());
3678    for (ElementDefinitionHolder child : edh.getChildren()) {
3679      writeElements(child, list);
3680    }
3681  }
3682
3683  /**
3684   * First compare element by path then by name if same
3685   */
3686  private static class ElementNameCompare implements Comparator<ElementDefinition> {
3687
3688    @Override
3689    public int compare(ElementDefinition o1, ElementDefinition o2) {
3690      String path1 = normalizePath(o1);
3691      String path2 = normalizePath(o2);
3692      int cmp = path1.compareTo(path2);
3693      if (cmp == 0) {
3694        String name1 = o1.hasSliceName() ? o1.getSliceName() : "";
3695        String name2 = o2.hasSliceName() ? o2.getSliceName() : "";
3696        cmp = name1.compareTo(name2);
3697      }
3698      return cmp;
3699    }
3700
3701    private static String normalizePath(ElementDefinition e) {
3702      if (!e.hasPath()) return "";
3703      String path = e.getPath();
3704      // if sorting element names make sure onset[x] appears before onsetAge, onsetDate, etc.
3705      // so strip off the [x] suffix when comparing the path names.
3706      if (path.endsWith("[x]")) {
3707        path = path.substring(0, path.length()-3);
3708      }
3709      return path;
3710    }
3711
3712  }
3713
3714
3715  // generate schematrons for the rules in a structure definition
3716  public void generateSchematrons(OutputStream dest, StructureDefinition structure) throws IOException, DefinitionException {
3717    if (structure.getDerivation() != TypeDerivationRule.CONSTRAINT)
3718      throw new DefinitionException(context.formatMessage(I18nConstants.NOT_THE_RIGHT_KIND_OF_STRUCTURE_TO_GENERATE_SCHEMATRONS_FOR));
3719    if (!structure.hasSnapshot())
3720      throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT));
3721
3722        StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBaseDefinition(), structure);
3723
3724        if (base != null) {
3725          SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName());
3726
3727          ElementDefinition ed = structure.getSnapshot().getElement().get(0);
3728          generateForChildren(sch, "f:"+ed.getPath(), ed, structure, base);
3729          sch.dump();
3730        }
3731  }
3732
3733  // generate a CSV representation of the structure definition
3734  public void generateCsv(OutputStream dest, StructureDefinition structure, boolean asXml) throws IOException, DefinitionException, Exception {
3735    if (!structure.hasSnapshot())
3736      throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT));
3737
3738    CSVWriter csv = new CSVWriter(dest, structure, asXml);
3739
3740    for (ElementDefinition child : structure.getSnapshot().getElement()) {
3741      csv.processElement(null, child);
3742    }
3743    csv.dump();
3744  }
3745  
3746  // generate a CSV representation of the structure definition
3747  public void addToCSV(CSVWriter csv, StructureDefinition structure) throws IOException, DefinitionException, Exception {
3748    if (!structure.hasSnapshot())
3749      throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT));
3750
3751    for (ElementDefinition child : structure.getSnapshot().getElement()) {
3752      csv.processElement(structure, child);
3753    }
3754  }
3755  
3756  
3757  private class Slicer extends ElementDefinitionSlicingComponent {
3758    String criteria = "";
3759    String name = "";   
3760    boolean check;
3761    public Slicer(boolean cantCheck) {
3762      super();
3763      this.check = cantCheck;
3764    }
3765  }
3766  
3767  private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, StructureDefinition structure) {
3768    // given a child in a structure, it's sliced. figure out the slicing xpath
3769    if (child.getPath().endsWith(".extension")) {
3770      ElementDefinition ued = getUrlFor(structure, child);
3771      if ((ued == null || !ued.hasFixed()) && !(child.hasType() && (child.getType().get(0).hasProfile())))
3772        return new Slicer(false);
3773      else {
3774      Slicer s = new Slicer(true);
3775      String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).getValue() : ((UriType) ued.getFixed()).asStringValue();
3776      s.name = " with URL = '"+url+"'";
3777      s.criteria = "[@url = '"+url+"']";
3778      return s;
3779      }
3780    } else
3781      return new Slicer(false);
3782  }
3783
3784  private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, StructureDefinition structure, StructureDefinition base) throws IOException {
3785    //    generateForChild(txt, structure, child);
3786    List<ElementDefinition> children = getChildList(structure, ed);
3787    String sliceName = null;
3788    ElementDefinitionSlicingComponent slicing = null;
3789    for (ElementDefinition child : children) {
3790      String name = tail(child.getPath());
3791      if (child.hasSlicing()) {
3792        sliceName = name;
3793        slicing = child.getSlicing();        
3794      } else if (!name.equals(sliceName))
3795        slicing = null;
3796
3797      ElementDefinition based = getByPath(base, child.getPath());
3798      boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin()));
3799      boolean doMax = child.hasMax() && !child.getMax().equals("*") && (based == null || (!child.getMax().equals(based.getMax())));
3800      Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure);
3801      if (slicer.check) {
3802        if (doMin || doMax) {
3803          Section s = sch.section(xpath);
3804          Rule r = s.rule(xpath);
3805          if (doMin) 
3806            r.assrt("count(f:"+name+slicer.criteria+") >= "+Integer.toString(child.getMin()), name+slicer.name+": minimum cardinality of '"+name+"' is "+Integer.toString(child.getMin()));
3807          if (doMax) 
3808            r.assrt("count(f:"+name+slicer.criteria+") <= "+child.getMax(), name+slicer.name+": maximum cardinality of '"+name+"' is "+child.getMax());
3809        }
3810      }
3811    }
3812/// xpath has been removed
3813//    for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
3814//      if (inv.hasXpath()) {
3815//        Section s = sch.section(ed.getPath());
3816//        Rule r = s.rule(xpath);
3817//        r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId()+": " : "")+inv.getHuman()+(inv.hasUserData(IS_DERIVED) ? " (inherited)" : ""));
3818//      }
3819//    }
3820    if (!ed.hasContentReference()) {
3821      for (ElementDefinition child : children) {
3822        String name = tail(child.getPath());
3823        generateForChildren(sch, xpath+"/f:"+name, child, structure, base);
3824      }
3825    }
3826  }
3827
3828
3829
3830
3831  private ElementDefinition getByPath(StructureDefinition base, String path) {
3832                for (ElementDefinition ed : base.getSnapshot().getElement()) {
3833                        if (ed.getPath().equals(path))
3834                                return ed;
3835                        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)))
3836                                return ed;
3837    }
3838          return null;
3839  }
3840
3841
3842  public void setIds(StructureDefinition sd, boolean checkFirst) throws DefinitionException  {
3843    if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) {
3844      if (!sd.hasDifferential())
3845        sd.setDifferential(new StructureDefinitionDifferentialComponent());
3846      generateIds(sd.getDifferential().getElement(), sd.getUrl(), sd.getType(), sd);
3847    }
3848    if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) {
3849      if (!sd.hasSnapshot())
3850        sd.setSnapshot(new StructureDefinitionSnapshotComponent());
3851      generateIds(sd.getSnapshot().getElement(), sd.getUrl(), sd.getType(), sd);
3852    }
3853  }
3854
3855
3856  private boolean hasMissingIds(List<ElementDefinition> list) {
3857    for (ElementDefinition ed : list) {
3858      if (!ed.hasId())
3859        return true;
3860    }    
3861    return false;
3862  }
3863
3864  private class SliceList {
3865
3866    private Map<String, String> slices = new HashMap<>();
3867    
3868    public void seeElement(ElementDefinition ed) {
3869      Iterator<Map.Entry<String,String>> iter = slices.entrySet().iterator();
3870      while (iter.hasNext()) {
3871        Map.Entry<String,String> entry = iter.next();
3872        if (entry.getKey().length() > ed.getPath().length() || entry.getKey().equals(ed.getPath()))
3873          iter.remove();
3874      }
3875      
3876      if (ed.hasSliceName()) 
3877        slices.put(ed.getPath(), ed.getSliceName());
3878    }
3879
3880    public String[] analyse(List<String> paths) {
3881      String s = paths.get(0);
3882      String[] res = new String[paths.size()];
3883      res[0] = null;
3884      for (int i = 1; i < paths.size(); i++) {
3885        s = s + "."+paths.get(i);
3886        if (slices.containsKey(s)) 
3887          res[i] = slices.get(s);
3888        else
3889          res[i] = null;
3890      }
3891      return res;
3892    }
3893
3894  }
3895
3896  protected void generateIds(List<ElementDefinition> list, String name, String type, StructureDefinition srcSD) throws DefinitionException  {
3897    if (list.isEmpty())
3898      return;
3899    
3900    Map<String, String> idList = new HashMap<String, String>();
3901    Map<String, String> replacedIds = new HashMap<String, String>();
3902    
3903    SliceList sliceInfo = new SliceList();
3904    // first pass, update the element ids
3905    for (ElementDefinition ed : list) {
3906      List<String> paths = new ArrayList<String>();
3907      if (!ed.hasPath())
3908        throw new DefinitionException(context.formatMessage(I18nConstants.NO_PATH_ON_ELEMENT_DEFINITION__IN_, Integer.toString(list.indexOf(ed)), name));
3909      sliceInfo.seeElement(ed);
3910      String[] pl = ed.getPath().split("\\.");
3911      for (int i = paths.size(); i < pl.length; i++) // -1 because the last path is in focus
3912        paths.add(pl[i]);
3913      String slices[] = sliceInfo.analyse(paths);
3914      
3915      StringBuilder b = new StringBuilder();
3916      b.append(paths.get(0));
3917      for (int i = 1; i < paths.size(); i++) {
3918        b.append(".");
3919        String s = paths.get(i);
3920        String p = slices[i];
3921        b.append(fixChars(s));
3922        if (p != null) {
3923          b.append(":");
3924          b.append(p);
3925        }
3926      }
3927      String bs = b.toString();
3928      if (ed.hasId()) {
3929        replacedIds.put(ed.getId(), ed.getPath());
3930      }
3931      ed.setId(bs);
3932      if (idList.containsKey(bs)) {
3933        if (exception || messages == null) {
3934          throw new DefinitionException(context.formatMessage(I18nConstants.SAME_ID_ON_MULTIPLE_ELEMENTS__IN_, bs, idList.get(bs), ed.getPath(), name));
3935        } else
3936          messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, name+"."+bs, "Duplicate Element id "+bs, ValidationMessage.IssueSeverity.ERROR));
3937      }
3938      idList.put(bs, ed.getPath());
3939      if (ed.hasContentReference() && ed.getContentReference().startsWith("#")) {
3940        String s = ed.getContentReference();
3941        String typeURL = getUrlForSource(type, srcSD);
3942        if (replacedIds.containsKey(s.substring(1))) {
3943          ed.setContentReference(typeURL+"#"+replacedIds.get(s.substring(1)));
3944        } else {
3945          ed.setContentReference(typeURL+s);
3946        }
3947      }
3948    }  
3949    // second path - fix up any broken path based id references
3950    
3951  }
3952
3953
3954  private String getUrlForSource(String type, StructureDefinition srcSD) {
3955    if (srcSD.getKind() == StructureDefinitionKind.LOGICAL) {
3956      return srcSD.getUrl();
3957    } else {
3958      return "http://hl7.org/fhir/StructureDefinition/"+type;
3959    }
3960  }
3961
3962  private Object fixChars(String s) {
3963    return s.replace("_", "-");
3964  }
3965
3966
3967//  private String describeExtension(ElementDefinition ed) {
3968//    if (!ed.hasType() || !ed.getTypeFirstRep().hasProfile())
3969//      return "";
3970//    return "$"+urlTail(ed.getTypeFirstRep().getProfile());
3971//  }
3972//
3973
3974  private static String urlTail(String profile) {
3975    return profile.contains("/") ? profile.substring(profile.lastIndexOf("/")+1) : profile;
3976  }
3977//
3978//
3979//  private String checkName(String name) {
3980////    if (name.contains("."))
3981//////      throw new Exception("Illegal name "+name+": no '.'");
3982////    if (name.contains(" "))
3983////      throw new Exception("Illegal name "+name+": no spaces");
3984//    StringBuilder b = new StringBuilder();
3985//    for (char c : name.toCharArray()) {
3986//      if (!Utilities.existsInList(c, '.', ' ', ':', '"', '\'', '(', ')', '&', '[', ']'))
3987//        b.append(c);
3988//    }
3989//    return b.toString().toLowerCase();
3990//  }
3991//
3992//
3993//  private int charCount(String path, char t) {
3994//    int res = 0;
3995//    for (char ch : path.toCharArray()) {
3996//      if (ch == t)
3997//        res++;
3998//    }
3999//    return res;
4000//  }
4001
4002//
4003//private void generateForChild(TextStreamWriter txt,
4004//    StructureDefinition structure, ElementDefinition child) {
4005//  // TODO Auto-generated method stub
4006//
4007//}
4008
4009  private interface ExampleValueAccessor {
4010    DataType getExampleValue(ElementDefinition ed);
4011    String getId();
4012  }
4013
4014  private class BaseExampleValueAccessor implements ExampleValueAccessor {
4015    @Override
4016    public DataType getExampleValue(ElementDefinition ed) {
4017      if (ed.hasFixed())
4018        return ed.getFixed();
4019      if (ed.hasExample())
4020        return ed.getExample().get(0).getValue();
4021      else
4022        return null;
4023    }
4024
4025    @Override
4026    public String getId() {
4027      return "-genexample";
4028    }
4029  }
4030  
4031  private class ExtendedExampleValueAccessor implements ExampleValueAccessor {
4032    private String index;
4033
4034    public ExtendedExampleValueAccessor(String index) {
4035      this.index = index;
4036    }
4037    @Override
4038    public DataType getExampleValue(ElementDefinition ed) {
4039      if (ed.hasFixed())
4040        return ed.getFixed();
4041      for (Extension ex : ed.getExtension()) {
4042       String ndx = ToolingExtensions.readStringExtension(ex, "index");
4043       DataType value = ToolingExtensions.getExtension(ex, "exValue").getValue();
4044       if (index.equals(ndx) && value != null)
4045         return value;
4046      }
4047      return null;
4048    }
4049    @Override
4050    public String getId() {
4051      return "-genexample-"+index;
4052    }
4053  }
4054  
4055  public List<org.hl7.fhir.r5.elementmodel.Element> generateExamples(StructureDefinition sd, boolean evenWhenNoExamples) throws FHIRException {
4056    List<org.hl7.fhir.r5.elementmodel.Element> examples = new ArrayList<org.hl7.fhir.r5.elementmodel.Element>();
4057    if (sd.hasSnapshot()) {
4058      if (evenWhenNoExamples || hasAnyExampleValues(sd)) 
4059        examples.add(generateExample(sd, new BaseExampleValueAccessor()));
4060      for (int i = 1; i <= 50; i++) {
4061        if (hasAnyExampleValues(sd, Integer.toString(i))) 
4062          examples.add(generateExample(sd, new ExtendedExampleValueAccessor(Integer.toString(i))));
4063      }
4064    }
4065    return examples;
4066  }
4067
4068  private org.hl7.fhir.r5.elementmodel.Element generateExample(StructureDefinition profile, ExampleValueAccessor accessor) throws FHIRException {
4069    ElementDefinition ed = profile.getSnapshot().getElementFirstRep();
4070    org.hl7.fhir.r5.elementmodel.Element r = new org.hl7.fhir.r5.elementmodel.Element(ed.getPath(), new Property(context, ed, profile));
4071    SourcedChildDefinitions children = getChildMap(profile, ed);
4072    for (ElementDefinition child : children.getList()) {
4073      if (child.getPath().endsWith(".id")) {
4074        org.hl7.fhir.r5.elementmodel.Element id = new org.hl7.fhir.r5.elementmodel.Element("id", new Property(context, child, profile));
4075        id.setValue(profile.getId()+accessor.getId());
4076        r.getChildren().add(id);
4077      } else { 
4078        org.hl7.fhir.r5.elementmodel.Element e = createExampleElement(profile, child, accessor);
4079        if (e != null)
4080          r.getChildren().add(e);
4081      }
4082    }
4083    return r;
4084  }
4085
4086  private org.hl7.fhir.r5.elementmodel.Element createExampleElement(StructureDefinition profile, ElementDefinition ed, ExampleValueAccessor accessor) throws FHIRException {
4087    DataType v = accessor.getExampleValue(ed);
4088    if (v != null) {
4089      return new ObjectConverter(context).convert(new Property(context, ed, profile), v);
4090    } else {
4091      org.hl7.fhir.r5.elementmodel.Element res = new org.hl7.fhir.r5.elementmodel.Element(tail(ed.getPath()), new Property(context, ed, profile));
4092      boolean hasValue = false;
4093      SourcedChildDefinitions children = getChildMap(profile, ed);
4094      for (ElementDefinition child : children.getList()) {
4095        if (!child.hasContentReference()) {
4096        org.hl7.fhir.r5.elementmodel.Element e = createExampleElement(profile, child, accessor);
4097        if (e != null) {
4098          hasValue = true;
4099          res.getChildren().add(e);
4100        }
4101      }
4102      }
4103      if (hasValue)
4104        return res;
4105      else
4106        return null;
4107    }
4108  }
4109
4110  private boolean hasAnyExampleValues(StructureDefinition sd, String index) {
4111    for (ElementDefinition ed : sd.getSnapshot().getElement())
4112      for (Extension ex : ed.getExtension()) {
4113        String ndx = ToolingExtensions.readStringExtension(ex, "index");
4114        Extension exv = ToolingExtensions.getExtension(ex, "exValue");
4115        if (exv != null) {
4116          DataType value = exv.getValue();
4117        if (index.equals(ndx) && value != null)
4118          return true;
4119        }
4120       }
4121    return false;
4122  }
4123
4124
4125  private boolean hasAnyExampleValues(StructureDefinition sd) {
4126    for (ElementDefinition ed : sd.getSnapshot().getElement())
4127      if (ed.hasExample())
4128        return true;
4129    return false;
4130  }
4131
4132
4133  public void populateLogicalSnapshot(StructureDefinition sd) throws FHIRException {
4134    sd.getSnapshot().getElement().add(sd.getDifferential().getElementFirstRep().copy());
4135    
4136    if (sd.hasBaseDefinition()) {
4137    StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd);
4138    if (base == null)
4139        throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_BASE_DEFINITION_FOR_LOGICAL_MODEL__FROM_, sd.getBaseDefinition(), sd.getUrl()));
4140    copyElements(sd, base.getSnapshot().getElement());
4141    }
4142    copyElements(sd, sd.getDifferential().getElement());
4143  }
4144
4145
4146  private void copyElements(StructureDefinition sd, List<ElementDefinition> list) {
4147    for (ElementDefinition ed : list) {
4148      if (ed.getPath().contains(".")) {
4149        ElementDefinition n = ed.copy();
4150        n.setPath(sd.getSnapshot().getElementFirstRep().getPath()+"."+ed.getPath().substring(ed.getPath().indexOf(".")+1));
4151        sd.getSnapshot().addElement(n);
4152      }
4153    }
4154  }
4155
4156    
4157  public void cleanUpDifferential(StructureDefinition sd) {
4158    if (sd.getDifferential().getElement().size() > 1)
4159      cleanUpDifferential(sd, 1);
4160  }
4161  
4162  private void cleanUpDifferential(StructureDefinition sd, int start) {
4163    int level = Utilities.charCount(sd.getDifferential().getElement().get(start).getPath(), '.');
4164    int c = start;
4165    int len = sd.getDifferential().getElement().size();
4166    HashSet<String> paths = new HashSet<String>();
4167    while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') == level) {
4168      ElementDefinition ed = sd.getDifferential().getElement().get(c);
4169      if (!paths.contains(ed.getPath())) {
4170        paths.add(ed.getPath());
4171        int ic = c+1; 
4172        while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 
4173          ic++;
4174        ElementDefinition slicer = null;
4175        List<ElementDefinition> slices = new ArrayList<ElementDefinition>();
4176        slices.add(ed);
4177        while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') == level) {
4178          ElementDefinition edi = sd.getDifferential().getElement().get(ic);
4179          if (ed.getPath().equals(edi.getPath())) {
4180            if (slicer == null) {
4181              slicer = new ElementDefinition();
4182              slicer.setPath(edi.getPath());
4183              slicer.getSlicing().setRules(SlicingRules.OPEN);
4184              sd.getDifferential().getElement().add(c, slicer);
4185              c++;
4186              ic++;
4187            }
4188            slices.add(edi);
4189          }
4190          ic++;
4191          while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 
4192            ic++;
4193        }
4194        // now we're at the end, we're going to figure out the slicing discriminator
4195        if (slicer != null)
4196          determineSlicing(slicer, slices);
4197      }
4198      c++;
4199      if (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) {
4200        cleanUpDifferential(sd, c);
4201        c++;
4202        while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) 
4203          c++;
4204      }
4205  }
4206  }
4207
4208
4209  private void determineSlicing(ElementDefinition slicer, List<ElementDefinition> slices) {
4210    // first, name them
4211    int i = 0;
4212    for (ElementDefinition ed : slices) {
4213      if (ed.hasUserData("slice-name")) {
4214        ed.setSliceName(ed.getUserString("slice-name"));
4215      } else {
4216        i++;
4217        ed.setSliceName("slice-"+Integer.toString(i));
4218      }
4219    }
4220    // now, the hard bit, how are they differentiated? 
4221    // right now, we hard code this...
4222    if (slicer.getPath().endsWith(".extension") || slicer.getPath().endsWith(".modifierExtension"))
4223      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url");
4224    else if (slicer.getPath().equals("DiagnosticReport.result"))
4225      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("reference.code");
4226    else if (slicer.getPath().equals("Observation.related"))
4227      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("target.reference.code");
4228    else if (slicer.getPath().equals("Bundle.entry"))
4229      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("resource.@profile");
4230    else  
4231      throw new Error("No slicing for "+slicer.getPath());
4232  }
4233
4234
4235  public static ElementDefinitionSlicingDiscriminatorComponent interpretR2Discriminator(String discriminator, boolean isExists) {
4236    if (discriminator.endsWith("@pattern"))
4237      return makeDiscriminator(DiscriminatorType.PATTERN, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 
4238    if (discriminator.endsWith("@profile"))
4239      return makeDiscriminator(DiscriminatorType.PROFILE, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 
4240    if (discriminator.endsWith("@type")) 
4241      return makeDiscriminator(DiscriminatorType.TYPE, discriminator.length() == 5 ? "" : discriminator.substring(0,discriminator.length()-6));
4242    if (discriminator.endsWith("@exists"))
4243      return makeDiscriminator(DiscriminatorType.EXISTS, discriminator.length() == 7 ? "" : discriminator.substring(0,discriminator.length()-8)); 
4244    if (isExists)
4245      return makeDiscriminator(DiscriminatorType.EXISTS, discriminator); 
4246    return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(discriminator);
4247  }
4248
4249
4250  private static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType dType, String str) {
4251    return new ElementDefinitionSlicingDiscriminatorComponent().setType(dType).setPath(Utilities.noString(str)? "$this" : str);
4252  }
4253
4254
4255  public static String buildR2Discriminator(ElementDefinitionSlicingDiscriminatorComponent t) throws FHIRException {
4256    switch (t.getType()) {
4257    case PROFILE: return t.getPath()+"/@profile";
4258    case PATTERN: return t.getPath()+"/@pattern";
4259    case TYPE: return t.getPath()+"/@type";
4260    case VALUE: return t.getPath();
4261    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
4262    default: throw new FHIRException("Unable to represent "+t.getType().toCode()+":"+t.getPath()+" in R2");    
4263    }
4264  }
4265
4266
4267  public static StructureDefinition makeExtensionForVersionedURL(IWorkerContext context, String url) {
4268    String epath = url.substring(54);
4269    if (!epath.contains("."))
4270      return null;
4271    String type = epath.substring(0, epath.indexOf("."));
4272    StructureDefinition sd = context.fetchTypeDefinition(type);
4273    if (sd == null)
4274      return null;
4275    ElementDefinition ed = null;
4276    for (ElementDefinition t : sd.getSnapshot().getElement()) {
4277      if (t.getPath().equals(epath)) {
4278        ed = t;
4279        break;
4280      }
4281    }
4282    if (ed == null)
4283      return null;
4284    if ("Element".equals(ed.typeSummary()) || "BackboneElement".equals(ed.typeSummary())) {
4285      return null;
4286    } else {
4287      StructureDefinition template = context.fetchResource(StructureDefinition.class, "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities");
4288      StructureDefinition ext = template.copy();
4289      ext.setUrl(url);
4290      ext.setId("extension-"+epath);
4291      ext.setName("Extension-"+epath);
4292      ext.setTitle("Extension for r4 "+epath);
4293      ext.setStatus(sd.getStatus());
4294      ext.setDate(sd.getDate());
4295      ext.getContact().clear();
4296      ext.getContact().addAll(sd.getContact());
4297      ext.setFhirVersion(sd.getFhirVersion());
4298      ext.setDescription(ed.getDefinition());
4299      ext.getContext().clear();
4300      ext.addContext().setType(ExtensionContextType.ELEMENT).setExpression(epath.substring(0, epath.lastIndexOf(".")));
4301      ext.getDifferential().getElement().clear();
4302      ext.getSnapshot().getElement().get(3).setFixed(new UriType(url));
4303      ext.getSnapshot().getElement().set(4, ed.copy());
4304      ext.getSnapshot().getElement().get(4).setPath("Extension.value"+Utilities.capitalize(ed.typeSummary()));
4305      return ext;      
4306    }
4307
4308  }
4309
4310
4311  public boolean isThrowException() {
4312    return exception;
4313  }
4314
4315
4316  public void setThrowException(boolean exception) {
4317    this.exception = exception;
4318  }
4319
4320
4321  public ValidationOptions getTerminologyServiceOptions() {
4322    return terminologyServiceOptions;
4323  }
4324
4325
4326  public void setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) {
4327    this.terminologyServiceOptions = terminologyServiceOptions;
4328  }
4329
4330
4331  public boolean isNewSlicingProcessing() {
4332    return newSlicingProcessing;
4333  }
4334
4335
4336  public ProfileUtilities setNewSlicingProcessing(boolean newSlicingProcessing) {
4337    this.newSlicingProcessing = newSlicingProcessing;
4338    return this;
4339  }
4340
4341
4342  public boolean isDebug() {
4343    return debug;
4344  }
4345
4346
4347  public void setDebug(boolean debug) {
4348    this.debug = debug;
4349  }
4350
4351
4352  public String getDefWebRoot() {
4353    return defWebRoot;
4354  }
4355
4356
4357  public void setDefWebRoot(String defWebRoot) {
4358    this.defWebRoot = defWebRoot;
4359    if (!this.defWebRoot.endsWith("/"))
4360      this.defWebRoot = this.defWebRoot + '/';
4361  }
4362
4363
4364  public static StructureDefinition makeBaseDefinition(FHIRVersion fhirVersion) {
4365    return makeBaseDefinition(fhirVersion.toCode());
4366  }
4367  public static StructureDefinition makeBaseDefinition(String fhirVersion) {
4368    StructureDefinition base = new StructureDefinition();
4369    base.setId("Base");
4370    base.setUrl("http://hl7.org/fhir/StructureDefinition/Base");
4371    base.setVersion(fhirVersion);
4372    base.setName("Base"); 
4373    base.setStatus(PublicationStatus.ACTIVE);
4374    base.setDate(new Date());
4375    base.setFhirVersion(FHIRVersion.fromCode(fhirVersion));
4376    base.setKind(StructureDefinitionKind.COMPLEXTYPE); 
4377    base.setAbstract(true); 
4378    base.setType("Base");
4379    base.setWebPath("http://build.fhir.org/types.html#Base");
4380    ElementDefinition e = base.getSnapshot().getElementFirstRep();
4381    e.setId("Base");
4382    e.setPath("Base"); 
4383    e.setMin(0); 
4384    e.setMax("*"); 
4385    e.getBase().setPath("Base");
4386    e.getBase().setMin(0); 
4387    e.getBase().setMax("*"); 
4388    e.setIsModifier(false); 
4389    e = base.getDifferential().getElementFirstRep();
4390    e.setId("Base");
4391    e.setPath("Base"); 
4392    e.setMin(0); 
4393    e.setMax("*"); 
4394    return base;
4395  }
4396
4397  public XVerExtensionManager getXver() {
4398    return xver;
4399  }
4400
4401  public ProfileUtilities setXver(XVerExtensionManager xver) {
4402    this.xver = xver;
4403    return this;
4404  }
4405
4406
4407  private List<ElementChoiceGroup> readChoices(ElementDefinition ed, List<ElementDefinition> children) {
4408    List<ElementChoiceGroup> result = new ArrayList<>();
4409    for (ElementDefinitionConstraintComponent c : ed.getConstraint()) {
4410      ElementChoiceGroup grp = processConstraint(children, c);
4411      if (grp != null) {
4412        result.add(grp);
4413      }
4414    }
4415    return result;
4416  }
4417
4418  public ElementChoiceGroup processConstraint(List<ElementDefinition> children, ElementDefinitionConstraintComponent c) {
4419    if (!c.hasExpression()) {
4420      return null;
4421    }
4422    ExpressionNode expr = null;
4423    try {
4424      expr = fpe.parse(c.getExpression());
4425    } catch (Exception e) {
4426      return null;
4427    }
4428    if (expr.getKind() != Kind.Group || expr.getOpNext() == null || !(expr.getOperation() == Operation.Equals || expr.getOperation() == Operation.LessOrEqual)) {
4429      return null;      
4430    }
4431    ExpressionNode n1 = expr.getGroup();
4432    ExpressionNode n2 = expr.getOpNext();
4433    if (n2.getKind() != Kind.Constant || n2.getInner() != null || n2.getOpNext() != null || !"1".equals(n2.getConstant().primitiveValue())) {
4434      return null;
4435    }
4436    ElementChoiceGroup grp = new ElementChoiceGroup(c.getKey(), expr.getOperation() == Operation.Equals);
4437    while (n1 != null) {
4438      if (n1.getKind() != Kind.Name || n1.getInner() != null) {
4439        return null;
4440      }
4441      grp.elements.add(n1.getName());
4442      if (n1.getOperation() == null || n1.getOperation() == Operation.Union) {
4443        n1 = n1.getOpNext();
4444      } else {
4445        return null;
4446      }
4447    }
4448    int total = 0;
4449    for (String n : grp.elements) {
4450      boolean found = false;
4451      for (ElementDefinition child : children) {
4452        String name = tail(child.getPath());
4453        if (n.equals(name)) {
4454          found = true;
4455          if (!"0".equals(child.getMax())) {
4456            total++;
4457          }
4458        }
4459      }
4460      if (!found) {
4461        return null;
4462      }
4463    }
4464    if (total <= 1) {
4465      return null;
4466    }
4467    return grp;
4468  }
4469
4470  public Set<String> getMasterSourceFileNames() {
4471    return masterSourceFileNames;
4472  }
4473
4474  public void setMasterSourceFileNames(Set<String> masterSourceFileNames) {
4475    this.masterSourceFileNames = masterSourceFileNames;
4476  }
4477
4478  
4479  public Set<String> getLocalFileNames() {
4480    return localFileNames;
4481  }
4482
4483  public void setLocalFileNames(Set<String> localFileNames) {
4484    this.localFileNames = localFileNames;
4485  }
4486
4487  public ProfileKnowledgeProvider getPkp() {
4488    return pkp;
4489  }
4490
4491
4492  public static final String UD_ERROR_STATUS = "error-status";
4493  public static final int STATUS_OK = 0;
4494  public static final int STATUS_HINT = 1;
4495  public static final int STATUS_WARNING = 2;
4496  public static final int STATUS_ERROR = 3;
4497  public static final int STATUS_FATAL = 4;
4498  private static final String ROW_COLOR_ERROR = "#ffcccc";
4499  private static final String ROW_COLOR_FATAL = "#ff9999";
4500  private static final String ROW_COLOR_WARNING = "#ffebcc";
4501  private static final String ROW_COLOR_HINT = "#ebf5ff";
4502  private static final String ROW_COLOR_NOT_MUST_SUPPORT = "#d6eaf8";
4503
4504  public String getRowColor(ElementDefinition element, boolean isConstraintMode) {
4505    switch (element.getUserInt(UD_ERROR_STATUS)) {
4506    case STATUS_HINT: return ROW_COLOR_HINT;
4507    case STATUS_WARNING: return ROW_COLOR_WARNING;
4508    case STATUS_ERROR: return ROW_COLOR_ERROR;
4509    case STATUS_FATAL: return ROW_COLOR_FATAL;
4510    }
4511    if (isConstraintMode && !element.getMustSupport() && !element.getIsModifier() && element.getPath().contains("."))
4512      return null; // ROW_COLOR_NOT_MUST_SUPPORT;
4513    else
4514      return null;
4515  }
4516
4517  public static boolean isExtensionDefinition(StructureDefinition sd) {
4518    return sd.getDerivation() == TypeDerivationRule.CONSTRAINT && sd.getType().equals("Extension");
4519  }
4520
4521  public AllowUnknownProfile getAllowUnknownProfile() {
4522    return allowUnknownProfile;
4523  }
4524
4525  public void setAllowUnknownProfile(AllowUnknownProfile allowUnknownProfile) {
4526    this.allowUnknownProfile = allowUnknownProfile;
4527  }
4528
4529  public static boolean isSimpleExtension(StructureDefinition sd) {
4530    if (!isExtensionDefinition(sd)) {
4531      return false;
4532    }
4533    ElementDefinition value = sd.getSnapshot().getElementByPath("Extension.value");
4534    return value != null && !value.isProhibited();
4535  }
4536
4537  public static boolean isComplexExtension(StructureDefinition sd) {
4538    if (!isExtensionDefinition(sd)) {
4539      return false;
4540    }
4541    ElementDefinition value = sd.getSnapshot().getElementByPath("Extension.value");
4542    return value == null || value.isProhibited();
4543  }
4544
4545  public static boolean isModifierExtension(StructureDefinition sd) {
4546    ElementDefinition defn = sd.getSnapshot().getElementByPath("Extension");
4547    return defn != null && defn.getIsModifier();
4548  }
4549
4550  public boolean isForPublication() {
4551    return forPublication;
4552  }
4553
4554  public void setForPublication(boolean forPublication) {
4555    this.forPublication = forPublication;
4556  }
4557
4558  public List<ValidationMessage> getMessages() {
4559    return messages;
4560  }
4561
4562  public static boolean isResourceBoundary(ElementDefinition ed) {
4563    return ed.getType().size() == 1 && "Resource".equals(ed.getTypeFirstRep().getCode());
4564  }
4565
4566  public static boolean isSuppressIgnorableExceptions() {
4567    return suppressIgnorableExceptions;
4568  }
4569
4570  public static void setSuppressIgnorableExceptions(boolean suppressIgnorableExceptions) {
4571    ProfileUtilities.suppressIgnorableExceptions = suppressIgnorableExceptions;
4572  }
4573
4574  public void setMessages(List<ValidationMessage> messages) {
4575    this.messages = messages; 
4576  }
4577
4578  private Map<String, List<Property>> propertyCache = new HashMap<>();
4579  
4580  public Map<String, List<Property>> getCachedPropertyList() {
4581    return propertyCache;
4582  }
4583
4584  public void checkExtensions(ElementDefinition outcome) {
4585    outcome.getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), ProfileUtilities.NON_INHERITED_ED_URLS));
4586    if (outcome.hasBinding()) {
4587      outcome.getBinding().getExtension().removeIf(ext -> Utilities.existsInList(ext.getUrl(), ProfileUtilities.NON_INHERITED_ED_URLS));      
4588    }
4589  }
4590
4591}