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