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