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