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