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