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