001package org.hl7.fhir.r4b.conformance;
002
003import java.io.BufferedReader;
004import java.io.FileNotFoundException;
005import java.io.FileReader;
006
007/*
008  Copyright (c) 2011+, HL7, Inc.
009  All rights reserved.
010  
011  Redistribution and use in source and binary forms, with or without modification, 
012  are permitted provided that the following conditions are met:
013    
014   * Redistributions of source code must retain the above copyright notice, this 
015     list of conditions and the following disclaimer.
016   * Redistributions in binary form must reproduce the above copyright notice, 
017     this list of conditions and the following disclaimer in the documentation 
018     and/or other materials provided with the distribution.
019   * Neither the name of HL7 nor the names of its contributors may be used to 
020     endorse or promote products derived from this software without specific 
021     prior written permission.
022  
023  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
024  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
025  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
026  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
027  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
028  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
029  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
030  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
031  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
032  POSSIBILITY OF SUCH DAMAGE.
033  
034 */
035
036
037import java.io.IOException;
038import java.io.OutputStream;
039import java.util.ArrayList;
040import java.util.Collections;
041import java.util.Comparator;
042import java.util.Date;
043import java.util.HashMap;
044import java.util.HashSet;
045import java.util.Iterator;
046import java.util.List;
047import java.util.Map;
048import java.util.Set;
049
050import org.apache.commons.lang3.StringUtils;
051import org.hl7.fhir.exceptions.DefinitionException;
052import org.hl7.fhir.exceptions.FHIRException;
053import org.hl7.fhir.exceptions.FHIRFormatError;
054import org.hl7.fhir.r4b.conformance.ProfileUtilities.ProfileKnowledgeProvider.BindingResolution;
055import org.hl7.fhir.r4b.context.IWorkerContext;
056import org.hl7.fhir.r4b.context.IWorkerContext.ValidationResult;
057import org.hl7.fhir.r4b.elementmodel.ObjectConverter;
058import org.hl7.fhir.r4b.elementmodel.Property;
059import org.hl7.fhir.r4b.formats.IParser;
060import org.hl7.fhir.r4b.model.Base;
061import org.hl7.fhir.r4b.model.BooleanType;
062import org.hl7.fhir.r4b.model.CanonicalType;
063import org.hl7.fhir.r4b.model.CodeType;
064import org.hl7.fhir.r4b.model.CodeableConcept;
065import org.hl7.fhir.r4b.model.Coding;
066import org.hl7.fhir.r4b.model.DataType;
067import org.hl7.fhir.r4b.model.Element;
068import org.hl7.fhir.r4b.model.ElementDefinition;
069import org.hl7.fhir.r4b.model.ElementDefinition.AggregationMode;
070import org.hl7.fhir.r4b.model.ElementDefinition.DiscriminatorType;
071import org.hl7.fhir.r4b.model.ElementDefinition.ElementDefinitionBaseComponent;
072import org.hl7.fhir.r4b.model.ElementDefinition.ElementDefinitionBindingComponent;
073import org.hl7.fhir.r4b.model.ElementDefinition.ElementDefinitionConstraintComponent;
074import org.hl7.fhir.r4b.model.ElementDefinition.ElementDefinitionExampleComponent;
075import org.hl7.fhir.r4b.model.ElementDefinition.ElementDefinitionMappingComponent;
076import org.hl7.fhir.r4b.model.ElementDefinition.ElementDefinitionSlicingComponent;
077import org.hl7.fhir.r4b.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent;
078import org.hl7.fhir.r4b.model.ElementDefinition.PropertyRepresentation;
079import org.hl7.fhir.r4b.model.ElementDefinition.SlicingRules;
080import org.hl7.fhir.r4b.model.ElementDefinition.TypeRefComponent;
081import org.hl7.fhir.r4b.model.Enumeration;
082import org.hl7.fhir.r4b.model.Enumerations.BindingStrength;
083import org.hl7.fhir.r4b.model.Enumerations.FHIRVersion;
084import org.hl7.fhir.r4b.model.Enumerations.PublicationStatus;
085import org.hl7.fhir.r4b.model.ExpressionNode;
086import org.hl7.fhir.r4b.model.ExpressionNode.Kind;
087import org.hl7.fhir.r4b.model.ExpressionNode.Operation;
088import org.hl7.fhir.r4b.model.Extension;
089import org.hl7.fhir.r4b.model.IdType;
090import org.hl7.fhir.r4b.model.IntegerType;
091import org.hl7.fhir.r4b.model.PrimitiveType;
092import org.hl7.fhir.r4b.model.Quantity;
093import org.hl7.fhir.r4b.model.Resource;
094import org.hl7.fhir.r4b.model.StringType;
095import org.hl7.fhir.r4b.model.StructureDefinition;
096import org.hl7.fhir.r4b.model.StructureDefinition.ExtensionContextType;
097import org.hl7.fhir.r4b.model.StructureDefinition.StructureDefinitionContextComponent;
098import org.hl7.fhir.r4b.model.StructureDefinition.StructureDefinitionDifferentialComponent;
099import org.hl7.fhir.r4b.model.StructureDefinition.StructureDefinitionKind;
100import org.hl7.fhir.r4b.model.StructureDefinition.StructureDefinitionMappingComponent;
101import org.hl7.fhir.r4b.model.StructureDefinition.StructureDefinitionSnapshotComponent;
102import org.hl7.fhir.r4b.model.StructureDefinition.TypeDerivationRule;
103import org.hl7.fhir.r4b.model.UriType;
104import org.hl7.fhir.r4b.model.ValueSet;
105import org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionComponent;
106import org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionContainsComponent;
107import org.hl7.fhir.r4b.renderers.TerminologyRenderer;
108import org.hl7.fhir.r4b.renderers.spreadsheets.SpreadsheetGenerator;
109import org.hl7.fhir.r4b.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
110import org.hl7.fhir.r4b.utils.FHIRLexer;
111import org.hl7.fhir.r4b.utils.FHIRPathEngine;
112import org.hl7.fhir.r4b.utils.PublicationHacker;
113import org.hl7.fhir.r4b.utils.ToolingExtensions;
114import org.hl7.fhir.r4b.utils.TranslatingUtilities;
115import org.hl7.fhir.r4b.utils.XVerExtensionManager;
116import org.hl7.fhir.r4b.utils.XVerExtensionManager.XVerExtensionStatus;
117import org.hl7.fhir.r4b.utils.formats.CSVWriter;
118import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
119import org.hl7.fhir.utilities.MarkDownProcessor;
120import org.hl7.fhir.utilities.Utilities;
121import org.hl7.fhir.utilities.VersionUtilities;
122import org.hl7.fhir.utilities.i18n.I18nConstants;
123import org.hl7.fhir.utilities.validation.ValidationMessage;
124import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
125import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
126import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
127import org.hl7.fhir.utilities.validation.ValidationOptions;
128import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
129import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
130import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
131import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
132import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
133import org.hl7.fhir.utilities.xhtml.XhtmlNode;
134import org.hl7.fhir.utilities.xml.SchematronWriter;
135import org.hl7.fhir.utilities.xml.SchematronWriter.Rule;
136import org.hl7.fhir.utilities.xml.SchematronWriter.SchematronType;
137import org.hl7.fhir.utilities.xml.SchematronWriter.Section;
138
139/** 
140 * This class provides a set of utility operations for working with Profiles.
141 * Key functionality:
142 *  * getChildMap --?
143 *  * getChildList
144 *  * generateSnapshot: Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
145 *  * closeDifferential: fill out a differential by excluding anything not mentioned
146 *  * generateExtensionsTable: generate the HTML for a hierarchical table presentation of the extensions
147 *  * generateTable: generate  the HTML for a hierarchical table presentation of a structure
148 *  * generateSpanningTable: generate the HTML for a table presentation of a network of structures, starting at a nominated point
149 *  * summarize: describe the contents of a profile
150 *  
151 * 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
152 *  
153 * @author Grahame
154 *
155 */
156public class ProfileUtilities extends TranslatingUtilities {
157
158  public class ElementDefinitionResolution {
159
160    private StructureDefinition source;
161    private ElementDefinition element;
162
163    public ElementDefinitionResolution(StructureDefinition source, ElementDefinition element) {
164      this.source = source;
165      this.element = element;
166    }
167
168    public StructureDefinition getSource() {
169      return source;
170    }
171
172    public ElementDefinition getElement() {
173      return element;
174    }
175
176  }
177
178  public class ElementRedirection {
179
180    private String path;
181    private ElementDefinition element;
182
183    public ElementRedirection(ElementDefinition element, String path) {
184      this.path = path;
185      this.element = element;
186    }
187
188    public ElementDefinition getElement() {
189      return element;
190    }
191
192    @Override
193    public String toString() {
194      return element.toString() + " : "+path;
195    }
196
197    public String getPath() {
198      return path;
199    }
200
201  }
202  
203  public class TypeSlice {
204    private ElementDefinition defn;
205    private String type;
206    public TypeSlice(ElementDefinition defn, String type) {
207      super();
208      this.defn = defn;
209      this.type = type;
210    }
211    public ElementDefinition getDefn() {
212      return defn;
213    }
214    public String getType() {
215      return type;
216    }
217    
218  }
219  public class BaseTypeSlice {
220    private ElementDefinition defn;
221    private String type;
222    private int start;
223    private int end;
224    public boolean handled;
225    public BaseTypeSlice(ElementDefinition defn, String type, int start, int end) {
226      super();
227      this.defn = defn;
228      this.type = type;
229      this.start = start;
230      this.end = end;
231    }
232  }
233
234  public static class ElementChoiceGroup {
235    private Row row;
236    private String name;
237    private boolean mandatory;
238    private List<String> elements = new ArrayList<>();
239    
240    public ElementChoiceGroup(String name, boolean mandatory) {
241      super();
242      this.name = name;
243      this.mandatory = mandatory;
244    }
245    public Row getRow() {
246      return row;
247    }
248    public List<String> getElements() {
249      return elements;
250    }
251    public void setRow(Row row) {
252      this.row = row;      
253    }
254    public String getName() {
255      return name;
256    }
257  }
258  
259  private static final int MAX_RECURSION_LIMIT = 10;
260  
261  public class ExtensionContext {
262
263    private ElementDefinition element;
264    private StructureDefinition defn;
265
266    public ExtensionContext(StructureDefinition ext, ElementDefinition ed) {
267      this.defn = ext;
268      this.element = ed;
269    }
270
271    public ElementDefinition getElement() {
272      return element;
273    }
274
275    public StructureDefinition getDefn() {
276      return defn;
277    }
278
279    public String getUrl() {
280      if (element == defn.getSnapshot().getElement().get(0))
281        return defn.getUrl();
282      else
283        return element.getSliceName();
284    }
285
286    public ElementDefinition getExtensionValueDefinition() {
287      int i = defn.getSnapshot().getElement().indexOf(element)+1;
288      while (i < defn.getSnapshot().getElement().size()) {
289        ElementDefinition ed = defn.getSnapshot().getElement().get(i);
290        if (ed.getPath().equals(element.getPath()))
291          return null;
292        if (ed.getPath().startsWith(element.getPath()+".value") && !ed.hasSlicing())
293          return ed;
294        i++;
295      }
296      return null;
297    }
298  }
299
300  private static final String ROW_COLOR_ERROR = "#ffcccc";
301  private static final String ROW_COLOR_FATAL = "#ff9999";
302  private static final String ROW_COLOR_WARNING = "#ffebcc";
303  private static final String ROW_COLOR_HINT = "#ebf5ff";
304  private static final String ROW_COLOR_NOT_MUST_SUPPORT = "#d6eaf8";
305  public static final int STATUS_OK = 0;
306  public static final int STATUS_HINT = 1;
307  public static final int STATUS_WARNING = 2;
308  public static final int STATUS_ERROR = 3;
309  public static final int STATUS_FATAL = 4;
310
311
312  private static final String DERIVATION_EQUALS = "derivation.equals";
313  public static final String DERIVATION_POINTER = "derived.pointer";
314  public static final String IS_DERIVED = "derived.fact";
315  public static final String UD_ERROR_STATUS = "error-status";
316  private static final String GENERATED_IN_SNAPSHOT = "profileutilities.snapshot.processed";
317  private static final boolean COPY_BINDING_EXTENSIONS = false;
318  private static final boolean DONT_DO_THIS = false;
319  private final boolean ADD_REFERENCE_TO_TABLE = true;
320
321  private boolean useTableForFixedValues = true;
322  private boolean debug;
323
324  // note that ProfileUtilities are used re-entrantly internally, so nothing with process state can be here
325  private final IWorkerContext context;
326  private FHIRPathEngine fpe;
327  private List<ValidationMessage> messages;
328  private List<String> snapshotStack = new ArrayList<String>();
329  private ProfileKnowledgeProvider pkp;
330  private boolean igmode;
331  private boolean exception;
332  private ValidationOptions terminologyServiceOptions = new ValidationOptions();
333  private boolean newSlicingProcessing;
334  private String defWebRoot;
335  private boolean autoFixSliceNames;
336  private XVerExtensionManager xver;
337  private boolean wantFixDifferentialFirstElementType;
338  private Set<String> masterSourceFileNames;
339
340  public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp, FHIRPathEngine fpe) {
341    super();
342    this.context = context;
343    this.messages = messages;
344    this.pkp = pkp;
345
346    this.fpe = fpe;
347    if (context != null && this.fpe == null) {
348      this.fpe = new FHIRPathEngine(context, this);
349    }
350  }
351
352  public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp) {
353    super();
354    this.context = context;
355    this.messages = messages;
356    this.pkp = pkp;
357    if (context != null) {
358      this.fpe = new FHIRPathEngine(context, this);
359    }
360  }
361  
362  public static class UnusedTracker {
363    private boolean used;
364  }
365
366  public boolean isIgmode() {
367    return igmode;
368  }
369
370  public void setIgmode(boolean igmode) {
371    this.igmode = igmode;
372  }
373  
374  public boolean isWantFixDifferentialFirstElementType() {
375    return wantFixDifferentialFirstElementType;
376  }
377
378  public void setWantFixDifferentialFirstElementType(boolean wantFixDifferentialFirstElementType) {
379    this.wantFixDifferentialFirstElementType = wantFixDifferentialFirstElementType;
380  }
381
382  public boolean isAutoFixSliceNames() {
383    return autoFixSliceNames;
384  }
385
386  public ProfileUtilities setAutoFixSliceNames(boolean autoFixSliceNames) {
387    this.autoFixSliceNames = autoFixSliceNames;
388    return this;
389  }
390
391  public interface ProfileKnowledgeProvider {
392    class BindingResolution {
393      public String display;
394      public String url;
395    }
396    boolean isDatatype(String typeSimple);
397    boolean isResource(String typeSimple);
398    boolean hasLinkFor(String typeSimple);
399    String getLinkFor(String corePath, String typeSimple);
400    BindingResolution resolveBinding(StructureDefinition def,
401      ElementDefinitionBindingComponent binding, String path) throws FHIRException;
402    BindingResolution resolveBinding(StructureDefinition def, String url, String path) throws FHIRException;
403    String getLinkForProfile(StructureDefinition profile, String url);
404    boolean prependLinks();
405    String getLinkForUrl(String corePath, String s);
406  }
407
408
409
410  public List<ElementDefinition> getChildMap(StructureDefinition profile, ElementDefinition element) throws DefinitionException {
411    if (element.getContentReference() != null) {
412      List<ElementDefinition> list = null;
413      String id = null;
414      if (element.getContentReference().startsWith("#")) {
415        // internal reference
416        id = element.getContentReference().substring(1);
417        list = profile.getSnapshot().getElement();
418      } else if (element.getContentReference().contains("#")) {
419        // external reference
420        String ref = element.getContentReference();
421        StructureDefinition sd = context.fetchResource(StructureDefinition.class, ref.substring(0, ref.indexOf("#")));
422        if (sd == null) {
423          throw new DefinitionException("unable to process contentReference '"+element.getContentReference()+"' on element '"+element.getId()+"'");
424        }
425        list = sd.getSnapshot().getElement();
426        id = ref.substring(ref.indexOf("#")+1);        
427      } else {
428        throw new DefinitionException("unable to process contentReference '"+element.getContentReference()+"' on element '"+element.getId()+"'");
429      }
430        
431      for (ElementDefinition e : list) {
432        if (id.equals(e.getId()))
433          return getChildMap(profile, e);
434      }
435      throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_NAME_REFERENCE__AT_PATH_, element.getContentReference(), element.getPath()));
436
437    } else {
438      List<ElementDefinition> res = new ArrayList<ElementDefinition>();
439      List<ElementDefinition> elements = profile.getSnapshot().getElement();
440      String path = element.getPath();
441      for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) {
442        ElementDefinition e = elements.get(index);
443        if (e.getPath().startsWith(path + ".")) {
444          // We only want direct children, not all descendants
445          if (!e.getPath().substring(path.length()+1).contains("."))
446            res.add(e);
447        } else
448          break;
449      }
450      return res;
451    }
452  }
453
454
455  public List<ElementDefinition> getSliceList(StructureDefinition profile, ElementDefinition element) throws DefinitionException {
456    if (!element.hasSlicing())
457      throw new Error(context.formatMessage(I18nConstants.GETSLICELIST_SHOULD_ONLY_BE_CALLED_WHEN_THE_ELEMENT_HAS_SLICING));
458
459    List<ElementDefinition> res = new ArrayList<ElementDefinition>();
460    List<ElementDefinition> elements = profile.getSnapshot().getElement();
461    String path = element.getPath();
462    for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) {
463      ElementDefinition e = elements.get(index);
464      if (e.getPath().startsWith(path + ".") || e.getPath().equals(path)) {
465        // We want elements with the same path (until we hit an element that doesn't start with the same path)
466        if (e.getPath().equals(element.getPath()))
467          res.add(e);
468      } else
469        break;
470    }
471    return res;
472  }
473
474
475  /**
476   * Given a Structure, navigate to the element given by the path and return the direct children of that element
477   *
478   * @param structure The structure to navigate into
479   * @param path The path of the element within the structure to get the children for
480   * @return A List containing the element children (all of them are Elements)
481   */
482  public List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id) {
483    return getChildList(profile, path, id, false);
484  }
485  
486  public List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id, boolean diff) {
487    return getChildList(profile, path, id, diff, false);
488  }
489  
490  public List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id, boolean diff, boolean refs) {
491    List<ElementDefinition> res = new ArrayList<ElementDefinition>();
492
493    boolean capturing = id==null;
494    if (id==null && !path.contains("."))
495      capturing = true;
496  
497    List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement();
498    for (ElementDefinition e : list) {
499      if (e == null)
500        throw new Error(context.formatMessage(I18nConstants.ELEMENT__NULL_, profile.getUrl()));
501      if (e.getId() == null)
502        throw new Error(context.formatMessage(I18nConstants.ELEMENT_ID__NULL__ON_, e.toString(), profile.getUrl()));
503      
504      if (!capturing && id!=null && e.getId().equals(id)) {
505        capturing = true;
506      }
507      
508      // If our element is a slice, stop capturing children as soon as we see the next slice
509      if (capturing && e.hasId() && id!= null && !e.getId().equals(id) && e.getPath().equals(path))
510        break;
511      
512      if (capturing) {
513        String p = e.getPath();
514  
515        if (refs && !Utilities.noString(e.getContentReference()) && path.startsWith(p)) {
516          if (path.length() > p.length()) {
517            return getChildList(profile, e.getContentReference()+"."+path.substring(p.length()+1), null, diff);
518          } else if (e.getContentReference().startsWith("#")) {
519            return getChildList(profile, e.getContentReference().substring(1), null, diff);            
520          } else if (e.getContentReference().contains("#")) {
521            String url = e.getContentReference().substring(0, e.getContentReference().indexOf("#"));
522            StructureDefinition sd = context.fetchResource(StructureDefinition.class, url);
523            if (sd == null) {
524              throw new DefinitionException("Unable to find Structure "+url);
525            }
526            return getChildList(sd, e.getContentReference().substring(e.getContentReference().indexOf("#")+1), null, diff);            
527          } else {
528            return getChildList(profile, e.getContentReference(), null, diff);
529          }
530          
531        } else if (p.startsWith(path+".") && !p.equals(path)) {
532          String tail = p.substring(path.length()+1);
533          if (!tail.contains(".")) {
534            res.add(e);
535          }
536        }
537      }
538    }
539
540    return res;
541  }
542
543  public List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element, boolean diff, boolean refs) {
544    return getChildList(structure, element.getPath(), element.getId(), diff, refs);
545  }
546
547  public List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element, boolean diff) {
548    return getChildList(structure, element.getPath(), element.getId(), diff);
549  }
550
551  public List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element) {
552    return getChildList(structure, element.getPath(), element.getId(), false);
553        }
554
555  public void updateMaps(StructureDefinition base, StructureDefinition derived) throws DefinitionException {
556    if (base == null)
557      throw new DefinitionException(context.formatMessage(I18nConstants.NO_BASE_PROFILE_PROVIDED));
558    if (derived == null)
559      throw new DefinitionException(context.formatMessage(I18nConstants.NO_DERIVED_STRUCTURE_PROVIDED));
560    
561    for (StructureDefinitionMappingComponent baseMap : base.getMapping()) {
562      boolean found = false;
563      for (StructureDefinitionMappingComponent derivedMap : derived.getMapping()) {
564        if (derivedMap.getUri() != null && derivedMap.getUri().equals(baseMap.getUri())) {
565          found = true;
566          break;
567        }
568      }
569      if (!found) {
570        derived.getMapping().add(baseMap);
571      }
572    }
573  }
574  
575  /**
576   * Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
577   *
578   * @param base - the base structure on which the differential will be applied
579   * @param differential - the differential to apply to the base
580   * @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)
581   * @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)
582   * @param trimDifferential - if this is true, then the snap short generator will remove any material in the element definitions that is not different to the base
583   * @return
584   * @throws FHIRException 
585   * @throws DefinitionException 
586   * @throws Exception
587   */
588  public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String webUrl, String profileName) throws DefinitionException, FHIRException {
589    if (base == null) {
590      throw new DefinitionException(context.formatMessage(I18nConstants.NO_BASE_PROFILE_PROVIDED));
591    }
592    if (derived == null) {
593      throw new DefinitionException(context.formatMessage(I18nConstants.NO_DERIVED_STRUCTURE_PROVIDED));
594    }
595    checkNotGenerating(base, "Base for generating a snapshot for the profile "+derived.getUrl());
596    checkNotGenerating(derived, "Focus for generating a snapshot");
597
598    if (!base.hasType()) {
599      throw new DefinitionException(context.formatMessage(I18nConstants.BASE_PROFILE__HAS_NO_TYPE, base.getUrl()));
600    }
601    if (!derived.hasType()) {
602      throw new DefinitionException(context.formatMessage(I18nConstants.DERIVED_PROFILE__HAS_NO_TYPE, derived.getUrl()));
603    }
604    if (!derived.hasDerivation()) {
605      throw new DefinitionException(context.formatMessage(I18nConstants.DERIVED_PROFILE__HAS_NO_DERIVATION_VALUE_AND_SO_CANT_BE_PROCESSED, derived.getUrl()));
606    }
607    if (!base.getType().equals(derived.getType()) && derived.getDerivation() == TypeDerivationRule.CONSTRAINT) {
608      throw new DefinitionException(context.formatMessage(I18nConstants.BASE__DERIVED_PROFILES_HAVE_DIFFERENT_TYPES____VS___, base.getUrl(), base.getType(), derived.getUrl(), derived.getType()));
609    }
610    
611    if (snapshotStack.contains(derived.getUrl())) {
612      throw new DefinitionException(context.formatMessage(I18nConstants.CIRCULAR_SNAPSHOT_REFERENCES_DETECTED_CANNOT_GENERATE_SNAPSHOT_STACK__, snapshotStack.toString()));
613    }
614    derived.setUserData("profileutils.snapshot.generating", true);
615    snapshotStack.add(derived.getUrl());
616
617    if (!Utilities.noString(webUrl) && !webUrl.endsWith("/"))
618      webUrl = webUrl + '/';
619
620    if (defWebRoot == null)
621      defWebRoot = webUrl;
622    derived.setSnapshot(new StructureDefinitionSnapshotComponent());
623
624    try {
625      checkDifferential(derived.getDifferential().getElement(), typeName(derived.getType()), derived.getUrl());
626      checkDifferentialBaseType(derived);
627      
628      // so we have two lists - the base list, and the differential list
629      // the differential list is only allowed to include things that are in the base list, but
630      // is allowed to include them multiple times - thereby slicing them
631
632      // our approach is to walk through the base list, and see whether the differential
633      // says anything about them.
634      int baseCursor = 0;
635      int diffCursor = 0; // we need a diff cursor because we can only look ahead, in the bound scoped by longer paths
636
637
638      for (ElementDefinition e : derived.getDifferential().getElement()) 
639        e.clearUserData(GENERATED_IN_SNAPSHOT);
640
641      // we actually delegate the work to a subroutine so we can re-enter it with a different cursors
642      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
643      StructureDefinitionSnapshotComponent baseSnapshot  = base.getSnapshot(); 
644      if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
645        String derivedType = derived.getType();
646        if (StructureDefinitionKind.LOGICAL.equals(derived.getKind()) && derived.getType().contains("/")) {
647          derivedType = derivedType.substring(derivedType.lastIndexOf("/")+1);
648        }
649        baseSnapshot = cloneSnapshot(baseSnapshot, base.getType(), derivedType);
650      }
651//      if (derived.getId().equals("2.16.840.1.113883.10.20.22.2.1.1")) {
652//        debug = true;
653//      }
654      processPaths("", derived.getSnapshot(), baseSnapshot, diff, baseCursor, diffCursor, baseSnapshot.getElement().size()-1, 
655          derived.getDifferential().hasElement() ? derived.getDifferential().getElement().size()-1 : -1, url, webUrl, derived.present(), null, null, false, base.getUrl(), null, false, null, null, new ArrayList<ElementRedirection>(), base);
656      checkGroupConstraints(derived);
657      if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
658        for (ElementDefinition e : diff.getElement()) {
659          if (!e.hasUserData(GENERATED_IN_SNAPSHOT)) {
660            ElementDefinition outcome = updateURLs(url, webUrl, e.copy());
661            e.setUserData(GENERATED_IN_SNAPSHOT, outcome);
662            derived.getSnapshot().addElement(outcome);
663          }
664        }
665      }
666      
667      if (derived.getKind() != StructureDefinitionKind.LOGICAL && !derived.getSnapshot().getElementFirstRep().getType().isEmpty())
668        throw new Error(context.formatMessage(I18nConstants.TYPE_ON_FIRST_SNAPSHOT_ELEMENT_FOR__IN__FROM_, derived.getSnapshot().getElementFirstRep().getPath(), derived.getUrl(), base.getUrl()));
669      updateMaps(base, derived);
670
671      setIds(derived, false);
672      if (debug) {
673        System.out.println("Differential: ");
674        for (ElementDefinition ed : derived.getDifferential().getElement())
675          System.out.println("  "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  "+constraintSummary(ed));
676        System.out.println("Snapshot: ");
677        for (ElementDefinition ed : derived.getSnapshot().getElement())
678          System.out.println("  "+ed.getId()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  "+constraintSummary(ed));
679      }
680      CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
681      //Check that all differential elements have a corresponding snapshot element
682      int ce = 0;
683      for (ElementDefinition e : diff.getElement()) {
684        if (!e.hasUserData("diff-source"))
685          throw new Error(context.formatMessage(I18nConstants.UNXPECTED_INTERNAL_CONDITION__NO_SOURCE_ON_DIFF_ELEMENT));
686        else {
687          if (e.hasUserData(DERIVATION_EQUALS))
688            ((Base) e.getUserData("diff-source")).setUserData(DERIVATION_EQUALS, e.getUserData(DERIVATION_EQUALS));
689          if (e.hasUserData(DERIVATION_POINTER))
690            ((Base) e.getUserData("diff-source")).setUserData(DERIVATION_POINTER, e.getUserData(DERIVATION_POINTER));
691        }
692        if (!e.hasUserData(GENERATED_IN_SNAPSHOT)) {
693          b.append(e.hasId() ? "id: "+e.getId() : "path: "+e.getPath());
694          ce++;
695          if (e.hasId()) {
696            String msg = "No match found in the generated snapshot: check that the path and definitions are legal in the differential (including order)";
697            messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+e.getId(), msg, ValidationMessage.IssueSeverity.ERROR));
698          }
699        }
700      }
701      if (!Utilities.noString(b.toString())) {
702        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)";
703        System.out.println("Error in snapshot generation: "+msg);
704        if (!debug) {
705          System.out.println("Differential: ");
706          for (ElementDefinition ed : derived.getDifferential().getElement())
707            System.out.println("  "+ed.getId()+" = "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  "+constraintSummary(ed));
708          System.out.println("Snapshot: ");
709          for (ElementDefinition ed : derived.getSnapshot().getElement())
710            System.out.println("  "+ed.getId()+" = "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  "+constraintSummary(ed));
711        }
712        if (exception)
713          throw new DefinitionException(msg);
714        else
715          messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url, msg, ValidationMessage.IssueSeverity.ERROR));
716      }
717      // hack around a problem in R4 definitions (somewhere?)
718      for (ElementDefinition ed : derived.getSnapshot().getElement()) {
719        for (ElementDefinitionMappingComponent mm : ed.getMapping()) {
720          if (mm.hasMap()) {
721            mm.setMap(mm.getMap().trim());
722          }
723        }
724        for (ElementDefinitionConstraintComponent s : ed.getConstraint()) {
725          if (s.hasSource()) {
726            String ref = s.getSource();
727            if (!Utilities.isAbsoluteUrl(ref)) {
728              if (ref.contains(".")) {
729                s.setSource("http://hl7.org/fhir/StructureDefinition/"+ref.substring(0, ref.indexOf("."))+"#"+ref);
730              } else {
731                s.setSource("http://hl7.org/fhir/StructureDefinition/"+ref);
732              }
733            }  
734          }
735        }
736      }
737      if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
738        for (ElementDefinition ed : derived.getSnapshot().getElement()) {
739          if (!ed.hasBase()) {
740            ed.getBase().setPath(ed.getPath()).setMin(ed.getMin()).setMax(ed.getMax());
741          }
742        }
743      }
744      // last, check for wrong profiles or target profiles
745      for (ElementDefinition ed : derived.getSnapshot().getElement()) {
746        for (TypeRefComponent t : ed.getType()) {
747          for (UriType u : t.getProfile()) {
748            StructureDefinition sd = context.fetchResource(StructureDefinition.class, u.getValue());
749            if (sd == null) {
750              if (xver != null && xver.matchingUrl(u.getValue()) && xver.status(u.getValue()) == XVerExtensionStatus.Valid) {
751                sd = xver.makeDefinition(u.getValue());              
752              }
753            }
754            if (sd == null) {
755              if (messages != null) {
756                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));
757              }
758            } else {
759              String wt = t.getWorkingCode();
760              if (ed.getPath().equals("Bundle.entry.response.outcome")) {
761                wt = "OperationOutcome";
762              }
763              if (!sd.getType().equals(wt)) {
764                boolean ok = isCompatibleType(wt, sd);
765                if (!ok) {
766                  String smsg = "The profile "+u.getValue()+" has type "+sd.getType()+" which is not consistent with the stated type "+wt;
767                  if (exception)
768                    throw new DefinitionException(smsg);
769                  else
770                    messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url+"#"+ed.getId(), smsg, IssueSeverity.ERROR));
771                }
772              }
773            }
774          }
775        }
776      }
777    } catch (Exception e) {
778      // if we had an exception generating the snapshot, make sure we don't leave any half generated snapshot behind
779      derived.setSnapshot(null);
780      derived.clearUserData("profileutils.snapshot.generating");
781      throw e;
782    }
783    derived.clearUserData("profileutils.snapshot.generating");
784  }
785
786  public void checkDifferentialBaseType(StructureDefinition derived) throws Error {
787    if (derived.hasDifferential() && !derived.getDifferential().getElementFirstRep().getPath().contains(".") && !derived.getDifferential().getElementFirstRep().getType().isEmpty()) {
788      if (wantFixDifferentialFirstElementType && typeMatchesAncestor(derived.getDifferential().getElementFirstRep().getType(), derived.getBaseDefinition())) {
789        derived.getDifferential().getElementFirstRep().getType().clear();
790      } else if (derived.getKind() != StructureDefinitionKind.LOGICAL) {
791        throw new Error(context.formatMessage(I18nConstants.TYPE_ON_FIRST_DIFFERENTIAL_ELEMENT));
792      }
793    }
794  }
795
796  private boolean typeMatchesAncestor(List<TypeRefComponent> type, String baseDefinition) {
797    StructureDefinition sd = context.fetchResource(StructureDefinition.class, baseDefinition);
798    return sd != null && type.size() == 1 && sd.getType().equals(type.get(0).getCode()); 
799  }
800
801  private String typeName(String type) {
802    if (Utilities.isAbsoluteUrl(type)) {
803      return type.substring(type.lastIndexOf("/")+1);
804    } else {
805      return type;
806    }
807  }
808
809  private void checkGroupConstraints(StructureDefinition derived) {
810    List<ElementDefinition> toRemove = new ArrayList<>();
811//    List<ElementDefinition> processed = new ArrayList<>();
812    for (ElementDefinition element : derived.getSnapshot().getElement()) {
813      if (!toRemove.contains(element) && !element.hasSlicing() && !"0".equals(element.getMax())) {
814        checkForChildrenInGroup(derived, toRemove, element);
815      }
816    }
817    derived.getSnapshot().getElement().removeAll(toRemove);
818  }
819
820  public void checkForChildrenInGroup(StructureDefinition derived, List<ElementDefinition> toRemove, ElementDefinition element) throws Error {
821    List<ElementDefinition> children = getChildren(derived, element);
822    List<ElementChoiceGroup> groups = readChoices(element, children);
823    for (ElementChoiceGroup group : groups) {
824//      System.out.println(children);
825      String mandated = null;
826      Set<String> names = new HashSet<>();
827      for (ElementDefinition ed : children) {
828        String name = tail(ed.getPath());
829        if (names.contains(name)) {
830          throw new Error("huh?");
831        } else {
832          names.add(name);
833        }
834        if (group.getElements().contains(name)) {
835          if (ed.getMin() == 1) {
836            if (mandated == null) {
837              mandated = name;
838            } else {
839              throw new Error("Error: there are two mandatory elements in "+derived.getUrl()+" when there can only be one: "+mandated+" and "+name);
840            }
841          }
842        }
843      }
844      if (mandated != null) {
845        for (ElementDefinition ed : children) {
846          String name = tail(ed.getPath());
847          if (group.getElements().contains(name) && !mandated.equals(name)) {
848            ed.setMax("0");
849            addAllChildren(derived, ed, toRemove);
850          }
851        }
852      }
853    }
854  }
855
856  private List<ElementDefinition> getChildren(StructureDefinition derived, ElementDefinition element) {
857    List<ElementDefinition> elements = derived.getSnapshot().getElement();
858    int index = elements.indexOf(element) + 1;
859    String path = element.getPath()+".";
860    List<ElementDefinition> list = new ArrayList<>();
861    while (index < elements.size()) {
862      ElementDefinition e = elements.get(index);
863      String p = e.getPath();
864      if (p.startsWith(path) && !e.hasSliceName()) {
865        if (!p.substring(path.length()).contains(".")) {
866          list.add(e);
867        }
868        index++;
869      } else  {
870        break;
871      }
872    }
873    return list;
874  }
875
876  private void addAllChildren(StructureDefinition derived, ElementDefinition element, List<ElementDefinition> toRemove) {
877    List<ElementDefinition> children = getChildList(derived, element);
878    for (ElementDefinition child : children) {
879      toRemove.add(child);
880      addAllChildren(derived, child, toRemove);
881    }
882  }
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()); 
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
1049  /**
1050   * @param trimDifferential
1051   * @param srcSD 
1052   * @throws DefinitionException, FHIRException 
1053   * @throws Exception
1054   */
1055  private ElementDefinition processPaths(String indent, StructureDefinitionSnapshotComponent result, StructureDefinitionSnapshotComponent base, StructureDefinitionDifferentialComponent differential, int baseCursor, int diffCursor, int baseLimit,
1056      int diffLimit, String url, String webUrl, String profileName, String contextPathSrc, String contextPathDst, boolean trimDifferential, String contextName, String resultPathBase, boolean slicingDone, ElementDefinition slicer, String typeSlicingPath, List<ElementRedirection> redirector, StructureDefinition srcSD) throws DefinitionException, FHIRException {
1057    if (debug) {
1058      System.out.println(indent+"PP @ "+resultPathBase+" / "+contextPathSrc+" : base = "+baseCursor+" to "+baseLimit+", diff = "+diffCursor+" to "+diffLimit+" (slicing = "+slicingDone+", k "+(redirector == null ? "null" : redirector.toString())+")");
1059    }
1060    ElementDefinition res = null; 
1061    List<TypeSlice> typeList = new ArrayList<>();
1062    // just repeat processing entries until we run out of our allowed scope (1st entry, the allowed scope is all the entries)
1063    while (baseCursor <= baseLimit) {
1064      // get the current focus of the base, and decide what to do
1065      ElementDefinition currentBase = base.getElement().get(baseCursor);
1066      String cpath = fixedPathSource(contextPathSrc, currentBase.getPath(), redirector);
1067      if (debug) {
1068        System.out.println(indent+" - "+cpath+": base = "+baseCursor+" ("+descED(base.getElement(),baseCursor)+") to "+baseLimit+" ("+descED(base.getElement(),baseLimit)+"), diff = "+diffCursor+" ("+descED(differential.getElement(),diffCursor)+") to "+diffLimit+" ("+descED(differential.getElement(),diffLimit)+") "+
1069           "(slicingDone = "+slicingDone+") (diffpath= "+(differential.getElement().size() > diffCursor ? differential.getElement().get(diffCursor).getPath() : "n/a")+")");
1070      }
1071      List<ElementDefinition> diffMatches = getDiffMatches(differential, cpath, diffCursor, diffLimit, profileName); // get a list of matching elements in scope
1072
1073      // in the simple case, source is not sliced.
1074      if (!currentBase.hasSlicing() || cpath.equals(typeSlicingPath)) {
1075        if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item
1076          // so we just copy it in
1077          ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy());
1078          outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
1079          updateFromBase(outcome, currentBase);
1080          markDerived(outcome);
1081          if (resultPathBase == null)
1082            resultPathBase = outcome.getPath();
1083          else if (!outcome.getPath().startsWith(resultPathBase))
1084            throw new DefinitionException(context.formatMessage(I18nConstants.ADDING_WRONG_PATH__OUTCOMEGETPATH___RESULTPATHBASE__, outcome.getPath(), resultPathBase));
1085          result.getElement().add(outcome);
1086          if (hasInnerDiffMatches(differential, cpath, diffCursor, diffLimit, base.getElement(), true)) {
1087            // well, the profile walks into this, so we need to as well
1088            // did we implicitly step into a new type?
1089            if (baseHasChildren(base, currentBase)) { // not a new type here
1090              processPaths(indent+"  ", result, base, differential, baseCursor+1, diffCursor, baseLimit, diffLimit, url, webUrl, profileName, contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, false, null, null, redirector, srcSD);
1091              baseCursor = indexOfFirstNonChild(base, currentBase, baseCursor+1, baseLimit);
1092            } else {
1093              if (outcome.getType().size() == 0 && !outcome.hasContentReference()) {
1094                throw new DefinitionException(context.formatMessage(I18nConstants._HAS_NO_CHILDREN__AND_NO_TYPES_IN_PROFILE_, cpath, differential.getElement().get(diffCursor).getPath(), profileName));
1095              }
1096              boolean nonExtension = false;
1097              if (outcome.getType().size() > 1) {
1098                for (TypeRefComponent t : outcome.getType()) {
1099                  if (!t.getWorkingCode().equals("Reference")) {
1100                    for (ElementDefinition ed : diffMatches) {
1101                      if (ed != diffMatches.get(0) && !ed.getPath().endsWith(".extension")) {
1102                        nonExtension = true;
1103                      }
1104                    }
1105                  }
1106                }
1107              }
1108              int start = diffCursor;
1109              while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), cpath+"."))
1110                diffCursor++;
1111              if (nonExtension) {
1112                throw new DefinitionException(context.formatMessage(I18nConstants._HAS_CHILDREN__AND_MULTIPLE_TYPES__IN_PROFILE_, cpath, differential.getElement().get(diffCursor).getPath(), typeCode(outcome.getType()), profileName));
1113              } 
1114              if (outcome.hasContentReference()) {                
1115                ElementDefinitionResolution tgt = getElementById(srcSD, base.getElement(), outcome.getContentReference());
1116                if (tgt == null)
1117                  throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_REFERENCE_TO_, outcome.getContentReference()));
1118                replaceFromContentReference(outcome, tgt.getElement());
1119                if (tgt.getSource() != srcSD) {
1120                  base = tgt.getSource().getSnapshot();
1121                  int nbc = base.getElement().indexOf(tgt.getElement())+1;
1122                  int nbl = nbc;
1123                  while (nbl < base.getElement().size() && base.getElement().get(nbl).getPath().startsWith(tgt.getElement().getPath()+"."))
1124                    nbl++;
1125                  processPaths(indent+"  ", result, base, differential, nbc, start - 1, nbl-1, diffCursor - 1, url, webUrl, profileName, tgt.getElement().getPath(), diffMatches.get(0).getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirectorStack(redirector, outcome, cpath), tgt.getSource());
1126                } else {
1127                  int nbc = base.getElement().indexOf(tgt.getElement())+1;
1128                  int nbl = nbc;
1129                  while (nbl < base.getElement().size() && base.getElement().get(nbl).getPath().startsWith(tgt.getElement().getPath()+"."))
1130                    nbl++;
1131                  System.out.println("Test!");
1132                  processPaths(indent+"  ", result, base, differential, nbc, start, nbl-1, diffCursor-1, url, webUrl, profileName, tgt.getElement().getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirectorStack(redirector, outcome, cpath), srcSD);
1133                }
1134              } else {
1135                StructureDefinition dt = outcome.getType().size() > 1 ? context.fetchTypeDefinition("Element") : getProfileForDataType(outcome.getType().get(0));
1136                if (dt == null) {
1137                  throw new DefinitionException(context.formatMessage(I18nConstants.UNKNOWN_TYPE__AT_, outcome.getType().get(0), cpath));
1138                }
1139                contextName = dt.getUrl();
1140                if (redirector.isEmpty()) {
1141                  processPaths(indent+"  ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1,
1142                      diffCursor-1, url, getWebUrl(dt, webUrl, indent), profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirector, srcSD);
1143                } else {
1144                  processPaths(indent+"  ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1,
1145                      diffCursor-1, url, getWebUrl(dt, webUrl, indent), profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirectorStack(redirector, currentBase, cpath), srcSD);
1146                }
1147              }
1148            }
1149          }
1150          baseCursor++;
1151        } else if (diffMatches.size() == 1 && (slicingDone || (!isImplicitSlicing(diffMatches.get(0), cpath) && !(diffMatches.get(0).hasSlicing() || (isExtension(diffMatches.get(0)) && diffMatches.get(0).hasSliceName()))))) {// one matching element in the differential
1152          ElementDefinition template = null;
1153          if (diffMatches.get(0).hasType() && "Reference".equals(diffMatches.get(0).getType().get(0).getWorkingCode()) && !isValidType(diffMatches.get(0).getType().get(0), currentBase)) {
1154            throw new DefinitionException(context.formatMessage(I18nConstants.VALIDATION_VAL_ILLEGAL_TYPE_CONSTRAINT, url, diffMatches.get(0).getPath(), diffMatches.get(0).getType().get(0), currentBase.typeSummary()));            
1155          }
1156          if (diffMatches.get(0).hasType() && diffMatches.get(0).getType().size() == 1 && diffMatches.get(0).getType().get(0).hasProfile() && !"Reference".equals(diffMatches.get(0).getType().get(0).getWorkingCode())) {
1157            CanonicalType p = diffMatches.get(0).getType().get(0).getProfile().get(0);
1158            StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getValue());
1159            if (sd == null && xver != null && xver.matchingUrl(p.getValue())) {
1160              switch (xver.status(p.getValue())) {
1161              case BadVersion: throw new FHIRException("Reference to invalid version in extension url "+p.getValue());
1162              case Invalid: throw new FHIRException("Reference to invalid extension "+p.getValue());
1163              case Unknown: throw new FHIRException("Reference to unknown extension "+p.getValue()); 
1164              case Valid: 
1165                sd = xver.makeDefinition(p.getValue());
1166                generateSnapshot(context.fetchTypeDefinition("Extension"), sd, sd.getUrl(), webUrl, sd.getName());
1167              }
1168            }
1169            if (sd != null) {
1170              if (!isMatchingType(sd, diffMatches.get(0).getType(), p.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT))) {
1171                throw new DefinitionException(context.formatMessage(I18nConstants.VALIDATION_VAL_PROFILE_WRONGTYPE2, sd.getUrl(), diffMatches.get(0).getPath(), sd.getType(), p.getValue(), diffMatches.get(0).getType().get(0).getWorkingCode()));            
1172              }
1173              if (isGenerating(sd)) {
1174                // this is a special case, because we're only going to access the first element, and we can rely on the fact that it's already populated.
1175                // but we check anyway
1176                if (sd.getSnapshot().getElementFirstRep().isEmpty()) {
1177                  throw new FHIRException(context.formatMessage(I18nConstants.ATTEMPT_TO_USE_A_SNAPSHOT_ON_PROFILE__AS__BEFORE_IT_IS_GENERATED, sd.getUrl(), "Source for first element"));
1178                }
1179              } else if (!sd.hasSnapshot()) {
1180                StructureDefinition sdb = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
1181                if (sdb == null)
1182                  throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_BASE__FOR_, sd.getBaseDefinition(), sd.getUrl()));
1183                checkNotGenerating(sdb, "an extension base");
1184                generateSnapshot(sdb, sd, sd.getUrl(), (sdb.hasUserData("path")) ? Utilities.extractBaseUrl(sdb.getUserString("path")) : webUrl, sd.getName());
1185              }
1186              ElementDefinition src;
1187              if (p.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT)) {
1188                 src = null;
1189                 String eid = p.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT);
1190                 for (ElementDefinition t : sd.getSnapshot().getElement()) {
1191                   if (eid.equals(t.getId()))
1192                     src = t;
1193                 }
1194                 if (src == null)
1195                  throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_ELEMENT__IN_, eid, p.getValue()));
1196              } else 
1197                src = sd.getSnapshot().getElement().get(0);
1198              template = src.copy().setPath(currentBase.getPath());
1199              template.setSliceName(null);
1200              // temporary work around
1201              if (!"Extension".equals(diffMatches.get(0).getType().get(0).getCode())) {
1202                template.setMin(currentBase.getMin());
1203                template.setMax(currentBase.getMax());
1204              }
1205            }
1206          } 
1207          if (template == null)
1208            template = currentBase.copy();
1209          else
1210            // some of what's in currentBase overrides template
1211            template = fillOutFromBase(template, currentBase);
1212          
1213          ElementDefinition outcome = updateURLs(url, webUrl, template);
1214          outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
1215          if (res == null)
1216            res = outcome;
1217          updateFromBase(outcome, currentBase);
1218          if (diffMatches.get(0).hasSliceName()) {
1219            outcome.setSliceName(diffMatches.get(0).getSliceName());
1220            if (!diffMatches.get(0).hasMin() && (diffMatches.size() > 1 || slicer == null || slicer.getSlicing().getRules() != SlicingRules.CLOSED)  && !currentBase.hasSliceName()) {
1221              if (!cpath.endsWith("xtension.value[x]")) { // hack work around for problems with snapshots in official releases
1222                outcome.setMin(0);
1223              }
1224            }
1225          }
1226          updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url, srcSD);
1227          removeStatusExtensions(outcome);
1228//          if (outcome.getPath().endsWith("[x]") && outcome.getType().size() == 1 && !outcome.getType().get(0).getCode().equals("*") && !diffMatches.get(0).hasSlicing()) // if the base profile allows multiple types, but the profile only allows one, rename it
1229//            outcome.setPath(outcome.getPath().substring(0, outcome.getPath().length()-3)+Utilities.capitalize(outcome.getType().get(0).getCode()));
1230          outcome.setSlicing(null);
1231          if (resultPathBase == null)
1232            resultPathBase = outcome.getPath();
1233          else if (!outcome.getPath().startsWith(resultPathBase))
1234            throw new DefinitionException(context.formatMessage(I18nConstants.ADDING_WRONG_PATH));
1235          result.getElement().add(outcome);
1236          baseCursor++;
1237          diffCursor = differential.getElement().indexOf(diffMatches.get(0))+1;
1238          if (diffLimit >= diffCursor && outcome.getPath().contains(".") && (isDataType(outcome.getType()) || isBaseResource(outcome.getType()) || outcome.hasContentReference())) {  // don't want to do this for the root, since that's base, and we're already processing it
1239            if (pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".") && !baseWalksInto(base.getElement(), baseCursor)) {
1240              if (outcome.getType().size() > 1) {
1241                if (outcome.getPath().endsWith("[x]") && !diffMatches.get(0).getPath().endsWith("[x]")) {
1242                  String en = tail(outcome.getPath());
1243                  String tn = tail(diffMatches.get(0).getPath());
1244                  String t = tn.substring(en.length()-3);
1245                  if (isPrimitive(Utilities.uncapitalize(t)))
1246                    t = Utilities.uncapitalize(t);
1247                  List<TypeRefComponent> ntr = getByTypeName(outcome.getType(), t); // keep any additional information
1248                  if (ntr.isEmpty()) 
1249                    ntr.add(new TypeRefComponent().setCode(t));
1250                  outcome.getType().clear();
1251                  outcome.getType().addAll(ntr);
1252                }
1253                if (outcome.getType().size() > 1)
1254                  for (TypeRefComponent t : outcome.getType()) {
1255                    if (!t.getCode().equals("Reference")) {
1256                      boolean nonExtension = false;
1257                      for (ElementDefinition ed : diffMatches)
1258                        if (ed != diffMatches.get(0) && !ed.getPath().endsWith(".extension"))
1259                          nonExtension = true;
1260                      if (nonExtension)
1261                        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));
1262                    }
1263                }
1264              }
1265              int start = diffCursor;
1266              while (diffCursor <= diffLimit && differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+"."))
1267                diffCursor++;
1268              if (outcome.hasContentReference()) {
1269                ElementDefinitionResolution tgt = getElementById(srcSD, base.getElement(), outcome.getContentReference());
1270                if (tgt == null)
1271                  throw new DefinitionException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_REFERENCE_TO_, outcome.getContentReference()));
1272                replaceFromContentReference(outcome, tgt.getElement());
1273                if (tgt.getSource() != srcSD) {
1274                  base = tgt.getSource().getSnapshot();
1275                  int nbc = base.getElement().indexOf(tgt.getElement())+1;
1276                  int nbl = nbc;
1277                  while (nbl < base.getElement().size() && base.getElement().get(nbl).getPath().startsWith(tgt.getElement().getPath()+"."))
1278                    nbl++;
1279                  processPaths(indent+"  ", result, base, differential, nbc, start - 1, nbl-1, diffCursor - 1, url, webUrl, profileName, tgt.getElement().getPath(), diffMatches.get(0).getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirectorStack(redirector, outcome, cpath), tgt.getSource());
1280                } else {
1281                  int nbc = base.getElement().indexOf(tgt.getElement())+1;
1282                  int nbl = nbc;
1283                  while (nbl < base.getElement().size() && base.getElement().get(nbl).getPath().startsWith(tgt.getElement().getPath()+"."))
1284                    nbl++;
1285                   processPaths(indent+"  ", result, base, differential, nbc, start - 1, nbl-1, diffCursor - 1, url, webUrl, profileName, tgt.getElement().getPath(), diffMatches.get(0).getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirectorStack(redirector, outcome, cpath), srcSD);
1286                }
1287              } else {
1288                StructureDefinition dt = outcome.getType().size() == 1 ? getProfileForDataType(outcome.getType().get(0)) : getProfileForDataType("Element");
1289                if (dt == null)
1290                  throw new DefinitionException(context.formatMessage(I18nConstants._HAS_CHILDREN__FOR_TYPE__IN_PROFILE__BUT_CANT_FIND_TYPE, diffMatches.get(0).getPath(), differential.getElement().get(diffCursor).getPath(), typeCode(outcome.getType()), profileName));
1291                contextName = dt.getUrl();
1292                processPaths(indent+"  ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1,
1293                    diffCursor - 1, url, getWebUrl(dt, webUrl, indent), profileName+pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, new ArrayList<ElementRedirection>(), srcSD);
1294              }
1295            }
1296          }
1297        } else if (diffsConstrainTypes(diffMatches, cpath, typeList)) {
1298          int start = 0;
1299          int nbl = findEndOfElement(base, baseCursor);
1300          int ndc = differential.getElement().indexOf(diffMatches.get(0));
1301          ElementDefinition elementToRemove = null;
1302          boolean shortCut = !typeList.isEmpty() && typeList.get(0).type != null;
1303          // we come here whether they are sliced in the diff, or whether the short cut is used.
1304          if (shortCut) {
1305            // this is the short cut method, we've just dived in and specified a type slice.
1306            // in R3 (and unpatched R4, as a workaround right now...
1307            if (!FHIRVersion.isR4Plus(context.getVersion()) || !newSlicingProcessing) { // newSlicingProcessing is a work around for editorial loop dependency
1308              // we insert a cloned element with the right types at the start of the diffMatches
1309              ElementDefinition ed = new ElementDefinition();
1310              ed.setPath(determineTypeSlicePath(diffMatches.get(0).getPath(), cpath));
1311              for (TypeSlice ts : typeList) 
1312                ed.addType().setCode(ts.type);
1313              ed.setSlicing(new ElementDefinitionSlicingComponent());
1314              ed.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
1315              ed.getSlicing().setRules(SlicingRules.CLOSED);
1316              ed.getSlicing().setOrdered(false);
1317              diffMatches.add(0, ed);
1318              differential.getElement().add(ndc, ed);
1319              elementToRemove = ed;
1320            } else {
1321              // as of R4, this changed; if there's no slice, there's no constraint on the slice types, only one the type.
1322              // so the element we insert specifies no types (= all types) allowed in the base, not just the listed type.
1323              // see also discussion here: https://chat.fhir.org/#narrow/stream/179177-conformance/topic/Slicing.20a.20non-repeating.20element
1324              ElementDefinition ed = new ElementDefinition();
1325              ed.setPath(determineTypeSlicePath(diffMatches.get(0).getPath(), cpath));
1326              ed.setSlicing(new ElementDefinitionSlicingComponent());
1327              ed.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
1328              ed.getSlicing().setRules(SlicingRules.CLOSED);
1329              ed.getSlicing().setOrdered(false);
1330              diffMatches.add(0, ed);
1331              differential.getElement().add(ndc, ed);
1332              elementToRemove = ed;
1333            }
1334          }
1335          int ndl = findEndOfElement(differential, ndc);
1336          // the first element is setting up the slicing
1337
1338          if (diffMatches.get(0).getSlicing().hasOrdered()) {
1339            if (diffMatches.get(0).getSlicing().getOrdered()) {
1340              throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGORDERED__TRUE, cpath, url));
1341            }
1342          }
1343          if (diffMatches.get(0).getSlicing().hasDiscriminator()) {
1344            if (diffMatches.get(0).getSlicing().getDiscriminator().size() != 1) {
1345              throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGDISCRIMINATORCOUNT__1, cpath, url));
1346            }
1347            if (diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getType() != DiscriminatorType.TYPE) {
1348              throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGDISCRIMINATORTYPE__TYPE, cpath, url));
1349            }
1350            if (!"$this".equals(diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getPath())) {
1351              throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGDISCRIMINATORPATH__THIS, cpath, url));
1352            }
1353          }
1354          // check the slice names too while we're at it...
1355          for (TypeSlice ts : typeList) {
1356              if (ts.type != null) {
1357                String tn = rootName(cpath)+Utilities.capitalize(ts.type);
1358                if (!ts.defn.hasSliceName()) {
1359                  ts.defn.setSliceName(tn);
1360                } else if (!ts.defn.getSliceName().equals(tn)) {
1361                  if (autoFixSliceNames) {
1362                    ts.defn.setSliceName(tn);
1363                  } else {
1364                    throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__SLICE_NAME_MUST_BE__BUT_IS_, (!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath), tn, ts.defn.getSliceName()));
1365                  }
1366                } if (!ts.defn.hasType()) {
1367                  ts.defn.addType().setCode(ts.type);
1368                } else if (ts.defn.getType().size() > 1) {
1369                  throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__SLICE_FOR_TYPE__HAS_MORE_THAN_ONE_TYPE_, (!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath), tn, ts.defn.typeSummary()));
1370                } else if (!ts.defn.getType().get(0).getCode().equals(ts.type)) {
1371                  throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__SLICE_FOR_TYPE__HAS_WRONG_TYPE_, (!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath), tn, ts.defn.typeSummary()));
1372                }
1373              }
1374            }
1375
1376          // ok passed the checks.
1377          // copy the root diff, and then process any children it has
1378          ElementDefinition e = processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, 0), contextPathSrc, contextPathDst, 
1379              trimDifferential, contextName, resultPathBase, true, null, null, redirector, srcSD);
1380          if (e==null)
1381            throw new FHIRException(context.formatMessage(I18nConstants.DID_NOT_FIND_TYPE_ROOT_, diffMatches.get(0).getPath()));
1382          // now set up slicing on the e (cause it was wiped by what we called.
1383          e.setSlicing(new ElementDefinitionSlicingComponent());
1384          e.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
1385          e.getSlicing().setRules(SlicingRules.CLOSED); // type slicing is always closed; the differential might call it open, but that just means it's not constraining the slices it doesn't mention
1386          e.getSlicing().setOrdered(false);
1387         
1388          start++;
1389
1390          String fixedType = null;
1391          // now process the siblings, which should each be type constrained - and may also have their own children
1392          // now we process the base scope repeatedly for each instance of the item in the differential list
1393          for (int i = start; i < diffMatches.size(); i++) {
1394            // our processing scope for the differential is the item in the list, and all the items before the next one in the list
1395            if (diffMatches.get(i).getMin() > 0) {
1396              if (diffMatches.size() > i+1) {
1397                throw new FHIRException(context.formatMessage(I18nConstants.INVALID_SLICING__THERE_IS_MORE_THAN_ONE_TYPE_SLICE_AT__BUT_ONE_OF_THEM__HAS_MIN__1_SO_THE_OTHER_SLICES_CANNOT_EXIST, diffMatches.get(i).getPath(), diffMatches.get(i).getSliceName()));
1398              } else {
1399                e.setMin(1);
1400              }
1401              fixedType = determineFixedType(diffMatches, fixedType, i);
1402            }
1403            ndc = differential.getElement().indexOf(diffMatches.get(i));
1404            ndl = findEndOfElement(differential, ndc);
1405            processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true, e, null, redirector, srcSD);
1406          }
1407          if (elementToRemove != null) {
1408            differential.getElement().remove(elementToRemove);
1409            ndl--;
1410          }
1411          if (fixedType != null) {
1412            for (Iterator<TypeRefComponent> iter = e.getType().iterator(); iter.hasNext(); ) {
1413              TypeRefComponent tr = iter.next();
1414              if (!tr.getCode().equals(fixedType)) {
1415                iter.remove();
1416              }
1417            }
1418          }
1419          if (!"0".equals(e.getMax())) {
1420            // check that there's a slice for each allowed types 
1421            Set<String> allowedTypes = getListOfTypes(e);
1422            for (TypeSlice t : typeList) {
1423              if (t.type != null) {
1424                allowedTypes.remove(t.type);
1425              } else if (t.getDefn().hasSliceName() && t.getDefn().getType().size() == 1) {
1426                allowedTypes.remove(t.getDefn().getType().get(0).getCode());              
1427              }
1428            }
1429            if (!allowedTypes.isEmpty()) {
1430              if (cpath.contains("xtension.value")) {
1431                for (Iterator<TypeRefComponent> iter = e.getType().iterator(); iter.hasNext(); ) {
1432                  TypeRefComponent tr = iter.next();
1433                  if (allowedTypes.contains(tr.getCode())) {
1434                    iter.remove();
1435                  }
1436                }
1437//                System.out.println("!!: Extension Error at "+cpath+": Allowed Types not sliced = "+allowedTypes+". !Extension!!");
1438//                throw new Error("Extension Error at "+cpath+": Allowed Types not sliced = "+allowedTypes+". !Extension!!");
1439
1440              } else {
1441                e.getSlicing().setRules(SlicingRules.OPEN);
1442              }
1443            }
1444          }
1445          // ok, done with that - next in the base list
1446          baseCursor = nbl+1;
1447          diffCursor = ndl+1;
1448          
1449        } else {
1450          // ok, the differential slices the item. Let's check our pre-conditions to ensure that this is correct
1451          if (!unbounded(currentBase) && !isSlicedToOneOnly(diffMatches.get(0)))
1452            // you can only slice an element that doesn't repeat if the sum total of your slices is limited to 1
1453            // (but you might do that in order to split up constraints by type)
1454            throw new DefinitionException(context.formatMessage(I18nConstants.ATTEMPT_TO_A_SLICE_AN_ELEMENT_THAT_DOES_NOT_REPEAT__FROM__IN_, currentBase.getPath(), currentBase.getPath(), contextName, url, diffMatches.get(0).getId(), sliceNames(diffMatches)));
1455          if (!diffMatches.get(0).hasSlicing() && !isExtension(currentBase)) // well, the diff has set up a slice, but hasn't defined it. this is an error
1456            throw new DefinitionException(context.formatMessage(I18nConstants.DIFFERENTIAL_DOES_NOT_HAVE_A_SLICE__B_OF_____IN_PROFILE_, currentBase.getPath(), baseCursor, baseLimit, diffCursor, diffLimit, url, cpath));
1457
1458          // well, if it passed those preconditions then we slice the dest.
1459          int start = 0;
1460          int nbl = findEndOfElement(base, baseCursor);
1461//          if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && differential.getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0))+1) {
1462          ElementDefinition slicerElement;
1463          if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && (nbl > baseCursor || differential.getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0))+1)) { // there's a default set before the slices
1464            int ndc = differential.getElement().indexOf(diffMatches.get(0));
1465            int ndl = findEndOfElement(differential, ndc);
1466            ElementDefinition e = processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, 0), contextPathSrc, contextPathDst, 
1467                trimDifferential, contextName, resultPathBase, true, null, null, redirector, srcSD);
1468            if (e==null)
1469              throw new FHIRException(context.formatMessage(I18nConstants.DID_NOT_FIND_SINGLE_SLICE_, diffMatches.get(0).getPath()));
1470            e.setSlicing(diffMatches.get(0).getSlicing());
1471            slicerElement = e;
1472            start++;
1473          } else {
1474            // we're just going to accept the differential slicing at face value
1475            ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy());
1476            outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
1477            updateFromBase(outcome, currentBase);
1478
1479            if (!diffMatches.get(0).hasSlicing())
1480              outcome.setSlicing(makeExtensionSlicing());
1481            else
1482              outcome.setSlicing(diffMatches.get(0).getSlicing().copy());
1483            if (!outcome.getPath().startsWith(resultPathBase))
1484              throw new DefinitionException(context.formatMessage(I18nConstants.ADDING_WRONG_PATH));
1485            result.getElement().add(outcome);
1486            slicerElement = outcome;
1487            
1488            // differential - if the first one in the list has a name, we'll process it. Else we'll treat it as the base definition of the slice.
1489            if (!diffMatches.get(0).hasSliceName()) {
1490              updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url, srcSD);
1491              removeStatusExtensions(outcome);
1492              if (!outcome.hasContentReference() && !outcome.hasType()) {
1493                throw new DefinitionException(context.formatMessage(I18nConstants.NOT_DONE_YET));
1494              }
1495              if (hasInnerDiffMatches(differential, currentBase.getPath(), diffCursor, diffLimit, base.getElement(), false)) {
1496                if (baseHasChildren(base, currentBase)) { // not a new type here
1497                  throw new Error("This situation is not yet handled (constrain slicing to 1..1 and fix base slice for inline structure - please report issue to grahame@fhir.org along with a test case that reproduces this error (@ "+cpath+" | "+currentBase.getPath()+")");
1498                } else {
1499                  StructureDefinition dt = getTypeForElement(differential, diffCursor, profileName, diffMatches, outcome);
1500                  contextName = dt.getUrl();
1501                  diffCursor++;
1502                  start = diffCursor;
1503                  while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), cpath+"."))
1504                    diffCursor++;
1505                  diffCursor--;
1506                  processPaths(indent+"  ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1,
1507                      diffCursor, url, getWebUrl(dt, webUrl, indent), profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirector, srcSD);
1508                }
1509              }
1510              start++;
1511              // result.getElement().remove(result.getElement().size()-1);
1512            } else 
1513              checkExtensionDoco(outcome);
1514          }
1515          // now, for each entry in the diff matches, we're going to process the base item
1516          // our processing scope for base is all the children of the current path
1517          int ndc = diffCursor;
1518          int ndl = diffCursor;
1519          for (int i = start; i < diffMatches.size(); i++) {
1520            // our processing scope for the differential is the item in the list, and all the items before the next one in the list
1521            ndc = differential.getElement().indexOf(diffMatches.get(i));
1522            ndl = findEndOfElement(differential, ndc);
1523/*            if (skipSlicingElement && i == 0) {
1524              ndc = ndc + 1;
1525              if (ndc > ndl)
1526                continue;
1527            }*/
1528            // now we process the base scope repeatedly for each instance of the item in the differential list
1529            processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true, slicerElement, null, redirector, srcSD);
1530          }
1531          // ok, done with that - next in the base list
1532          baseCursor = nbl+1;
1533          diffCursor = ndl+1;
1534        }
1535      } else {
1536        // the item is already sliced in the base profile.
1537        // here's the rules
1538        //  1. irrespective of whether the slicing is ordered or not, the definition order must be maintained
1539        //  2. slice element names have to match.
1540        //  3. new slices must be introduced at the end
1541        // corallory: you can't re-slice existing slices. is that ok?
1542
1543        // we're going to need this:
1544        String path = currentBase.getPath();
1545        ElementDefinition original = currentBase;
1546
1547        if (diffMatches.isEmpty()) {
1548          if (hasInnerDiffMatches(differential, path, diffCursor, diffLimit, base.getElement(), true)) {
1549            // so we just copy it in
1550            ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy());
1551            outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
1552            updateFromBase(outcome, currentBase);
1553            markDerived(outcome);
1554            if (resultPathBase == null)
1555              resultPathBase = outcome.getPath();
1556            else if (!outcome.getPath().startsWith(resultPathBase))
1557              throw new DefinitionException(context.formatMessage(I18nConstants.ADDING_WRONG_PATH));
1558            result.getElement().add(outcome);
1559            // the profile walks into this, so we need to as well
1560            // did we implicitly step into a new type?
1561            if (baseHasChildren(base, currentBase)) { // not a new type here
1562              processPaths(indent+"  ", result, base, differential, baseCursor+1, diffCursor, baseLimit, diffLimit, url, webUrl, profileName, contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, false, null, null, redirector, srcSD);
1563              baseCursor = indexOfFirstNonChild(base, currentBase, baseCursor, baseLimit);
1564            } else {
1565              StructureDefinition dt = getTypeForElement(differential, diffCursor, profileName, diffMatches, outcome);
1566              contextName = dt.getUrl();
1567              int start = diffCursor;
1568              while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), cpath+"."))
1569                diffCursor++;
1570              processPaths(indent+"  ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1,
1571                  diffCursor-1, url, getWebUrl(dt, webUrl, indent), profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirector, srcSD);
1572            }
1573            baseCursor++;
1574          } else {
1575            // the differential doesn't say anything about this item
1576            // copy across the currentbase, and all of its children and siblings
1577            while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path)) {
1578              ElementDefinition outcome = updateURLs(url, webUrl, base.getElement().get(baseCursor).copy());
1579              outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
1580              if (!outcome.getPath().startsWith(resultPathBase))
1581                throw new DefinitionException(context.formatMessage(I18nConstants.ADDING_WRONG_PATH_IN_PROFILE___VS_, profileName, outcome.getPath(), resultPathBase));
1582              result.getElement().add(outcome); // so we just copy it in
1583              baseCursor++;
1584            }
1585          }
1586        } else if (diffsConstrainTypes(diffMatches, cpath, typeList)) {
1587          int start = 0;
1588          int nbl = findEndOfElement(base, baseCursor);
1589          int ndc = differential.getElement().indexOf(diffMatches.get(0));
1590          ElementDefinition elementToRemove = null;
1591          boolean shortCut = (!typeList.isEmpty() && typeList.get(0).type != null) || (diffMatches.get(0).hasSliceName() && !diffMatches.get(0).hasSlicing());
1592          // we come here whether they are sliced in the diff, or whether the short cut is used.
1593          if (shortCut) {
1594            // this is the short cut method, we've just dived in and specified a type slice.
1595            // in R3 (and unpatched R4, as a workaround right now...
1596            if (!FHIRVersion.isR4Plus(context.getVersion()) || !newSlicingProcessing) { // newSlicingProcessing is a work around for editorial loop dependency
1597              // we insert a cloned element with the right types at the start of the diffMatches
1598              ElementDefinition ed = new ElementDefinition();
1599              ed.setPath(determineTypeSlicePath(diffMatches.get(0).getPath(), cpath));
1600              for (TypeSlice ts : typeList) 
1601                ed.addType().setCode(ts.type);
1602              ed.setSlicing(new ElementDefinitionSlicingComponent());
1603              ed.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
1604              ed.getSlicing().setRules(SlicingRules.CLOSED);
1605              ed.getSlicing().setOrdered(false);
1606              diffMatches.add(0, ed);
1607              differential.getElement().add(ndc, ed);
1608              elementToRemove = ed;
1609            } else {
1610              // as of R4, this changed; if there's no slice, there's no constraint on the slice types, only one the type.
1611              // so the element we insert specifies no types (= all types) allowed in the base, not just the listed type.
1612              // see also discussion here: https://chat.fhir.org/#narrow/stream/179177-conformance/topic/Slicing.20a.20non-repeating.20element
1613              ElementDefinition ed = new ElementDefinition();
1614              ed.setPath(determineTypeSlicePath(diffMatches.get(0).getPath(), cpath));
1615              ed.setSlicing(new ElementDefinitionSlicingComponent());
1616              ed.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
1617              ed.getSlicing().setRules(SlicingRules.CLOSED);
1618              ed.getSlicing().setOrdered(false);
1619              diffMatches.add(0, ed);
1620              differential.getElement().add(ndc, ed);
1621              elementToRemove = ed;
1622            }
1623          }
1624          int ndl = findEndOfElement(differential, ndc);
1625          // the first element is setting up the slicing
1626
1627          if (diffMatches.get(0).getSlicing().hasOrdered()) {
1628            if (diffMatches.get(0).getSlicing().getOrdered()) {
1629              throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGORDERED__TRUE, cpath, url));
1630            }
1631          }
1632          if (diffMatches.get(0).getSlicing().hasDiscriminator()) {
1633            if (diffMatches.get(0).getSlicing().getDiscriminator().size() != 1) {
1634              throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGDISCRIMINATORCOUNT__1, cpath, url));
1635            }
1636            if (diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getType() != DiscriminatorType.TYPE) {
1637              throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGDISCRIMINATORTYPE__TYPE, cpath, url));
1638            }
1639            if (!"$this".equals(diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getPath())) {
1640              throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__IN__TYPE_SLICING_WITH_SLICINGDISCRIMINATORPATH__THIS, cpath, url));
1641            }
1642          }
1643          // check the slice names too while we're at it...
1644          for (TypeSlice ts : typeList) {
1645            if (ts.type != null) {
1646              String tn = rootName(cpath)+Utilities.capitalize(ts.type);
1647              if (!ts.defn.hasSliceName()) {
1648                ts.defn.setSliceName(tn);
1649              } else if (!ts.defn.getSliceName().equals(tn)) {
1650                throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__SLICE_NAME_MUST_BE__BUT_IS_, (!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath), tn, ts.defn.getSliceName()));
1651              } if (!ts.defn.hasType()) {
1652                ts.defn.addType().setCode(ts.type);
1653              } else if (ts.defn.getType().size() > 1) {
1654                throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__SLICE_FOR_TYPE__HAS_MORE_THAN_ONE_TYPE_, (!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath), tn, ts.defn.typeSummary()));
1655              } else if (!ts.defn.getType().get(0).getCode().equals(ts.type)) {
1656                throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT_PATH__SLICE_FOR_TYPE__HAS_WRONG_TYPE_, (!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath), tn, ts.defn.typeSummary()));
1657              }
1658            }
1659          }
1660
1661          // ok passed the checks.
1662          // copy the root diff, and then process any children it has
1663          ElementDefinition e = processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, 0), contextPathSrc, contextPathDst, 
1664              trimDifferential, contextName, resultPathBase, true, null, cpath, redirector, srcSD);
1665          if (e==null)
1666            throw new FHIRException(context.formatMessage(I18nConstants.DID_NOT_FIND_TYPE_ROOT_, diffMatches.get(0).getPath()));
1667          // now set up slicing on the e (cause it was wiped by what we called.
1668          e.setSlicing(new ElementDefinitionSlicingComponent());
1669          e.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
1670          e.getSlicing().setRules(SlicingRules.CLOSED); // type slicing is always closed; the differential might call it open, but that just means it's not constraining the slices it doesn't mention
1671          e.getSlicing().setOrdered(false);
1672          start++;
1673
1674          String fixedType = null;
1675          List<BaseTypeSlice> baseSlices = findBaseSlices(base, nbl);
1676          // now process the siblings, which should each be type constrained - and may also have their own children. they may match existing slices
1677          // now we process the base scope repeatedly for each instance of the item in the differential list
1678          for (int i = start; i < diffMatches.size(); i++) {
1679            String type = determineFixedType(diffMatches, fixedType, i);
1680            // our processing scope for the differential is the item in the list, and all the items before the next one in the list
1681            if (diffMatches.get(i).getMin() > 0) {
1682              if (diffMatches.size() > i+1) {
1683                throw new FHIRException(context.formatMessage(I18nConstants.INVALID_SLICING__THERE_IS_MORE_THAN_ONE_TYPE_SLICE_AT__BUT_ONE_OF_THEM__HAS_MIN__1_SO_THE_OTHER_SLICES_CANNOT_EXIST, diffMatches.get(i).getPath(), diffMatches.get(i).getSliceName()));
1684              }
1685              fixedType = type;
1686            }
1687            ndc = differential.getElement().indexOf(diffMatches.get(i));
1688            ndl = findEndOfElement(differential, ndc);
1689            int sStart = baseCursor;
1690            int sEnd = nbl;
1691            BaseTypeSlice bs = chooseMatchingBaseSlice(baseSlices, type);
1692            if (bs != null) {
1693              sStart = bs.start;
1694              sEnd = bs.end;
1695              bs.handled = true;
1696            }
1697            processPaths(indent+"  ", result, base, differential, sStart, ndc, sEnd, ndl, url, webUrl, profileName+pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true, e, cpath, redirector, srcSD);
1698          }
1699          if (elementToRemove != null) {
1700            differential.getElement().remove(elementToRemove);
1701            ndl--;
1702          }
1703          if (fixedType != null) {
1704            for (Iterator<TypeRefComponent> iter = e.getType().iterator(); iter.hasNext(); ) {
1705              TypeRefComponent tr = iter.next();
1706              if (!tr.getCode().equals(fixedType)) {
1707                iter.remove();
1708              }
1709            }
1710          }
1711          for (BaseTypeSlice bs : baseSlices) {
1712            if (!bs.handled) {
1713              // ok we gimme up a fake differential that says nothing, and run that against the slice.
1714              StructureDefinitionDifferentialComponent fakeDiff = new StructureDefinitionDifferentialComponent();
1715              fakeDiff.getElementFirstRep().setPath(bs.defn.getPath());
1716              processPaths(indent+"  ", result, base, fakeDiff, bs.start, 0, bs.end, 0, url, webUrl, profileName+tail(bs.defn.getPath()), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true, e, cpath, redirector, srcSD);
1717              
1718            }
1719          }
1720          // ok, done with that - next in the base list
1721          baseCursor = baseSlices.get(baseSlices.size()-1).end+1;
1722          diffCursor = ndl+1;
1723          //throw new Error("not done yet - slicing / types @ "+cpath);
1724        } else {
1725          // first - check that the slicing is ok
1726          boolean closed = currentBase.getSlicing().getRules() == SlicingRules.CLOSED;
1727          int diffpos = 0;
1728          boolean isExtension = cpath.endsWith(".extension") || cpath.endsWith(".modifierExtension");
1729          if (diffMatches.get(0).hasSlicing()) { // it might be null if the differential doesn't want to say anything about slicing
1730//            if (!isExtension)
1731//              diffpos++; // if there's a slice on the first, we'll ignore any content it has
1732            ElementDefinitionSlicingComponent dSlice = diffMatches.get(0).getSlicing();
1733            ElementDefinitionSlicingComponent bSlice = currentBase.getSlicing();
1734            if (dSlice.hasOrderedElement() && bSlice.hasOrderedElement() && !orderMatches(dSlice.getOrderedElement(), bSlice.getOrderedElement()))
1735              throw new DefinitionException(context.formatMessage(I18nConstants.SLICING_RULES_ON_DIFFERENTIAL__DO_NOT_MATCH_THOSE_ON_BASE___ORDER___, summarizeSlicing(dSlice), summarizeSlicing(bSlice), path, contextName));
1736            if (!discriminatorMatches(dSlice.getDiscriminator(), bSlice.getDiscriminator()))
1737              throw new DefinitionException(context.formatMessage(I18nConstants.SLICING_RULES_ON_DIFFERENTIAL__DO_NOT_MATCH_THOSE_ON_BASE___DISCIMINATOR___, summarizeSlicing(dSlice), summarizeSlicing(bSlice), path, contextName));
1738            if (!currentBase.isChoice() && !ruleMatches(dSlice.getRules(), bSlice.getRules()))
1739              throw new DefinitionException(context.formatMessage(I18nConstants.SLICING_RULES_ON_DIFFERENTIAL__DO_NOT_MATCH_THOSE_ON_BASE___RULE___, summarizeSlicing(dSlice), summarizeSlicing(bSlice), path, contextName));
1740          }
1741          ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy());
1742          outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
1743          updateFromBase(outcome, currentBase);
1744          if (diffMatches.get(0).hasSlicing() || !diffMatches.get(0).hasSliceName()) {
1745            updateFromSlicing(outcome.getSlicing(), diffMatches.get(0).getSlicing());
1746            updateFromDefinition(outcome, diffMatches.get(0), profileName, closed, url, srcSD); // if there's no slice, we don't want to update the unsliced description
1747            removeStatusExtensions(outcome);
1748          } else if (!diffMatches.get(0).hasSliceName()) {
1749            diffMatches.get(0).setUserData(GENERATED_IN_SNAPSHOT, outcome); // because of updateFromDefinition isn't called
1750          }
1751          
1752          result.getElement().add(outcome);
1753
1754          if (!diffMatches.get(0).hasSliceName()) { // it's not real content, just the slice
1755            diffpos++; 
1756          }
1757          if (hasInnerDiffMatches(differential, cpath, diffCursor, diffLimit, base.getElement(), false)) {
1758            int nbl = findEndOfElement(base, baseCursor);
1759            int ndx = differential.getElement().indexOf(diffMatches.get(0));
1760            int ndc = ndx+(diffMatches.get(0).hasSlicing() ? 1 : 0);
1761            int ndl = findEndOfElement(differential, ndx);
1762            if (nbl == baseCursor) {
1763              if (base.getElement().get(baseCursor).getType().size() != 1) {
1764                throw new Error(context.formatMessage(I18nConstants.DIFFERENTIAL_WALKS_INTO____BUT_THE_BASE_DOES_NOT_AND_THERE_IS_NOT_A_SINGLE_FIXED_TYPE_THE_TYPE_IS__THIS_IS_NOT_HANDLED_YET, cpath, diffMatches.get(0).toString(), base.getElement().get(baseCursor).typeSummary()));
1765              }
1766              StructureDefinition dt = getProfileForDataType(base.getElement().get(baseCursor).getType().get(0));
1767              if (dt == null) {
1768                throw new DefinitionException(context.formatMessage(I18nConstants.UNKNOWN_TYPE__AT_, outcome.getType().get(0), diffMatches.get(0).getPath()));
1769              }
1770              contextName = dt.getUrl();
1771              while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), cpath+"."))
1772                diffCursor++;
1773              processPaths(indent+"  ", result, dt.getSnapshot(), differential, 1, ndc, dt.getSnapshot().getElement().size()-1, ndl, 
1774                  url, getWebUrl(dt, webUrl, indent), profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirector, srcSD);
1775            } else {
1776              processPaths(indent+"  ", result, base, differential, baseCursor+1, ndc, nbl, ndl, 
1777                  url, webUrl, profileName+pathTail(diffMatches, 0), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, false, null, null, null, srcSD);
1778            }
1779//            throw new Error("Not done yet");
1780//          } else if (currentBase.getType().get(0).getCode().equals("BackboneElement") && diffMatches.size() > 0 && diffMatches.get(0).hasSliceName()) {
1781          } else if (currentBase.getType().get(0).getCode().equals("BackboneElement")) {
1782            // We need to copy children of the backbone element before we start messing around with slices
1783            int nbl = findEndOfElement(base, baseCursor);
1784            for (int i = baseCursor+1; i<=nbl; i++) {
1785              outcome = updateURLs(url, webUrl, base.getElement().get(i).copy());
1786              result.getElement().add(outcome);
1787            }
1788          }
1789
1790          // now, we have two lists, base and diff. we're going to work through base, looking for matches in diff.
1791          List<ElementDefinition> baseMatches = getSiblings(base.getElement(), currentBase);
1792          for (ElementDefinition baseItem : baseMatches) {
1793            baseCursor = base.getElement().indexOf(baseItem);
1794            outcome = updateURLs(url, webUrl, baseItem.copy());
1795            updateFromBase(outcome, currentBase);
1796            outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
1797            outcome.setSlicing(null);
1798            if (!outcome.getPath().startsWith(resultPathBase))
1799              throw new DefinitionException(context.formatMessage(I18nConstants.ADDING_WRONG_PATH));
1800            if (diffpos < diffMatches.size() && diffMatches.get(diffpos).hasSliceName() && diffMatches.get(diffpos).getSliceName().equals(outcome.getSliceName())) {
1801              // if there's a diff, we update the outcome with diff
1802              // no? updateFromDefinition(outcome, diffMatches.get(diffpos), profileName, closed, url);
1803              //then process any children
1804              int nbl = findEndOfElement(base, baseCursor);
1805              int ndc = differential.getElement().indexOf(diffMatches.get(diffpos));
1806              int ndl = findEndOfElement(differential, ndc);
1807              // now we process the base scope repeatedly for each instance of the item in the differential list
1808              processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, diffpos), contextPathSrc, contextPathDst, closed, contextName, resultPathBase, true, null, null, redirector, srcSD);
1809              // ok, done with that - now set the cursors for if this is the end
1810              baseCursor = nbl;
1811              diffCursor = ndl+1;
1812              diffpos++;
1813            } else {
1814              result.getElement().add(outcome);
1815              baseCursor++;
1816              // just copy any children on the base
1817              while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path) && !base.getElement().get(baseCursor).getPath().equals(path)) {
1818                outcome = updateURLs(url, webUrl, base.getElement().get(baseCursor).copy());
1819                outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
1820                if (!outcome.getPath().startsWith(resultPathBase))
1821                  throw new DefinitionException(context.formatMessage(I18nConstants.ADDING_WRONG_PATH));
1822                result.getElement().add(outcome);
1823                baseCursor++;
1824              }
1825              //Lloyd - add this for test T15
1826              baseCursor--;
1827            }
1828          }
1829          // finally, we process any remaining entries in diff, which are new (and which are only allowed if the base wasn't closed
1830          boolean checkImplicitTypes = false;
1831          if (closed && diffpos < diffMatches.size()) {
1832            // this is a problem, unless we're on a polymorhpic type and we're going to constrain a slice that actually implicitly exists
1833            if (currentBase.getPath().endsWith("[x]")) {
1834              checkImplicitTypes = true;
1835            } else {
1836              throw new DefinitionException(context.formatMessage(I18nConstants.THE_BASE_SNAPSHOT_MARKS_A_SLICING_AS_CLOSED_BUT_THE_DIFFERENTIAL_TRIES_TO_EXTEND_IT_IN__AT__, profileName, path, cpath));
1837            }
1838          } 
1839          if (diffpos == diffMatches.size()) {
1840//Lloyd This was causing problems w/ Telus
1841//            diffCursor++;
1842          } else {
1843            while (diffpos < diffMatches.size()) {
1844              ElementDefinition diffItem = diffMatches.get(diffpos);
1845              for (ElementDefinition baseItem : baseMatches)
1846                if (baseItem.getSliceName().equals(diffItem.getSliceName()))
1847                  throw new DefinitionException(context.formatMessage(I18nConstants.NAMED_ITEMS_ARE_OUT_OF_ORDER_IN_THE_SLICE));
1848              outcome = updateURLs(url, webUrl, currentBase.copy());
1849              //            outcome = updateURLs(url, diffItem.copy());
1850              outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
1851              updateFromBase(outcome, currentBase);
1852              outcome.setSlicing(null);
1853              outcome.setMin(0); // we're in a slice, so it's only a mandatory if it's explicitly marked so
1854              if (!outcome.getPath().startsWith(resultPathBase))
1855                throw new DefinitionException(context.formatMessage(I18nConstants.ADDING_WRONG_PATH));
1856              result.getElement().add(outcome);
1857              updateFromDefinition(outcome, diffItem, profileName, trimDifferential, url, srcSD);
1858              removeStatusExtensions(outcome);
1859              // --- LM Added this
1860              diffCursor = differential.getElement().indexOf(diffItem)+1;
1861              if (!outcome.getType().isEmpty() && (/*outcome.getType().get(0).getCode().equals("Extension") || */differential.getElement().size() > diffCursor) && outcome.getPath().contains(".")/* && isDataType(outcome.getType())*/) {  // don't want to do this for the root, since that's base, and we're already processing it
1862                if (!baseWalksInto(base.getElement(), baseCursor)) {
1863                  if (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".")) {
1864                    if (outcome.getType().size() > 1)
1865                      for (TypeRefComponent t : outcome.getType()) {
1866                        if (!t.getCode().equals("Reference"))
1867                          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));
1868                      }
1869                    TypeRefComponent t = outcome.getType().get(0);
1870                    if (t.getCode().equals("BackboneElement")) {
1871                      int baseStart = base.getElement().indexOf(currentBase)+1;
1872                      int baseMax = baseStart + 1;
1873                      while (baseMax < base.getElement().size() && base.getElement().get(baseMax).getPath().startsWith(currentBase.getPath()+"."))
1874                       baseMax++;
1875                      int start = diffCursor;
1876                      while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+"."))
1877                        diffCursor++;
1878                      processPaths(indent+"  ", result, base, differential, baseStart, start-1, baseMax-1,
1879                          diffCursor - 1, url, webUrl, profileName+pathTail(diffMatches, 0), base.getElement().get(0).getPath(), base.getElement().get(0).getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirector, srcSD);
1880                      
1881                    } else {
1882                      StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
1883                      //                if (t.getCode().equals("Extension") && t.hasProfile() && !t.getProfile().contains(":")) {
1884                      // lloydfix                  dt =
1885                      //                }
1886                      if (dt == null)
1887                        throw new DefinitionException(context.formatMessage(I18nConstants._HAS_CHILDREN__FOR_TYPE__IN_PROFILE__BUT_CANT_FIND_TYPE, diffMatches.get(0).getPath(), differential.getElement().get(diffCursor).getPath(), typeCode(outcome.getType()), profileName));
1888                      contextName = dt.getUrl();
1889                      int start = diffCursor;
1890                      while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+"."))
1891                        diffCursor++;
1892                      processPaths(indent+"  ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start-1, dt.getSnapshot().getElement().size()-1,
1893                          diffCursor - 1, url, getWebUrl(dt, webUrl, indent), profileName+pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false, null, null, redirector, srcSD);
1894                    }
1895                  }
1896                }
1897              }
1898              // ---
1899              diffpos++;
1900            }
1901          }
1902          baseCursor++;
1903        }
1904      }
1905    }
1906    
1907    int i = 0;
1908    for (ElementDefinition e : result.getElement()) {
1909      i++;
1910      if (e.hasMinElement() && e.getMinElement().getValue()==null)
1911        throw new Error(context.formatMessage(I18nConstants.NULL_MIN));
1912    }
1913    return res;
1914  }
1915
1916  private Set<String> getListOfTypes(ElementDefinition e) {
1917    Set<String> result = new HashSet<>();
1918    for (TypeRefComponent t : e.getType()) {
1919      result.add(t.getCode());
1920    }
1921    return result;
1922  }
1923
1924  public StructureDefinition getTypeForElement(StructureDefinitionDifferentialComponent differential, int diffCursor, String profileName,
1925      List<ElementDefinition> diffMatches, ElementDefinition outcome) {
1926    if (outcome.getType().size() == 0) {
1927      throw new DefinitionException(context.formatMessage(I18nConstants._HAS_NO_CHILDREN__AND_NO_TYPES_IN_PROFILE_, diffMatches.get(0).getPath(), differential.getElement().get(diffCursor).getPath(), profileName));
1928    }
1929    if (outcome.getType().size() > 1) {
1930      for (TypeRefComponent t : outcome.getType()) {
1931        if (!t.getWorkingCode().equals("Reference"))
1932          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));
1933      }
1934    }
1935    StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
1936    if (dt == null)
1937      throw new DefinitionException(context.formatMessage(I18nConstants.UNKNOWN_TYPE__AT_, outcome.getType().get(0), diffMatches.get(0).getPath()));
1938    return dt;
1939  }
1940
1941  private String sliceNames(List<ElementDefinition> diffMatches) {
1942    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1943    for (ElementDefinition ed : diffMatches) {
1944      if (ed.hasSliceName()) {
1945        b.append(ed.getSliceName());
1946      }
1947    }
1948    return b.toString();
1949  }
1950
1951  private boolean isMatchingType(StructureDefinition sd, List<TypeRefComponent> types, String inner) {
1952    while (sd != null) {
1953      for (TypeRefComponent tr : types) {
1954        if (sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition") && sd.getType().equals(tr.getCode())) {
1955          return true;
1956        }
1957        if (inner == null && sd.getUrl().equals(tr.getCode())) {
1958          return true;
1959        }
1960        if (inner != null) {
1961          ElementDefinition ed = null;
1962          for (ElementDefinition t : sd.getSnapshot().getElement()) {
1963            if (inner.equals(t.getId())) {
1964              ed = t;
1965            }
1966          }
1967          if (ed != null) {
1968            return isMatchingType(ed.getType(), types);
1969          }
1970        }
1971      }
1972      sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());    
1973    }
1974    return false;
1975  }
1976
1977  private boolean isMatchingType(List<TypeRefComponent> test, List<TypeRefComponent> desired) {
1978    for (TypeRefComponent t : test) {
1979      for (TypeRefComponent d : desired) {
1980        if (t.getCode().equals(d.getCode())) {
1981          return true;          
1982        }
1983      }
1984    }
1985    return false;
1986  }
1987
1988  private boolean isValidType(TypeRefComponent t, ElementDefinition base) {
1989    for (TypeRefComponent tr : base.getType()) {
1990      if (tr.getCode().equals(t.getCode())) {
1991        return true;
1992      }
1993      if (tr.getWorkingCode().equals(t.getCode())) {
1994        System.out.println("Type error: use of a simple type \""+t.getCode()+"\" wrongly constraining "+base.getPath());
1995        return true;
1996      }
1997    }
1998    return false;
1999  }
2000
2001  private boolean isGenerating(StructureDefinition sd) {
2002    return sd.hasUserData("profileutils.snapshot.generating");
2003  }
2004
2005
2006  private void checkNotGenerating(StructureDefinition sd, String role) {
2007    if (sd.hasUserData("profileutils.snapshot.generating")) {
2008      throw new FHIRException(context.formatMessage(I18nConstants.ATTEMPT_TO_USE_A_SNAPSHOT_ON_PROFILE__AS__BEFORE_IT_IS_GENERATED, sd.getUrl(), role));
2009    }
2010  }
2011
2012  private boolean isBaseResource(List<TypeRefComponent> types) {
2013    if (types.isEmpty())
2014      return false;
2015    for (TypeRefComponent type : types) {
2016      String t = type.getWorkingCode();
2017      if ("Resource".equals(t))
2018        return false;
2019    }
2020    return true;
2021    
2022  }
2023
2024  public String determineFixedType(List<ElementDefinition> diffMatches, String fixedType, int i) {
2025    if (diffMatches.get(i).getType().size() == 0 && diffMatches.get(i).hasSliceName()) {
2026      String n = tail(diffMatches.get(i).getPath()).replace("[x]", "");
2027      String t = diffMatches.get(i).getSliceName().substring(n.length());
2028      if (isDataType(t)) {
2029        fixedType = t;
2030      } else if (isPrimitive(Utilities.uncapitalize(t))) {
2031        fixedType = Utilities.uncapitalize(t);
2032      } else {
2033        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()));
2034      }                
2035    } else if (diffMatches.get(i).getType().size() == 1) {
2036      fixedType = diffMatches.get(i).getType().get(0).getCode();
2037    } else {
2038      throw new FHIRException(context.formatMessage(I18nConstants.UNEXPECTED_CONDITION_IN_DIFFERENTIAL_TYPESLICETYPELISTSIZE__1_AT_, diffMatches.get(i).getPath(), diffMatches.get(i).getSliceName()));
2039    }
2040    return fixedType;
2041  }
2042
2043
2044  private BaseTypeSlice chooseMatchingBaseSlice(List<BaseTypeSlice> baseSlices, String type) {
2045    for (BaseTypeSlice bs : baseSlices) {
2046      if (bs.type.equals(type)) {
2047        return bs;
2048      }
2049    }
2050    return null;
2051  }
2052
2053
2054  private List<BaseTypeSlice> findBaseSlices(StructureDefinitionSnapshotComponent list, int start) {
2055    List<BaseTypeSlice> res = new ArrayList<>();
2056    ElementDefinition base = list.getElement().get(start);
2057    int i = start + 1;
2058    while (i <  list.getElement().size() && list.getElement().get(i).getPath().startsWith(base.getPath()+".")) {
2059      i++;      
2060    };
2061    while (i <  list.getElement().size() && list.getElement().get(i).getPath().equals(base.getPath()) && list.getElement().get(i).hasSliceName()) {
2062      int s = i;
2063      i++;
2064      while (i <  list.getElement().size() && list.getElement().get(i).getPath().startsWith(base.getPath()+".")) {
2065        i++;      
2066      };
2067      res.add(new BaseTypeSlice(list.getElement().get(s), list.getElement().get(s).getTypeFirstRep().getCode(), s, i-1));
2068    }
2069    return res;
2070  }
2071
2072
2073  private String getWebUrl(StructureDefinition dt, String webUrl, String indent) {
2074    if (dt.hasUserData("path")) {
2075      // this is a hack, but it works for now, since we don't have deep folders
2076      String url = dt.getUserString("path");
2077      int i = url.lastIndexOf("/");
2078      if (i < 1) {
2079        return defWebRoot;
2080      } else {
2081        return url.substring(0, i+1);
2082      }
2083    } else {  
2084      return webUrl;
2085    }
2086  }
2087
2088  private void removeStatusExtensions(ElementDefinition outcome) {
2089    outcome.removeExtension(ToolingExtensions.EXT_FMM_LEVEL);
2090    outcome.removeExtension(ToolingExtensions.EXT_STANDARDS_STATUS);
2091    outcome.removeExtension(ToolingExtensions.EXT_NORMATIVE_VERSION);
2092    outcome.removeExtension(ToolingExtensions.EXT_WORKGROUP);    
2093    outcome.removeExtension(ToolingExtensions.EXT_FMM_SUPPORT);
2094    outcome.removeExtension(ToolingExtensions.EXT_FMM_DERIVED);
2095  }
2096
2097  private String descED(List<ElementDefinition> list, int index) {
2098    return index >=0 && index < list.size() ? list.get(index).present() : "X";
2099  }
2100
2101  private boolean baseHasChildren(StructureDefinitionSnapshotComponent base, ElementDefinition ed) {
2102    int index = base.getElement().indexOf(ed);
2103    if (index == -1 || index >= base.getElement().size()-1)
2104      return false;
2105    String p = base.getElement().get(index+1).getPath();
2106    return isChildOf(p, ed.getPath());
2107  }
2108
2109
2110  private boolean isChildOf(String sub, String focus) {
2111    if (focus.endsWith("[x]")) {
2112      focus = focus.substring(0, focus.length()-3);
2113      return sub.startsWith(focus);
2114    } else 
2115      return sub.startsWith(focus+".");
2116  }
2117
2118
2119  private int indexOfFirstNonChild(StructureDefinitionSnapshotComponent base, ElementDefinition currentBase, int i, int baseLimit) {
2120    return baseLimit+1;
2121  }
2122
2123
2124  private String rootName(String cpath) {
2125    String t = tail(cpath);
2126    return t.replace("[x]", "");
2127  }
2128
2129
2130  private String determineTypeSlicePath(String path, String cpath) {
2131    String headP = path.substring(0, path.lastIndexOf("."));
2132//    String tailP = path.substring(path.lastIndexOf(".")+1);
2133    String tailC = cpath.substring(cpath.lastIndexOf(".")+1);
2134    return headP+"."+tailC;
2135  }
2136
2137
2138  private boolean isImplicitSlicing(ElementDefinition ed, String path) {
2139    if (ed == null || ed.getPath() == null || path == null)
2140      return false;
2141    if (path.equals(ed.getPath()))
2142      return false;
2143    boolean ok = path.endsWith("[x]") && ed.getPath().startsWith(path.substring(0, path.length()-3));
2144    return ok;
2145  }
2146
2147
2148  private boolean diffsConstrainTypes(List<ElementDefinition> diffMatches, String cPath, List<TypeSlice> typeList) {
2149//    if (diffMatches.size() < 2)
2150    //      return false;
2151    String p = diffMatches.get(0).getPath();
2152    if (!p.endsWith("[x]") && !cPath.endsWith("[x]"))
2153      return false;
2154    typeList.clear();
2155    String rn = tail(cPath);
2156    rn = rn.substring(0, rn.length()-3);
2157    for (int i = 0; i < diffMatches.size(); i++) {
2158      ElementDefinition ed = diffMatches.get(i);
2159      String n = tail(ed.getPath());
2160      if (!n.startsWith(rn))
2161        return false;
2162      String s = n.substring(rn.length());
2163      if (!s.contains(".")) {
2164        if (ed.hasSliceName() && ed.getType().size() == 1) {
2165          typeList.add(new TypeSlice(ed, ed.getTypeFirstRep().getWorkingCode()));
2166        } else if (ed.hasSliceName() && ed.getType().size() == 0) {
2167          if (isDataType(s)) {
2168            typeList.add(new TypeSlice(ed, s));
2169          } else if (isPrimitive(Utilities.uncapitalize(s))) {
2170            typeList.add(new TypeSlice(ed, Utilities.uncapitalize(s)));
2171          } else {
2172            String tn = ed.getSliceName().substring(n.length());
2173            if (isDataType(tn)) {
2174              typeList.add(new TypeSlice(ed, tn));
2175            } else if (isPrimitive(Utilities.uncapitalize(tn))) {
2176              typeList.add(new TypeSlice(ed, Utilities.uncapitalize(tn)));
2177            }
2178          }
2179        } else if (!ed.hasSliceName() && !s.equals("[x]")) {
2180          if (isDataType(s))
2181            typeList.add(new TypeSlice(ed, s));
2182          else if (isConstrainedDataType(s))
2183            typeList.add(new TypeSlice(ed, baseType(s)));
2184          else if (isPrimitive(Utilities.uncapitalize(s)))
2185            typeList.add(new TypeSlice(ed, Utilities.uncapitalize(s)));
2186        } else if (!ed.hasSliceName() && s.equals("[x]"))
2187            typeList.add(new TypeSlice(ed, null));
2188          }
2189        }
2190    return true;
2191  }
2192
2193
2194  private List<ElementRedirection> redirectorStack(List<ElementRedirection> redirector, ElementDefinition outcome, String path) {
2195    List<ElementRedirection> result = new ArrayList<ElementRedirection>();
2196    result.addAll(redirector);
2197    result.add(new ElementRedirection(outcome, path));
2198    return result;
2199  }
2200
2201
2202  private List<TypeRefComponent> getByTypeName(List<TypeRefComponent> type, String t) {
2203    List<TypeRefComponent> res = new ArrayList<TypeRefComponent>();
2204    for (TypeRefComponent tr : type) {
2205      if (t.equals(tr.getWorkingCode()))
2206          res.add(tr);
2207    }
2208    return res;
2209  }
2210
2211
2212  private void replaceFromContentReference(ElementDefinition outcome, ElementDefinition tgt) {
2213    outcome.setContentReference(null);
2214    outcome.getType().clear(); // though it should be clear anyway
2215    outcome.getType().addAll(tgt.getType());    
2216  }
2217
2218
2219  private boolean baseWalksInto(List<ElementDefinition> elements, int cursor) {
2220    if (cursor >= elements.size())
2221      return false;
2222    String path = elements.get(cursor).getPath();
2223    String prevPath = elements.get(cursor - 1).getPath();
2224    return path.startsWith(prevPath + ".");
2225  }
2226
2227
2228  private ElementDefinition fillOutFromBase(ElementDefinition profile, ElementDefinition usage) throws FHIRFormatError {
2229    ElementDefinition res = profile.copy();
2230    if (!res.hasSliceName())
2231      res.setSliceName(usage.getSliceName());
2232    if (!res.hasLabel())
2233      res.setLabel(usage.getLabel());
2234    for (Coding c : usage.getCode())
2235      if (!res.hasCode(c))
2236        res.addCode(c);
2237    
2238    if (!res.hasDefinition())
2239      res.setDefinition(usage.getDefinition());
2240    if (!res.hasShort() && usage.hasShort())
2241      res.setShort(usage.getShort());
2242    if (!res.hasComment() && usage.hasComment())
2243      res.setComment(usage.getComment());
2244    if (!res.hasRequirements() && usage.hasRequirements())
2245      res.setRequirements(usage.getRequirements());
2246    for (StringType c : usage.getAlias())
2247      if (!res.hasAlias(c.getValue()))
2248        res.addAlias(c.getValue());
2249    if (!res.hasMin() && usage.hasMin())
2250      res.setMin(usage.getMin());
2251    if (!res.hasMax() && usage.hasMax())
2252      res.setMax(usage.getMax());
2253     
2254    if (!res.hasFixed() && usage.hasFixed())
2255      res.setFixed(usage.getFixed());
2256    if (!res.hasPattern() && usage.hasPattern())
2257      res.setPattern(usage.getPattern());
2258    if (!res.hasExample() && usage.hasExample())
2259      res.setExample(usage.getExample());
2260    if (!res.hasMinValue() && usage.hasMinValue())
2261      res.setMinValue(usage.getMinValue());
2262    if (!res.hasMaxValue() && usage.hasMaxValue())
2263      res.setMaxValue(usage.getMaxValue());     
2264    if (!res.hasMaxLength() && usage.hasMaxLength())
2265      res.setMaxLength(usage.getMaxLength());
2266    if (!res.hasMustSupport() && usage.hasMustSupport())
2267      res.setMustSupport(usage.getMustSupport());
2268    if (!res.hasBinding() && usage.hasBinding())
2269      res.setBinding(usage.getBinding().copy());
2270    for (ElementDefinitionConstraintComponent c : usage.getConstraint())
2271      if (!res.hasConstraint(c.getKey()))
2272        res.addConstraint(c);
2273    for (Extension e : usage.getExtension()) {
2274      if (!res.hasExtension(e.getUrl()))
2275        res.addExtension(e.copy());
2276    }
2277    
2278    return res;
2279  }
2280
2281
2282  private boolean checkExtensionDoco(ElementDefinition base) {
2283    // see task 3970. For an extension, there's no point copying across all the underlying definitional stuff
2284    boolean isExtension = (base.getPath().equals("Extension") || base.getPath().endsWith(".extension") || base.getPath().endsWith(".modifierExtension")) &&
2285          (!base.hasBase() || !"II.extension".equals(base.getBase().getPath()));
2286    if (isExtension) {
2287      base.setDefinition("An Extension");
2288      base.setShort("Extension");
2289      base.setCommentElement(null);
2290      base.setRequirementsElement(null);
2291      base.getAlias().clear();
2292      base.getMapping().clear();
2293    }
2294    return isExtension;
2295  }
2296
2297
2298  private String pathTail(List<ElementDefinition> diffMatches, int i) {
2299    
2300    ElementDefinition d = diffMatches.get(i);
2301    String s = d.getPath().contains(".") ? d.getPath().substring(d.getPath().lastIndexOf(".")+1) : d.getPath();
2302    return "."+s + (d.hasType() && d.getType().get(0).hasProfile() ? "["+d.getType().get(0).getProfile()+"]" : "");
2303  }
2304
2305
2306  private void markDerived(ElementDefinition outcome) {
2307    for (ElementDefinitionConstraintComponent inv : outcome.getConstraint())
2308      inv.setUserData(IS_DERIVED, true);
2309  }
2310
2311
2312  public static String summarizeSlicing(ElementDefinitionSlicingComponent slice) {
2313    StringBuilder b = new StringBuilder();
2314    boolean first = true;
2315    for (ElementDefinitionSlicingDiscriminatorComponent d : slice.getDiscriminator()) {
2316      if (first)
2317        first = false;
2318      else
2319        b.append(", ");
2320      b.append(d.getType().toCode()+":"+d.getPath());
2321    }
2322    b.append(" (");
2323    if (slice.hasOrdered())
2324      b.append(slice.getOrdered() ? "ordered" : "unordered");
2325    b.append("/");
2326    if (slice.hasRules())
2327      b.append(slice.getRules().toCode());
2328    b.append(")");
2329    if (slice.hasDescription()) {
2330      b.append(" \"");
2331      b.append(slice.getDescription());
2332      b.append("\"");
2333    }
2334    return b.toString();
2335  }
2336
2337
2338  private void updateFromBase(ElementDefinition derived, ElementDefinition base) {
2339    if (base.hasBase()) {
2340      if (!derived.hasBase())
2341        derived.setBase(new ElementDefinitionBaseComponent());
2342      derived.getBase().setPath(base.getBase().getPath());
2343      derived.getBase().setMin(base.getBase().getMin());
2344      derived.getBase().setMax(base.getBase().getMax());
2345    } else {
2346      if (!derived.hasBase())
2347        derived.setBase(new ElementDefinitionBaseComponent());
2348      derived.getBase().setPath(base.getPath());
2349      derived.getBase().setMin(base.getMin());
2350      derived.getBase().setMax(base.getMax());
2351    }
2352  }
2353
2354
2355  private boolean pathStartsWith(String p1, String p2) {
2356    return p1.startsWith(p2) || (p2.endsWith("[x].") && p1.startsWith(p2.substring(0, p2.length()-4)));
2357  }
2358
2359  private boolean pathMatches(String p1, String p2) {
2360    return p1.equals(p2) || (p2.endsWith("[x]") && p1.startsWith(p2.substring(0, p2.length()-3)) && !p1.substring(p2.length()-3).contains("."));
2361  }
2362
2363
2364  private String fixedPathSource(String contextPath, String pathSimple, List<ElementRedirection> redirector) {
2365    if (contextPath == null)
2366      return pathSimple;
2367//    String ptail = pathSimple.substring(contextPath.length() + 1);
2368    if (redirector.size() > 0) {
2369      String ptail = null;
2370      if (contextPath.length() >= pathSimple.length()) {
2371        ptail = pathSimple.substring(pathSimple.indexOf(".")+1);
2372      } else {
2373        ptail = pathSimple.substring(contextPath.length()+1);
2374      }
2375      return redirector.get(redirector.size()-1).getPath()+"."+ptail;
2376//      return contextPath+"."+tail(redirector.getPath())+"."+ptail.substring(ptail.indexOf(".")+1);
2377    } else {
2378      String ptail = pathSimple.substring(pathSimple.indexOf(".")+1);
2379      return contextPath+"."+ptail;
2380    }
2381  }
2382  
2383  private String fixedPathDest(String contextPath, String pathSimple, List<ElementRedirection> redirector, String redirectSource) {
2384    String s;
2385    if (contextPath == null)
2386      s = pathSimple;
2387    else {
2388      if (redirector.size() > 0) {
2389        String ptail = null;
2390        if (redirectSource.length() >= pathSimple.length()) {
2391          ptail = pathSimple.substring(pathSimple.indexOf(".")+1);
2392        } else {
2393          ptail = pathSimple.substring(redirectSource.length()+1);
2394        }
2395  //      ptail = ptail.substring(ptail.indexOf(".")+1);
2396        s = contextPath+"."+/*tail(redirector.getPath())+"."+*/ptail;
2397      } else {
2398        String ptail = pathSimple.substring(pathSimple.indexOf(".")+1);
2399        s = contextPath+"."+ptail;
2400      }
2401    }
2402    return s;
2403  }  
2404
2405  private StructureDefinition getProfileForDataType(TypeRefComponent type)  {
2406    StructureDefinition sd = null;
2407    if (type.hasProfile()) {
2408      sd = context.fetchResource(StructureDefinition.class, type.getProfile().get(0).getValue());
2409      if (sd == null)
2410        System.out.println("Failed to find referenced profile: " + type.getProfile());
2411    }
2412    if (sd == null)
2413      sd = context.fetchTypeDefinition(type.getWorkingCode());
2414    if (sd == null)
2415      System.out.println("XX: failed to find profle for type: " + type.getWorkingCode()); // debug GJM
2416    return sd;
2417  }
2418
2419  private StructureDefinition getProfileForDataType(String type)  {
2420    StructureDefinition sd = context.fetchTypeDefinition(type);
2421    if (sd == null)
2422      System.out.println("XX: failed to find profle for type: " + type); // debug GJM
2423    return sd;
2424  }
2425
2426
2427  public static String typeCode(List<TypeRefComponent> types) {
2428    StringBuilder b = new StringBuilder();
2429    boolean first = true;
2430    for (TypeRefComponent type : types) {
2431      if (first) first = false; else b.append(", ");
2432      b.append(type.getWorkingCode());
2433      if (type.hasTargetProfile())
2434        b.append("{"+type.getTargetProfile()+"}");
2435      else if (type.hasProfile())
2436        b.append("{"+type.getProfile()+"}");
2437    }
2438    return b.toString();
2439  }
2440
2441
2442  private boolean isDataType(List<TypeRefComponent> types) {
2443    if (types.isEmpty())
2444      return false;
2445    for (TypeRefComponent type : types) {
2446      String t = type.getWorkingCode();
2447      if (!isDataType(t) && !isPrimitive(t))
2448        return false;
2449    }
2450    return true;
2451  }
2452
2453
2454  /**
2455   * Finds internal references in an Element's Binding and StructureDefinition references (in TypeRef) and bases them on the given url
2456   * @param url - the base url to use to turn internal references into absolute references
2457   * @param element - the Element to update
2458   * @return - the updated Element
2459   */
2460  private ElementDefinition updateURLs(String url, String webUrl, ElementDefinition element) {
2461    if (element != null) {
2462      ElementDefinition defn = element;
2463      if (defn.hasBinding() && defn.getBinding().hasValueSet() && defn.getBinding().getValueSet().startsWith("#"))
2464        defn.getBinding().setValueSet(url+defn.getBinding().getValueSet());
2465      for (TypeRefComponent t : defn.getType()) {
2466        for (UriType u : t.getProfile()) {
2467          if (u.getValue().startsWith("#"))
2468            u.setValue(url+t.getProfile());
2469        }
2470        for (UriType u : t.getTargetProfile()) {
2471          if (u.getValue().startsWith("#"))
2472            u.setValue(url+t.getTargetProfile());
2473        }
2474      }
2475      if (webUrl != null) {
2476        // also, must touch up the markdown
2477        if (element.hasDefinition())
2478          element.setDefinition(processRelativeUrls(element.getDefinition(), webUrl, baseSpecUrl(), context.getResourceNames(), masterSourceFileNames));
2479        if (element.hasComment())
2480          element.setComment(processRelativeUrls(element.getComment(), webUrl, baseSpecUrl(), context.getResourceNames(), masterSourceFileNames));
2481        if (element.hasRequirements())
2482          element.setRequirements(processRelativeUrls(element.getRequirements(), webUrl, baseSpecUrl(), context.getResourceNames(), masterSourceFileNames));
2483        if (element.hasMeaningWhenMissing())
2484          element.setMeaningWhenMissing(processRelativeUrls(element.getMeaningWhenMissing(), webUrl, baseSpecUrl(), context.getResourceNames(), masterSourceFileNames));
2485      }
2486    }
2487    return element;
2488  }
2489
2490  public static String processRelativeUrls(String markdown, String webUrl, String basePath, List<String> resourceNames, Set<String> filenames) {
2491    StringBuilder b = new StringBuilder();
2492    int i = 0;
2493    while (i < markdown.length()) {
2494      if (i < markdown.length()-3 && markdown.substring(i, i+2).equals("](")) {
2495        int j = i + 2;
2496        while (j < markdown.length() && markdown.charAt(j) != ')')
2497          j++;
2498        if (j < markdown.length()) {
2499          String url = markdown.substring(i+2, j);
2500          if (!Utilities.isAbsoluteUrl(url) && !url.startsWith("..")) {
2501            // 
2502            // In principle, relative URLs are supposed to be converted to absolute URLs in snapshots. 
2503            // that's what this code is doing. 
2504            // 
2505            // But that hasn't always happened and there's packages out there where the snapshots 
2506            // contain relative references that actually are references to the main specification 
2507            // 
2508            // This code is trying to guess which relative references are actually to the
2509            // base specification.
2510            // 
2511            if (isLikelySourceURLReference(url, resourceNames, filenames)) {
2512              b.append("](");
2513              b.append(basePath);
2514              i = i + 1;
2515            } else {
2516              b.append("](");
2517              // disabled 7-Dec 2021 GDG - we don't want to fool with relative URLs at all? 
2518              // b.append(webUrl);
2519              i = i + 1;
2520            }
2521          } else
2522            b.append(markdown.charAt(i));
2523        } else 
2524          b.append(markdown.charAt(i));
2525      } else {
2526        b.append(markdown.charAt(i));
2527      }
2528      i++;
2529    }
2530    return b.toString();
2531  }
2532
2533
2534  public static boolean isLikelySourceURLReference(String url, List<String> resourceNames, Set<String> filenames) {
2535    if (resourceNames != null) {
2536      for (String n : resourceNames) {
2537        if (url.startsWith(n.toLowerCase()+".html")) {
2538          return true;
2539        }
2540        if (url.startsWith(n.toLowerCase()+"-definitions.html")) {
2541          return true;
2542        }
2543      }
2544    }
2545    if (filenames != null) {
2546      for (String n : filenames) {
2547        if (url.startsWith(n.toLowerCase())) {
2548          return true;
2549        }
2550      }
2551    }
2552    return 
2553        url.startsWith("extensibility.html") || 
2554        url.startsWith("terminologies.html") || 
2555        url.startsWith("observation.html") || 
2556        url.startsWith("codesystem.html") || 
2557        url.startsWith("fhirpath.html") || 
2558        url.startsWith("datatypes.html") || 
2559        url.startsWith("operations.html") || 
2560        url.startsWith("resource.html") || 
2561        url.startsWith("elementdefinition.html") ||
2562        url.startsWith("element-definitions.html") ||
2563        url.startsWith("snomedct.html") ||
2564        url.startsWith("loinc.html") ||
2565        url.startsWith("http.html") ||
2566        url.startsWith("references") ||
2567        url.startsWith("narrative.html") || 
2568        url.startsWith("search.html") ||
2569        url.startsWith("patient-operation-match.html") ||
2570        (url.startsWith("extension-") && url.contains(".html")) || 
2571        url.startsWith("resource-definitions.html");
2572  }
2573
2574  private String baseSpecUrl() {
2575    if (VersionUtilities.isR5Ver(context.getVersion())) {
2576      return "http://build.fhir.org/";
2577    }
2578    if (VersionUtilities.isR4Ver(context.getVersion())) {
2579      return "http://hl7.org/fhir/R4/";
2580    }
2581    if (VersionUtilities.isR3Ver(context.getVersion())) {
2582      return "http://hl7.org/fhir/STU3/";
2583    }
2584    if (VersionUtilities.isR2BVer(context.getVersion())) {
2585      return "http://hl7.org/fhir/2016May/";
2586    }
2587    if (VersionUtilities.isR2Ver(context.getVersion())) {
2588      return "http://hl7.org/fhir/DSTU2/";
2589    }
2590    if (VersionUtilities.isR4BVer(context.getVersion())) {
2591      return "http://hl7.org/fhir/2021Mar/";
2592    }
2593    return "";
2594  }
2595
2596  private List<ElementDefinition> getSiblings(List<ElementDefinition> list, ElementDefinition current) {
2597    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
2598    String path = current.getPath();
2599    int cursor = list.indexOf(current)+1;
2600    while (cursor < list.size() && list.get(cursor).getPath().length() >= path.length()) {
2601      if (pathMatches(list.get(cursor).getPath(), path))
2602        result.add(list.get(cursor));
2603      cursor++;
2604    }
2605    return result;
2606  }
2607
2608  private void updateFromSlicing(ElementDefinitionSlicingComponent dst, ElementDefinitionSlicingComponent src) {
2609    if (src.hasOrderedElement())
2610      dst.setOrderedElement(src.getOrderedElement().copy());
2611    if (src.hasDiscriminator()) {
2612      //    dst.getDiscriminator().addAll(src.getDiscriminator());  Can't use addAll because it uses object equality, not string equality
2613      for (ElementDefinitionSlicingDiscriminatorComponent s : src.getDiscriminator()) {
2614        boolean found = false;
2615        for (ElementDefinitionSlicingDiscriminatorComponent d : dst.getDiscriminator()) {
2616          if (matches(d, s)) {
2617            found = true;
2618            break;
2619          }
2620        }
2621        if (!found)
2622          dst.getDiscriminator().add(s);
2623      }
2624    }
2625    if (src.hasRulesElement())
2626      dst.setRulesElement(src.getRulesElement().copy());
2627  }
2628
2629  private boolean orderMatches(BooleanType diff, BooleanType base) {
2630    return (diff == null) || (base == null) || (diff.getValue() == base.getValue());
2631  }
2632
2633  private boolean discriminatorMatches(List<ElementDefinitionSlicingDiscriminatorComponent> diff, List<ElementDefinitionSlicingDiscriminatorComponent> base) {
2634    if (diff.isEmpty() || base.isEmpty())
2635        return true;
2636    if (diff.size() != base.size())
2637        return false;
2638    for (int i = 0; i < diff.size(); i++)
2639        if (!matches(diff.get(i), base.get(i)))
2640                return false;
2641    return true;
2642  }
2643
2644  private boolean matches(ElementDefinitionSlicingDiscriminatorComponent c1, ElementDefinitionSlicingDiscriminatorComponent c2) {
2645    return c1.getType().equals(c2.getType()) && c1.getPath().equals(c2.getPath());
2646  }
2647
2648
2649  private boolean ruleMatches(SlicingRules diff, SlicingRules base) {
2650    return (diff == null) || (base == null) || (diff == base) || (base == SlicingRules.OPEN) ||
2651        ((diff == SlicingRules.OPENATEND && base == SlicingRules.CLOSED));
2652  }
2653
2654  private boolean isSlicedToOneOnly(ElementDefinition e) {
2655    return (e.hasSlicing() && e.hasMaxElement() && e.getMax().equals("1"));
2656  }
2657
2658  private ElementDefinitionSlicingComponent makeExtensionSlicing() {
2659        ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent();
2660    slice.addDiscriminator().setPath("url").setType(DiscriminatorType.VALUE);
2661    slice.setOrdered(false);
2662    slice.setRules(SlicingRules.OPEN);
2663    return slice;
2664  }
2665
2666  private boolean isExtension(ElementDefinition currentBase) {
2667    return currentBase.getPath().endsWith(".extension") || currentBase.getPath().endsWith(".modifierExtension");
2668  }
2669
2670  private boolean hasInnerDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, List<ElementDefinition> base, boolean allowSlices) throws DefinitionException {
2671    end = Math.min(context.getElement().size(), end);
2672    start = Math.max(0,  start);
2673    
2674    for (int i = start; i <= end; i++) {
2675      ElementDefinition ed = context.getElement().get(i);
2676      String statedPath = ed.getPath();
2677      if (!allowSlices && statedPath.equals(path) && ed.hasSliceName()) {
2678        return false;
2679      } else if (statedPath.startsWith(path+".")) {
2680        return true;
2681      } else if (path.endsWith("[x]") && statedPath.startsWith(path.substring(0, path.length() -3))) {
2682        return true;
2683      } else if (i != start && !allowSlices && !statedPath.startsWith(path+".")) {
2684        break;
2685      } else if (i != start && allowSlices && !statedPath.startsWith(path)) {
2686        break;
2687      }
2688    }
2689    return false;
2690  }
2691
2692  private List<ElementDefinition> getDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, String profileName) throws DefinitionException {
2693    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
2694    String[] p = path.split("\\.");
2695    for (int i = start; i <= end; i++) {
2696      String statedPath = context.getElement().get(i).getPath();
2697      String[] sp = statedPath.split("\\.");
2698      boolean ok = sp.length == p.length;
2699      for (int j = 0; j < p.length; j++) {
2700        ok = ok && sp.length > j && (p[j].equals(sp[j]) || isSameBase(p[j], sp[j]));
2701      }
2702// don't need this debug check - everything is ok
2703//      if (ok != (statedPath.equals(path) || (path.endsWith("[x]") && statedPath.length() > path.length() - 2 &&
2704//            statedPath.substring(0, path.length()-3).equals(path.substring(0, path.length()-3)) &&
2705//            (statedPath.length() < path.length() || !statedPath.substring(path.length()).contains("."))))) {
2706//        System.out.println("mismatch in paths: "+statedPath +" vs " +path);
2707//      }
2708      if (ok) {
2709        /*
2710         * Commenting this out because it raises warnings when profiling inherited elements.  For example,
2711         * Error: unknown element 'Bundle.meta.profile' (or it is out of order) in profile ... (looking for 'Bundle.entry')
2712         * Not sure we have enough information here to do the check properly.  Might be better done when we're sorting the profile?
2713
2714        if (i != start && result.isEmpty() && !path.startsWith(context.getElement().get(start).getPath()))
2715          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));
2716
2717         */
2718        result.add(context.getElement().get(i));
2719      }
2720    }
2721    return result;
2722  }
2723
2724
2725  public boolean isSameBase(String p, String sp) {
2726    return (p.endsWith("[x]") && sp.startsWith(p.substring(0, p.length()-3))) || (sp.endsWith("[x]") && p.startsWith(sp.substring(0, sp.length()-3))) ;
2727  }
2728
2729  private int findEndOfElement(StructureDefinitionDifferentialComponent context, int cursor) {
2730            int result = cursor;
2731            if (cursor >= context.getElement().size())
2732              return result;
2733            String path = context.getElement().get(cursor).getPath()+".";
2734            while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path))
2735              result++;
2736            return result;
2737          }
2738
2739  private int findEndOfElement(StructureDefinitionSnapshotComponent context, int cursor) {
2740            int result = cursor;
2741            String path = context.getElement().get(cursor).getPath()+".";
2742            while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path))
2743              result++;
2744            return result;
2745          }
2746
2747  private boolean unbounded(ElementDefinition definition) {
2748    StringType max = definition.getMaxElement();
2749    if (max == null)
2750      return false; // this is not valid
2751    if (max.getValue().equals("1"))
2752      return false;
2753    if (max.getValue().equals("0"))
2754      return false;
2755    return true;
2756  }
2757
2758  private void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, boolean trimDifferential, String purl, StructureDefinition srcSD) throws DefinitionException, FHIRException {
2759    source.setUserData(GENERATED_IN_SNAPSHOT, dest);
2760    // we start with a clone of the base profile ('dest') and we copy from the profile ('source')
2761    // over the top for anything the source has
2762    ElementDefinition base = dest;
2763    ElementDefinition derived = source;
2764    derived.setUserData(DERIVATION_POINTER, base);
2765    boolean isExtension = checkExtensionDoco(base);
2766
2767
2768    // Before applying changes, apply them to what's in the profile
2769    StructureDefinition profile = null;
2770    if (base.hasSliceName())
2771      profile = base.getType().size() == 1 && base.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, base.getTypeFirstRep().getProfile().get(0).getValue()) : null;
2772    if (profile==null)
2773      profile = source.getType().size() == 1 && source.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, source.getTypeFirstRep().getProfile().get(0).getValue()) : null;
2774    if (profile != null) {
2775      ElementDefinition e = profile.getSnapshot().getElement().get(0);
2776      base.setDefinition(e.getDefinition());
2777      base.setShort(e.getShort());
2778      if (e.hasCommentElement())
2779        base.setCommentElement(e.getCommentElement());
2780      if (e.hasRequirementsElement())
2781        base.setRequirementsElement(e.getRequirementsElement());
2782      base.getAlias().clear();
2783      base.getAlias().addAll(e.getAlias());
2784      base.getMapping().clear();
2785      base.getMapping().addAll(e.getMapping());
2786    } 
2787    if (derived != null) {
2788      if (derived.hasSliceName()) {
2789        base.setSliceName(derived.getSliceName());
2790      }
2791      
2792      if (derived.hasShortElement()) {
2793        if (!Base.compareDeep(derived.getShortElement(), base.getShortElement(), false))
2794          base.setShortElement(derived.getShortElement().copy());
2795        else if (trimDifferential)
2796          derived.setShortElement(null);
2797        else if (derived.hasShortElement())
2798          derived.getShortElement().setUserData(DERIVATION_EQUALS, true);
2799      }
2800
2801      if (derived.hasDefinitionElement()) {
2802        if (derived.getDefinition().startsWith("..."))
2803          base.setDefinition(base.getDefinition()+"\r\n"+derived.getDefinition().substring(3));
2804        else if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false))
2805          base.setDefinitionElement(derived.getDefinitionElement().copy());
2806        else if (trimDifferential)
2807          derived.setDefinitionElement(null);
2808        else if (derived.hasDefinitionElement())
2809          derived.getDefinitionElement().setUserData(DERIVATION_EQUALS, true);
2810      }
2811
2812      if (derived.hasCommentElement()) {
2813        if (derived.getComment().startsWith("..."))
2814          base.setComment(base.getComment()+"\r\n"+derived.getComment().substring(3));
2815        else if (derived.hasCommentElement()!= base.hasCommentElement() || !Base.compareDeep(derived.getCommentElement(), base.getCommentElement(), false))
2816          base.setCommentElement(derived.getCommentElement().copy());
2817        else if (trimDifferential)
2818          base.setCommentElement(derived.getCommentElement().copy());
2819        else if (derived.hasCommentElement())
2820          derived.getCommentElement().setUserData(DERIVATION_EQUALS, true);
2821      }
2822
2823      if (derived.hasLabelElement()) {
2824        if (derived.getLabel().startsWith("..."))
2825          base.setLabel(base.getLabel()+"\r\n"+derived.getLabel().substring(3));
2826        else if (!base.hasLabelElement() || !Base.compareDeep(derived.getLabelElement(), base.getLabelElement(), false))
2827          base.setLabelElement(derived.getLabelElement().copy());
2828        else if (trimDifferential)
2829          base.setLabelElement(derived.getLabelElement().copy());
2830        else if (derived.hasLabelElement())
2831          derived.getLabelElement().setUserData(DERIVATION_EQUALS, true);
2832      }
2833
2834      if (derived.hasRequirementsElement()) {
2835        if (derived.getRequirements().startsWith("..."))
2836          base.setRequirements(base.getRequirements()+"\r\n"+derived.getRequirements().substring(3));
2837        else if (!base.hasRequirementsElement() || !Base.compareDeep(derived.getRequirementsElement(), base.getRequirementsElement(), false))
2838          base.setRequirementsElement(derived.getRequirementsElement().copy());
2839        else if (trimDifferential)
2840          base.setRequirementsElement(derived.getRequirementsElement().copy());
2841        else if (derived.hasRequirementsElement())
2842          derived.getRequirementsElement().setUserData(DERIVATION_EQUALS, true);
2843      }
2844      // sdf-9
2845      if (derived.hasRequirements() && !base.getPath().contains("."))
2846        derived.setRequirements(null);
2847      if (base.hasRequirements() && !base.getPath().contains("."))
2848        base.setRequirements(null);
2849
2850      if (derived.hasAlias()) {
2851        if (!Base.compareDeep(derived.getAlias(), base.getAlias(), false))
2852          for (StringType s : derived.getAlias()) {
2853            if (!base.hasAlias(s.getValue()))
2854              base.getAlias().add(s.copy());
2855          }
2856        else if (trimDifferential)
2857          derived.getAlias().clear();
2858        else
2859          for (StringType t : derived.getAlias())
2860            t.setUserData(DERIVATION_EQUALS, true);
2861      }
2862
2863      if (derived.hasMinElement()) {
2864        if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) {
2865          if (derived.getMin() < base.getMin() && !derived.hasSliceName()) // in a slice, minimum cardinality rules do not apply
2866            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));
2867          base.setMinElement(derived.getMinElement().copy());
2868        } else if (trimDifferential)
2869          derived.setMinElement(null);
2870        else
2871          derived.getMinElement().setUserData(DERIVATION_EQUALS, true);
2872      }
2873
2874      if (derived.hasMaxElement()) {
2875        if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) {
2876          if (isLargerMax(derived.getMax(), base.getMax()))
2877            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));
2878          base.setMaxElement(derived.getMaxElement().copy());
2879        } else if (trimDifferential)
2880          derived.setMaxElement(null);
2881        else
2882          derived.getMaxElement().setUserData(DERIVATION_EQUALS, true);
2883      }
2884
2885      if (derived.hasFixed()) {
2886        if (!Base.compareDeep(derived.getFixed(), base.getFixed(), true)) {
2887          base.setFixed(derived.getFixed().copy());
2888        } else if (trimDifferential)
2889          derived.setFixed(null);
2890        else
2891          derived.getFixed().setUserData(DERIVATION_EQUALS, true);
2892      }
2893
2894      if (derived.hasPattern()) {
2895        if (!Base.compareDeep(derived.getPattern(), base.getPattern(), false)) {
2896          base.setPattern(derived.getPattern().copy());
2897        } else
2898          if (trimDifferential)
2899            derived.setPattern(null);
2900          else
2901            derived.getPattern().setUserData(DERIVATION_EQUALS, true);
2902      }
2903
2904      for (ElementDefinitionExampleComponent ex : derived.getExample()) {
2905        boolean found = false;
2906        for (ElementDefinitionExampleComponent exS : base.getExample())
2907          if (Base.compareDeep(ex, exS, false))
2908            found = true;
2909        if (!found)
2910          base.addExample(ex.copy());
2911        else if (trimDifferential)
2912          derived.getExample().remove(ex);
2913        else
2914          ex.setUserData(DERIVATION_EQUALS, true);
2915      }
2916
2917      if (derived.hasMaxLengthElement()) {
2918        if (!Base.compareDeep(derived.getMaxLengthElement(), base.getMaxLengthElement(), false))
2919          base.setMaxLengthElement(derived.getMaxLengthElement().copy());
2920        else if (trimDifferential)
2921          derived.setMaxLengthElement(null);
2922        else
2923          derived.getMaxLengthElement().setUserData(DERIVATION_EQUALS, true);
2924      }
2925  
2926      if (derived.hasMaxValue()) {
2927        if (!Base.compareDeep(derived.getMaxValue(), base.getMaxValue(), false))
2928          base.setMaxValue(derived.getMaxValue().copy());
2929        else if (trimDifferential)
2930          derived.setMaxValue(null);
2931        else
2932          derived.getMaxValue().setUserData(DERIVATION_EQUALS, true);
2933      }
2934  
2935      if (derived.hasMinValue()) {
2936        if (!Base.compareDeep(derived.getMinValue(), base.getMinValue(), false))
2937          base.setMinValue(derived.getMinValue().copy());
2938        else if (trimDifferential)
2939          derived.setMinValue(null);
2940        else
2941          derived.getMinValue().setUserData(DERIVATION_EQUALS, true);
2942      }
2943
2944      // todo: what to do about conditions?
2945      // condition : id 0..*
2946
2947      if (derived.hasMustSupportElement()) {
2948        if (!(base.hasMustSupportElement() && Base.compareDeep(derived.getMustSupportElement(), base.getMustSupportElement(), false))) {
2949          if (base.hasMustSupport() && base.getMustSupport() && !derived.getMustSupport()) {
2950            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));
2951          }
2952          base.setMustSupportElement(derived.getMustSupportElement().copy());
2953        } else if (trimDifferential)
2954          derived.setMustSupportElement(null);
2955        else
2956          derived.getMustSupportElement().setUserData(DERIVATION_EQUALS, true);
2957      }
2958
2959
2960      // profiles cannot change : isModifier, defaultValue, meaningWhenMissing
2961      // but extensions can change isModifier
2962      if (isExtension) {
2963        if (derived.hasIsModifierElement() && !(base.hasIsModifierElement() && Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false)))
2964          base.setIsModifierElement(derived.getIsModifierElement().copy());
2965        else if (trimDifferential)
2966          derived.setIsModifierElement(null);
2967        else if (derived.hasIsModifierElement())
2968          derived.getIsModifierElement().setUserData(DERIVATION_EQUALS, true);
2969        if (derived.hasIsModifierReasonElement() && !(base.hasIsModifierReasonElement() && Base.compareDeep(derived.getIsModifierReasonElement(), base.getIsModifierReasonElement(), false)))
2970          base.setIsModifierReasonElement(derived.getIsModifierReasonElement().copy());
2971        else if (trimDifferential)
2972          derived.setIsModifierReasonElement(null);
2973        else if (derived.hasIsModifierReasonElement())
2974          derived.getIsModifierReasonElement().setUserData(DERIVATION_EQUALS, true);
2975      }
2976
2977      if (derived.hasBinding()) {
2978        if (!base.hasBinding() || !Base.compareDeep(derived.getBinding(), base.getBinding(), false)) {
2979          if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && derived.getBinding().getStrength() != BindingStrength.REQUIRED)
2980            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));
2981//            throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode());
2982          else if (base.hasBinding() && derived.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && base.getBinding().hasValueSet() && derived.getBinding().hasValueSet()) {
2983            ValueSet baseVs = context.fetchResource(ValueSet.class, base.getBinding().getValueSet());
2984            ValueSet contextVs = context.fetchResource(ValueSet.class, derived.getBinding().getValueSet());
2985            if (baseVs == null) {
2986              messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING));
2987            } else if (contextVs == null) {
2988              messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING));
2989            } else {
2990              ValueSetExpansionOutcome expBase = context.expandVS(baseVs, true, false);
2991              ValueSetExpansionOutcome expDerived = context.expandVS(contextVs, true, false);
2992              if (expBase.getValueset() == null)
2993                messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING));
2994              else if (expDerived.getValueset() == null)
2995                messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING));
2996              else if (ToolingExtensions.hasExtension(expBase.getValueset().getExpansion(), ToolingExtensions.EXT_EXP_TOOCOSTLY))
2997                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));
2998              else if (!isSubset(expBase.getValueset(), expDerived.getValueset()))
2999                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));
3000            }
3001          }
3002          ElementDefinitionBindingComponent d = derived.getBinding();
3003          ElementDefinitionBindingComponent nb = base.getBinding().copy();
3004          if (!COPY_BINDING_EXTENSIONS) {
3005            nb.getExtension().clear();
3006          }
3007          nb.setDescription(null);
3008          nb.getExtension().addAll(d.getExtension());
3009          if (d.hasStrength()) {
3010            nb.setStrength(d.getStrength());
3011          }
3012          if (d.hasDescription()) {
3013            nb.setDescription(d.getDescription());
3014          }
3015          if (d.hasValueSet()) {
3016            nb.setValueSet(d.getValueSet());
3017          }
3018          base.setBinding(nb);
3019        } else if (trimDifferential)
3020          derived.setBinding(null);
3021        else
3022          derived.getBinding().setUserData(DERIVATION_EQUALS, true);
3023      } // else if (base.hasBinding() && doesn't have bindable type )
3024        //  base
3025
3026      if (derived.hasIsSummaryElement()) {
3027        if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false)) {
3028          if (base.hasIsSummary() && !context.getVersion().equals("1.4.0")) // work around a known issue with some 1.4.0 cosntraints
3029            throw new Error(context.formatMessage(I18nConstants.ERROR_IN_PROFILE__AT__BASE_ISSUMMARY___DERIVED_ISSUMMARY__, purl, derived.getPath(), base.getIsSummaryElement().asStringValue(), derived.getIsSummaryElement().asStringValue()));
3030          base.setIsSummaryElement(derived.getIsSummaryElement().copy());
3031        } else if (trimDifferential)
3032          derived.setIsSummaryElement(null);
3033        else
3034          derived.getIsSummaryElement().setUserData(DERIVATION_EQUALS, true);
3035      }
3036
3037      if (derived.hasType()) {
3038        if (!Base.compareDeep(derived.getType(), base.getType(), false)) {
3039          if (base.hasType()) {
3040            for (TypeRefComponent ts : derived.getType()) {
3041              checkTypeDerivation(purl, srcSD, base, derived, ts);
3042            }
3043          }
3044          base.getType().clear();
3045          for (TypeRefComponent t : derived.getType()) {
3046            TypeRefComponent tt = t.copy();
3047//            tt.setUserData(DERIVATION_EQUALS, true);
3048            base.getType().add(tt);
3049          }
3050        }
3051        else if (trimDifferential)
3052          derived.getType().clear();
3053        else
3054          for (TypeRefComponent t : derived.getType())
3055            t.setUserData(DERIVATION_EQUALS, true);
3056      }
3057
3058      if (derived.hasMapping()) {
3059        // todo: mappings are not cumulative - one replaces another
3060        if (!Base.compareDeep(derived.getMapping(), base.getMapping(), false)) {
3061          for (ElementDefinitionMappingComponent s : derived.getMapping()) {
3062            boolean found = false;
3063            for (ElementDefinitionMappingComponent d : base.getMapping()) {
3064              found = found || (d.getIdentity().equals(s.getIdentity()) && d.getMap().equals(s.getMap()));
3065            }
3066            if (!found) {
3067              base.getMapping().add(s);
3068            }
3069          }
3070        }
3071        else if (trimDifferential) {
3072          derived.getMapping().clear();
3073        } else { 
3074          for (ElementDefinitionMappingComponent t : derived.getMapping()) {
3075            t.setUserData(DERIVATION_EQUALS, true);
3076          }
3077        }
3078      }
3079      for (ElementDefinitionMappingComponent m : base.getMapping()) {
3080        if (m.hasMap()) {
3081          m.setMap(m.getMap().trim());
3082        }
3083      }
3084
3085      // todo: constraints are cumulative. there is no replacing
3086      for (ElementDefinitionConstraintComponent s : base.getConstraint()) { 
3087        s.setUserData(IS_DERIVED, true);
3088        if (!s.hasSource()) {
3089          s.setSource(srcSD.getUrl());
3090        } 
3091      }
3092      if (derived.hasConstraint()) {
3093        for (ElementDefinitionConstraintComponent s : derived.getConstraint()) {
3094          if (!base.hasConstraint(s.getKey())) {
3095            ElementDefinitionConstraintComponent inv = s.copy();
3096            base.getConstraint().add(inv);
3097          }
3098        }
3099      }
3100      for (IdType id : derived.getCondition()) {
3101        if (!base.hasCondition(id)) {
3102          base.getCondition().add(id);
3103        }
3104      }
3105      
3106      // now, check that we still have a bindable type; if not, delete the binding - see task 8477
3107      if (dest.hasBinding() && !hasBindableType(dest)) {
3108        dest.setBinding(null);
3109      }
3110        
3111      // finally, we copy any extensions from source to dest
3112      for (Extension ex : derived.getExtension()) {
3113        StructureDefinition sd  = context.fetchResource(StructureDefinition.class, ex.getUrl());
3114        if (sd == null || sd.getSnapshot() == null || sd.getSnapshot().getElementFirstRep().getMax().equals("1")) {
3115          ToolingExtensions.removeExtension(dest, ex.getUrl());
3116        }
3117        dest.addExtension(ex.copy());
3118      }
3119    }
3120    if (dest.hasFixed()) {
3121      checkTypeOk(dest, dest.getFixed().fhirType(), srcSD);
3122    }
3123    if (dest.hasPattern()) {
3124      checkTypeOk(dest, dest.getPattern().fhirType(), srcSD);
3125    }
3126  }
3127
3128  public void checkTypeDerivation(String purl, StructureDefinition srcSD, ElementDefinition base, ElementDefinition derived, TypeRefComponent ts) {
3129    boolean ok = false;
3130    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
3131    String t = ts.getWorkingCode();
3132    for (TypeRefComponent td : base.getType()) {;
3133      String tt = td.getWorkingCode();
3134      b.append(tt);
3135      if (td.hasCode() && (tt.equals(t))) {
3136        ok = true;
3137      }
3138      if (!ok) {
3139        StructureDefinition sdt = context.fetchTypeDefinition(tt);
3140        if (sdt != null && (sdt.getAbstract() || sdt.getKind() == StructureDefinitionKind.LOGICAL)) {
3141          StructureDefinition sdb = context.fetchTypeDefinition(t);
3142          while (sdb != null && !ok) {
3143            ok = sdb.getType().equals(sdt.getType());
3144            sdb = context.fetchResource(StructureDefinition.class, sdb.getBaseDefinition());
3145          }
3146        }
3147      }
3148     // work around for old badly generated SDs
3149      if (DONT_DO_THIS && Utilities.existsInList(tt, "Extension", "uri", "string", "Element")) {
3150        ok = true;
3151      }
3152      if (DONT_DO_THIS && Utilities.existsInList(tt, "Resource","DomainResource") && pkp.isResource(t)) {
3153        ok = true;
3154      }
3155      if (ok && ts.hasTargetProfile()) {
3156        // check that any derived target has a reference chain back to one of the base target profiles
3157        for (UriType u : ts.getTargetProfile()) {
3158          String url = u.getValue();
3159          boolean tgtOk = !td.hasTargetProfile() || td.hasTargetProfile(url);
3160          while (url != null && !tgtOk) {
3161            StructureDefinition sd = context.fetchRawProfile(url);
3162            if (sd == null) {
3163              if (messages != null) {
3164                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));
3165              }
3166              url = null;
3167              tgtOk = true; // suppress error message
3168            } else {
3169              url = sd.getBaseDefinition();
3170              tgtOk = td.hasTargetProfile(url);
3171            }
3172          }
3173          if (!tgtOk) {
3174            if (messages == null) {
3175              throw new FHIRException(context.formatMessage(I18nConstants.ERROR_AT__THE_TARGET_PROFILE__IS_NOT__VALID_CONSTRAINT_ON_THE_BASE_, purl, derived.getPath(), url, td.getTargetProfile()));
3176            } else {
3177              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));
3178            }
3179          }
3180        }
3181      }
3182    }
3183    if (!ok) {
3184      throw new DefinitionException(context.formatMessage(I18nConstants.STRUCTUREDEFINITION__AT__ILLEGAL_CONSTRAINED_TYPE__FROM__IN_, purl, derived.getPath(), t, b.toString(), srcSD.getUrl()));
3185    }
3186  }
3187
3188
3189  public void checkTypeOk(ElementDefinition dest, String ft, StructureDefinition sd) {
3190    boolean ok = false;
3191    Set<String> types = new HashSet<>();
3192    if (dest.getPath().contains(".")) {
3193      for (TypeRefComponent t : dest.getType()) {
3194        if (t.hasCode()) {
3195          types.add(t.getWorkingCode());
3196        }
3197        ok = ft.equals(t.getWorkingCode());
3198      }
3199    } else {
3200      types.add(sd.getType());
3201      ok = ft.equals(sd.getType());
3202
3203    }
3204    if (!ok) {
3205      messages.add(new ValidationMessage(Source.InstanceValidator, IssueType.CONFLICT, dest.getId(), "The fixed value has type '"+ft+"' which is not valid (valid "+Utilities.pluralize("type", dest.getType().size())+": "+types.toString()+")", IssueSeverity.ERROR));
3206    }
3207  }
3208
3209  private boolean hasBindableType(ElementDefinition ed) {
3210    for (TypeRefComponent tr : ed.getType()) {
3211      if (Utilities.existsInList(tr.getWorkingCode(), "Coding", "CodeableConcept", "Quantity", "uri", "string", "code")) {
3212        return true;
3213      }
3214      StructureDefinition sd = context.fetchTypeDefinition(tr.getCode());
3215      if (sd != null && sd.hasExtension(ToolingExtensions.EXT_BINDING_STYLE)) {
3216        return true;
3217      }
3218    }
3219    return false;
3220  }
3221
3222
3223  private boolean isLargerMax(String derived, String base) {
3224    if ("*".equals(base)) {
3225      return false;
3226    }
3227    if ("*".equals(derived)) {
3228      return true;
3229    }
3230    return Integer.parseInt(derived) > Integer.parseInt(base);
3231  }
3232
3233
3234  private boolean isSubset(ValueSet expBase, ValueSet expDerived) {
3235    return codesInExpansion(expDerived.getExpansion().getContains(), expBase.getExpansion());
3236  }
3237
3238
3239  private boolean codesInExpansion(List<ValueSetExpansionContainsComponent> contains, ValueSetExpansionComponent expansion) {
3240    for (ValueSetExpansionContainsComponent cc : contains) {
3241      if (!inExpansion(cc, expansion.getContains())) {
3242        return false;
3243      }
3244      if (!codesInExpansion(cc.getContains(), expansion)) {
3245        return false;
3246      }
3247    }
3248    return true;
3249  }
3250
3251
3252  private boolean inExpansion(ValueSetExpansionContainsComponent cc, List<ValueSetExpansionContainsComponent> contains) {
3253    for (ValueSetExpansionContainsComponent cc1 : contains) {
3254      if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode())) {
3255        return true;
3256      }
3257      if (inExpansion(cc,  cc1.getContains())) {
3258        return true;
3259      }
3260    }
3261    return false;
3262  }
3263
3264  public void closeDifferential(StructureDefinition base, StructureDefinition derived) throws FHIRException {
3265    for (ElementDefinition edb : base.getSnapshot().getElement()) {
3266      if (isImmediateChild(edb) && !edb.getPath().endsWith(".id")) {
3267        ElementDefinition edm = getMatchInDerived(edb, derived.getDifferential().getElement());
3268        if (edm == null) {
3269          ElementDefinition edd = derived.getDifferential().addElement();
3270          edd.setPath(edb.getPath());
3271          edd.setMax("0");
3272        } else if (edb.hasSlicing()) {
3273          closeChildren(base, edb, derived, edm);
3274        }
3275      }
3276    }
3277    sortDifferential(base, derived, derived.getName(), new ArrayList<String>(), false);
3278  }
3279
3280  private void closeChildren(StructureDefinition base, ElementDefinition edb, StructureDefinition derived, ElementDefinition edm) {
3281    String path = edb.getPath()+".";
3282    int baseStart = base.getSnapshot().getElement().indexOf(edb);
3283    int baseEnd = findEnd(base.getSnapshot().getElement(), edb, baseStart+1);
3284    int diffStart = derived.getDifferential().getElement().indexOf(edm);
3285    int diffEnd = findEnd(derived.getDifferential().getElement(), edm, diffStart+1);
3286    
3287    for (int cBase = baseStart; cBase < baseEnd; cBase++) {
3288      ElementDefinition edBase = base.getSnapshot().getElement().get(cBase);
3289      if (isImmediateChild(edBase, edb)) {
3290        ElementDefinition edMatch = getMatchInDerived(edBase, derived.getDifferential().getElement(), diffStart, diffEnd);
3291        if (edMatch == null) {
3292          ElementDefinition edd = derived.getDifferential().addElement();
3293          edd.setPath(edBase.getPath());
3294          edd.setMax("0");
3295        } else {
3296          closeChildren(base, edBase, derived, edMatch);
3297        }        
3298      }
3299    }
3300  }
3301
3302
3303
3304
3305  private int findEnd(List<ElementDefinition> list, ElementDefinition ed, int cursor) {
3306    String path = ed.getPath()+".";
3307    while (cursor < list.size() && list.get(cursor).getPath().startsWith(path)) {
3308      cursor++;
3309    }
3310    return cursor;
3311  }
3312
3313
3314  private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list) {
3315    for (ElementDefinition t : list) {
3316      if (t.getPath().equals(ed.getPath())) {
3317        return t;
3318      }
3319    }
3320    return null;
3321  }
3322
3323  private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list, int start, int end) {
3324    for (int i = start; i < end; i++) {
3325      ElementDefinition t = list.get(i);
3326      if (t.getPath().equals(ed.getPath())) {
3327        return t;
3328      }
3329    }
3330    return null;
3331  }
3332
3333
3334  private boolean isImmediateChild(ElementDefinition ed) {
3335    String p = ed.getPath();
3336    if (!p.contains(".")) {
3337      return false;
3338    }
3339    p = p.substring(p.indexOf(".")+1);
3340    return !p.contains(".");
3341  }
3342
3343  private boolean isImmediateChild(ElementDefinition candidate, ElementDefinition base) {
3344    String p = candidate.getPath();
3345    if (!p.contains("."))
3346      return false;
3347    if (!p.startsWith(base.getPath()+"."))
3348      return false;
3349    p = p.substring(base.getPath().length()+1);
3350    return !p.contains(".");
3351  }
3352
3353  public XhtmlNode generateExtensionTable(String defFile, StructureDefinition ed, String imageFolder, boolean inlineGraphics, boolean full, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException {
3354    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true);
3355    gen.setTranslator(getTranslator());
3356    TableModel model = gen.initNormalTable(corePath, false, true, ed.getId()+(full ? "f" : "n"), true);
3357
3358    boolean deep = false;
3359    String m = "";
3360    boolean vdeep = false;
3361    if (ed.getSnapshot().getElementFirstRep().getIsModifier())
3362      m = "modifier_";
3363    for (ElementDefinition eld : ed.getSnapshot().getElement()) {
3364      deep = deep || eld.getPath().contains("Extension.extension.");
3365      vdeep = vdeep || eld.getPath().contains("Extension.extension.extension.");
3366    }
3367    Row r = gen.new Row();
3368    model.getRows().add(r);
3369    String en;
3370    if (!full)
3371      en = ed.getName();
3372    else if (ed.getSnapshot().getElement().get(0).getIsModifier())
3373      en = "modifierExtension";
3374    else 
3375      en = "extension";
3376    
3377    r.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), en, null, null));
3378    r.getCells().add(gen.new Cell());
3379    r.getCells().add(gen.new Cell(null, null, describeCardinality(ed.getSnapshot().getElement().get(0), null, new UnusedTracker()), null, null));
3380
3381    ElementDefinition ved = null;
3382    if (full || vdeep) {
3383      r.getCells().add(gen.new Cell("", "", "Extension", null, null));
3384
3385      r.setIcon(deep ? "icon_"+m+"extension_complex.png" : "icon_extension_simple.png", deep ? HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX : HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
3386      List<ElementDefinition> children = getChildren(ed.getSnapshot().getElement(), ed.getSnapshot().getElement().get(0));
3387      for (ElementDefinition child : children)
3388        if (!child.getPath().endsWith(".id")) {
3389          List<StructureDefinition> sdl = new ArrayList<>();
3390          sdl.add(ed);
3391          genElement(defFile == null ? "" : defFile+"-definitions.html#extension.", gen, r.getSubRows(), child, ed.getSnapshot().getElement(), sdl, true, defFile, true, full, corePath, imagePath, true, false, false, false, null, false);
3392        }
3393    } else if (deep) {
3394      List<ElementDefinition> children = new ArrayList<ElementDefinition>();
3395      for (ElementDefinition ted : ed.getSnapshot().getElement()) {
3396        if (ted.getPath().equals("Extension.extension"))
3397          children.add(ted);
3398      }
3399
3400      r.getCells().add(gen.new Cell("", "", "Extension", null, null));
3401      r.setIcon("icon_"+m+"extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
3402      
3403      for (ElementDefinition c : children) {
3404        ved = getValueFor(ed, c);
3405        ElementDefinition ued = getUrlFor(ed, c);
3406        if (ved != null && ued != null) {
3407          Row r1 = gen.new Row();
3408          r.getSubRows().add(r1);
3409          r1.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), ((UriType) ued.getFixed()).getValue(), null, null));
3410          r1.getCells().add(gen.new Cell());
3411          r1.getCells().add(gen.new Cell(null, null, describeCardinality(c, null, new UnusedTracker()), null, null));
3412          genTypes(gen, r1, ved, defFile, ed, corePath, imagePath, false, false);
3413          Cell cell = gen.new Cell();
3414          cell.addMarkdown(c.getDefinition());
3415          r1.getCells().add(cell);
3416          r1.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);      
3417        }
3418      }
3419    } else  {
3420      for (ElementDefinition ted : ed.getSnapshot().getElement()) {
3421        if (ted.getPath().startsWith("Extension.value"))
3422          ved = ted;
3423      }
3424
3425      genTypes(gen, r, ved, defFile, ed, corePath, imagePath, false, false);
3426
3427      r.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);      
3428    }
3429    Cell c = gen.new Cell("", "", "URL = "+ed.getUrl(), null, null);
3430    Piece cc = gen.new Piece(null, ed.getName()+": ", null);
3431    c.addPiece(gen.new Piece("br")).addPiece(cc);
3432    c.addMarkdown(ed.getDescription());
3433    
3434    if (!full && !(deep || vdeep) && ved != null && ved.hasBinding()) {  
3435        c.addPiece(gen.new Piece("br"));
3436      BindingResolution br = pkp.resolveBinding(ed, ved.getBinding(), ved.getPath());
3437      c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold")));
3438      c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath+br.url, br.display, null)));
3439      if (ved.getBinding().hasStrength()) {
3440        c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, " (", null)));
3441        c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(corePath+"terminologies.html#"+ved.getBinding().getStrength().toCode(), egt(ved.getBinding().getStrengthElement()), ved.getBinding().getStrength().getDefinition())));              
3442        c.getPieces().add(gen.new Piece(null, ")", null));
3443      }
3444      if (ved.getBinding().hasDescription() && MarkDownProcessor.isSimpleMarkdown(ved.getBinding().getDescription())) {
3445        c.getPieces().add(gen.new Piece(null, ": ", null));
3446        c.addMarkdownNoPara(PublicationHacker.fixBindingDescriptions(context, ved.getBinding().getDescriptionElement()).asStringValue());
3447      }
3448    }
3449    c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, describeExtensionContext(ed), null));
3450    r.getCells().add(c);
3451    
3452    try {
3453      return gen.generate(model, corePath, 0, outputTracker);
3454        } catch (org.hl7.fhir.exceptions.FHIRException e) {
3455                throw new FHIRException(e.getMessage(), e);
3456        }
3457  }
3458
3459  private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) {
3460    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
3461    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
3462      if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url"))
3463        return ed.getSnapshot().getElement().get(i);
3464      i++;
3465    }
3466    return null;
3467  }
3468
3469  private ElementDefinition getValueFor(StructureDefinition ed, ElementDefinition c) {
3470    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
3471    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
3472      if (ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".value"))
3473        return ed.getSnapshot().getElement().get(i);
3474      i++;
3475    }
3476    return null;
3477  }
3478
3479  private static final int AGG_NONE = 0;
3480  private static final int AGG_IND = 1;
3481  private static final int AGG_GR = 2;
3482  private static final boolean TABLE_FORMAT_FOR_FIXED_VALUES = false;
3483  
3484  private Cell genTypes(HierarchicalTableGenerator gen, Row r, ElementDefinition e, String profileBaseFileName, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean mustSupportMode) {
3485    Cell c = gen.new Cell();
3486    r.getCells().add(c);
3487    if (e.hasContentReference()) {
3488      ElementInStructure ed = getElementByName(profile.getSnapshot().getElement(), e.getContentReference(), profile);
3489      if (ed == null)
3490        c.getPieces().add(gen.new Piece(null, translate("sd.table", "Unknown reference to %s", e.getContentReference()), null));
3491      else {
3492        if (ed.getSource() == profile) {
3493          c.getPieces().add(gen.new Piece(null, translate("sd.table", "See ", ed.getElement().getPath()), null));
3494          c.getPieces().add(gen.new Piece("#"+ed.getElement().getPath(), tail(ed.getElement().getPath()), ed.getElement().getPath()));
3495        } else {
3496          c.getPieces().add(gen.new Piece(null, translate("sd.table", "See ", ed.getElement().getPath()), null));
3497          c.getPieces().add(gen.new Piece(pfx(corePath, ed.getSource().getUserString("path"))+"#"+ed.getElement().getPath(), tail(ed.getElement().getPath())+" ("+ed.getSource().getType()+")", ed.getElement().getPath()));
3498        }
3499      }
3500      return c;
3501    }
3502    List<TypeRefComponent> types = e.getType();
3503    if (!e.hasType()) {
3504      if (root) { // we'll use base instead of types then
3505        StructureDefinition bsd = context.fetchResource(StructureDefinition.class, profile.getBaseDefinition());
3506        if (bsd != null) {
3507          if (bsd.hasUserData("path")) {
3508            c.getPieces().add(gen.new Piece(Utilities.isAbsoluteUrl(bsd.getUserString("path")) ? bsd.getUserString("path") : imagePath +bsd.getUserString("path"), bsd.getName(), null));
3509          } else {
3510            c.getPieces().add(gen.new Piece(null, bsd.getName(), null));
3511          }
3512        }
3513        return c;
3514      } else if (e.hasContentReference()) {
3515        return c;
3516      } else {
3517        ElementDefinition d = (ElementDefinition) e.getUserData(DERIVATION_POINTER);
3518        if (d != null && d.hasType()) {
3519          types = new ArrayList<ElementDefinition.TypeRefComponent>();
3520          for (TypeRefComponent tr : d.getType()) {
3521            TypeRefComponent tt = tr.copy();
3522            tt.setUserData(DERIVATION_EQUALS, true);
3523            types.add(tt);
3524          }
3525        } else {
3526          return c;
3527        }
3528      }
3529    }
3530
3531    boolean first = true;
3532
3533    TypeRefComponent tl = null;
3534    for (TypeRefComponent t : types) {
3535      if (!mustSupportMode || allTypesMustSupport(e) || isMustSupport(t)) {
3536        if (first) {
3537          first = false;
3538        } else {
3539          c.addPiece(checkForNoChange(tl, gen.new Piece(null,", ", null)));
3540        }
3541        tl = t;
3542        if (t.hasTarget()) {
3543          c.getPieces().add(gen.new Piece(corePath+"references.html", t.getWorkingCode(), null));
3544          if (!mustSupportMode && isMustSupportDirect(t) && e.getMustSupport()) {
3545            c.addPiece(gen.new Piece(null, " ", null));
3546            c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false);
3547          }
3548          c.getPieces().add(gen.new Piece(null, "(", null));
3549          boolean tfirst = true;
3550          for (CanonicalType u : t.getTargetProfile()) {
3551            if (!mustSupportMode || allProfilesMustSupport(t.getTargetProfile()) || isMustSupport(u)) {
3552              if (tfirst)
3553                tfirst = false;
3554              else
3555                c.addPiece(gen.new Piece(null, " | ", null));
3556              genTargetLink(gen, profileBaseFileName, corePath, c, t, u.getValue());
3557              if (!mustSupportMode && isMustSupport(u) && e.getMustSupport()) {
3558                c.addPiece(gen.new Piece(null, " ", null));
3559                c.addStyledText(translate("sd.table", "This target must be supported"), "S", "white", "red", null, false);
3560              }
3561            }
3562          }
3563          c.getPieces().add(gen.new Piece(null, ")", null));
3564          if (t.getAggregation().size() > 0) {
3565            c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", " {", null));
3566            boolean firstA = true;
3567            for (Enumeration<AggregationMode> a : t.getAggregation()) {
3568              if (firstA = true)
3569                firstA = false;
3570              else
3571                c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", ", ", null));
3572              c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", codeForAggregation(a.getValue()), hintForAggregation(a.getValue())));
3573            }
3574            c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", "}", null));
3575          }
3576        } else if (t.hasProfile() && (!t.getWorkingCode().equals("Extension") || isProfiledType(t.getProfile()))) { // a profiled type
3577          String ref;
3578          boolean pfirst = true;
3579          for (CanonicalType p : t.getProfile()) {
3580            if (!mustSupportMode || allProfilesMustSupport(t.getProfile()) || isMustSupport(p)) {
3581              if (pfirst) {
3582                pfirst = false;
3583              } else {
3584                c.addPiece(checkForNoChange(tl, gen.new Piece(null,", ", null)));
3585              }          
3586
3587              ref = pkp.getLinkForProfile(profile, p.getValue());
3588              if (ref != null) {
3589                String[] parts = ref.split("\\|");
3590                if (parts[0].startsWith("http:") || parts[0].startsWith("https:")) {
3591                  //            c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], "<" + parts[1] + ">", t.getCode()))); Lloyd
3592                  c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], parts[1], t.getWorkingCode())));
3593                } else {
3594                  //            c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().startsWith(corePath)? corePath: "")+parts[0], "<" + parts[1] + ">", t.getCode())));
3595                  c.addPiece(checkForNoChange(t, gen.new Piece((p.getValue().startsWith(corePath+"StructureDefinition")? corePath: "")+parts[0], parts[1], t.getWorkingCode())));
3596                }
3597              } else
3598                c.addPiece(checkForNoChange(t, gen.new Piece((p.getValue().startsWith(corePath)? corePath: "")+ref, t.getWorkingCode(), null)));
3599              if (!mustSupportMode && isMustSupport(p) && e.getMustSupport()) {
3600                c.addPiece(gen.new Piece(null, " ", null));
3601                c.addStyledText(translate("sd.table", "This profile must be supported"), "S", "white", "red", null, false);
3602              }
3603            }
3604          }
3605        } else {
3606          String tc = t.getWorkingCode();
3607          if (Utilities.isAbsoluteUrl(tc)) {
3608            StructureDefinition sd = context.fetchTypeDefinition(tc);
3609            if (sd == null) {
3610              c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, tc), tc, null)));
3611            } else {
3612              c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, tc), sd.getType(), null)));           
3613            }
3614          } else if (pkp != null && pkp.hasLinkFor(tc)) {
3615            c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, tc), tc, null)));
3616          } else {
3617            c.addPiece(checkForNoChange(t, gen.new Piece(null, tc, null)));
3618          }
3619          if (!mustSupportMode && isMustSupportDirect(t) && e.getMustSupport()) {
3620            c.addPiece(gen.new Piece(null, " ", null));
3621            c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false);
3622          }
3623        }
3624      }
3625    }
3626    return c;
3627  }
3628
3629
3630  private String pfx(String prefix, String url) {
3631    return Utilities.isAbsoluteUrl(url) ? url : prefix + url;
3632  }
3633
3634  public void genTargetLink(HierarchicalTableGenerator gen, String profileBaseFileName, String corePath, Cell c, TypeRefComponent t, String u) {
3635    if (u.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
3636      StructureDefinition sd = context.fetchResource(StructureDefinition.class, u);
3637      if (sd != null) {
3638        String disp = sd.hasTitle() ? sd.getTitle() : sd.getName();
3639        c.addPiece(checkForNoChange(t, gen.new Piece(checkPrepend(corePath, sd.getUserString("path")), disp, null)));
3640      } else {
3641        String rn = u.substring(40);
3642        c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, rn), rn, null)));
3643      }
3644    } else if (Utilities.isAbsoluteUrl(u)) {
3645      StructureDefinition sd = context.fetchResource(StructureDefinition.class, u);
3646      if (sd != null) {
3647        String disp = sd.hasTitle() ? sd.getTitle() : sd.getName();
3648        String ref = pkp.getLinkForProfile(null, sd.getUrl());
3649        if (ref != null && ref.contains("|"))
3650          ref = ref.substring(0,  ref.indexOf("|"));
3651        c.addPiece(checkForNoChange(t, gen.new Piece(ref, disp, null)));
3652      } else
3653        c.addPiece(checkForNoChange(t, gen.new Piece(null, u, null)));        
3654    } else if (t.hasTargetProfile() && u.startsWith("#"))
3655      c.addPiece(checkForNoChange(t, gen.new Piece(corePath+profileBaseFileName+"."+u.substring(1).toLowerCase()+".html", u, null)));
3656  }
3657
3658  private boolean isProfiledType(List<CanonicalType> theProfile) {
3659    for (CanonicalType next : theProfile){
3660      if (StringUtils.defaultString(next.getValueAsString()).contains(":")) {
3661        return true;
3662      }
3663    }
3664    return false;
3665  }
3666
3667
3668  private String codeForAggregation(AggregationMode a) {
3669    switch (a) {
3670    case BUNDLED : return "b";
3671    case CONTAINED : return "c";
3672    case REFERENCED: return "r";
3673    default: return "?";
3674    }
3675  }
3676
3677  private String hintForAggregation(AggregationMode a) {
3678    if (a != null)
3679      return a.getDefinition();
3680    else 
3681      return null;
3682  }
3683
3684
3685  private String checkPrepend(String corePath, String path) {
3686    if (pkp.prependLinks() && !(path.startsWith("http:") || path.startsWith("https:")))
3687      return corePath+path;
3688    else 
3689      return path;
3690  }
3691
3692
3693  private class ElementInStructure {
3694
3695    private StructureDefinition source;
3696    private ElementDefinition element;
3697
3698    public ElementInStructure(StructureDefinition source, ElementDefinition ed) {
3699      this.source = source;
3700      this.element = ed;
3701    }
3702
3703    public StructureDefinition getSource() {
3704      return source;
3705    }
3706
3707    public ElementDefinition getElement() {
3708      return element;
3709    }
3710    
3711  }
3712  private ElementInStructure getElementByName(List<ElementDefinition> elements, String contentReference, StructureDefinition source) {
3713    if (contentReference.contains("#")) {
3714      String url = contentReference.substring(0, contentReference.indexOf("#"));
3715      contentReference = contentReference.substring(contentReference.indexOf("#"));
3716      if (!url.equals(source.getUrl())) {
3717        source = context.fetchResource(StructureDefinition.class, url);
3718        if (source == null) {
3719          throw new FHIRException("Unable to resolve StructureDefinition "+url+" resolving content reference "+contentReference);
3720        }
3721        elements = source.getSnapshot().getElement();
3722      }
3723    } 
3724    for (ElementDefinition ed : elements) {
3725      if (("#"+ed.getPath()).equals(contentReference)) {
3726        return new ElementInStructure(source, ed);
3727      }
3728      if (("#"+ed.getId()).equals(contentReference)) {
3729        return new ElementInStructure(source, ed);
3730      }
3731    }
3732    throw new Error("getElementByName: can't find "+contentReference+" in "+elements.toString()+" from "+source.getUrl());
3733//    return null;
3734  }
3735
3736  private ElementDefinitionResolution getElementById(StructureDefinition source, List<ElementDefinition> elements, String contentReference) {
3737    if (!contentReference.startsWith("#") && contentReference.contains("#")) {
3738      String url = contentReference.substring(0, contentReference.indexOf("#"));
3739      contentReference = contentReference.substring(contentReference.indexOf("#"));
3740      if (!url.equals(source.getUrl())){
3741        source = context.fetchResource(StructureDefinition.class, url);
3742        if (source == null) {
3743          return null;
3744        }
3745        elements = source.getSnapshot().getElement();
3746      }      
3747    }
3748    for (ElementDefinition ed : elements)
3749      if (ed.hasId() && ("#"+ed.getId()).equals(contentReference))
3750        return new ElementDefinitionResolution(source, ed);
3751    return null;
3752  }
3753
3754
3755  public static String describeExtensionContext(StructureDefinition ext) {
3756    StringBuilder b = new StringBuilder();
3757    b.append("Use on ");
3758    for (int i = 0; i < ext.getContext().size(); i++) {
3759      StructureDefinitionContextComponent ec = ext.getContext().get(i);
3760      if (i > 0) 
3761        b.append(i < ext.getContext().size() - 1 ? ", " : " or ");
3762      b.append(ec.getType().getDisplay());
3763      b.append(" ");
3764      b.append(ec.getExpression());
3765    }
3766    if (ext.hasContextInvariant()) {
3767      b.append(", with <a href=\"structuredefinition-definitions.html#StructureDefinition.contextInvariant\">Context Invariant</a> = ");
3768      boolean first = true;
3769      for (StringType s : ext.getContextInvariant()) {
3770        if (first)
3771          first = false;
3772        else
3773          b.append(", ");
3774        b.append("<code>"+s.getValue()+"</code>");
3775      }
3776    }
3777    return b.toString(); 
3778  }
3779
3780  private String describeCardinality(ElementDefinition definition, ElementDefinition fallback, UnusedTracker tracker) {
3781    IntegerType min = definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
3782    StringType max = definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
3783    if (min.isEmpty() && fallback != null)
3784      min = fallback.getMinElement();
3785    if (max.isEmpty() && fallback != null)
3786      max = fallback.getMaxElement();
3787
3788    tracker.used = !max.isEmpty() && !max.getValue().equals("0");
3789
3790    if (min.isEmpty() && max.isEmpty())
3791      return null;
3792    else
3793      return (!min.hasValue() ? "" : Integer.toString(min.getValue())) + ".." + (!max.hasValue() ? "" : max.getValue());
3794  }
3795
3796  private Cell genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef, UnusedTracker tracker, ElementDefinition fallback) {
3797    IntegerType min = !hasDef ? new IntegerType() : definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
3798    StringType max = !hasDef ? new StringType() : definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
3799    if (min.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) {
3800      ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER);
3801      if (base.hasMinElement()) {
3802        min = base.getMinElement().copy();
3803        min.setUserData(DERIVATION_EQUALS, true);
3804      }
3805    }
3806    if (max.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) {
3807      ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER);
3808      if (base.hasMaxElement()) {
3809        max = base.getMaxElement().copy();
3810        max.setUserData(DERIVATION_EQUALS, true);
3811      }
3812    }
3813    if (min.isEmpty() && fallback != null)
3814      min = fallback.getMinElement();
3815    if (max.isEmpty() && fallback != null)
3816      max = fallback.getMaxElement();
3817
3818    if (!max.isEmpty())
3819      tracker.used = !max.getValue().equals("0");
3820
3821    Cell cell = gen.new Cell(null, null, null, null, null);
3822    row.getCells().add(cell);
3823    if (!min.isEmpty() || !max.isEmpty()) {
3824      cell.addPiece(checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), null)));
3825      cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", null)));
3826      cell.addPiece(checkForNoChange(max, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), null)));
3827    }
3828    return cell;
3829  }
3830
3831
3832  private Piece checkForNoChange(Element source, Piece piece) {
3833    if (source.hasUserData(DERIVATION_EQUALS)) {
3834      piece.addStyle("opacity: 0.5");
3835    }
3836    return piece;
3837  }
3838
3839  private String checkForNoChange(Element source) {
3840    if (source.hasUserData(DERIVATION_EQUALS)) {
3841      return "opacity: 0.5";
3842    } else { 
3843      return null;
3844    }
3845  }
3846
3847
3848  private Piece applyAsUnchanged(Piece piece) {
3849    piece.addStyle("opacity: 0.5");
3850    return piece;
3851  }
3852
3853  private String applyAsUnchanged() {
3854    return "opacity: 0.5";
3855  }
3856
3857
3858  private Piece checkForNoChange(Element src1, Element src2, Piece piece) {
3859    if (src1.hasUserData(DERIVATION_EQUALS) && src2.hasUserData(DERIVATION_EQUALS)) {
3860      piece.addStyle("opacity: 0.5");
3861    }
3862    return piece;
3863  }
3864
3865  public XhtmlNode generateTable(String defFile, StructureDefinition profile, boolean diff, String imageFolder, boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, String imagePath, 
3866      boolean logicalModel, boolean allInvariants, Set<String> outputTracker, boolean active, boolean mustSupport) throws IOException, FHIRException {
3867    assert(diff != snapshot);// check it's ok to get rid of one of these
3868    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true);
3869    gen.setTranslator(getTranslator());
3870    TableModel model = gen.initNormalTable(corePath, false, true, profile.getId()+(diff ? "d" : "s"), active);
3871    List<ElementDefinition> list = new ArrayList<>();
3872    if (diff)
3873      list.addAll(profile.getDifferential().getElement());
3874    else
3875      list.addAll(profile.getSnapshot().getElement());
3876    List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
3877    profiles.add(profile);
3878    if (list.isEmpty()) {
3879      ElementDefinition root = new ElementDefinition().setPath(profile.getType());
3880      root.setId(profile.getType());
3881      list.add(root);
3882    } else {
3883      if (list.get(0).getPath().contains(".")) {
3884        ElementDefinition root = new ElementDefinition().setPath(profile.getType());
3885        root.setId(profile.getType());
3886        list.add(0, root);
3887      }
3888    }
3889    if (diff) {
3890      insertMissingSparseElements(list);
3891    }
3892    genElement(defFile == null ? null : defFile+"#", gen, model.getRows(), list.get(0), list, profiles, diff, profileBaseFileName, null, snapshot, corePath, imagePath, true, logicalModel, profile.getDerivation() == TypeDerivationRule.CONSTRAINT && usesMustSupport(list), allInvariants, null, mustSupport);
3893    try {
3894      return gen.generate(model, imagePath, 0, outputTracker);
3895        } catch (org.hl7.fhir.exceptions.FHIRException e) {
3896      throw new FHIRException(context.formatMessage(I18nConstants.ERROR_GENERATING_TABLE_FOR_PROFILE__, profile.getUrl(), e.getMessage()), e);
3897        }
3898  }
3899
3900
3901  private void insertMissingSparseElements(List<ElementDefinition> list) {
3902    int i = 1;
3903    while (i < list.size()) {
3904      String[] pathCurrent = list.get(i).getPath().split("\\.");
3905      String[] pathLast = list.get(i-1).getPath().split("\\.");
3906      int firstDiff = 0; // the first entry must be a match
3907      while (firstDiff < pathCurrent.length && firstDiff < pathLast.length && pathCurrent[firstDiff].equals(pathLast[firstDiff])) {
3908        firstDiff++;
3909      }
3910      if (!(isSibling(pathCurrent, pathLast, firstDiff) || isChild(pathCurrent, pathLast, firstDiff))) {
3911        // now work backwards down to lastMatch inserting missing path nodes
3912        ElementDefinition parent = findParent(list, i, list.get(i).getPath());
3913        int parentDepth = Utilities.charCount(parent.getPath(), '.')+1;
3914        int childDepth =  Utilities.charCount(list.get(i).getPath(), '.')+1;
3915        if (childDepth > parentDepth + 1) {
3916          String basePath = parent.getPath();
3917          String baseId = parent.getId();
3918          for (int index = parentDepth; index >= firstDiff; index--) {
3919            String mtail = makeTail(pathCurrent, parentDepth, index);
3920            ElementDefinition root = new ElementDefinition().setPath(basePath+"."+mtail);
3921            root.setId(baseId+"."+mtail);
3922            list.add(i, root);
3923          }
3924        }
3925      } 
3926      i++;
3927    }
3928  }
3929
3930  private ElementDefinition findParent(List<ElementDefinition> list, int i, String path) {
3931    while (i > 0 && !path.startsWith(list.get(i).getPath()+".")) {
3932      i--;
3933    }
3934    return list.get(i);
3935  }
3936
3937  private boolean isSibling(String[] pathCurrent, String[] pathLast, int firstDiff) {
3938    return pathCurrent.length == pathLast.length && firstDiff == pathCurrent.length-1;
3939  }
3940
3941
3942  private boolean isChild(String[] pathCurrent, String[] pathLast, int firstDiff) {
3943    return pathCurrent.length == pathLast.length+1 && firstDiff == pathLast.length;
3944  }
3945
3946  private String makeTail(String[] pathCurrent, int start, int index) {
3947    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(".");
3948    for (int i = start; i <= index; i++) {
3949      b.append(pathCurrent[i]);
3950    }
3951    return b.toString();
3952  }
3953
3954  private String makePath(String[] pathCurrent, int index) {
3955    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(".");
3956    for (int i = 0; i <= index; i++) {
3957      b.append(pathCurrent[i]);
3958    }
3959    return b.toString();
3960  }
3961
3962
3963  public XhtmlNode generateGrid(String defFile, StructureDefinition profile, String imageFolder, boolean inlineGraphics, String profileBaseFileName, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException {
3964    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true);
3965    gen.setTranslator(getTranslator());
3966    TableModel model = gen.initGridTable(corePath, profile.getId());
3967    List<ElementDefinition> list = profile.getSnapshot().getElement();
3968    List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
3969    profiles.add(profile);
3970    genGridElement(defFile == null ? null : defFile+"#", gen, model.getRows(), list.get(0), list, profiles, true, profileBaseFileName, null, corePath, imagePath, true, profile.getDerivation() == TypeDerivationRule.CONSTRAINT && usesMustSupport(list));
3971    try {
3972      return gen.generate(model, imagePath, 1, outputTracker);
3973    } catch (org.hl7.fhir.exceptions.FHIRException e) {
3974      throw new FHIRException(e.getMessage(), e);
3975    }
3976  }
3977
3978
3979  private boolean usesMustSupport(List<ElementDefinition> list) {
3980    for (ElementDefinition ed : list)
3981      if (ed.hasMustSupport() && ed.getMustSupport())
3982        return true;
3983    return false;
3984  }
3985
3986
3987  private Row genElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element, List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName, Boolean extensions, 
3988      boolean snapshot, String corePath, String imagePath, boolean root, boolean logicalModel, boolean isConstraintMode, boolean allInvariants, Row slicingRow, boolean mustSupport) throws IOException, FHIRException {
3989    Row originalRow = slicingRow;
3990    StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1);
3991    Row typesRow = null;
3992    
3993    List<ElementDefinition> children = getChildren(all, element);
3994//    if (!snapshot && isExtension && extensions != null && extensions != isExtension)
3995//      return;
3996
3997    if (!onlyInformationIsMapping(all, element)) {
3998      Row row = gen.new Row();
3999      row.setAnchor(element.getPath());
4000      row.setColor(getRowColor(element, isConstraintMode));
4001      if (element.hasSlicing())
4002        row.setLineColor(1);
4003      else if (element.hasSliceName())
4004        row.setLineColor(2);
4005      else
4006        row.setLineColor(0);
4007      boolean hasDef = element != null;
4008      boolean ext = false;
4009      if (tail(element.getPath()).equals("extension")) {
4010        if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue()))
4011          row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
4012        else
4013          row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
4014        ext = true;
4015      } else if (tail(element.getPath()).equals("modifierExtension")) {
4016        if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue()))
4017          row.setIcon("icon_modifier_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
4018        else
4019          row.setIcon("icon_modifier_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
4020      } else if (!hasDef || element.getType().size() == 0) {
4021        if (root && context.getResourceNames().contains(profile.getType())) {
4022          row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
4023        } else {
4024          row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
4025        }
4026      } else if (hasDef && element.getType().size() > 1) {
4027        if (allAreReference(element.getType())) {
4028          row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
4029        } else {
4030          row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE);
4031          typesRow = row;
4032        }
4033      } else if (hasDef && element.getType().get(0).getWorkingCode() != null && element.getType().get(0).getWorkingCode().startsWith("@")) {
4034        row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE);
4035      } else if (hasDef && isPrimitive(element.getType().get(0).getWorkingCode())) {
4036        row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
4037      } else if (hasDef && element.getType().get(0).hasTarget()) {
4038        row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
4039      } else if (hasDef && isDataType(element.getType().get(0).getWorkingCode())) {
4040        row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
4041      } else if (hasDef && Utilities.existsInList(element.getType().get(0).getWorkingCode(), "Element", "BackboneElement")) {
4042        row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
4043      } else {
4044        row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
4045      }
4046      if (element.hasUserData("render.opaque")) {
4047        row.setOpacity("0.5");
4048      }
4049      UnusedTracker used = new UnusedTracker();
4050      String ref = defPath == null ? null : defPath + element.getId();
4051      String sName = tail(element.getPath());
4052      if (element.hasSliceName())
4053        sName = sName +":"+element.getSliceName();
4054      used.used = true;
4055      if (logicalModel && element.hasRepresentation(PropertyRepresentation.XMLATTR))
4056        sName = "@"+sName;
4057      Cell nc = genElementNameCell(gen, element, profileBaseFileName, snapshot, corePath, imagePath, root, logicalModel, allInvariants, profile, typesRow, row, hasDef, ext, used, ref, sName);
4058      genElementCells(gen, element, profileBaseFileName, snapshot, corePath, imagePath, root, logicalModel, allInvariants, profile, typesRow, row, hasDef, ext, used, ref, sName, nc, mustSupport);
4059      if (element.hasSlicing()) {
4060        if (standardExtensionSlicing(element)) {
4061          used.used = true; // doesn't matter whether we have a type, we're used if we're setting up slicing ... element.hasType() && element.getType().get(0).hasProfile();
4062          showMissing = false; //?
4063        } else {
4064          row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE);
4065          slicingRow = row;
4066          for (Cell cell : row.getCells())
4067            for (Piece p : cell.getPieces()) {
4068              p.addStyle("font-style: italic");
4069            }
4070        }
4071      } else if (element.hasSliceName()) {
4072        row.setIcon("icon_slice_item.png", HierarchicalTableGenerator.TEXT_ICON_SLICE_ITEM);
4073      }
4074      if (used.used || showMissing)
4075        rows.add(row);
4076      if (!used.used && !element.hasSlicing()) {
4077        for (Cell cell : row.getCells())
4078          for (Piece p : cell.getPieces()) {
4079            p.setStyle("text-decoration:line-through");
4080            p.setReference(null);
4081          }
4082      } else {
4083        if (slicingRow != originalRow && !children.isEmpty()) {
4084          // we've entered a slice; we're going to create a holder row for the slice children
4085          Row hrow = gen.new Row();
4086          hrow.setAnchor(element.getPath());
4087          hrow.setColor(getRowColor(element, isConstraintMode));
4088          hrow.setLineColor(1);
4089          hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
4090          hrow.getCells().add(gen.new Cell(null, null, sName+":All Slices", "", null));
4091          hrow.getCells().add(gen.new Cell());
4092          hrow.getCells().add(gen.new Cell());
4093          hrow.getCells().add(gen.new Cell());
4094          hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all slices", "", null));
4095          row.getSubRows().add(hrow);
4096          row = hrow;
4097        }
4098        if (typesRow != null && !children.isEmpty()) {
4099          // we've entered a typing slice; we're going to create a holder row for the all types children
4100          Row hrow = gen.new Row();
4101          hrow.setAnchor(element.getPath());
4102          hrow.setColor(getRowColor(element, isConstraintMode));
4103          hrow.setLineColor(1);
4104          hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
4105          hrow.getCells().add(gen.new Cell(null, null, sName+":All Types", "", null));
4106          hrow.getCells().add(gen.new Cell());
4107          hrow.getCells().add(gen.new Cell());
4108          hrow.getCells().add(gen.new Cell());
4109          hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all Types", "", null));
4110          row.getSubRows().add(hrow);
4111          row = hrow;
4112        }
4113          
4114        Row currRow = row;
4115        List<ElementChoiceGroup> groups = readChoices(element, children);
4116        boolean isExtension = Utilities.existsInList(tail(element.getPath()), "extension", "modifierExtension");
4117        for (ElementDefinition child : children) {
4118          if (!child.hasSliceName()) {
4119            currRow = row; 
4120          }
4121          Row childRow = chooseChildRowByGroup(gen, currRow, groups, child, element, isConstraintMode);
4122          
4123          if (logicalModel || !child.getPath().endsWith(".id") || (child.getPath().endsWith(".id") && (profile != null) && (profile.getDerivation() == TypeDerivationRule.CONSTRAINT))) {  
4124            currRow = genElement(defPath, gen, childRow.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, snapshot, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants, currRow, mustSupport);
4125          }
4126        }
4127//        if (!snapshot && (extensions == null || !extensions))
4128//          for (ElementDefinition child : children)
4129//            if (child.getPath().endsWith(".extension") || child.getPath().endsWith(".modifierExtension"))
4130//              genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, true, false, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants);
4131      }
4132      if (typesRow != null) {
4133        makeChoiceRows(typesRow.getSubRows(), element, gen, corePath, profileBaseFileName, mustSupport);
4134      }
4135    }
4136    return slicingRow;
4137  }
4138
4139  private Row chooseChildRowByGroup(HierarchicalTableGenerator gen, Row row, List<ElementChoiceGroup> groups, ElementDefinition element, ElementDefinition parent, boolean isConstraintMode) {
4140    String name = tail(element.getPath());
4141    for (ElementChoiceGroup grp : groups) {
4142      if (grp.getElements().contains(name)) {
4143        if (grp.getRow() == null) {
4144          grp.setRow(makeChoiceElementRow(gen, row, grp, parent, isConstraintMode));
4145        }
4146        return grp.getRow();
4147      }
4148    }
4149    return row;
4150  }
4151
4152  private Row makeChoiceElementRow(HierarchicalTableGenerator gen, Row prow, ElementChoiceGroup grp, ElementDefinition parent, boolean isConstraintMode) {
4153    Row row = gen.new Row();
4154    row.setAnchor(parent.getPath()+"-"+grp.getName());
4155    row.setColor(getRowColor(parent, isConstraintMode));
4156    row.setLineColor(1);
4157    row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE);
4158    row.getCells().add(gen.new Cell(null, null, "(Choice of one)", "", null));
4159    row.getCells().add(gen.new Cell());
4160    row.getCells().add(gen.new Cell(null, null, (grp.mandatory ? "1" : "0")+"..1", "", null));
4161    row.getCells().add(gen.new Cell());
4162    row.getCells().add(gen.new Cell());
4163    prow.getSubRows().add(row);
4164    return row;
4165  }
4166
4167  public Cell genElementNameCell(HierarchicalTableGenerator gen, ElementDefinition element, String profileBaseFileName, boolean snapshot, String corePath,
4168      String imagePath, boolean root, boolean logicalModel, boolean allInvariants, StructureDefinition profile, Row typesRow, Row row, boolean hasDef,
4169      boolean ext, UnusedTracker used, String ref, String sName) throws IOException {
4170    String hint = "";
4171    hint = checkAdd(hint, (element.hasSliceName() ? translate("sd.table", "Slice")+" "+element.getSliceName() : ""));
4172    if (hasDef && element.hasDefinition()) {
4173      hint = checkAdd(hint, (hasDef && element.hasSliceName() ? ": " : ""));
4174      hint = checkAdd(hint, !hasDef ? null : gt(element.getDefinitionElement()));
4175    }
4176    if (element.hasSlicing()) {
4177      sName = "Slices for "+sName;
4178    }
4179    Cell left = gen.new Cell(null, ref, sName, hint, null);
4180    row.getCells().add(left);
4181    return left;
4182  }
4183
4184  public List<Cell> genElementCells(HierarchicalTableGenerator gen, ElementDefinition element, String profileBaseFileName, boolean snapshot, String corePath,
4185      String imagePath, boolean root, boolean logicalModel, boolean allInvariants, StructureDefinition profile, Row typesRow, Row row, boolean hasDef,
4186      boolean ext, UnusedTracker used, String ref, String sName, Cell nameCell, boolean mustSupport) throws IOException {
4187    List<Cell> res = new ArrayList<>();
4188    Cell gc = gen.new Cell();
4189    row.getCells().add(gc);
4190    res.add(gc);
4191    if (element != null && element.getIsModifier()) {
4192      checkForNoChange(element.getIsModifierElement(), gc.addStyledText(translate("sd.table", "This element is a modifier element"), "?!", null, null, null, false));
4193    }
4194    if (element != null && element.getMustSupport()) {
4195      checkForNoChange(element.getMustSupportElement(), gc.addStyledText(translate("sd.table", "This element must be supported"), "S", "white", "red", null, false));
4196    }
4197    if (element != null && element.getIsSummary()) {
4198      checkForNoChange(element.getIsSummaryElement(), gc.addStyledText(translate("sd.table", "This element is included in summaries"), "\u03A3", null, null, null, false));
4199    }
4200    if (element != null && (hasNonBaseConstraints(element.getConstraint()) || hasNonBaseConditions(element.getCondition()))) {
4201      gc.addStyledText(translate("sd.table", "This element has or is affected by some invariants ("+listConstraintsAndConditions(element)+")"), "I", null, null, null, false);
4202    }
4203
4204    ExtensionContext extDefn = null;
4205    if (ext) {
4206      if (element != null && element.getType().size() == 1 && element.getType().get(0).hasProfile()) {
4207        String eurl = element.getType().get(0).getProfile().get(0).getValue();
4208        extDefn = locateExtension(StructureDefinition.class, eurl);
4209        if (extDefn == null) {
4210          res.add(genCardinality(gen, element, row, hasDef, used, null));
4211          res.add(addCell(row, gen.new Cell(null, null, "?gen-e1? "+element.getType().get(0).getProfile(), null, null)));
4212          res.add(generateDescription(gen, row, element, (ElementDefinition) element.getUserData(DERIVATION_POINTER), used.used, profile == null ? "" : profile.getUrl(), eurl, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot, mustSupport));
4213        } else {
4214          String name = urltail(eurl);
4215          nameCell.getPieces().get(0).setText(name);
4216          // left.getPieces().get(0).setReference((String) extDefn.getExtensionStructure().getTag("filename"));
4217          nameCell.getPieces().get(0).setHint(translate("sd.table", "Extension URL")+" = "+extDefn.getUrl());
4218          res.add(genCardinality(gen, element, row, hasDef, used, extDefn.getElement()));
4219          ElementDefinition valueDefn = extDefn.getExtensionValueDefinition();
4220          if (valueDefn != null && !"0".equals(valueDefn.getMax()))
4221            res.add(genTypes(gen, row, valueDefn, profileBaseFileName, profile, corePath, imagePath, root, mustSupport));
4222           else // if it's complex, we just call it nothing
4223              // genTypes(gen, row, extDefn.getSnapshot().getElement().get(0), profileBaseFileName, profile);
4224            res.add(addCell(row, gen.new Cell(null, null, "("+translate("sd.table", "Complex")+")", null, null)));
4225          res.add(generateDescription(gen, row, element, extDefn.getElement(), used.used, null, extDefn.getUrl(), profile, corePath, imagePath, root, logicalModel, allInvariants, valueDefn, snapshot, mustSupport));
4226        }
4227      } else {
4228        res.add(genCardinality(gen, element, row, hasDef, used, null));
4229        if ("0".equals(element.getMax()))
4230          res.add(addCell(row, gen.new Cell()));            
4231        else
4232          res.add(genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath, root, mustSupport));
4233        res.add(generateDescription(gen, row, element, (ElementDefinition) element.getUserData(DERIVATION_POINTER), used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot, mustSupport));
4234      }
4235    } else {
4236      res.add(genCardinality(gen, element, row, hasDef, used, null));
4237      if (hasDef && !"0".equals(element.getMax()) && typesRow == null)
4238        res.add(genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath, root, mustSupport));
4239      else
4240        res.add(addCell(row, gen.new Cell()));
4241      res.add(generateDescription(gen, row, element, (ElementDefinition) element.getUserData(DERIVATION_POINTER), used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot, mustSupport));
4242    }
4243    return res;
4244  }
4245
4246
4247    private Cell addCell(Row row, Cell cell) {
4248    row.getCells().add(cell);
4249    return (cell);
4250  }
4251
4252  private String checkAdd(String src, String app) {
4253    return app == null ? src : src + app;
4254  }
4255
4256  private boolean hasNonBaseConditions(List<IdType> conditions) {
4257    for (IdType c : conditions) {
4258      if (!isBaseCondition(c)) {
4259        return true;
4260      }
4261    }
4262    return false;
4263  }
4264
4265
4266  private boolean hasNonBaseConstraints(List<ElementDefinitionConstraintComponent> constraints) {
4267    for (ElementDefinitionConstraintComponent c : constraints) {
4268      if (!isBaseConstraint(c)) {
4269        return true;
4270      }
4271    }
4272    return false;
4273  }
4274
4275  private String listConstraintsAndConditions(ElementDefinition element) {
4276    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
4277    for (ElementDefinitionConstraintComponent con : element.getConstraint()) {
4278      if (!isBaseConstraint(con)) {
4279        b.append(con.getKey());
4280      }
4281    }
4282    for (IdType id : element.getCondition()) {
4283      if (!isBaseCondition(id)) {
4284        b.append(id.asStringValue());
4285      }
4286    }
4287    return b.toString();
4288  }
4289
4290  private boolean isBaseCondition(IdType c) {
4291    String key = c.asStringValue();
4292    return key.startsWith("ele-") || key.startsWith("res-") || key.startsWith("ext-") || key.startsWith("dom-") || key.startsWith("dr-");
4293  }
4294
4295  private boolean isBaseConstraint(ElementDefinitionConstraintComponent con) {
4296    String key = con.getKey();
4297    return key.startsWith("ele-") || key.startsWith("res-") || key.startsWith("ext-") || key.startsWith("dom-") || key.startsWith("dr-");
4298  }
4299
4300  private void makeChoiceRows(List<Row> subRows, ElementDefinition element, HierarchicalTableGenerator gen, String corePath, String profileBaseFileName, boolean mustSupportMode) {
4301    // create a child for each choice
4302    for (TypeRefComponent tr : element.getType()) {
4303      if (!mustSupportMode || allTypesMustSupport(element) || isMustSupport(tr)) {
4304        Row choicerow = gen.new Row();
4305        String t = tr.getWorkingCode();
4306        if (isReference(t)) {
4307          choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), null, null));
4308          choicerow.getCells().add(gen.new Cell());
4309          choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
4310          choicerow.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
4311          Cell c = gen.new Cell();
4312          choicerow.getCells().add(c);
4313          if (ADD_REFERENCE_TO_TABLE) {
4314            if (tr.getWorkingCode().equals("canonical"))
4315              c.getPieces().add(gen.new Piece(corePath+"datatypes.html#canonical", "canonical", null));
4316            else
4317              c.getPieces().add(gen.new Piece(corePath+"references.html#Reference", "Reference", null));
4318            if (!mustSupportMode && isMustSupportDirect(tr) && element.getMustSupport()) {
4319              c.addPiece(gen.new Piece(null, " ", null));
4320              c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false);
4321            }
4322            c.getPieces().add(gen.new Piece(null, "(", null));
4323          }
4324          boolean first = true;
4325          for (CanonicalType rt : tr.getTargetProfile()) {
4326            if (!mustSupportMode || allProfilesMustSupport(tr.getTargetProfile()) || isMustSupport(rt)) {
4327              if (!first)
4328                c.getPieces().add(gen.new Piece(null, " | ", null));
4329              genTargetLink(gen, profileBaseFileName, corePath, c, tr, rt.getValue());
4330              if (!mustSupportMode && isMustSupport(rt) && element.getMustSupport()) {
4331                c.addPiece(gen.new Piece(null, " ", null));
4332                c.addStyledText(translate("sd.table", "This target must be supported"), "S", "white", "red", null, false);
4333              }
4334              first = false;
4335            }
4336          }
4337          if (first) {
4338            c.getPieces().add(gen.new Piece(null, "Any", null));
4339          }
4340
4341          if (ADD_REFERENCE_TO_TABLE) { 
4342            c.getPieces().add(gen.new Piece(null, ")", null));
4343          }
4344
4345        } else {
4346          StructureDefinition sd = context.fetchTypeDefinition(t);
4347          if (sd == null) {
4348            System.out.println("Unable to find "+t);
4349            sd = context.fetchTypeDefinition(t);
4350          } else if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) {
4351            choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]",  Utilities.capitalize(t)), sd.getDescription(), null));
4352            choicerow.getCells().add(gen.new Cell());
4353            choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
4354            choicerow.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
4355            Cell c = gen.new Cell(null, corePath+"datatypes.html#"+t, sd.getType(), null, null);
4356            choicerow.getCells().add(c);
4357            if (!mustSupportMode && isMustSupport(tr) && element.getMustSupport()) {
4358              c.addPiece(gen.new Piece(null, " ", null));
4359              c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false);
4360            }
4361          } else {
4362            choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]",  Utilities.capitalize(t)), sd.getDescription(), null));
4363            choicerow.getCells().add(gen.new Cell());
4364            choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
4365            choicerow.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
4366            Cell c = gen.new Cell(null, pkp.getLinkFor(corePath, t), sd.getType(), null, null);
4367            choicerow.getCells().add(c);
4368            if (!mustSupportMode && isMustSupport(tr) && element.getMustSupport()) {
4369              c.addPiece(gen.new Piece(null, " ", null));
4370              c.addStyledText(translate("sd.table", "This type must be supported"), "S", "white", "red", null, false);
4371            }
4372          }
4373          if (tr.hasProfile()) {
4374            Cell typeCell = choicerow.getCells().get(3);
4375            typeCell.addPiece(gen.new Piece(null, "(", null));
4376            boolean first = true;
4377            for (CanonicalType pt : tr.getProfile()) {
4378              if (!mustSupportMode || allProfilesMustSupport(tr.getProfile()) || isMustSupport(pt)) {
4379                if (first) first = false; else typeCell.addPiece(gen.new Piece(null, " | ", null));
4380                StructureDefinition psd = context.fetchResource(StructureDefinition.class, pt.getValue());
4381                if (psd == null)
4382                  typeCell.addPiece(gen.new Piece(null, "?gen-e2?", null));
4383                else
4384                  typeCell.addPiece(gen.new Piece(psd.getUserString("path"), psd.getName(), psd.present()));
4385                if (!mustSupportMode && isMustSupport(pt) && element.getMustSupport()) {
4386                  typeCell.addPiece(gen.new Piece(null, " ", null));
4387                  typeCell.addStyledText(translate("sd.table", "This profile must be supported"), "S", "white", "red", null, false);
4388                }
4389              }
4390            }
4391            typeCell.addPiece(gen.new Piece(null, ")", null));
4392          }
4393        }    
4394        choicerow.getCells().add(gen.new Cell());
4395        subRows.add(choicerow);
4396      }
4397    }
4398  }
4399
4400  private boolean isReference(String t) {
4401    return t.equals("Reference") || t.equals("canonical"); 
4402  }  
4403
4404
4405
4406  private void genGridElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element, List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName, Boolean extensions, String corePath, String imagePath, boolean root, boolean isConstraintMode) throws IOException, FHIRException {
4407    StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1);
4408    String s = tail(element.getPath());
4409    List<ElementDefinition> children = getChildren(all, element);
4410    boolean isExtension = (s.equals("extension") || s.equals("modifierExtension"));
4411
4412    if (!onlyInformationIsMapping(all, element)) {
4413      Row row = gen.new Row();
4414      row.setAnchor(element.getPath());
4415      row.setColor(getRowColor(element, isConstraintMode));
4416      if (element.hasSlicing())
4417        row.setLineColor(1);
4418      else if (element.hasSliceName())
4419        row.setLineColor(2);
4420      else
4421        row.setLineColor(0);
4422      boolean hasDef = element != null;
4423      String ref = defPath == null ? null : defPath + element.getId();
4424      UnusedTracker used = new UnusedTracker();
4425      used.used = true;
4426      Cell left = gen.new Cell();
4427      if (element.getType().size() == 1 && element.getType().get(0).isPrimitive())
4428        left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())).addStyle("font-weight:bold"));
4429      else
4430        left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())));
4431      if (element.hasSliceName()) {
4432        left.getPieces().add(gen.new Piece("br"));
4433        String indent = StringUtils.repeat('\u00A0', 1+2*(element.getPath().split("\\.").length));
4434        left.getPieces().add(gen.new Piece(null, indent + "("+element.getSliceName() + ")", null));
4435      }
4436      row.getCells().add(left);
4437
4438      ExtensionContext extDefn = null;
4439      genCardinality(gen, element, row, hasDef, used, null);
4440      if (hasDef && !"0".equals(element.getMax()))
4441        genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath, root, false);
4442      else
4443        row.getCells().add(gen.new Cell());
4444      generateGridDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, null);
4445/*      if (element.hasSlicing()) {
4446        if (standardExtensionSlicing(element)) {
4447          used.used = element.hasType() && element.getType().get(0).hasProfile();
4448          showMissing = false;
4449        } else {
4450          row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE);
4451          row.getCells().get(2).getPieces().clear();
4452          for (Cell cell : row.getCells())
4453            for (Piece p : cell.getPieces()) {
4454              p.addStyle("font-style: italic");
4455            }
4456        }
4457      }*/
4458      rows.add(row);
4459      for (ElementDefinition child : children)
4460        if (child.getMustSupport())
4461          genGridElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, corePath, imagePath, false, isConstraintMode);
4462    }
4463  }
4464
4465
4466  private ExtensionContext locateExtension(Class<StructureDefinition> class1, String value)  {
4467    if (value.contains("#")) {
4468      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
4469      if (ext == null)
4470        return null;
4471      String tail = value.substring(value.indexOf("#")+1);
4472      ElementDefinition ed = null;
4473      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
4474        if (tail.equals(ted.getSliceName())) {
4475          ed = ted;
4476          return new ExtensionContext(ext, ed);
4477        }
4478      }
4479      return null;
4480    } else {
4481      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value);
4482      if (ext == null)
4483        return null;
4484      else 
4485        return new ExtensionContext(ext, ext.getSnapshot().getElement().get(0));
4486    }
4487  }
4488
4489
4490  private boolean extensionIsComplex(String value) {
4491    if (value.contains("#")) {
4492      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
4493    if (ext == null)
4494      return false;
4495      String tail = value.substring(value.indexOf("#")+1);
4496      ElementDefinition ed = null;
4497      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
4498        if (tail.equals(ted.getSliceName())) {
4499          ed = ted;
4500          break;
4501        }
4502      }
4503      if (ed == null)
4504        return false;
4505      int i = ext.getSnapshot().getElement().indexOf(ed);
4506      int j = i+1;
4507      while (j < ext.getSnapshot().getElement().size() && !ext.getSnapshot().getElement().get(j).getPath().equals(ed.getPath()))
4508        j++;
4509      return j - i > 5;
4510    } else {
4511      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value);
4512      return ext != null && ext.getSnapshot().getElement().size() > 5;
4513    }
4514  }
4515
4516
4517  public String getRowColor(ElementDefinition element, boolean isConstraintMode) {
4518    switch (element.getUserInt(UD_ERROR_STATUS)) {
4519    case STATUS_HINT: return ROW_COLOR_HINT;
4520    case STATUS_WARNING: return ROW_COLOR_WARNING;
4521    case STATUS_ERROR: return ROW_COLOR_ERROR;
4522    case STATUS_FATAL: return ROW_COLOR_FATAL;
4523    }
4524    if (isConstraintMode && !element.getMustSupport() && !element.getIsModifier() && element.getPath().contains("."))
4525      return null; // ROW_COLOR_NOT_MUST_SUPPORT;
4526    else
4527      return null;
4528  }
4529
4530
4531  private String urltail(String path) {
4532    if (path.contains("#"))
4533      return path.substring(path.lastIndexOf('#')+1);
4534    if (path.contains("/"))
4535      return path.substring(path.lastIndexOf('/')+1);
4536    else
4537      return path;
4538
4539  }
4540
4541  private boolean standardExtensionSlicing(ElementDefinition element) {
4542    String t = tail(element.getPath());
4543    return (t.equals("extension") || t.equals("modifierExtension"))
4544          && element.getSlicing().getRules() != SlicingRules.CLOSED && element.getSlicing().getDiscriminator().size() == 1 && element.getSlicing().getDiscriminator().get(0).getPath().equals("url") && element.getSlicing().getDiscriminator().get(0).getType().equals(DiscriminatorType.VALUE);
4545  }
4546
4547  private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, boolean snapshot, boolean mustSupportOnly) throws IOException, FHIRException {
4548    return generateDescription(gen, row, definition, fallback, used, baseURL, url, profile, corePath, imagePath, root, logicalModel, allInvariants, null, snapshot, mustSupportOnly);
4549  }
4550  
4551  private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, ElementDefinition valueDefn, boolean snapshot, boolean mustSupportOnly) throws IOException, FHIRException {
4552    Cell c = gen.new Cell();
4553    row.getCells().add(c);
4554
4555    if (used) {
4556      if (logicalModel && ToolingExtensions.hasExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) {
4557        if (root) {
4558          c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold"));
4559          c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null));        
4560        } else if (!root && ToolingExtensions.hasExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace") && 
4561            !ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace").equals(ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))) {
4562          c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold"));
4563          c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null));        
4564        }
4565      }
4566      if (root) {
4567        if (profile.getAbstract()) {
4568          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
4569          c.addPiece(gen.new Piece(null, "This is an abstract profile", null));          
4570        }
4571      }
4572      if (definition.getPath().endsWith("url") && definition.hasFixed()) {
4573        c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen")));
4574      } else {
4575        if (definition != null && definition.hasShort()) {
4576          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
4577          c.addPiece(checkForNoChange(definition.getShortElement(), gen.new Piece(null, gt(definition.getShortElement()), null)));
4578        } else if (fallback != null && fallback.hasShort()) {
4579          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
4580          c.addPiece(gen.new Piece(null, gt(fallback.getShortElement()), null).addStyle("opacity: 0.5"));
4581        }
4582        if (url != null) {
4583          if (!c.getPieces().isEmpty()) 
4584            c.addPiece(gen.new Piece("br"));
4585          String fullUrl = url.startsWith("#") ? baseURL+url : url;
4586          StructureDefinition ed = context.fetchResource(StructureDefinition.class, url);
4587          String ref = null;
4588          String ref2 = null;
4589          String fixedUrl = null;
4590          if (ed != null) {
4591            String p = ed.getUserString("path");
4592            if (p != null) {
4593              ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p);
4594            }             
4595            fixedUrl = getFixedUrl(ed);
4596            if (fixedUrl != null) {// if its null, we guess that it's not a profiled extension?
4597              if (fixedUrl.equals(url))
4598                fixedUrl = null;
4599              else {
4600                StructureDefinition ed2 = context.fetchResource(StructureDefinition.class, fixedUrl);
4601                if (ed2 != null) {
4602                  String p2 = ed2.getUserString("path");
4603                  if (p2 != null) {
4604                    ref2 = p2.startsWith("http:") || igmode ? p2 : Utilities.pathURL(corePath, p2);
4605                  }                              
4606                }
4607              }
4608            }
4609          }
4610          if (fixedUrl == null) {
4611            c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold"));
4612            c.getPieces().add(gen.new Piece(ref, fullUrl, null));
4613          } else { 
4614            // reference to a profile take on the extension show the base URL
4615            c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold"));
4616            c.getPieces().add(gen.new Piece(ref2, fixedUrl, null));
4617            c.getPieces().add(gen.new Piece(null, translate("sd.table", " profiled by ")+" ", null).addStyle("font-weight:bold"));
4618            c.getPieces().add(gen.new Piece(ref, fullUrl, null));
4619          
4620          }
4621        }
4622
4623        if (definition.hasSlicing()) {
4624          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
4625          c.getPieces().add(gen.new Piece(null, translate("sd.table", "Slice")+": ", null).addStyle("font-weight:bold"));
4626          c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null));
4627        }
4628        if (!definition.getPath().contains(".") && ToolingExtensions.hasExtension(profile, ToolingExtensions.EXT_BINDING_STYLE)) {
4629          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
4630          c.getPieces().add(gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold"));
4631          c.getPieces().add(gen.new Piece(null, "This type can be bound to a value set using the ", null));
4632          c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(profile, ToolingExtensions.EXT_BINDING_STYLE), null));
4633          c.getPieces().add(gen.new Piece(null, " binding style", null));            
4634          
4635        }
4636        if (definition.hasExtension(ToolingExtensions.EXT_XML_NAME)) {
4637          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
4638          if (definition.hasExtension(ToolingExtensions.EXT_XML_NAMESPACE)) {
4639            c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML")+": ", null).addStyle("font-weight:bold"));
4640            c.getPieces().add(gen.new Piece(null, definition.getExtensionString(ToolingExtensions.EXT_XML_NAME), null));
4641            c.getPieces().add(gen.new Piece(null, " (", null));
4642            c.getPieces().add(gen.new Piece(null, definition.getExtensionString(ToolingExtensions.EXT_XML_NAMESPACE), null));
4643            c.getPieces().add(gen.new Piece(null, ")", null));            
4644          } else {
4645            c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Element Name")+": ", null).addStyle("font-weight:bold"));
4646            c.getPieces().add(gen.new Piece(null, definition.getExtensionString(ToolingExtensions.EXT_XML_NAME), null));
4647          }            
4648        } else if (definition.hasExtension(ToolingExtensions.EXT_XML_NAMESPACE)) {
4649          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
4650          c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold"));
4651          c.getPieces().add(gen.new Piece(null, definition.getExtensionString(ToolingExtensions.EXT_XML_NAMESPACE), null));          
4652        }
4653        if (definition != null) {
4654          ElementDefinitionBindingComponent binding = null;
4655          if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty())
4656            binding = makeUnifiedBinding(valueDefn.getBinding(), valueDefn);
4657          else if (definition.hasBinding())
4658            binding = makeUnifiedBinding(definition.getBinding(), definition);
4659          if (binding!=null && !binding.isEmpty()) {
4660            if (!c.getPieces().isEmpty()) 
4661              c.addPiece(gen.new Piece("br"));
4662            BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath());
4663            c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold")));
4664              c.getPieces().add(checkForNoChange(binding.getValueSetElement(), gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath+br.url, br.display, null)));
4665            if (binding.hasStrength()) {
4666              c.getPieces().add(checkForNoChange(binding.getStrengthElement(), gen.new Piece(null, " (", null)));
4667              c.getPieces().add(checkForNoChange(binding.getStrengthElement(), gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), egt(binding.getStrengthElement()), binding.getStrength().getDefinition())));                            
4668              c.getPieces().add(checkForNoChange(binding.getStrengthElement(), gen.new Piece(null, ")", null)));
4669            }
4670            if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) {
4671              br = pkp.resolveBinding(profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), definition.getPath());
4672              c.addPiece(gen.new Piece("br"));
4673              c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"extension-elementdefinition-maxvalueset.html", translate("sd.table", "Max Binding")+": ", "Max Value Set Extension").addStyle("font-weight:bold")));             
4674              c.getPieces().add(checkForNoChange(binding, gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath+br.url, br.display, null)));
4675            }
4676            if (binding.hasExtension(ToolingExtensions.EXT_MIN_VALUESET)) {
4677              br = pkp.resolveBinding(profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MIN_VALUESET), definition.getPath());
4678              c.addPiece(gen.new Piece("br"));
4679              c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"extension-elementdefinition-minvalueset.html", translate("sd.table", "Min Binding")+": ", "Min Value Set Extension").addStyle("font-weight:bold")));             
4680              c.getPieces().add(checkForNoChange(binding, gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath+br.url, br.display, null)));
4681            }
4682            if (binding.hasDescription() && MarkDownProcessor.isSimpleMarkdown(binding.getDescription())) {
4683              c.getPieces().add(gen.new Piece(null, ": ", null));
4684              c.addMarkdownNoPara(PublicationHacker.fixBindingDescriptions(context, binding.getDescriptionElement()).asStringValue(), checkForNoChange(PublicationHacker.fixBindingDescriptions(context, binding.getDescriptionElement())));
4685            } 
4686          }
4687          for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
4688            if (!inv.hasSource() || profile == null || inv.getSource().equals(profile.getUrl()) || allInvariants) {
4689              if (!c.getPieces().isEmpty()) 
4690                c.addPiece(gen.new Piece("br"));
4691              c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold")));
4692              c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, gt(inv.getHumanElement()), null)));
4693            }
4694          }
4695          if ((definition.hasBase() && "*".equals(definition.getBase().getMax())) || (definition.hasMax() && "*".equals(definition.getMax()))) {
4696            if (c.getPieces().size() > 0)
4697              c.addPiece(gen.new Piece("br"));
4698            if (definition.hasOrderMeaning()) {
4699              c.getPieces().add(gen.new Piece(null, "This repeating element order: "+definition.getOrderMeaning(), null));
4700            } else {
4701              // don't show this, this it's important: c.getPieces().add(gen.new Piece(null, "This repeating element has no defined order", null));
4702            }           
4703          }
4704          if (definition.hasFixed()) {
4705            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
4706            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, translate("sd.table", "Fixed Value")+": ", null).addStyle("font-weight:bold")));
4707            if (!useTableForFixedValues || definition.getFixed().isPrimitive()) {
4708              String s = buildJson(definition.getFixed());
4709              String link = null;
4710              if (Utilities.isAbsoluteUrl(s))
4711                link = pkp.getLinkForUrl(corePath, s);
4712              c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen")));
4713            } else {
4714              c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "As shown", null).addStyle("color: darkgreen")));
4715              genFixedValue(gen, row, definition.getFixed(), snapshot, false, corePath, false);              
4716            }
4717            if (isCoded(definition.getFixed()) && !hasDescription(definition.getFixed())) {
4718              Piece p = describeCoded(gen, definition.getFixed());
4719              if (p != null)
4720                c.getPieces().add(p);
4721            }
4722          } else if (definition.hasPattern()) {
4723            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
4724            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, translate("sd.table", "Required Pattern")+": ", null).addStyle("font-weight:bold")));
4725            if (!useTableForFixedValues || definition.getPattern().isPrimitive())
4726              c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen")));
4727            else {
4728              c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "At least the following", null).addStyle("color: darkgreen")));
4729              genFixedValue(gen, row, definition.getPattern(), snapshot, true, corePath, mustSupportOnly);
4730            }
4731          } else if (definition.hasExample()) {
4732            for (ElementDefinitionExampleComponent ex : definition.getExample()) {
4733              if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
4734              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, translate("sd.table", "Example")+("".equals("General")? "" : " "+ex.getLabel())+": ", null).addStyle("font-weight:bold")));
4735              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen")));
4736            }
4737          }
4738          if (definition.hasMaxLength() && definition.getMaxLength()!=0) {
4739            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
4740            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold")));
4741            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen")));
4742          }
4743          if (profile != null) {
4744            for (StructureDefinitionMappingComponent md : profile.getMapping()) {
4745              if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) {
4746                ElementDefinitionMappingComponent map = null;
4747                for (ElementDefinitionMappingComponent m : definition.getMapping()) 
4748                  if (m.getIdentity().equals(md.getIdentity()))
4749                    map = m;
4750                if (map != null) {
4751                  for (int i = 0; i<definition.getMapping().size(); i++){
4752                    c.addPiece(gen.new Piece("br"));
4753                    c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null));
4754                  }
4755                }
4756              }
4757            }
4758          }
4759        }
4760      }
4761    }
4762    return c;
4763  }
4764
4765  private ElementDefinitionBindingComponent makeUnifiedBinding(ElementDefinitionBindingComponent binding, ElementDefinition element) {
4766    if (!element.hasUserData(DERIVATION_POINTER)) {
4767      return binding;
4768    }
4769    ElementDefinition base = (ElementDefinition) element.getUserData(DERIVATION_POINTER);
4770    if (!base.hasBinding()) {
4771      return binding;
4772    }
4773    ElementDefinitionBindingComponent o = base.getBinding();
4774    ElementDefinitionBindingComponent b = new ElementDefinitionBindingComponent();
4775    b.setUserData(DERIVATION_POINTER, o);
4776    if (binding.hasValueSet()) {
4777      b.setValueSet(binding.getValueSet());
4778    } else if (o.hasValueSet()) {
4779      b.setValueSet(o.getValueSet());
4780      b.getValueSetElement().setUserData(DERIVATION_EQUALS, o.getValueSetElement());
4781    }
4782    if (binding.hasStrength()) {
4783      b.setStrength(binding.getStrength());
4784    } else if (o.hasStrength()) {
4785      b.setStrength(o.getStrength());
4786      b.getStrengthElement().setUserData(DERIVATION_EQUALS, o.getStrengthElement());
4787    }
4788    if (binding.hasDescription()) {
4789      b.setDescription(binding.getDescription());
4790    } else if (o.hasDescription()) {
4791      b.setDescription(o.getDescription());
4792      b.getDescriptionElement().setUserData(DERIVATION_EQUALS, o.getDescriptionElement());
4793    }
4794    return b;
4795  }
4796
4797  private void genFixedValue(HierarchicalTableGenerator gen, Row erow, DataType value, boolean snapshot, boolean pattern, String corePath, boolean skipnoValue) {
4798    String ref = pkp.getLinkFor(corePath, value.fhirType());
4799    if (ref != null && ref.contains(".html")) {
4800      ref = ref.substring(0, ref.indexOf(".html"))+"-definitions.html#";
4801    } else {
4802      ref = "?gen-fv?";
4803    }
4804    StructureDefinition sd = context.fetchTypeDefinition(value.fhirType());
4805    
4806    for (org.hl7.fhir.r4b.model.Property t : value.children()) {
4807      if (t.getValues().size() > 0 || snapshot) {
4808        ElementDefinition ed = findElementDefinition(sd, t.getName());
4809        if (t.getValues().size() == 0 || (t.getValues().size() == 1 && t.getValues().get(0).isEmpty())) {
4810          if (!skipnoValue) {
4811            Row row = gen.new Row();
4812            erow.getSubRows().add(row);
4813            Cell c = gen.new Cell();
4814            row.getCells().add(c);
4815            c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref+ed.getPath() : corePath+(VersionUtilities.isR5Ver(context.getVersion()) ? "types-definitions.html#"+ed.getBase().getPath() : "element-definitions.html#"+ed.getBase().getPath())), t.getName(), null));
4816            c = gen.new Cell();
4817            row.getCells().add(c);
4818            c.addPiece(gen.new Piece(null, null, null));
4819            c = gen.new Cell();
4820            row.getCells().add(c);
4821            if (!pattern) {
4822              c.addPiece(gen.new Piece(null, "0..0", null));
4823              row.setIcon("icon_fixed.gif", "Fixed Value" /*HierarchicalTableGenerator.TEXT_ICON_FIXED*/);
4824            } else if (isPrimitive(t.getTypeCode())) {
4825              row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
4826              c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null));
4827            } else if (isReference(t.getTypeCode())) { 
4828              row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
4829              c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null));
4830            } else { 
4831              row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
4832              c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null));
4833            }
4834            c = gen.new Cell();
4835            row.getCells().add(c);
4836            if (t.getTypeCode().contains("(")) {
4837              String tc = t.getTypeCode();
4838              String tn = tc.substring(0, tc.indexOf("("));
4839              c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, tn), tn, null));
4840              c.addPiece(gen.new Piece(null, "(", null));
4841              String[] p = tc.substring(tc.indexOf("(")+1, tc.indexOf(")")).split("\\|");
4842              for (String s : p) {
4843                c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, s), s, null));
4844              }
4845              c.addPiece(gen.new Piece(null, ")", null));            
4846            } else {
4847              c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, t.getTypeCode()), t.getTypeCode(), null));
4848            }
4849            c = gen.new Cell();
4850            c.addPiece(gen.new Piece(null, ed.getShort(), null));
4851            row.getCells().add(c);
4852          }
4853        } else {
4854          for (Base b : t.getValues()) {
4855            Row row = gen.new Row();
4856            erow.getSubRows().add(row);
4857            row.setIcon("icon_fixed.gif", "Fixed Value" /*HierarchicalTableGenerator.TEXT_ICON_FIXED*/);
4858
4859            Cell c = gen.new Cell();
4860            row.getCells().add(c);
4861            c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref+ed.getPath() : (VersionUtilities.isThisOrLater("4.1", context.getVersion()) ? corePath+"types-definitions.html#"+ed.getBase().getPath() : corePath+"element-definitions.html#"+ed.getBase().getPath())), t.getName(), null));
4862
4863            c = gen.new Cell();
4864            row.getCells().add(c);
4865            c.addPiece(gen.new Piece(null, null, null));
4866
4867            c = gen.new Cell();
4868            row.getCells().add(c);
4869            if (pattern)
4870              c.addPiece(gen.new Piece(null, "1.."+(t.getMaxCardinality() == 2147483647 ? "*" : Integer.toString(t.getMaxCardinality())), null));
4871            else
4872              c.addPiece(gen.new Piece(null, "1..1", null));
4873
4874            c = gen.new Cell();
4875            row.getCells().add(c);
4876            if (b.fhirType().contains("(")) {
4877              String tc = b.fhirType();
4878              String tn = tc.substring(0, tc.indexOf("("));
4879              c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, tn), tn, null));
4880              c.addPiece(gen.new Piece(null, "(", null));
4881              String[] p = tc.substring(tc.indexOf("(")+1, tc.indexOf(")")).split("\\|");
4882              for (String s : p) {
4883                c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, s), s, null));
4884              }
4885              c.addPiece(gen.new Piece(null, ")", null));            
4886            } else {
4887              c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, b.fhirType()), b.fhirType(), null));
4888            }
4889
4890            if (b.isPrimitive()) {
4891              c = gen.new Cell();
4892              row.getCells().add(c);
4893              c.addPiece(gen.new Piece(null, ed.getShort(), null));
4894              c.addPiece(gen.new Piece("br"));
4895              c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold"));
4896              String s = b.primitiveValue();
4897              // ok. let's see if we can find a relevant link for this
4898              String link = null;
4899              if (Utilities.isAbsoluteUrl(s)) {
4900                link = pkp.getLinkForUrl(corePath, s);
4901              }
4902              c.getPieces().add(gen.new Piece(link, s, null).addStyle("color: darkgreen"));
4903            } else {
4904              c = gen.new Cell();
4905              row.getCells().add(c);
4906              c.addPiece(gen.new Piece(null, ed.getShort(), null));
4907              c.addPiece(gen.new Piece("br"));
4908              c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold"));
4909              c.getPieces().add(gen.new Piece(null, "(complex)", null).addStyle("color: darkgreen"));
4910              genFixedValue(gen, row, (DataType) b, snapshot, pattern, corePath, skipnoValue);
4911            }
4912          }
4913        }
4914      }
4915    }
4916  }
4917
4918
4919  private ElementDefinition findElementDefinition(StructureDefinition sd, String name) {
4920    String path = sd.getType()+"."+name;
4921    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
4922      if (ed.getPath().equals(path))
4923        return ed;
4924    }
4925    throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_ELEMENT_, path));
4926  }
4927
4928
4929  private String getFixedUrl(StructureDefinition sd) {
4930    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
4931      if (ed.getPath().equals("Extension.url")) {
4932        if (ed.hasFixed() && ed.getFixed() instanceof UriType)
4933          return ed.getFixed().primitiveValue();
4934      }
4935    }
4936    return null;
4937  }
4938
4939
4940  private Piece describeCoded(HierarchicalTableGenerator gen, DataType fixed) {
4941    if (fixed instanceof Coding) {
4942      Coding c = (Coding) fixed;
4943      ValidationResult vr = context.validateCode(terminologyServiceOptions , c.getSystem(), c.getVersion(), c.getCode(), c.getDisplay());
4944      if (vr.getDisplay() != null)
4945        return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen");
4946    } else if (fixed instanceof CodeableConcept) {
4947      CodeableConcept cc = (CodeableConcept) fixed;
4948      for (Coding c : cc.getCoding()) {
4949        ValidationResult vr = context.validateCode(terminologyServiceOptions, c.getSystem(), c.getVersion(), c.getCode(), c.getDisplay());
4950        if (vr.getDisplay() != null)
4951          return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen");
4952      }
4953    }
4954    return null;
4955  }
4956
4957
4958  private boolean hasDescription(DataType fixed) {
4959    if (fixed instanceof Coding) {
4960      return ((Coding) fixed).hasDisplay();
4961    } else if (fixed instanceof CodeableConcept) {
4962      CodeableConcept cc = (CodeableConcept) fixed;
4963      if (cc.hasText())
4964        return true;
4965      for (Coding c : cc.getCoding())
4966        if (c.hasDisplay())
4967         return true;
4968    } // (fixed instanceof CodeType) || (fixed instanceof Quantity);
4969    return false;
4970  }
4971
4972
4973  private boolean isCoded(DataType fixed) {
4974    return (fixed instanceof Coding) || (fixed instanceof CodeableConcept) || (fixed instanceof CodeType) || (fixed instanceof Quantity);
4975  }
4976
4977
4978  private Cell generateGridDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, ElementDefinition valueDefn) throws IOException, FHIRException {
4979    Cell c = gen.new Cell();
4980    row.getCells().add(c);
4981
4982    if (used) {
4983      if (definition.hasContentReference()) {
4984        ElementInStructure ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference(), profile);
4985        if (ed == null)
4986          c.getPieces().add(gen.new Piece(null, "Unknown reference to "+definition.getContentReference(), null));
4987        else {
4988          if (ed.getSource() == profile) {
4989            c.getPieces().add(gen.new Piece("#"+ed.getElement().getPath(), "See "+ed.getElement().getPath(), null));
4990          } else {
4991            c.getPieces().add(gen.new Piece(ed.getSource().getUserData("path")+"#"+ed.getElement().getPath(), "See "+ed.getSource().getType()+"."+ed.getElement().getPath(), null));
4992          }          
4993        }
4994      }
4995      if (definition.getPath().endsWith("url") && definition.hasFixed()) {
4996        c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen")));
4997      } else {
4998        if (url != null) {
4999          if (!c.getPieces().isEmpty()) 
5000            c.addPiece(gen.new Piece("br"));
5001          String fullUrl = url.startsWith("#") ? baseURL+url : url;
5002          StructureDefinition ed = context.fetchResource(StructureDefinition.class, url);
5003          String ref = null;
5004          if (ed != null) {
5005            String p = ed.getUserString("path");
5006            if (p != null) {
5007              ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p);
5008            }
5009          }
5010          c.getPieces().add(gen.new Piece(null, "URL: ", null).addStyle("font-weight:bold"));
5011          c.getPieces().add(gen.new Piece(ref, fullUrl, null));
5012        }
5013
5014        if (definition.hasSlicing()) {
5015          if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
5016          c.getPieces().add(gen.new Piece(null, "Slice: ", null).addStyle("font-weight:bold"));
5017          c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null));
5018        }
5019        if (definition != null) {
5020          ElementDefinitionBindingComponent binding = null;
5021          if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty())
5022            binding = valueDefn.getBinding();
5023          else if (definition.hasBinding())
5024            binding = definition.getBinding();
5025          if (binding!=null && !binding.isEmpty()) {
5026            if (!c.getPieces().isEmpty()) 
5027              c.addPiece(gen.new Piece("br"));
5028            BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath());
5029            c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, "Binding: ", null).addStyle("font-weight:bold")));
5030            c.getPieces().add(checkForNoChange(binding, gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath+br.url, br.display, null)));
5031            if (binding.hasStrength()) {
5032              c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null)));
5033              c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), binding.getStrength().toCode(), binding.getStrength().getDefinition())));              c.getPieces().add(gen.new Piece(null, ")", null));
5034            }
5035            if (binding.hasDescription() && MarkDownProcessor.isSimpleMarkdown(binding.getDescription())) {
5036              c.getPieces().add(gen.new Piece(null, ": ", null));
5037              c.addMarkdownNoPara(PublicationHacker.fixBindingDescriptions(context, binding.getDescriptionElement()).asStringValue());
5038            }
5039          }
5040          for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
5041            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
5042            c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold")));
5043            if (inv.getHumanElement().hasExtension("http://hl7.org/fhir/StructureDefinition/rendering-markdown")) {
5044              c.addMarkdown(inv.getHumanElement().getExtensionString("http://hl7.org/fhir/StructureDefinition/rendering-markdown"));
5045            } else {
5046              c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null)));
5047            }
5048          }
5049          if (definition.hasFixed()) {
5050            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
5051            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight:bold")));
5052            String s = buildJson(definition.getFixed());
5053            String link = null;
5054            if (Utilities.isAbsoluteUrl(s))
5055              link = pkp.getLinkForUrl(corePath, s);
5056            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen")));
5057          } else if (definition.hasPattern()) {
5058            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
5059            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "Required Pattern: ", null).addStyle("font-weight:bold")));
5060            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen")));
5061          } else if (definition.hasExample()) {
5062            for (ElementDefinitionExampleComponent ex : definition.getExample()) {
5063              if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
5064              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, "Example'"+("".equals("General")? "" : " "+ex.getLabel()+"'")+": ", null).addStyle("font-weight:bold")));
5065              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen")));
5066            }
5067          }
5068          if (definition.hasMaxLength() && definition.getMaxLength()!=0) {
5069            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
5070            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold")));
5071            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen")));
5072          }
5073          if (profile != null) {
5074            for (StructureDefinitionMappingComponent md : profile.getMapping()) {
5075              if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) {
5076                ElementDefinitionMappingComponent map = null;
5077                for (ElementDefinitionMappingComponent m : definition.getMapping()) 
5078                  if (m.getIdentity().equals(md.getIdentity()))
5079                    map = m;
5080                if (map != null) {
5081                  for (int i = 0; i<definition.getMapping().size(); i++){
5082                    c.addPiece(gen.new Piece("br"));
5083                    c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null));
5084                  }
5085                }
5086              }
5087            }
5088          }
5089          if (definition.hasDefinition()) {
5090            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
5091            c.getPieces().add(gen.new Piece(null, "Definition: ", null).addStyle("font-weight:bold"));
5092            c.addPiece(gen.new Piece("br"));
5093            c.addMarkdown(definition.getDefinition());
5094//            c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null)));
5095          }
5096          if (definition.getComment()!=null) {
5097            if (!c.getPieces().isEmpty()) { c.addPiece(gen.new Piece("br")); }
5098            c.getPieces().add(gen.new Piece(null, "Comments: ", null).addStyle("font-weight:bold"));
5099            c.addPiece(gen.new Piece("br"));
5100            c.addMarkdown(definition.getComment());
5101//            c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null)));
5102          }
5103        }
5104      }
5105    }
5106    return c;
5107  }
5108
5109
5110
5111  private String buildJson(DataType value) throws IOException {
5112    if (value instanceof PrimitiveType)
5113      return ((PrimitiveType) value).asStringValue();
5114
5115    IParser json = context.newJsonParser();
5116    return json.composeString(value, null);
5117  }
5118
5119
5120  public String describeSlice(ElementDefinitionSlicingComponent slicing) {
5121    return translate("sd.table", "%s, %s by %s", slicing.getOrdered() ? translate("sd.table", "Ordered") : translate("sd.table", "Unordered"), describe(slicing.getRules()), commas(slicing.getDiscriminator()));
5122  }
5123
5124  private String commas(List<ElementDefinitionSlicingDiscriminatorComponent> list) {
5125    CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder();
5126    for (ElementDefinitionSlicingDiscriminatorComponent id : list)
5127      c.append((id.hasType() ? id.getType().toCode() : "??")+":"+id.getPath());
5128    return c.toString();
5129  }
5130
5131
5132  private String describe(SlicingRules rules) {
5133    if (rules == null)
5134      return translate("sd.table", "Unspecified");
5135    switch (rules) {
5136    case CLOSED : return translate("sd.table", "Closed");
5137    case OPEN : return translate("sd.table", "Open");
5138    case OPENATEND : return translate("sd.table", "Open At End");
5139    default:
5140      return "?gen-sr?";
5141    }
5142  }
5143
5144  private boolean onlyInformationIsMapping(List<ElementDefinition> list, ElementDefinition e) {
5145    return (!e.hasSliceName() && !e.hasSlicing() && (onlyInformationIsMapping(e))) &&
5146        getChildren(list, e).isEmpty();
5147  }
5148
5149  private boolean onlyInformationIsMapping(ElementDefinition d) {
5150    return !d.hasShort() && !d.hasDefinition() &&
5151        !d.hasRequirements() && !d.getAlias().isEmpty() && !d.hasMinElement() &&
5152        !d.hasMax() && !d.getType().isEmpty() && !d.hasContentReference() &&
5153        !d.hasExample() && !d.hasFixed() && !d.hasMaxLengthElement() &&
5154        !d.getCondition().isEmpty() && !d.getConstraint().isEmpty() && !d.hasMustSupportElement() &&
5155        !d.hasBinding();
5156  }
5157
5158  private boolean allAreReference(List<TypeRefComponent> types) {
5159    for (TypeRefComponent t : types) {
5160      if (!t.hasTarget())
5161        return false;
5162    }
5163    return true;
5164  }
5165
5166  private List<ElementDefinition> getChildren(List<ElementDefinition> all, ElementDefinition element) {
5167    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
5168    int i = all.indexOf(element)+1;
5169    while (i < all.size() && all.get(i).getPath().length() > element.getPath().length()) {
5170      if ((all.get(i).getPath().substring(0, element.getPath().length()+1).equals(element.getPath()+".")) && !all.get(i).getPath().substring(element.getPath().length()+1).contains("."))
5171        result.add(all.get(i));
5172      i++;
5173    }
5174    return result;
5175  }
5176
5177  private String tail(String path) {
5178    if (path.contains("."))
5179      return path.substring(path.lastIndexOf('.')+1);
5180    else
5181      return path;
5182  }
5183
5184  private boolean isDataType(String value) {
5185    StructureDefinition sd = context.fetchTypeDefinition(value);
5186    if (sd == null) // might be running before all SDs are available
5187      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", 
5188            "ContactDetail", "Contributor", "DataRequirement", "Expression", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition", "UsageContext");
5189    else 
5190      return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION;
5191  }
5192
5193  private boolean isConstrainedDataType(String value) {
5194    StructureDefinition sd = context.fetchTypeDefinition(value);
5195    if (sd == null) // might be running before all SDs are available
5196      return Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity");
5197    else 
5198      return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.CONSTRAINT;
5199  }
5200
5201  private String baseType(String value) {
5202    StructureDefinition sd = context.fetchTypeDefinition(value);
5203    if (sd != null) // might be running before all SDs are available
5204      return sd.getType();
5205    if (Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity"))
5206      return "Quantity";
5207    throw new Error(context.formatMessage(I18nConstants.INTERNAL_ERROR___TYPE_NOT_KNOWN_, value));
5208  }
5209
5210
5211  public boolean isPrimitive(String value) {
5212    StructureDefinition sd = context.fetchTypeDefinition(value);
5213    if (sd == null) // might be running before all SDs are available
5214      return Utilities.existsInList(value, "base64Binary", "boolean", "canonical", "code", "date", "dateTime", "decimal", "id", "instant", "integer", "integer64", "markdown", "oid", "positiveInt", "string", "time", "unsignedInt", "uri", "url", "uuid");
5215    else 
5216      return sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
5217  }
5218
5219//  private static String listStructures(StructureDefinition p) {
5220//    StringBuilder b = new StringBuilder();
5221//    boolean first = true;
5222//    for (ProfileStructureComponent s : p.getStructure()) {
5223//      if (first)
5224//        first = false;
5225//      else
5226//        b.append(", ");
5227//      if (pkp != null && pkp.hasLinkFor(s.getType()))
5228//        b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>");
5229//      else
5230//        b.append(s.getType());
5231//    }
5232//    return b.toString();
5233//  }
5234
5235
5236  public StructureDefinition getProfile(StructureDefinition source, String url) {
5237        StructureDefinition profile = null;
5238        String code = null;
5239        if (url.startsWith("#")) {
5240                profile = source;
5241                code = url.substring(1);
5242        } else if (context != null) {
5243                String[] parts = url.split("\\#");
5244                profile = context.fetchResource(StructureDefinition.class, parts[0]);
5245      code = parts.length == 1 ? null : parts[1];
5246        }         
5247        if (profile == null)
5248                return null;
5249        if (code == null)
5250                return profile;
5251        for (Resource r : profile.getContained()) {
5252                if (r instanceof StructureDefinition && r.getId().equals(code))
5253                        return (StructureDefinition) r;
5254        }
5255        return null;
5256  }
5257
5258
5259
5260  public static class ElementDefinitionHolder {
5261    private String name;
5262    private ElementDefinition self;
5263    private int baseIndex = 0;
5264    private List<ElementDefinitionHolder> children;
5265    private boolean placeHolder = false;
5266
5267    public ElementDefinitionHolder(ElementDefinition self, boolean isPlaceholder) {
5268      super();
5269      this.self = self;
5270      this.name = self.getPath();
5271      this.placeHolder = isPlaceholder;
5272      children = new ArrayList<ElementDefinitionHolder>();      
5273    }
5274
5275    public ElementDefinitionHolder(ElementDefinition self) {
5276      this(self, false);
5277    }
5278
5279    public ElementDefinition getSelf() {
5280      return self;
5281    }
5282
5283    public List<ElementDefinitionHolder> getChildren() {
5284      return children;
5285    }
5286
5287    public int getBaseIndex() {
5288      return baseIndex;
5289    }
5290
5291    public void setBaseIndex(int baseIndex) {
5292      this.baseIndex = baseIndex;
5293    }
5294
5295    public boolean isPlaceHolder() {
5296      return this.placeHolder;
5297    }
5298
5299    @Override
5300    public String toString() {
5301      if (self.hasSliceName())
5302        return self.getPath()+"("+self.getSliceName()+")";
5303      else
5304        return self.getPath();
5305    }
5306  }
5307
5308  public static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> {
5309
5310    private boolean inExtension;
5311    private List<ElementDefinition> snapshot;
5312    private int prefixLength;
5313    private String base;
5314    private String name;
5315    private Set<String> errors = new HashSet<String>();
5316
5317    public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, int prefixLength, String name) {
5318      this.inExtension = inExtension;
5319      this.snapshot = snapshot;
5320      this.prefixLength = prefixLength;
5321      this.base = base;
5322      if (Utilities.isAbsoluteUrl(base)) {
5323        this.base = urlTail(base);
5324      }
5325      this.name = name;
5326    }
5327
5328    @Override
5329    public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) {
5330      if (o1.getBaseIndex() == 0)
5331        o1.setBaseIndex(find(o1.getSelf().getPath(), true));
5332      if (o2.getBaseIndex() == 0)
5333        o2.setBaseIndex(find(o2.getSelf().getPath(), true));
5334      return o1.getBaseIndex() - o2.getBaseIndex();
5335    }
5336
5337    private int find(String path, boolean mandatory) {
5338      String op = path;
5339      int lc = 0;
5340      String actual = base+path.substring(prefixLength);
5341      for (int i = 0; i < snapshot.size(); i++) {
5342        String p = snapshot.get(i).getPath();
5343        if (p.equals(actual)) {
5344          return i;
5345        }
5346        if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length()-3)) && !(actual.endsWith("[x]")) && !actual.substring(p.length()-3).contains(".")) {
5347          return i;
5348        }
5349        if (actual.endsWith("[x]") && p.startsWith(actual.substring(0, actual.length()-3)) && !p.substring(actual.length()-3).contains(".")) {
5350          return i;
5351        }
5352        if (path.startsWith(p+".") && snapshot.get(i).hasContentReference()) {
5353          String ref = snapshot.get(i).getContentReference();
5354          if (ref.substring(1, 2).toUpperCase().equals(ref.substring(1,2))) {
5355            actual = base+(ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength);
5356            path = actual;
5357          } else if (ref.startsWith("http:")) {
5358            actual = base+(ref.substring(ref.indexOf("#")+1)+"."+path.substring(p.length()+1)).substring(prefixLength);
5359            path = actual;            
5360          } else {
5361            // Older versions of FHIR (e.g. 2016May) had reference of the style #parameter instead of #Parameters.parameter, so we have to handle that
5362            actual = base+(path.substring(0,  path.indexOf(".")+1) + ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength);
5363            path = actual;
5364          }
5365            
5366          i = 0;
5367          lc++;
5368          if (lc > MAX_RECURSION_LIMIT)
5369            throw new Error("Internal recursion detection: find() loop path recursion > "+MAX_RECURSION_LIMIT+" - check paths are valid (for path "+path+"/"+op+")");
5370        }
5371      }
5372      if (mandatory) {
5373        if (prefixLength == 0)
5374          errors.add("Differential contains path "+path+" which is not found in the in base "+name);
5375        else
5376          errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the in base "+name);
5377      }
5378      return 0;
5379    }
5380
5381    public void checkForErrors(List<String> errorList) {
5382      if (errors.size() > 0) {
5383//        CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
5384//        for (String s : errors)
5385//          b.append("StructureDefinition "+name+": "+s);
5386//        throw new DefinitionException(b.toString());
5387        for (String s : errors)
5388          if (s.startsWith("!"))
5389            errorList.add("!StructureDefinition "+name+": "+s.substring(1));
5390          else
5391            errorList.add("StructureDefinition "+name+": "+s);
5392      }
5393    }
5394  }
5395
5396
5397  public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors, boolean errorIfChanges) throws FHIRException  {
5398    List<ElementDefinition> original = new ArrayList<>();
5399    original.addAll(diff.getDifferential().getElement());
5400    final List<ElementDefinition> diffList = diff.getDifferential().getElement();
5401    int lastCount = diffList.size();
5402    // first, we move the differential elements into a tree
5403    if (diffList.isEmpty())
5404      return;
5405    
5406    ElementDefinitionHolder edh = null;
5407    int i = 0;
5408    if (diffList.get(0).getPath().contains(".")) {
5409      String newPath = diffList.get(0).getPath().split("\\.")[0];
5410      ElementDefinition e = new ElementDefinition(newPath);
5411      edh = new ElementDefinitionHolder(e, true);
5412    } else {
5413      edh = new ElementDefinitionHolder(diffList.get(0));
5414      i = 1;
5415    }
5416
5417    boolean hasSlicing = false;
5418    List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly
5419    for(ElementDefinition elt : diffList) {
5420      if (elt.hasSlicing() || paths.contains(elt.getPath())) {
5421        hasSlicing = true;
5422        break;
5423      }
5424      paths.add(elt.getPath());
5425    }
5426    if(!hasSlicing) {
5427      // if Differential does not have slicing then safe to pre-sort the list
5428      // so elements and subcomponents are together
5429      Collections.sort(diffList, new ElementNameCompare());
5430    }
5431
5432    processElementsIntoTree(edh, i, diff.getDifferential().getElement());
5433
5434    // now, we sort the siblings throughout the tree
5435    ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name);
5436    sortElements(edh, cmp, errors);
5437
5438    // now, we serialise them back to a list
5439    List<ElementDefinition> newDiff = new ArrayList<>();
5440    writeElements(edh, newDiff);
5441    if (errorIfChanges) {
5442      compareDiffs(original, newDiff, errors);
5443    }
5444    diffList.clear();
5445    diffList.addAll(newDiff);
5446    
5447    if (lastCount != diffList.size())
5448      errors.add("Sort failed: counts differ; at least one of the paths in the differential is illegal");
5449  }
5450
5451  private void compareDiffs(List<ElementDefinition> diffList, List<ElementDefinition> newDiff, List<String> errors) {
5452    if (diffList.size() != newDiff.size()) {
5453      errors.add("The diff list size changed when sorting - was "+diffList.size()+" is now "+newDiff.size());
5454    } else {
5455      for (int i = 0; i < Integer.min(diffList.size(), newDiff.size()); i++) {
5456        ElementDefinition e = diffList.get(i);
5457        ElementDefinition n = newDiff.get(i);
5458        if (!n.getPath().equals(e.getPath())) {
5459          errors.add("The element "+e.getPath()+" is out of order (and maybe others after it)");
5460          return;
5461        }   
5462      }
5463    }
5464  }
5465
5466
5467  private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) {
5468    String path = edh.getSelf().getPath();
5469    final String prefix = path + ".";
5470    while (i < list.size() && list.get(i).getPath().startsWith(prefix)) {
5471      if (list.get(i).getPath().substring(prefix.length()+1).contains(".")) {
5472        String newPath = prefix + list.get(i).getPath().substring(prefix.length()).split("\\.")[0];
5473        ElementDefinition e = new ElementDefinition(newPath);
5474        ElementDefinitionHolder child = new ElementDefinitionHolder(e, true);
5475        edh.getChildren().add(child);
5476        i = processElementsIntoTree(child, i, list);
5477        
5478      } else {
5479        ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i));
5480        edh.getChildren().add(child);
5481        i = processElementsIntoTree(child, i+1, list);
5482      }
5483    }
5484    return i;
5485  }
5486
5487  private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) throws FHIRException {
5488    if (edh.getChildren().size() == 1)
5489      // 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
5490      edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath(), false);
5491    else
5492      Collections.sort(edh.getChildren(), cmp);
5493    cmp.checkForErrors(errors);
5494
5495    for (ElementDefinitionHolder child : edh.getChildren()) {
5496      if (child.getChildren().size() > 0) {
5497        ElementDefinitionComparer ccmp = getComparer(cmp, child);
5498        if (ccmp != null) {
5499          sortElements(child, ccmp, errors);
5500        }
5501      }
5502    }
5503  }
5504
5505
5506  public ElementDefinitionComparer getComparer(ElementDefinitionComparer cmp, ElementDefinitionHolder child) throws FHIRException, Error {
5507    // what we have to check for here is running off the base profile into a data type profile
5508    ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex());
5509    ElementDefinitionComparer ccmp;
5510    if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getWorkingCode()) || ed.getType().get(0).getWorkingCode().equals(ed.getPath())) {
5511      if (ed.hasType() && "Resource".equals(ed.getType().get(0).getWorkingCode()) && (child.getSelf().hasType() && child.getSelf().getType().get(0).hasProfile())) {
5512        if (child.getSelf().getType().get(0).getProfile().size() > 1) {
5513          throw new FHIRException(context.formatMessage(I18nConstants.UNHANDLED_SITUATION_RESOURCE_IS_PROFILED_TO_MORE_THAN_ONE_OPTION__CANNOT_SORT_PROFILE));
5514        }
5515        StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue());
5516        while (profile != null && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
5517          profile = context.fetchResource(StructureDefinition.class, profile.getBaseDefinition());          
5518        }
5519        if (profile==null) {
5520          ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case
5521        } else {
5522          ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), profile.getType(), child.getSelf().getPath().length(), cmp.name);
5523        }
5524      } else {
5525        ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name);
5526      }
5527    } else if (ed.getType().get(0).getWorkingCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) {
5528      StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue());
5529      if (profile==null)
5530        ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case
5531      else
5532      ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode()), child.getSelf().getPath().length(), cmp.name);
5533    } else if (ed.getType().size() == 1 && !ed.getType().get(0).getWorkingCode().equals("*")) {
5534      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
5535      if (profile==null)
5536        throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath()));
5537      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), resolveType(ed.getType().get(0).getWorkingCode()), child.getSelf().getPath().length(), cmp.name);
5538    } else if (child.getSelf().getType().size() == 1) {
5539      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(child.getSelf().getType().get(0).getWorkingCode()));
5540      if (profile==null)
5541        throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath()));
5542      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), child.getSelf().getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
5543    } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) {
5544      String edLastNode = ed.getPath().replaceAll("(.*\\.)*(.*)", "$2");
5545      String childLastNode = child.getSelf().getPath().replaceAll("(.*\\.)*(.*)", "$2");
5546      String p = childLastNode.substring(edLastNode.length()-3);
5547      if (isPrimitive(Utilities.uncapitalize(p)))
5548        p = Utilities.uncapitalize(p);
5549      StructureDefinition sd = context.fetchResource(StructureDefinition.class, sdNs(p));
5550      if (sd == null)
5551        throw new Error(context.formatMessage(I18nConstants.UNABLE_TO_FIND_PROFILE__AT_, p, ed.getId()));
5552      ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name);
5553    } else if (child.getSelf().hasType() && child.getSelf().getType().get(0).getWorkingCode().equals("Reference")) {
5554      for (TypeRefComponent t: child.getSelf().getType()) {
5555        if (!t.getWorkingCode().equals("Reference")) {
5556          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())));
5557        }
5558      }
5559      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
5560      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
5561    } else if (!child.getSelf().hasType() && ed.getType().get(0).getWorkingCode().equals("Reference")) {
5562      for (TypeRefComponent t: ed.getType()) {
5563        if (!t.getWorkingCode().equals("Reference")) {
5564          throw new Error(context.formatMessage(I18nConstants.NOT_HANDLED_YET_SORTELEMENTS_, ed.getPath(), typeCode(ed.getType())));
5565        }
5566      }
5567      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
5568      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
5569    } else {
5570      // this is allowed if we only profile the extensions
5571      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs("Element"));
5572      if (profile==null)
5573        throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_RESOLVE_PROFILE__IN_ELEMENT_, sdNs(ed.getType().get(0).getWorkingCode()), ed.getPath()));
5574      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), "Element", child.getSelf().getPath().length(), cmp.name);
5575//      throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")");
5576    }
5577    return ccmp;
5578  }
5579
5580  private String resolveType(String code) {
5581    if (Utilities.isAbsoluteUrl(code)) {
5582      StructureDefinition sd = context.fetchResource(StructureDefinition.class, code);
5583      if (sd != null) {
5584        return sd.getType();
5585      }
5586    }
5587    return code;
5588  }
5589
5590  private static String sdNs(String type) {
5591    return sdNs(type, null);
5592  }
5593  
5594  public static String sdNs(String type, String overrideVersionNs) {
5595    if (Utilities.isAbsoluteUrl(type))
5596      return type;
5597    else if (overrideVersionNs != null)
5598      return Utilities.pathURL(overrideVersionNs, type);
5599    else
5600      return "http://hl7.org/fhir/StructureDefinition/"+type;
5601  }
5602
5603
5604  private boolean isAbstract(String code) {
5605    return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") || code.equals("DomainResource");
5606  }
5607
5608
5609  private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) {
5610    if (!edh.isPlaceHolder())
5611      list.add(edh.getSelf());
5612    for (ElementDefinitionHolder child : edh.getChildren()) {
5613      writeElements(child, list);
5614    }
5615  }
5616
5617  /**
5618   * First compare element by path then by name if same
5619   */
5620  private static class ElementNameCompare implements Comparator<ElementDefinition> {
5621
5622    @Override
5623    public int compare(ElementDefinition o1, ElementDefinition o2) {
5624      String path1 = normalizePath(o1);
5625      String path2 = normalizePath(o2);
5626      int cmp = path1.compareTo(path2);
5627      if (cmp == 0) {
5628        String name1 = o1.hasSliceName() ? o1.getSliceName() : "";
5629        String name2 = o2.hasSliceName() ? o2.getSliceName() : "";
5630        cmp = name1.compareTo(name2);
5631      }
5632      return cmp;
5633    }
5634
5635    private static String normalizePath(ElementDefinition e) {
5636      if (!e.hasPath()) return "";
5637      String path = e.getPath();
5638      // if sorting element names make sure onset[x] appears before onsetAge, onsetDate, etc.
5639      // so strip off the [x] suffix when comparing the path names.
5640      if (path.endsWith("[x]")) {
5641        path = path.substring(0, path.length()-3);
5642      }
5643      return path;
5644    }
5645
5646  }
5647
5648
5649  // generate schematrons for the rules in a structure definition
5650  public void generateSchematrons(OutputStream dest, StructureDefinition structure) throws IOException, DefinitionException {
5651    if (structure.getDerivation() != TypeDerivationRule.CONSTRAINT)
5652      throw new DefinitionException(context.formatMessage(I18nConstants.NOT_THE_RIGHT_KIND_OF_STRUCTURE_TO_GENERATE_SCHEMATRONS_FOR));
5653    if (!structure.hasSnapshot())
5654      throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT));
5655
5656        StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBaseDefinition());
5657
5658        if (base != null) {
5659          SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName());
5660
5661          ElementDefinition ed = structure.getSnapshot().getElement().get(0);
5662          generateForChildren(sch, "f:"+ed.getPath(), ed, structure, base);
5663          sch.dump();
5664        }
5665  }
5666
5667  // generate a CSV representation of the structure definition
5668  public void generateCsvs(OutputStream dest, StructureDefinition structure, boolean asXml) throws IOException, DefinitionException, Exception {
5669    if (!structure.hasSnapshot())
5670      throw new DefinitionException(context.formatMessage(I18nConstants.NEEDS_A_SNAPSHOT));
5671
5672    CSVWriter csv = new CSVWriter(dest, structure, asXml);
5673
5674    for (ElementDefinition child : structure.getSnapshot().getElement()) {
5675      csv.processElement(child);
5676    }
5677    csv.dump();
5678  }
5679  
5680  
5681  private class Slicer extends ElementDefinitionSlicingComponent {
5682    String criteria = "";
5683    String name = "";   
5684    boolean check;
5685    public Slicer(boolean cantCheck) {
5686      super();
5687      this.check = cantCheck;
5688    }
5689  }
5690  
5691  private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, StructureDefinition structure) {
5692    // given a child in a structure, it's sliced. figure out the slicing xpath
5693    if (child.getPath().endsWith(".extension")) {
5694      ElementDefinition ued = getUrlFor(structure, child);
5695      if ((ued == null || !ued.hasFixed()) && !(child.hasType() && (child.getType().get(0).hasProfile())))
5696        return new Slicer(false);
5697      else {
5698      Slicer s = new Slicer(true);
5699      String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).getValue() : ((UriType) ued.getFixed()).asStringValue();
5700      s.name = " with URL = '"+url+"'";
5701      s.criteria = "[@url = '"+url+"']";
5702      return s;
5703      }
5704    } else
5705      return new Slicer(false);
5706  }
5707
5708  private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, StructureDefinition structure, StructureDefinition base) throws IOException {
5709    //    generateForChild(txt, structure, child);
5710    List<ElementDefinition> children = getChildList(structure, ed);
5711    String sliceName = null;
5712    ElementDefinitionSlicingComponent slicing = null;
5713    for (ElementDefinition child : children) {
5714      String name = tail(child.getPath());
5715      if (child.hasSlicing()) {
5716        sliceName = name;
5717        slicing = child.getSlicing();        
5718      } else if (!name.equals(sliceName))
5719        slicing = null;
5720
5721      ElementDefinition based = getByPath(base, child.getPath());
5722      boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin()));
5723      boolean doMax = child.hasMax() && !child.getMax().equals("*") && (based == null || (!child.getMax().equals(based.getMax())));
5724      Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure);
5725      if (slicer.check) {
5726        if (doMin || doMax) {
5727          Section s = sch.section(xpath);
5728          Rule r = s.rule(xpath);
5729          if (doMin) 
5730            r.assrt("count(f:"+name+slicer.criteria+") >= "+Integer.toString(child.getMin()), name+slicer.name+": minimum cardinality of '"+name+"' is "+Integer.toString(child.getMin()));
5731          if (doMax) 
5732            r.assrt("count(f:"+name+slicer.criteria+") <= "+child.getMax(), name+slicer.name+": maximum cardinality of '"+name+"' is "+child.getMax());
5733        }
5734      }
5735    }
5736    for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
5737      if (inv.hasXpath()) {
5738        Section s = sch.section(ed.getPath());
5739        Rule r = s.rule(xpath);
5740        r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId()+": " : "")+inv.getHuman()+(inv.hasUserData(IS_DERIVED) ? " (inherited)" : ""));
5741      }
5742    }
5743    if (!ed.hasContentReference()) {
5744      for (ElementDefinition child : children) {
5745        String name = tail(child.getPath());
5746        generateForChildren(sch, xpath+"/f:"+name, child, structure, base);
5747      }
5748    }
5749  }
5750
5751
5752
5753
5754  private ElementDefinition getByPath(StructureDefinition base, String path) {
5755                for (ElementDefinition ed : base.getSnapshot().getElement()) {
5756                        if (ed.getPath().equals(path))
5757                                return ed;
5758                        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)))
5759                                return ed;
5760    }
5761          return null;
5762  }
5763
5764
5765  public void setIds(StructureDefinition sd, boolean checkFirst) throws DefinitionException  {
5766    if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) {
5767      if (!sd.hasDifferential())
5768        sd.setDifferential(new StructureDefinitionDifferentialComponent());
5769      generateIds(sd.getDifferential().getElement(), sd.getUrl(), sd.getType());
5770    }
5771    if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) {
5772      if (!sd.hasSnapshot())
5773        sd.setSnapshot(new StructureDefinitionSnapshotComponent());
5774      generateIds(sd.getSnapshot().getElement(), sd.getUrl(), sd.getType());
5775    }
5776  }
5777
5778
5779  private boolean hasMissingIds(List<ElementDefinition> list) {
5780    for (ElementDefinition ed : list) {
5781      if (!ed.hasId())
5782        return true;
5783    }    
5784    return false;
5785  }
5786
5787  public class SliceList {
5788
5789    private Map<String, String> slices = new HashMap<>();
5790    
5791    public void seeElement(ElementDefinition ed) {
5792      Iterator<Map.Entry<String,String>> iter = slices.entrySet().iterator();
5793      while (iter.hasNext()) {
5794        Map.Entry<String,String> entry = iter.next();
5795        if (entry.getKey().length() > ed.getPath().length() || entry.getKey().equals(ed.getPath()))
5796          iter.remove();
5797      }
5798      
5799      if (ed.hasSliceName()) 
5800        slices.put(ed.getPath(), ed.getSliceName());
5801    }
5802
5803    public String[] analyse(List<String> paths) {
5804      String s = paths.get(0);
5805      String[] res = new String[paths.size()];
5806      res[0] = null;
5807      for (int i = 1; i < paths.size(); i++) {
5808        s = s + "."+paths.get(i);
5809        if (slices.containsKey(s)) 
5810          res[i] = slices.get(s);
5811        else
5812          res[i] = null;
5813      }
5814      return res;
5815    }
5816
5817  }
5818
5819  private void generateIds(List<ElementDefinition> list, String name, String type) throws DefinitionException  {
5820    if (list.isEmpty())
5821      return;
5822    
5823    Map<String, String> idList = new HashMap<String, String>();
5824    Map<String, String> replacedIds = new HashMap<String, String>();
5825    
5826    SliceList sliceInfo = new SliceList();
5827    // first pass, update the element ids
5828    for (ElementDefinition ed : list) {
5829      List<String> paths = new ArrayList<String>();
5830      if (!ed.hasPath())
5831        throw new DefinitionException(context.formatMessage(I18nConstants.NO_PATH_ON_ELEMENT_DEFINITION__IN_, Integer.toString(list.indexOf(ed)), name));
5832      sliceInfo.seeElement(ed);
5833      String[] pl = ed.getPath().split("\\.");
5834      for (int i = paths.size(); i < pl.length; i++) // -1 because the last path is in focus
5835        paths.add(pl[i]);
5836      String slices[] = sliceInfo.analyse(paths);
5837      
5838      StringBuilder b = new StringBuilder();
5839      b.append(paths.get(0));
5840      for (int i = 1; i < paths.size(); i++) {
5841        b.append(".");
5842        String s = paths.get(i);
5843        String p = slices[i];
5844        b.append(fixChars(s));
5845        if (p != null) {
5846          b.append(":");
5847          b.append(p);
5848        }
5849      }
5850      String bs = b.toString();
5851      if (ed.hasId()) {
5852        replacedIds.put(ed.getId(), ed.getPath());
5853      }
5854      ed.setId(bs);
5855      if (idList.containsKey(bs)) {
5856        if (exception || messages == null) {
5857          throw new DefinitionException(context.formatMessage(I18nConstants.SAME_ID_ON_MULTIPLE_ELEMENTS__IN_, bs, idList.get(bs), ed.getPath(), name));
5858        } else
5859          messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, name+"."+bs, "Duplicate Element id "+bs, ValidationMessage.IssueSeverity.ERROR));
5860      }
5861      idList.put(bs, ed.getPath());
5862      if (ed.hasContentReference() && ed.getContentReference().startsWith("#")) {
5863        String s = ed.getContentReference();
5864        if (replacedIds.containsKey(s.substring(1))) {
5865          ed.setContentReference("http://hl7.org/fhir/StructureDefinition/"+type+"#"+replacedIds.get(s.substring(1)));
5866        } else {
5867          ed.setContentReference("http://hl7.org/fhir/StructureDefinition/"+type+s);
5868        }
5869      }
5870    }  
5871    // second path - fix up any broken path based id references
5872    
5873  }
5874
5875
5876  private Object fixChars(String s) {
5877    return s.replace("_", "-");
5878  }
5879
5880
5881//  private String describeExtension(ElementDefinition ed) {
5882//    if (!ed.hasType() || !ed.getTypeFirstRep().hasProfile())
5883//      return "";
5884//    return "$"+urlTail(ed.getTypeFirstRep().getProfile());
5885//  }
5886//
5887
5888  private static String urlTail(String profile) {
5889    return profile.contains("/") ? profile.substring(profile.lastIndexOf("/")+1) : profile;
5890  }
5891
5892
5893  private String checkName(String name) {
5894//    if (name.contains("."))
5895////      throw new Exception("Illegal name "+name+": no '.'");
5896//    if (name.contains(" "))
5897//      throw new Exception("Illegal name "+name+": no spaces");
5898    StringBuilder b = new StringBuilder();
5899    for (char c : name.toCharArray()) {
5900      if (!Utilities.existsInList(c, '.', ' ', ':', '"', '\'', '(', ')', '&', '[', ']'))
5901        b.append(c);
5902    }
5903    return b.toString().toLowerCase();
5904  }
5905
5906
5907  private int charCount(String path, char t) {
5908    int res = 0;
5909    for (char ch : path.toCharArray()) {
5910      if (ch == t)
5911        res++;
5912    }
5913    return res;
5914  }
5915
5916//
5917//private void generateForChild(TextStreamWriter txt,
5918//    StructureDefinition structure, ElementDefinition child) {
5919//  // TODO Auto-generated method stub
5920//
5921//}
5922
5923  private interface ExampleValueAccessor {
5924    DataType getExampleValue(ElementDefinition ed);
5925    String getId();
5926  }
5927
5928  private class BaseExampleValueAccessor implements ExampleValueAccessor {
5929    @Override
5930    public DataType getExampleValue(ElementDefinition ed) {
5931      if (ed.hasFixed())
5932        return ed.getFixed();
5933      if (ed.hasExample())
5934        return ed.getExample().get(0).getValue();
5935      else
5936        return null;
5937    }
5938
5939    @Override
5940    public String getId() {
5941      return "-genexample";
5942    }
5943  }
5944  
5945  private class ExtendedExampleValueAccessor implements ExampleValueAccessor {
5946    private String index;
5947
5948    public ExtendedExampleValueAccessor(String index) {
5949      this.index = index;
5950    }
5951    @Override
5952    public DataType getExampleValue(ElementDefinition ed) {
5953      if (ed.hasFixed())
5954        return ed.getFixed();
5955      for (Extension ex : ed.getExtension()) {
5956       String ndx = ToolingExtensions.readStringExtension(ex, "index");
5957       DataType value = ToolingExtensions.getExtension(ex, "exValue").getValue();
5958       if (index.equals(ndx) && value != null)
5959         return value;
5960      }
5961      return null;
5962    }
5963    @Override
5964    public String getId() {
5965      return "-genexample-"+index;
5966    }
5967  }
5968  
5969  public List<org.hl7.fhir.r4b.elementmodel.Element> generateExamples(StructureDefinition sd, boolean evenWhenNoExamples) throws FHIRException {
5970    List<org.hl7.fhir.r4b.elementmodel.Element> examples = new ArrayList<org.hl7.fhir.r4b.elementmodel.Element>();
5971    if (sd.hasSnapshot()) {
5972      if (evenWhenNoExamples || hasAnyExampleValues(sd)) 
5973        examples.add(generateExample(sd, new BaseExampleValueAccessor()));
5974      for (int i = 1; i <= 50; i++) {
5975        if (hasAnyExampleValues(sd, Integer.toString(i))) 
5976          examples.add(generateExample(sd, new ExtendedExampleValueAccessor(Integer.toString(i))));
5977      }
5978    }
5979    return examples;
5980  }
5981
5982  private org.hl7.fhir.r4b.elementmodel.Element generateExample(StructureDefinition profile, ExampleValueAccessor accessor) throws FHIRException {
5983    ElementDefinition ed = profile.getSnapshot().getElementFirstRep();
5984    org.hl7.fhir.r4b.elementmodel.Element r = new org.hl7.fhir.r4b.elementmodel.Element(ed.getPath(), new Property(context, ed, profile));
5985    List<ElementDefinition> children = getChildMap(profile, ed);
5986    for (ElementDefinition child : children) {
5987      if (child.getPath().endsWith(".id")) {
5988        org.hl7.fhir.r4b.elementmodel.Element id = new org.hl7.fhir.r4b.elementmodel.Element("id", new Property(context, child, profile));
5989        id.setValue(profile.getId()+accessor.getId());
5990        r.getChildren().add(id);
5991      } else { 
5992        org.hl7.fhir.r4b.elementmodel.Element e = createExampleElement(profile, child, accessor);
5993        if (e != null)
5994          r.getChildren().add(e);
5995      }
5996    }
5997    return r;
5998  }
5999
6000  private org.hl7.fhir.r4b.elementmodel.Element createExampleElement(StructureDefinition profile, ElementDefinition ed, ExampleValueAccessor accessor) throws FHIRException {
6001    DataType v = accessor.getExampleValue(ed);
6002    if (v != null) {
6003      return new ObjectConverter(context).convert(new Property(context, ed, profile), v);
6004    } else {
6005      org.hl7.fhir.r4b.elementmodel.Element res = new org.hl7.fhir.r4b.elementmodel.Element(tail(ed.getPath()), new Property(context, ed, profile));
6006      boolean hasValue = false;
6007      List<ElementDefinition> children = getChildMap(profile, ed);
6008      for (ElementDefinition child : children) {
6009        if (!child.hasContentReference()) {
6010        org.hl7.fhir.r4b.elementmodel.Element e = createExampleElement(profile, child, accessor);
6011        if (e != null) {
6012          hasValue = true;
6013          res.getChildren().add(e);
6014        }
6015      }
6016      }
6017      if (hasValue)
6018        return res;
6019      else
6020        return null;
6021    }
6022  }
6023
6024  private boolean hasAnyExampleValues(StructureDefinition sd, String index) {
6025    for (ElementDefinition ed : sd.getSnapshot().getElement())
6026      for (Extension ex : ed.getExtension()) {
6027        String ndx = ToolingExtensions.readStringExtension(ex, "index");
6028        Extension exv = ToolingExtensions.getExtension(ex, "exValue");
6029        if (exv != null) {
6030          DataType value = exv.getValue();
6031        if (index.equals(ndx) && value != null)
6032          return true;
6033        }
6034       }
6035    return false;
6036  }
6037
6038
6039  private boolean hasAnyExampleValues(StructureDefinition sd) {
6040    for (ElementDefinition ed : sd.getSnapshot().getElement())
6041      if (ed.hasExample())
6042        return true;
6043    return false;
6044  }
6045
6046
6047  public void populateLogicalSnapshot(StructureDefinition sd) throws FHIRException {
6048    sd.getSnapshot().getElement().add(sd.getDifferential().getElementFirstRep().copy());
6049    
6050    if (sd.hasBaseDefinition()) {
6051    StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
6052    if (base == null)
6053        throw new FHIRException(context.formatMessage(I18nConstants.UNABLE_TO_FIND_BASE_DEFINITION_FOR_LOGICAL_MODEL__FROM_, sd.getBaseDefinition(), sd.getUrl()));
6054    copyElements(sd, base.getSnapshot().getElement());
6055    }
6056    copyElements(sd, sd.getDifferential().getElement());
6057  }
6058
6059
6060  private void copyElements(StructureDefinition sd, List<ElementDefinition> list) {
6061    for (ElementDefinition ed : list) {
6062      if (ed.getPath().contains(".")) {
6063        ElementDefinition n = ed.copy();
6064        n.setPath(sd.getSnapshot().getElementFirstRep().getPath()+"."+ed.getPath().substring(ed.getPath().indexOf(".")+1));
6065        sd.getSnapshot().addElement(n);
6066      }
6067    }
6068  }
6069
6070    
6071  public void cleanUpDifferential(StructureDefinition sd) {
6072    if (sd.getDifferential().getElement().size() > 1)
6073      cleanUpDifferential(sd, 1);
6074  }
6075  
6076  private void cleanUpDifferential(StructureDefinition sd, int start) {
6077    int level = Utilities.charCount(sd.getDifferential().getElement().get(start).getPath(), '.');
6078    int c = start;
6079    int len = sd.getDifferential().getElement().size();
6080    HashSet<String> paths = new HashSet<String>();
6081    while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') == level) {
6082      ElementDefinition ed = sd.getDifferential().getElement().get(c);
6083      if (!paths.contains(ed.getPath())) {
6084        paths.add(ed.getPath());
6085        int ic = c+1; 
6086        while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 
6087          ic++;
6088        ElementDefinition slicer = null;
6089        List<ElementDefinition> slices = new ArrayList<ElementDefinition>();
6090        slices.add(ed);
6091        while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') == level) {
6092          ElementDefinition edi = sd.getDifferential().getElement().get(ic);
6093          if (ed.getPath().equals(edi.getPath())) {
6094            if (slicer == null) {
6095              slicer = new ElementDefinition();
6096              slicer.setPath(edi.getPath());
6097              slicer.getSlicing().setRules(SlicingRules.OPEN);
6098              sd.getDifferential().getElement().add(c, slicer);
6099              c++;
6100              ic++;
6101            }
6102            slices.add(edi);
6103          }
6104          ic++;
6105          while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 
6106            ic++;
6107        }
6108        // now we're at the end, we're going to figure out the slicing discriminator
6109        if (slicer != null)
6110          determineSlicing(slicer, slices);
6111      }
6112      c++;
6113      if (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) {
6114        cleanUpDifferential(sd, c);
6115        c++;
6116        while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) 
6117          c++;
6118      }
6119  }
6120  }
6121
6122
6123  private void determineSlicing(ElementDefinition slicer, List<ElementDefinition> slices) {
6124    // first, name them
6125    int i = 0;
6126    for (ElementDefinition ed : slices) {
6127      if (ed.hasUserData("slice-name")) {
6128        ed.setSliceName(ed.getUserString("slice-name"));
6129      } else {
6130        i++;
6131        ed.setSliceName("slice-"+Integer.toString(i));
6132      }
6133    }
6134    // now, the hard bit, how are they differentiated? 
6135    // right now, we hard code this...
6136    if (slicer.getPath().endsWith(".extension") || slicer.getPath().endsWith(".modifierExtension"))
6137      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url");
6138    else if (slicer.getPath().equals("DiagnosticReport.result"))
6139      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("reference.code");
6140    else if (slicer.getPath().equals("Observation.related"))
6141      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("target.reference.code");
6142    else if (slicer.getPath().equals("Bundle.entry"))
6143      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("resource.@profile");
6144    else  
6145      throw new Error("No slicing for "+slicer.getPath());
6146  }
6147
6148  public class SpanEntry {
6149    private List<SpanEntry> children = new ArrayList<SpanEntry>();
6150    private boolean profile;
6151    private String id;
6152    private String name;
6153    private String resType;
6154    private String cardinality;
6155    private String description;
6156    private String profileLink;
6157    private String resLink;
6158    private String type;
6159    
6160    public String getName() {
6161      return name;
6162    }
6163    public void setName(String name) {
6164      this.name = name;
6165    }
6166    public String getResType() {
6167      return resType;
6168    }
6169    public void setResType(String resType) {
6170      this.resType = resType;
6171    }
6172    public String getCardinality() {
6173      return cardinality;
6174    }
6175    public void setCardinality(String cardinality) {
6176      this.cardinality = cardinality;
6177    }
6178    public String getDescription() {
6179      return description;
6180    }
6181    public void setDescription(String description) {
6182      this.description = description;
6183    }
6184    public String getProfileLink() {
6185      return profileLink;
6186    }
6187    public void setProfileLink(String profileLink) {
6188      this.profileLink = profileLink;
6189    }
6190    public String getResLink() {
6191      return resLink;
6192    }
6193    public void setResLink(String resLink) {
6194      this.resLink = resLink;
6195    }
6196    public String getId() {
6197      return id;
6198    }
6199    public void setId(String id) {
6200      this.id = id;
6201    }
6202    public boolean isProfile() {
6203      return profile;
6204    }
6205    public void setProfile(boolean profile) {
6206      this.profile = profile;
6207    }
6208    public List<SpanEntry> getChildren() {
6209      return children;
6210    }
6211    public String getType() {
6212      return type;
6213    }
6214    public void setType(String type) {
6215      this.type = type;
6216    }
6217    
6218  }
6219
6220  public XhtmlNode generateSpanningTable(StructureDefinition profile, String imageFolder, boolean onlyConstraints, String constraintPrefix, Set<String> outputTracker) throws IOException, FHIRException {
6221    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, false, true);
6222    gen.setTranslator(getTranslator());
6223    TableModel model = initSpanningTable(gen, "", false, profile.getId());
6224    Set<String> processed = new HashSet<String>();
6225    SpanEntry span = buildSpanningTable("(focus)", "", profile, processed, onlyConstraints, constraintPrefix);
6226    
6227    genSpanEntry(gen, model.getRows(), span);
6228    return gen.generate(model, "", 0, outputTracker);
6229  }
6230
6231  private SpanEntry buildSpanningTable(String name, String cardinality, StructureDefinition profile, Set<String> processed, boolean onlyConstraints, String constraintPrefix) throws IOException {
6232    SpanEntry res = buildSpanEntryFromProfile(name, cardinality, profile);
6233    boolean wantProcess = !processed.contains(profile.getUrl());
6234    processed.add(profile.getUrl());
6235    if (wantProcess && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
6236      for (ElementDefinition ed : profile.getSnapshot().getElement()) {
6237        if (!"0".equals(ed.getMax()) && ed.getType().size() > 0) {
6238          String card = getCardinality(ed, profile.getSnapshot().getElement());
6239          if (!card.endsWith(".0")) {
6240            List<String> refProfiles = listReferenceProfiles(ed);
6241            if (refProfiles.size() > 0) {
6242              String uri = refProfiles.get(0);
6243              if (uri != null) {
6244                StructureDefinition sd = context.fetchResource(StructureDefinition.class, uri);
6245                if (sd != null && (!onlyConstraints || (sd.getDerivation() == TypeDerivationRule.CONSTRAINT && (constraintPrefix == null || sd.getUrl().startsWith(constraintPrefix))))) {
6246                  res.getChildren().add(buildSpanningTable(nameForElement(ed), card, sd, processed, onlyConstraints, constraintPrefix));
6247                }
6248              }
6249            }
6250          }
6251        } 
6252      }
6253    }
6254    return res;
6255  }
6256
6257
6258  private String getCardinality(ElementDefinition ed, List<ElementDefinition> list) {
6259    int min = ed.getMin();
6260    int max = !ed.hasMax() || ed.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(ed.getMax());
6261    ElementDefinition ned = ed;
6262    while (ned != null && ned.getPath().contains(".")) {
6263      ned = findParent(ned, list);
6264      if (ned != null) { // todo: this can happen if we've walked into a resoruce. Not sure what to about that?
6265        if ("0".equals(ned.getMax()))
6266          max = 0;
6267        else if (!ned.getMax().equals("1") && !ned.hasSlicing())
6268          max = Integer.MAX_VALUE;
6269        if (ned.getMin() == 0) {
6270          min = 0;
6271        }
6272      }
6273    }
6274    return Integer.toString(min)+".."+(max == Integer.MAX_VALUE ? "*" : Integer.toString(max));
6275  }
6276
6277
6278  private ElementDefinition findParent(ElementDefinition ed, List<ElementDefinition> list) {
6279    int i = list.indexOf(ed)-1;
6280    while (i >= 0 && !ed.getPath().startsWith(list.get(i).getPath()+"."))
6281      i--;
6282    if (i == -1)
6283      return null;
6284    else
6285      return list.get(i);
6286  }
6287
6288
6289  private List<String> listReferenceProfiles(ElementDefinition ed) {
6290    List<String> res = new ArrayList<String>();
6291    for (TypeRefComponent tr : ed.getType()) {
6292      // code is null if we're dealing with "value" and profile is null if we just have Reference()
6293      if (tr.hasTarget() && tr.hasTargetProfile())
6294        for (UriType u : tr.getTargetProfile())
6295          res.add(u.getValue());
6296    }
6297    return res;
6298  }
6299
6300
6301  private String nameForElement(ElementDefinition ed) {
6302    return ed.getPath().substring(ed.getPath().indexOf(".")+1);
6303  }
6304
6305
6306  private SpanEntry buildSpanEntryFromProfile(String name, String cardinality, StructureDefinition profile) throws IOException {
6307    SpanEntry res = new SpanEntry();
6308    res.setName(name);
6309    res.setCardinality(cardinality);
6310    res.setProfileLink(profile.getUserString("path"));
6311    res.setResType(profile.getType());
6312    StructureDefinition base = context.fetchResource(StructureDefinition.class, res.getResType());
6313    if (base != null)
6314      res.setResLink(base.getUserString("path"));
6315    res.setId(profile.getId());
6316    res.setProfile(profile.getDerivation() == TypeDerivationRule.CONSTRAINT);
6317    StringBuilder b = new StringBuilder();
6318    b.append(res.getResType());
6319    boolean first = true;
6320    boolean open = false;
6321    if (profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
6322      res.setDescription(profile.getName());
6323      for (ElementDefinition ed : profile.getSnapshot().getElement()) {
6324        if (isKeyProperty(ed.getBase().getPath()) && ed.hasFixed()) {
6325          if (first) {
6326            open = true;
6327            first = false;
6328            b.append("[");
6329          } else {
6330            b.append(", ");
6331          }
6332          b.append(tail(ed.getBase().getPath()));
6333          b.append("=");
6334          b.append(summarize(ed.getFixed()));
6335        }
6336      }
6337      if (open)
6338        b.append("]");
6339    } else
6340      res.setDescription("Base FHIR "+profile.getName());
6341    res.setType(b.toString());
6342    return res ;
6343  }
6344
6345
6346  private String summarize(DataType value) throws IOException {
6347    if (value instanceof Coding)
6348      return summarizeCoding((Coding) value);
6349    else if (value instanceof CodeableConcept)
6350      return summarizeCodeableConcept((CodeableConcept) value);
6351    else
6352      return buildJson(value);
6353  }
6354
6355
6356  private String summarizeCoding(Coding value) {
6357    String uri = value.getSystem();
6358    String system = TerminologyRenderer.describeSystem(uri);
6359    if (Utilities.isURL(system)) {
6360      if (system.equals("http://cap.org/protocols"))
6361        system = "CAP Code";
6362    }
6363    return system+" "+value.getCode();
6364  }
6365
6366
6367  private String summarizeCodeableConcept(CodeableConcept value) {
6368    if (value.hasCoding())
6369      return summarizeCoding(value.getCodingFirstRep());
6370    else
6371      return value.getText();
6372  }
6373
6374
6375  private boolean isKeyProperty(String path) {
6376    return Utilities.existsInList(path, "Observation.code");
6377  }
6378
6379
6380  public TableModel initSpanningTable(HierarchicalTableGenerator gen, String prefix, boolean isLogical, String id) {
6381    TableModel model = gen.new TableModel(id, true);
6382    
6383    model.setDocoImg(prefix+"help16.png");
6384    model.setDocoRef(Utilities.pathURL(prefix, "formats.html#table")); // todo: change to graph definition
6385    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Property", "A profiled resource", null, 0));
6386    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Card.", "Minimum and Maximum # of times the the element can appear in the instance", null, 0));
6387    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Content", "What goes here", null, 0));
6388    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Description", "Description of the profile", null, 0));
6389    return model;
6390  }
6391
6392  private void genSpanEntry(HierarchicalTableGenerator gen, List<Row> rows, SpanEntry span) throws IOException {
6393    Row row = gen.new Row();
6394    rows.add(row);
6395    row.setAnchor(span.getId());
6396    //row.setColor(..?);
6397    if (span.isProfile()) {
6398      row.setIcon("icon_profile.png", HierarchicalTableGenerator.TEXT_ICON_PROFILE);
6399    } else {
6400      row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
6401    }
6402    
6403    row.getCells().add(gen.new Cell(null, null, span.getName(), null, null));
6404    row.getCells().add(gen.new Cell(null, null, span.getCardinality(), null, null));
6405    row.getCells().add(gen.new Cell(null, span.getProfileLink(), span.getType(), null, null));
6406    row.getCells().add(gen.new Cell(null, null, span.getDescription(), null, null));
6407
6408    for (SpanEntry child : span.getChildren()) {
6409      genSpanEntry(gen, row.getSubRows(), child);
6410    }
6411  }
6412
6413
6414  public static ElementDefinitionSlicingDiscriminatorComponent interpretR2Discriminator(String discriminator, boolean isExists) {
6415    if (discriminator.endsWith("@pattern"))
6416      return makeDiscriminator(DiscriminatorType.PATTERN, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 
6417    if (discriminator.endsWith("@profile"))
6418      return makeDiscriminator(DiscriminatorType.PROFILE, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 
6419    if (discriminator.endsWith("@type")) 
6420      return makeDiscriminator(DiscriminatorType.TYPE, discriminator.length() == 5 ? "" : discriminator.substring(0,discriminator.length()-6));
6421    if (discriminator.endsWith("@exists"))
6422      return makeDiscriminator(DiscriminatorType.EXISTS, discriminator.length() == 7 ? "" : discriminator.substring(0,discriminator.length()-8)); 
6423    if (isExists)
6424      return makeDiscriminator(DiscriminatorType.EXISTS, discriminator); 
6425    return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(discriminator);
6426  }
6427
6428
6429  private static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType dType, String str) {
6430    return new ElementDefinitionSlicingDiscriminatorComponent().setType(dType).setPath(Utilities.noString(str)? "$this" : str);
6431  }
6432
6433
6434  public static String buildR2Discriminator(ElementDefinitionSlicingDiscriminatorComponent t) throws FHIRException {
6435    switch (t.getType()) {
6436    case PROFILE: return t.getPath()+"/@profile";
6437    case PATTERN: return t.getPath()+"/@pattern";
6438    case TYPE: return t.getPath()+"/@type";
6439    case VALUE: return t.getPath();
6440    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
6441    default: throw new FHIRException("Unable to represent "+t.getType().toCode()+":"+t.getPath()+" in R2");    
6442    }
6443  }
6444
6445
6446  public static StructureDefinition makeExtensionForVersionedURL(IWorkerContext context, String url) {
6447    String epath = url.substring(54);
6448    if (!epath.contains("."))
6449      return null;
6450    String type = epath.substring(0, epath.indexOf("."));
6451    StructureDefinition sd = context.fetchTypeDefinition(type);
6452    if (sd == null)
6453      return null;
6454    ElementDefinition ed = null;
6455    for (ElementDefinition t : sd.getSnapshot().getElement()) {
6456      if (t.getPath().equals(epath)) {
6457        ed = t;
6458        break;
6459      }
6460    }
6461    if (ed == null)
6462      return null;
6463    if ("Element".equals(ed.typeSummary()) || "BackboneElement".equals(ed.typeSummary())) {
6464      return null;
6465    } else {
6466      StructureDefinition template = context.fetchResource(StructureDefinition.class, "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities");
6467      StructureDefinition ext = template.copy();
6468      ext.setUrl(url);
6469      ext.setId("extension-"+epath);
6470      ext.setName("Extension-"+epath);
6471      ext.setTitle("Extension for r4 "+epath);
6472      ext.setStatus(sd.getStatus());
6473      ext.setDate(sd.getDate());
6474      ext.getContact().clear();
6475      ext.getContact().addAll(sd.getContact());
6476      ext.setFhirVersion(sd.getFhirVersion());
6477      ext.setDescription(ed.getDefinition());
6478      ext.getContext().clear();
6479      ext.addContext().setType(ExtensionContextType.ELEMENT).setExpression(epath.substring(0, epath.lastIndexOf(".")));
6480      ext.getDifferential().getElement().clear();
6481      ext.getSnapshot().getElement().get(3).setFixed(new UriType(url));
6482      ext.getSnapshot().getElement().set(4, ed.copy());
6483      ext.getSnapshot().getElement().get(4).setPath("Extension.value"+Utilities.capitalize(ed.typeSummary()));
6484      return ext;      
6485    }
6486
6487  }
6488
6489
6490  public boolean isThrowException() {
6491    return exception;
6492  }
6493
6494
6495  public void setThrowException(boolean exception) {
6496    this.exception = exception;
6497  }
6498
6499
6500  public ValidationOptions getTerminologyServiceOptions() {
6501    return terminologyServiceOptions;
6502  }
6503
6504
6505  public void setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) {
6506    this.terminologyServiceOptions = terminologyServiceOptions;
6507  }
6508
6509
6510  public boolean isNewSlicingProcessing() {
6511    return newSlicingProcessing;
6512  }
6513
6514
6515  public void setNewSlicingProcessing(boolean newSlicingProcessing) {
6516    this.newSlicingProcessing = newSlicingProcessing;
6517  }
6518
6519
6520  public boolean isDebug() {
6521    return debug;
6522  }
6523
6524
6525  public void setDebug(boolean debug) {
6526    this.debug = debug;
6527  }
6528
6529
6530  public String getDefWebRoot() {
6531    return defWebRoot;
6532  }
6533
6534
6535  public void setDefWebRoot(String defWebRoot) {
6536    this.defWebRoot = defWebRoot;
6537    if (!this.defWebRoot.endsWith("/"))
6538      this.defWebRoot = this.defWebRoot + '/';
6539  }
6540
6541
6542  public static StructureDefinition makeBaseDefinition(FHIRVersion fhirVersion) {
6543    StructureDefinition base = new StructureDefinition();
6544    base.setId("Base");
6545    base.setUrl("http://hl7.org/fhir/StructureDefinition/Base");
6546    base.setVersion(fhirVersion.toCode());
6547    base.setName("Base"); 
6548    base.setStatus(PublicationStatus.ACTIVE);
6549    base.setDate(new Date());
6550    base.setFhirVersion(fhirVersion);
6551    base.setKind(StructureDefinitionKind.COMPLEXTYPE); 
6552    base.setAbstract(true); 
6553    base.setType("Base");
6554    ElementDefinition e = base.getSnapshot().getElementFirstRep();
6555    e.setId("Base");
6556    e.setPath("Base"); 
6557    e.setMin(0); 
6558    e.setMax("*"); 
6559    e.getBase().setPath("Base");
6560    e.getBase().setMin(0); 
6561    e.getBase().setMax("*"); 
6562    e.setIsModifier(false); 
6563    e = base.getDifferential().getElementFirstRep();
6564    e.setId("Base");
6565    e.setPath("Base"); 
6566    e.setMin(0); 
6567    e.setMax("*"); 
6568    return base;
6569  }
6570
6571  public XVerExtensionManager getXver() {
6572    return xver;
6573  }
6574
6575  public ProfileUtilities setXver(XVerExtensionManager xver) {
6576    this.xver = xver;
6577    return this;
6578  }
6579
6580
6581  public List<ElementChoiceGroup> readChoices(ElementDefinition ed, List<ElementDefinition> children) {
6582    List<ElementChoiceGroup> result = new ArrayList<>();
6583    for (ElementDefinitionConstraintComponent c : ed.getConstraint()) {
6584      ElementChoiceGroup grp = processConstraint(children, c);
6585      if (grp != null) {
6586        result.add(grp);
6587      }
6588    }
6589    return result;
6590  }
6591
6592  private ElementChoiceGroup processConstraint(List<ElementDefinition> children, ElementDefinitionConstraintComponent c) {
6593    if (!c.hasExpression()) {
6594      return null;
6595    }
6596    ExpressionNode expr = null;
6597    try {
6598      expr = fpe.parse(c.getExpression());
6599    } catch (Exception e) {
6600      return null;
6601    }
6602    if (expr.getKind() != Kind.Group || expr.getOpNext() == null || !(expr.getOperation() == Operation.Equals || expr.getOperation() == Operation.LessOrEqual)) {
6603      return null;      
6604    }
6605    ExpressionNode n1 = expr.getGroup();
6606    ExpressionNode n2 = expr.getOpNext();
6607    if (n2.getKind() != Kind.Constant || n2.getInner() != null || n2.getOpNext() != null || !"1".equals(n2.getConstant().primitiveValue())) {
6608      return null;
6609    }
6610    ElementChoiceGroup grp = new ElementChoiceGroup(c.getKey(), expr.getOperation() == Operation.Equals);
6611    while (n1 != null) {
6612      if (n1.getKind() != Kind.Name || n1.getInner() != null) {
6613        return null;
6614      }
6615      grp.elements.add(n1.getName());
6616      if (n1.getOperation() == null || n1.getOperation() == Operation.Union) {
6617        n1 = n1.getOpNext();
6618      } else {
6619        return null;
6620      }
6621    }
6622    int total = 0;
6623    for (String n : grp.elements) {
6624      boolean found = false;
6625      for (ElementDefinition child : children) {
6626        String name = tail(child.getPath());
6627        if (n.equals(name)) {
6628          found = true;
6629          if (!"0".equals(child.getMax())) {
6630            total++;
6631          }
6632        }
6633      }
6634      if (!found) {
6635        return null;
6636      }
6637    }
6638    if (total <= 1) {
6639      return null;
6640    }
6641    return grp;
6642  }
6643
6644  public static boolean allTypesMustSupport(ElementDefinition e) {
6645    boolean all = true;
6646    boolean any = false;
6647    for (TypeRefComponent tr : e.getType()) {
6648      all = all && isMustSupport(tr);
6649      any = any || isMustSupport(tr);
6650    }
6651    return !all && !any;
6652  }
6653  
6654  public static boolean allProfilesMustSupport(List<CanonicalType> profiles) {
6655    boolean all = true;
6656    boolean any = false;
6657    for (CanonicalType u : profiles) {
6658      all = all && isMustSupport(u);
6659      any = any || isMustSupport(u);
6660    }
6661    return !all && !any;
6662  }
6663  public static boolean isMustSupportDirect(TypeRefComponent tr) {
6664    return ("true".equals(ToolingExtensions.readStringExtension(tr, ToolingExtensions.EXT_MUST_SUPPORT)));
6665  }
6666
6667  public static boolean isMustSupport(TypeRefComponent tr) {
6668    if ("true".equals(ToolingExtensions.readStringExtension(tr, ToolingExtensions.EXT_MUST_SUPPORT))) {
6669      return true;
6670    }
6671    if (isMustSupport(tr.getProfile())) {
6672      return true;
6673    }
6674    return isMustSupport(tr.getTargetProfile());
6675  }
6676
6677  public static boolean isMustSupport(List<CanonicalType> profiles) {
6678    for (CanonicalType ct : profiles) {
6679      if (isMustSupport(ct)) {
6680        return true;
6681      }
6682    }
6683    return false;
6684  }
6685
6686
6687  public static boolean isMustSupport(CanonicalType profile) {
6688    return "true".equals(ToolingExtensions.readStringExtension(profile, ToolingExtensions.EXT_MUST_SUPPORT));
6689  }
6690
6691  public ElementDefinitionResolution resolveContentRef(StructureDefinition structure, ElementDefinition element) {
6692    return getElementById(structure, structure.getSnapshot().getElement(), element.getContentReference());
6693  }
6694
6695  public Set<String> getMasterSourceFileNames() {
6696    return masterSourceFileNames;
6697  }
6698
6699  public void setMasterSourceFileNames(Set<String> masterSourceFileNames) {
6700    this.masterSourceFileNames = masterSourceFileNames;
6701  }
6702
6703  
6704}