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