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