001package org.hl7.fhir.r4.conformance;
002
003/*
004  Copyright (c) 2011+, HL7, Inc.
005  All rights reserved.
006  
007  Redistribution and use in source and binary forms, with or without modification, 
008  are permitted provided that the following conditions are met:
009    
010   * Redistributions of source code must retain the above copyright notice, this 
011     list of conditions and the following disclaimer.
012   * Redistributions in binary form must reproduce the above copyright notice, 
013     this list of conditions and the following disclaimer in the documentation 
014     and/or other materials provided with the distribution.
015   * Neither the name of HL7 nor the names of its contributors may be used to 
016     endorse or promote products derived from this software without specific 
017     prior written permission.
018  
019  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
020  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
021  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
022  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
023  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
024  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
025  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
027  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
028  POSSIBILITY OF SUCH DAMAGE.
029  
030 */
031
032
033import java.io.IOException;
034import java.io.OutputStream;
035import java.util.ArrayList;
036import java.util.Collections;
037import java.util.Comparator;
038import java.util.HashMap;
039import java.util.HashSet;
040import java.util.Iterator;
041import java.util.List;
042import java.util.Map;
043import java.util.Set;
044
045import org.apache.commons.lang3.StringUtils;
046import org.hl7.fhir.exceptions.DefinitionException;
047import org.hl7.fhir.exceptions.FHIRException;
048import org.hl7.fhir.exceptions.FHIRFormatError;
049import org.hl7.fhir.r4.conformance.ProfileUtilities.ProfileKnowledgeProvider.BindingResolution;
050import org.hl7.fhir.r4.context.IWorkerContext;
051import org.hl7.fhir.r4.context.IWorkerContext.ValidationResult;
052import org.hl7.fhir.r4.elementmodel.ObjectConverter;
053import org.hl7.fhir.r4.elementmodel.Property;
054import org.hl7.fhir.r4.formats.IParser;
055import org.hl7.fhir.r4.model.Base;
056import org.hl7.fhir.r4.model.BooleanType;
057import org.hl7.fhir.r4.model.CanonicalType;
058import org.hl7.fhir.r4.model.CodeType;
059import org.hl7.fhir.r4.model.CodeableConcept;
060import org.hl7.fhir.r4.model.Coding;
061import org.hl7.fhir.r4.model.Element;
062import org.hl7.fhir.r4.model.ElementDefinition;
063import org.hl7.fhir.r4.model.ElementDefinition.AggregationMode;
064import org.hl7.fhir.r4.model.ElementDefinition.DiscriminatorType;
065import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBaseComponent;
066import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent;
067import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionConstraintComponent;
068import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionExampleComponent;
069import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionMappingComponent;
070import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingComponent;
071import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent;
072import org.hl7.fhir.r4.model.ElementDefinition.PropertyRepresentation;
073import org.hl7.fhir.r4.model.ElementDefinition.SlicingRules;
074import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent;
075import org.hl7.fhir.r4.model.Enumeration;
076import org.hl7.fhir.r4.model.Enumerations.BindingStrength;
077import org.hl7.fhir.r4.model.Enumerations.FHIRVersion;
078import org.hl7.fhir.r4.model.Extension;
079import org.hl7.fhir.r4.model.IntegerType;
080import org.hl7.fhir.r4.model.PrimitiveType;
081import org.hl7.fhir.r4.model.Quantity;
082import org.hl7.fhir.r4.model.Resource;
083import org.hl7.fhir.r4.model.StringType;
084import org.hl7.fhir.r4.model.StructureDefinition;
085import org.hl7.fhir.r4.model.StructureDefinition.ExtensionContextType;
086import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionContextComponent;
087import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionDifferentialComponent;
088import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind;
089import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionMappingComponent;
090import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionSnapshotComponent;
091import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule;
092import org.hl7.fhir.r4.model.Type;
093import org.hl7.fhir.r4.model.UriType;
094import org.hl7.fhir.r4.model.ValueSet;
095import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent;
096import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent;
097import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
098import org.hl7.fhir.r4.utils.NarrativeGenerator;
099import org.hl7.fhir.r4.utils.ToolingExtensions;
100import org.hl7.fhir.r4.utils.TranslatingUtilities;
101import org.hl7.fhir.r4.utils.formats.CSVWriter;
102import org.hl7.fhir.r4.utils.formats.XLSXWriter;
103import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
104import org.hl7.fhir.utilities.TerminologyServiceOptions;
105import org.hl7.fhir.utilities.Utilities;
106import org.hl7.fhir.utilities.VersionUtilities;
107import org.hl7.fhir.utilities.validation.ValidationMessage;
108import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
109import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
110import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
111import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
112import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
113import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableGenerationMode;
114import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
115import org.hl7.fhir.utilities.xhtml.XhtmlNode;
116import org.hl7.fhir.utilities.xml.SchematronWriter;
117import org.hl7.fhir.utilities.xml.SchematronWriter.Rule;
118import org.hl7.fhir.utilities.xml.SchematronWriter.SchematronType;
119import org.hl7.fhir.utilities.xml.SchematronWriter.Section;
120
121/** 
122 * This class provides a set of utility operations for working with Profiles.
123 * Key functionality:
124 *  * getChildMap --?
125 *  * getChildList
126 *  * generateSnapshot: Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
127 *  * closeDifferential: fill out a differential by excluding anything not mentioned
128 *  * generateExtensionsTable: generate the HTML for a hierarchical table presentation of the extensions
129 *  * generateTable: generate  the HTML for a hierarchical table presentation of a structure
130 *  * generateSpanningTable: generate the HTML for a table presentation of a network of structures, starting at a nominated point
131 *  * summarize: describe the contents of a profile
132 *  
133 * 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
134 *  
135 * @author Grahame
136 *
137 */
138public class ProfileUtilities extends TranslatingUtilities {
139
140  public class ElementRedirection {
141
142    private String path;
143    private ElementDefinition element;
144
145    public ElementRedirection(ElementDefinition element, String path) {
146      this.path = path;
147      this.element = element;
148    }
149
150    public ElementDefinition getElement() {
151      return element;
152    }
153
154    @Override
155    public String toString() {
156      return element.toString() + " : "+path;
157    }
158
159    public String getPath() {
160      return path;
161    }
162
163  }
164
165  public class TypeSlice {
166    private ElementDefinition defn;
167    private String type;
168    public TypeSlice(ElementDefinition defn, String type) {
169      super();
170      this.defn = defn;
171      this.type = type;
172    }
173    public ElementDefinition getDefn() {
174      return defn;
175    }
176    public String getType() {
177      return type;
178    }
179    
180  }
181  private static final int MAX_RECURSION_LIMIT = 10;
182  
183  public class ExtensionContext {
184
185    private ElementDefinition element;
186    private StructureDefinition defn;
187
188    public ExtensionContext(StructureDefinition ext, ElementDefinition ed) {
189      this.defn = ext;
190      this.element = ed;
191    }
192
193    public ElementDefinition getElement() {
194      return element;
195    }
196
197    public StructureDefinition getDefn() {
198      return defn;
199    }
200
201    public String getUrl() {
202      if (element == defn.getSnapshot().getElement().get(0))
203        return defn.getUrl();
204      else
205        return element.getSliceName();
206    }
207
208    public ElementDefinition getExtensionValueDefinition() {
209      int i = defn.getSnapshot().getElement().indexOf(element)+1;
210      while (i < defn.getSnapshot().getElement().size()) {
211        ElementDefinition ed = defn.getSnapshot().getElement().get(i);
212        if (ed.getPath().equals(element.getPath()))
213          return null;
214        if (ed.getPath().startsWith(element.getPath()+".value"))
215          return ed;
216        i++;
217      }
218      return null;
219    }
220  }
221
222  private static final String ROW_COLOR_ERROR = "#ffcccc";
223  private static final String ROW_COLOR_FATAL = "#ff9999";
224  private static final String ROW_COLOR_WARNING = "#ffebcc";
225  private static final String ROW_COLOR_HINT = "#ebf5ff";
226  private static final String ROW_COLOR_NOT_MUST_SUPPORT = "#d6eaf8";
227  public static final int STATUS_OK = 0;
228  public static final int STATUS_HINT = 1;
229  public static final int STATUS_WARNING = 2;
230  public static final int STATUS_ERROR = 3;
231  public static final int STATUS_FATAL = 4;
232
233
234  private static final String DERIVATION_EQUALS = "derivation.equals";
235  public static final String DERIVATION_POINTER = "derived.pointer";
236  public static final String IS_DERIVED = "derived.fact";
237  public static final String UD_ERROR_STATUS = "error-status";
238  private static final String GENERATED_IN_SNAPSHOT = "profileutilities.snapshot.processed";
239  private final boolean ADD_REFERENCE_TO_TABLE = true;
240
241  private boolean useTableForFixedValues = true;
242  private boolean debug;
243
244  // note that ProfileUtilities are used re-entrantly internally, so nothing with process state can be here
245  private final IWorkerContext context;
246  private List<ValidationMessage> messages;
247  private List<String> snapshotStack = new ArrayList<String>();
248  private ProfileKnowledgeProvider pkp;
249  private boolean igmode;
250  private boolean exception;
251  private TerminologyServiceOptions terminologyServiceOptions = new TerminologyServiceOptions();
252  private boolean newSlicingProcessing;
253
254  public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp) {
255    super();
256    this.context = context;
257    this.messages = messages;
258    this.pkp = pkp;
259  }
260
261  private class UnusedTracker {
262    private boolean used;
263  }
264
265  public boolean isIgmode() {
266    return igmode;
267  }
268
269
270  public void setIgmode(boolean igmode) {
271    this.igmode = igmode;
272  }
273
274  public interface ProfileKnowledgeProvider {
275    public class BindingResolution {
276      public String display;
277      public String url;
278    }
279    public boolean isDatatype(String typeSimple);
280    public boolean isResource(String typeSimple);
281    public boolean hasLinkFor(String typeSimple);
282    public String getLinkFor(String corePath, String typeSimple);
283    public BindingResolution resolveBinding(StructureDefinition def, ElementDefinitionBindingComponent binding, String path) throws FHIRException;
284    public BindingResolution resolveBinding(StructureDefinition def, String url, String path) throws FHIRException;
285    public String getLinkForProfile(StructureDefinition profile, String url);
286    public boolean prependLinks();
287    public String getLinkForUrl(String corePath, String s);
288  }
289
290
291
292  public static List<ElementDefinition> getChildMap(StructureDefinition profile, ElementDefinition element) throws DefinitionException {
293    if (element.getContentReference()!=null) {
294      for (ElementDefinition e : profile.getSnapshot().getElement()) {
295        if (element.getContentReference().equals("#"+e.getId()))
296          return getChildMap(profile, e);
297      }
298      throw new DefinitionException("Unable to resolve name reference "+element.getContentReference()+" at path "+element.getPath());
299
300    } else {
301      List<ElementDefinition> res = new ArrayList<ElementDefinition>();
302      List<ElementDefinition> elements = profile.getSnapshot().getElement();
303      String path = element.getPath();
304      for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) {
305        ElementDefinition e = elements.get(index);
306        if (e.getPath().startsWith(path + ".")) {
307          // We only want direct children, not all descendants
308          if (!e.getPath().substring(path.length()+1).contains("."))
309            res.add(e);
310        } else
311          break;
312      }
313      return res;
314    }
315  }
316
317
318  public static List<ElementDefinition> getSliceList(StructureDefinition profile, ElementDefinition element) throws DefinitionException {
319    if (!element.hasSlicing())
320      throw new Error("getSliceList should only be called when the element has slicing");
321
322    List<ElementDefinition> res = new ArrayList<ElementDefinition>();
323    List<ElementDefinition> elements = profile.getSnapshot().getElement();
324    String path = element.getPath();
325    for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) {
326      ElementDefinition e = elements.get(index);
327      if (e.getPath().startsWith(path + ".") || e.getPath().equals(path)) {
328        // We want elements with the same path (until we hit an element that doesn't start with the same path)
329        if (e.getPath().equals(element.getPath()))
330          res.add(e);
331      } else
332        break;
333    }
334    return res;
335  }
336
337
338  /**
339   * Given a Structure, navigate to the element given by the path and return the direct children of that element
340   *
341   * @param structure The structure to navigate into
342   * @param path The path of the element within the structure to get the children for
343   * @return A List containing the element children (all of them are Elements)
344   */
345  public static List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id) {
346    return getChildList(profile, path, id, false);
347  }
348  
349  public static List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id, boolean diff) {
350    List<ElementDefinition> res = new ArrayList<ElementDefinition>();
351
352    boolean capturing = id==null;
353    if (id==null && !path.contains("."))
354      capturing = true;
355    
356    List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement();
357    for (ElementDefinition e : list) {
358      if (e == null)
359        throw new Error("element = null: "+profile.getUrl());
360      if (e.getId() == null)
361        throw new Error("element id = null: "+e.toString()+" on "+profile.getUrl());
362      
363      if (!capturing && id!=null && e.getId().equals(id)) {
364        capturing = true;
365      }
366      
367      // If our element is a slice, stop capturing children as soon as we see the next slice
368      if (capturing && e.hasId() && id!= null && !e.getId().equals(id) && e.getPath().equals(path))
369        break;
370      
371      if (capturing) {
372        String p = e.getPath();
373  
374        if (!Utilities.noString(e.getContentReference()) && path.startsWith(p)) {
375          if (path.length() > p.length())
376            return getChildList(profile, e.getContentReference()+"."+path.substring(p.length()+1), null, diff);
377          else
378            return getChildList(profile, e.getContentReference(), null, diff);
379          
380        } else if (p.startsWith(path+".") && !p.equals(path)) {
381          String tail = p.substring(path.length()+1);
382          if (!tail.contains(".")) {
383            res.add(e);
384          }
385        }
386      }
387    }
388
389    return res;
390  }
391
392  public static List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element, boolean diff) {
393    return getChildList(structure, element.getPath(), element.getId(), diff);
394  }
395
396  public static List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element) {
397    return getChildList(structure, element.getPath(), element.getId(), false);
398        }
399
400  public void updateMaps(StructureDefinition base, StructureDefinition derived) throws DefinitionException {
401    if (base == null)
402        throw new DefinitionException("no base profile provided");
403    if (derived == null)
404      throw new DefinitionException("no derived structure provided");
405    
406    for (StructureDefinitionMappingComponent baseMap : base.getMapping()) {
407      boolean found = false;
408      for (StructureDefinitionMappingComponent derivedMap : derived.getMapping()) {
409        if (derivedMap.getUri() != null && derivedMap.getUri().equals(baseMap.getUri())) {
410          found = true;
411          break;
412        }
413      }
414      if (!found)
415        derived.getMapping().add(baseMap);
416    }
417  }
418  
419  /**
420   * Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
421   *
422   * @param base - the base structure on which the differential will be applied
423   * @param differential - the differential to apply to the base
424   * @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)
425   * @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)
426   * @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
427   * @return
428   * @throws FHIRException 
429   * @throws DefinitionException 
430   * @throws Exception
431   */
432  public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String webUrl, String profileName) throws DefinitionException, FHIRException {
433    if (base == null)
434      throw new DefinitionException("no base profile provided");
435    if (derived == null)
436      throw new DefinitionException("no derived structure provided");
437
438    if (snapshotStack.contains(derived.getUrl()))
439      throw new DefinitionException("Circular snapshot references detected; cannot generate snapshot (stack = "+snapshotStack.toString()+")");
440    snapshotStack.add(derived.getUrl());
441    
442    if (!Utilities.noString(webUrl) && !webUrl.endsWith("/"))
443      webUrl = webUrl + '/';
444
445    derived.setSnapshot(new StructureDefinitionSnapshotComponent());
446
447    try {
448    // so we have two lists - the base list, and the differential list
449    // the differential list is only allowed to include things that are in the base list, but
450    // is allowed to include them multiple times - thereby slicing them
451
452    // our approach is to walk through the base list, and see whether the differential
453    // says anything about them.
454    int baseCursor = 0;
455    int diffCursor = 0; // we need a diff cursor because we can only look ahead, in the bound scoped by longer paths
456
457    if (derived.hasDifferential() && !derived.getDifferential().getElementFirstRep().getPath().contains(".") && !derived.getDifferential().getElementFirstRep().getType().isEmpty())
458      throw new Error("type on first differential element!");
459
460    for (ElementDefinition e : derived.getDifferential().getElement()) 
461      e.clearUserData(GENERATED_IN_SNAPSHOT);
462    
463    // we actually delegate the work to a subroutine so we can re-enter it with a different cursors
464      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
465
466      processPaths("", derived.getSnapshot(), base.getSnapshot(), diff, baseCursor, diffCursor, base.getSnapshot().getElement().size()-1, 
467          derived.getDifferential().hasElement() ? derived.getDifferential().getElement().size()-1 : -1, url, webUrl, derived.present(), null, null, false, base.getUrl(), null, false, new ArrayList<ElementRedirection>(), base);
468    if (!derived.getSnapshot().getElementFirstRep().getType().isEmpty())
469      throw new Error("type on first snapshot element for "+derived.getSnapshot().getElementFirstRep().getPath()+" in "+derived.getUrl()+" from "+base.getUrl());
470    updateMaps(base, derived);
471    
472      if (debug) {
473      System.out.println("Differential: ");
474      for (ElementDefinition ed : derived.getDifferential().getElement())
475        System.out.println("  "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  id = "+ed.getId()+" "+constraintSummary(ed));
476      System.out.println("Snapshot: ");
477      for (ElementDefinition ed : derived.getSnapshot().getElement())
478        System.out.println("  "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  id = "+ed.getId()+" "+constraintSummary(ed));
479    }
480      setIds(derived, false);
481    //Check that all differential elements have a corresponding snapshot element
482      for (ElementDefinition e : diff.getElement()) {
483        if (!e.hasUserData("diff-source"))
484          throw new Error("Unxpected internal condition - no source on diff element");
485        else {
486          if (e.hasUserData(DERIVATION_EQUALS))
487            ((Base) e.getUserData("diff-source")).setUserData(DERIVATION_EQUALS, e.getUserData(DERIVATION_EQUALS));
488          if (e.hasUserData(DERIVATION_POINTER))
489            ((Base) e.getUserData("diff-source")).setUserData(DERIVATION_POINTER, e.getUserData(DERIVATION_POINTER));
490        }
491      if (!e.hasUserData(GENERATED_IN_SNAPSHOT)) {
492          System.out.println("Error in snapshot generation: Differential for "+derived.getUrl()+" with " + (e.hasId() ? "id: "+e.getId() : "path: "+e.getPath())+" has an element that is not marked with a snapshot match");
493        if (exception)
494            throw new DefinitionException("Snapshot for "+derived.getUrl()+" does not contain an element that matches an existing differential element that has "+(e.hasId() ? "id: "+e.getId() : "path: "+e.getPath()));
495        else
496          messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url, "Snapshot for "+derived.getUrl()+" does not contain an element that matches an existing differential element that has id: " + e.getId(), ValidationMessage.IssueSeverity.ERROR));
497      }
498    }
499    if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
500      for (ElementDefinition ed : derived.getSnapshot().getElement()) {
501        if (!ed.hasBase()) {
502          ed.getBase().setPath(ed.getPath()).setMin(ed.getMin()).setMax(ed.getMax());
503        }
504      }
505    }
506    } catch (Exception e) {
507      // if we had an exception generating the snapshot, make sure we don't leave any half generated snapshot behind
508      derived.setSnapshot(null);
509      throw e;
510    }
511  }
512
513  private StructureDefinitionDifferentialComponent cloneDiff(StructureDefinitionDifferentialComponent source) {
514    StructureDefinitionDifferentialComponent diff = new StructureDefinitionDifferentialComponent();
515    for (ElementDefinition sed : source.getElement()) {
516      ElementDefinition ted = sed.copy();
517      diff.getElement().add(ted);
518      ted.setUserData("diff-source", sed);
519    }
520    return diff;
521  }
522
523
524  private String constraintSummary(ElementDefinition ed) {
525    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
526    if (ed.hasPattern())
527      b.append("pattern="+ed.getPattern().fhirType());
528    if (ed.hasFixed())
529      b.append("fixed="+ed.getFixed().fhirType());
530    if (ed.hasConstraint())
531      b.append("constraints="+ed.getConstraint().size());
532    return b.toString();
533  }
534
535
536  private String sliceSummary(ElementDefinition ed) {
537    if (!ed.hasSlicing() && !ed.hasSliceName())
538      return "";
539    if (ed.hasSliceName())
540      return " (slicename = "+ed.getSliceName()+")";
541    
542    StringBuilder b = new StringBuilder();
543    boolean first = true;
544    for (ElementDefinitionSlicingDiscriminatorComponent d : ed.getSlicing().getDiscriminator()) {
545      if (first) 
546        first = false;
547      else
548        b.append("|");
549      b.append(d.getPath());
550    }
551    return " (slicing by "+b.toString()+")";
552  }
553
554
555  private String typeSummary(ElementDefinition ed) {
556    StringBuilder b = new StringBuilder();
557    boolean first = true;
558    for (TypeRefComponent tr : ed.getType()) {
559      if (first) 
560        first = false;
561      else
562        b.append("|");
563      b.append(tr.getWorkingCode());
564    }
565    return b.toString();
566  }
567
568  private String typeSummaryWithProfile(ElementDefinition ed) {
569    StringBuilder b = new StringBuilder();
570    boolean first = true;
571    for (TypeRefComponent tr : ed.getType()) {
572      if (first) 
573        first = false;
574      else
575        b.append("|");
576      b.append(tr.getWorkingCode());
577      if (tr.hasProfile()) {
578        b.append("(");
579        b.append(tr.getProfile());
580        b.append(")");
581        
582      }
583    }
584    return b.toString();
585  }
586
587
588  private boolean findMatchingElement(String id, List<ElementDefinition> list) {
589    for (ElementDefinition ed : list) {
590      if (ed.getId().equals(id))
591        return true;
592      if (id.endsWith("[x]")) {
593        if (ed.getId().startsWith(id.substring(0, id.length()-3)) && !ed.getId().substring(id.length()-3).contains("."))
594          return true;
595      }
596    }
597    return false;
598  }
599
600
601  /**
602   * @param trimDifferential
603   * @param srcSD 
604   * @throws DefinitionException, FHIRException 
605   * @throws Exception
606   */
607  private ElementDefinition processPaths(String indent, StructureDefinitionSnapshotComponent result, StructureDefinitionSnapshotComponent base, StructureDefinitionDifferentialComponent differential, int baseCursor, int diffCursor, int baseLimit,
608      int diffLimit, String url, String webUrl, String profileName, String contextPathSrc, String contextPathDst, boolean trimDifferential, String contextName, String resultPathBase, boolean slicingDone, List<ElementRedirection> redirector, StructureDefinition srcSD) throws DefinitionException, FHIRException {
609    if (debug) 
610      System.out.println(indent+"PP @ "+resultPathBase+" / "+contextPathSrc+" : base = "+baseCursor+" to "+baseLimit+", diff = "+diffCursor+" to "+diffLimit+" (slicing = "+slicingDone+", redirector = "+(redirector == null ? "null" : redirector.toString())+")");
611    ElementDefinition res = null; 
612    List<TypeSlice> typeList = new ArrayList<>();
613    // just repeat processing entries until we run out of our allowed scope (1st entry, the allowed scope is all the entries)
614    while (baseCursor <= baseLimit) {
615      // get the current focus of the base, and decide what to do
616      ElementDefinition currentBase = base.getElement().get(baseCursor);
617      String cpath = fixedPathSource(contextPathSrc, currentBase.getPath(), redirector);
618      if (debug) 
619        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)+") "+
620           "(slicingDone = "+slicingDone+") (diffpath= "+(differential.getElement().size() > diffCursor ? differential.getElement().get(diffCursor).getPath() : "n/a")+")");
621      List<ElementDefinition> diffMatches = getDiffMatches(differential, cpath, diffCursor, diffLimit, profileName); // get a list of matching elements in scope
622
623      // in the simple case, source is not sliced.
624      if (!currentBase.hasSlicing()) {
625        if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item
626          // so we just copy it in
627          ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy());
628          outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
629          updateFromBase(outcome, currentBase);
630          markDerived(outcome);
631          if (resultPathBase == null)
632            resultPathBase = outcome.getPath();
633          else if (!outcome.getPath().startsWith(resultPathBase))
634            throw new DefinitionException("Adding wrong path");
635          result.getElement().add(outcome);
636          if (hasInnerDiffMatches(differential, cpath, diffCursor, diffLimit, base.getElement(), true)) {
637            // well, the profile walks into this, so we need to as well
638            // did we implicitly step into a new type?
639            if (baseHasChildren(base, currentBase)) { // not a new type here
640              processPaths(indent+"  ", result, base, differential, baseCursor+1, diffCursor, baseLimit, diffLimit, url, webUrl, profileName, contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, false, redirector, srcSD);
641              baseCursor = indexOfFirstNonChild(base, currentBase, baseCursor+1, baseLimit);
642            } else {
643              if (outcome.getType().size() == 0) {
644                throw new DefinitionException(diffMatches.get(0).getPath()+" has no children ("+differential.getElement().get(diffCursor).getPath()+") and no types in profile "+profileName);
645              }
646            if (outcome.getType().size() > 1) {
647              for (TypeRefComponent t : outcome.getType()) {
648                  if (!t.getWorkingCode().equals("Reference"))
649                  throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName);
650              }
651            }
652              StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
653            if (dt == null)
654                throw new DefinitionException("Unknown type "+outcome.getType().get(0)+" at "+diffMatches.get(0).getPath());
655            contextName = dt.getUrl();
656            int start = diffCursor;
657            while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), cpath+"."))
658              diffCursor++;
659            processPaths(indent+"  ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1,
660                  diffCursor-1, url, webUrl, profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false, redirector, srcSD);
661            }
662          }
663          baseCursor++;
664        } 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
665          ElementDefinition template = null;
666          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())) {
667            CanonicalType p = diffMatches.get(0).getType().get(0).getProfile().get(0);
668            StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getValue());
669            if (sd != null) {
670              if (!sd.hasSnapshot()) {
671                StructureDefinition sdb = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
672                if (sdb == null)
673                  throw new DefinitionException("no base for "+sd.getBaseDefinition());
674                generateSnapshot(sdb, sd, sd.getUrl(), (sdb.hasUserData("path")) ? Utilities.extractBaseUrl(sdb.getUserString("path")) : webUrl, sd.getName());
675              }
676              ElementDefinition src;
677              if (p.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT)) {
678                 src = null;
679                 String eid = p.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT);
680                 for (ElementDefinition t : sd.getSnapshot().getElement()) {
681                   if (eid.equals(t.getId()))
682                     src = t;
683                 }
684                 if (src == null)
685                   throw new DefinitionException("Unable to find element "+eid+" in "+p.getValue());
686              } else 
687                src = sd.getSnapshot().getElement().get(0);
688              template = src.copy().setPath(currentBase.getPath());
689              template.setSliceName(null);
690              // temporary work around
691              if (!"Extension".equals(diffMatches.get(0).getType().get(0).getCode())) {
692                template.setMin(currentBase.getMin());
693                template.setMax(currentBase.getMax());
694              }
695            }
696          } 
697          if (template == null)
698            template = currentBase.copy();
699          else
700            // some of what's in currentBase overrides template
701            template = overWriteWithCurrent(template, currentBase);
702          
703          ElementDefinition outcome = updateURLs(url, webUrl, template);
704          outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
705          if (res == null)
706            res = outcome;
707          updateFromBase(outcome, currentBase);
708          if (diffMatches.get(0).hasSliceName())
709            outcome.setSliceName(diffMatches.get(0).getSliceName());
710          updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url, srcSD);
711          removeStatusExtensions(outcome);
712//          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
713//            outcome.setPath(outcome.getPath().substring(0, outcome.getPath().length()-3)+Utilities.capitalize(outcome.getType().get(0).getCode()));
714          outcome.setSlicing(null);
715          if (resultPathBase == null)
716            resultPathBase = outcome.getPath();
717          else if (!outcome.getPath().startsWith(resultPathBase))
718            throw new DefinitionException("Adding wrong path");
719          result.getElement().add(outcome);
720          baseCursor++;
721          diffCursor = differential.getElement().indexOf(diffMatches.get(0))+1;
722          if (differential.getElement().size() > diffCursor && outcome.getPath().contains(".") && (isDataType(outcome.getType()) || outcome.hasContentReference())) {  // don't want to do this for the root, since that's base, and we're already processing it
723            if (pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".") && !baseWalksInto(base.getElement(), baseCursor)) {
724              if (outcome.getType().size() > 1) {
725                if (outcome.getPath().endsWith("[x]") && !diffMatches.get(0).getPath().endsWith("[x]")) {
726                  String en = tail(outcome.getPath());
727                  String tn = tail(diffMatches.get(0).getPath());
728                  String t = tn.substring(en.length()-3);
729                  if (isPrimitive(Utilities.uncapitalize(t)))
730                    t = Utilities.uncapitalize(t);
731                  List<TypeRefComponent> ntr = getByTypeName(outcome.getType(), t); // keep any additional information
732                  if (ntr.isEmpty()) 
733                    ntr.add(new TypeRefComponent().setCode(t));
734                  outcome.getType().clear();
735                  outcome.getType().addAll(ntr);
736                }
737                if (outcome.getType().size() > 1)
738                  for (TypeRefComponent t : outcome.getType()) {
739                    if (!t.getCode().equals("Reference")) {
740                      boolean nonExtension = false;
741                      for (ElementDefinition ed : diffMatches)
742                        if (ed != diffMatches.get(0) && !ed.getPath().endsWith(".extension"))
743                          nonExtension = true;
744                      if (nonExtension)
745                        throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName);
746                    }
747                }
748              }
749              int start = diffCursor;
750              while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+"."))
751                diffCursor++;
752              if (outcome.hasContentReference()) {
753                ElementDefinition tgt = getElementById(base.getElement(), outcome.getContentReference());
754                if (tgt == null)
755                  throw new DefinitionException("Unable to resolve reference to "+outcome.getContentReference());
756                replaceFromContentReference(outcome, tgt);
757                int nbc = base.getElement().indexOf(tgt)+1;
758                int nbl = nbc;
759                while (nbl < base.getElement().size() && base.getElement().get(nbl).getPath().startsWith(tgt.getPath()+"."))
760                  nbl++;
761                processPaths(indent+"  ", result, base, differential, nbc, start - 1, nbl-1, diffCursor - 1, url, webUrl, profileName, tgt.getPath(), diffMatches.get(0).getPath(), trimDifferential, contextName, resultPathBase, false, redirectorStack(redirector, outcome, cpath), srcSD);
762              } else {
763                StructureDefinition dt = outcome.getType().size() == 1 ? getProfileForDataType(outcome.getType().get(0)) : getProfileForDataType("Element");
764                if (dt == null)
765                  throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") for type "+typeCode(outcome.getType())+" in profile "+profileName+", but can't find type");
766                contextName = dt.getUrl();
767                processPaths(indent+"  ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1,
768                    diffCursor - 1, url, webUrl, profileName+pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false, new ArrayList<ElementRedirection>(), srcSD);
769              }
770            }
771          }
772        } else if (diffsConstrainTypes(diffMatches, cpath, typeList)) {
773          int start = 0;
774          int nbl = findEndOfElement(base, baseCursor);
775          int ndc = differential.getElement().indexOf(diffMatches.get(0));
776          ElementDefinition elementToRemove = null;
777          // we come here whether they are sliced in the diff, or whether the short cut is used. 
778          if (typeList.get(0).type != null) {
779            // this is the short cut method, we've just dived in and specified a type slice. 
780            // in R3 (and unpatched R4, as a workaround right now...
781            if (!VersionUtilities.isR4Plus(context.getVersion()) || !newSlicingProcessing) { // newSlicingProcessing is a work around for editorial loop dependency
782              // we insert a cloned element with the right types at the start of the diffMatches
783              ElementDefinition ed = new ElementDefinition();
784              ed.setPath(determineTypeSlicePath(diffMatches.get(0).getPath(), cpath));
785              for (TypeSlice ts : typeList) 
786                ed.addType().setCode(ts.type);
787              ed.setSlicing(new ElementDefinitionSlicingComponent());
788              ed.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
789              ed.getSlicing().setRules(SlicingRules.CLOSED);
790              ed.getSlicing().setOrdered(false);
791              diffMatches.add(0, ed);
792              differential.getElement().add(ndc, ed);
793              elementToRemove = ed;
794            } else {
795              // as of R4, this changed; if there's no slice, there's no constraint on the slice types, only one the type. 
796              // so the element we insert specifies no types (= all types) allowed in the base, not just the listed type. 
797              // see also discussion here: https://chat.fhir.org/#narrow/stream/179177-conformance/topic/Slicing.20a.20non-repeating.20element             
798              ElementDefinition ed = new ElementDefinition();
799              ed.setPath(determineTypeSlicePath(diffMatches.get(0).getPath(), cpath));
800              ed.setSlicing(new ElementDefinitionSlicingComponent());
801              ed.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
802              ed.getSlicing().setRules(SlicingRules.CLOSED);
803              ed.getSlicing().setOrdered(false);
804              diffMatches.add(0, ed);
805              differential.getElement().add(ndc, ed);
806              elementToRemove = ed;
807            }
808          }
809          int ndl = findEndOfElement(differential, ndc);
810          // the first element is setting up the slicing
811          if (diffMatches.get(0).getSlicing().hasRules())
812            if (diffMatches.get(0).getSlicing().getRules() != SlicingRules.CLOSED)
813              throw new FHIRException("Error at path "+contextPathSrc+": Type slicing with slicing.rules != closed");
814          if (diffMatches.get(0).getSlicing().hasOrdered())
815            if (diffMatches.get(0).getSlicing().getOrdered())
816              throw new FHIRException("Error at path "+contextPathSrc+": Type slicing with slicing.ordered = true");
817          if (diffMatches.get(0).getSlicing().hasDiscriminator()) {
818            if (diffMatches.get(0).getSlicing().getDiscriminator().size() != 1)
819              throw new FHIRException("Error at path "+contextPathSrc+": Type slicing with slicing.discriminator.count() > 1");
820            if (!"$this".equals(diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getPath()))
821              throw new FHIRException("Error at path "+contextPathSrc+": Type slicing with slicing.discriminator.path != '$this'");
822            if (diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getType() != DiscriminatorType.TYPE)
823              throw new FHIRException("Error at path "+contextPathSrc+": Type slicing with slicing.discriminator.type != 'type'");
824          }
825          // check the slice names too while we're at it...
826          for (TypeSlice ts : typeList)
827            if (ts.type != null) {
828              String tn = rootName(cpath)+Utilities.capitalize(ts.type);
829              if (!ts.defn.hasSliceName())
830                ts.defn.setSliceName(tn);
831              else if (!ts.defn.getSliceName().equals(tn))
832                throw new FHIRException("Error at path "+(!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath)+": Slice name must be '"+tn+"' but is '"+ts.defn.getSliceName()+"'"); 
833              if (!ts.defn.hasType())
834                ts.defn.addType().setCode(ts.type);
835              else if (ts.defn.getType().size() > 1)
836                throw new FHIRException("Error at path "+(!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath)+": Slice for type '"+tn+"' has more than one type '"+ts.defn.typeSummary()+"'"); 
837              else if (!ts.defn.getType().get(0).getCode().equals(ts.type))
838                throw new FHIRException("Error at path "+(!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath)+": Slice for type '"+tn+"' has wrong type '"+ts.defn.typeSummary()+"'"); 
839            }
840
841          // ok passed the checks. 
842          // copy the root diff, and then process any children it has
843          ElementDefinition e = processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, 0), contextPathSrc, contextPathDst, 
844              trimDifferential, contextName, resultPathBase, true, redirector, srcSD);
845          if (e==null)
846            throw new FHIRException("Did not find type root: " + diffMatches.get(0).getPath());
847          // now set up slicing on the e (cause it was wiped by what we called.
848          e.setSlicing(new ElementDefinitionSlicingComponent());
849          e.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
850          e.getSlicing().setRules(SlicingRules.CLOSED);
851          e.getSlicing().setOrdered(false);
852          start++;
853          // now process the siblings, which should each be type constrained - and may also have their own children
854          // now we process the base scope repeatedly for each instance of the item in the differential list
855          for (int i = start; i < diffMatches.size(); i++) {
856            // our processing scope for the differential is the item in the list, and all the items before the next one in the list
857            ndc = differential.getElement().indexOf(diffMatches.get(i));
858            ndl = findEndOfElement(differential, ndc);
859            processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true, redirector, srcSD);
860          }
861          if (elementToRemove != null) {
862            differential.getElement().remove(elementToRemove);
863            ndl--;
864          }
865          
866          // ok, done with that - next in the base list
867          baseCursor = nbl+1;
868          diffCursor = ndl+1;
869          
870        } else {
871          // ok, the differential slices the item. Let's check our pre-conditions to ensure that this is correct
872          if (!unbounded(currentBase) && !isSlicedToOneOnly(diffMatches.get(0)))
873            // you can only slice an element that doesn't repeat if the sum total of your slices is limited to 1
874            // (but you might do that in order to split up constraints by type)
875            throw new DefinitionException("Attempt to a slice an element that does not repeat: "+currentBase.getPath()+"/"+currentBase.getPath()+" from "+contextName+" in "+url);
876          if (!diffMatches.get(0).hasSlicing() && !isExtension(currentBase)) // well, the diff has set up a slice, but hasn't defined it. this is an error
877            throw new DefinitionException("Differential does not have a slice: "+currentBase.getPath()+"/ (b:"+baseCursor+" of "+ baseLimit+" / "+ diffCursor +"/ "+diffLimit+") in profile "+url);
878
879          // well, if it passed those preconditions then we slice the dest.
880          int start = 0;
881          int nbl = findEndOfElement(base, baseCursor);
882//          if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && differential.getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0))+1) {
883          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
884            int ndc = differential.getElement().indexOf(diffMatches.get(0));
885            int ndl = findEndOfElement(differential, ndc);
886            ElementDefinition e = processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, 0), contextPathSrc, contextPathDst, 
887                trimDifferential, contextName, resultPathBase, true, redirector, srcSD);
888            if (e==null)
889              throw new FHIRException("Did not find single slice: " + diffMatches.get(0).getPath());
890            e.setSlicing(diffMatches.get(0).getSlicing());
891            start++;
892          } else {
893            // we're just going to accept the differential slicing at face value
894            ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy());
895            outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
896            updateFromBase(outcome, currentBase);
897
898            if (!diffMatches.get(0).hasSlicing())
899              outcome.setSlicing(makeExtensionSlicing());
900            else
901              outcome.setSlicing(diffMatches.get(0).getSlicing().copy());
902            if (!outcome.getPath().startsWith(resultPathBase))
903              throw new DefinitionException("Adding wrong path");
904            result.getElement().add(outcome);
905
906            // 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.
907            if (!diffMatches.get(0).hasSliceName()) {
908              updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url, srcSD);
909              removeStatusExtensions(outcome);
910              if (!outcome.hasContentReference() && !outcome.hasType()) {
911                throw new DefinitionException("not done yet");
912              }
913              start++;
914              // result.getElement().remove(result.getElement().size()-1);
915            } else 
916              checkExtensionDoco(outcome);
917          }
918          // now, for each entry in the diff matches, we're going to process the base item
919          // our processing scope for base is all the children of the current path
920          int ndc = diffCursor;
921          int ndl = diffCursor;
922          for (int i = start; i < diffMatches.size(); i++) {
923            // our processing scope for the differential is the item in the list, and all the items before the next one in the list
924            ndc = differential.getElement().indexOf(diffMatches.get(i));
925            ndl = findEndOfElement(differential, ndc);
926/*            if (skipSlicingElement && i == 0) {
927              ndc = ndc + 1;
928              if (ndc > ndl)
929                continue;
930            }*/
931            // now we process the base scope repeatedly for each instance of the item in the differential list
932            processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true, redirector, srcSD);
933          }
934          // ok, done with that - next in the base list
935          baseCursor = nbl+1;
936          diffCursor = ndl+1;
937        }
938      } else {
939        // the item is already sliced in the base profile.
940        // here's the rules
941        //  1. irrespective of whether the slicing is ordered or not, the definition order must be maintained
942        //  2. slice element names have to match.
943        //  3. new slices must be introduced at the end
944        // corallory: you can't re-slice existing slices. is that ok?
945
946        // we're going to need this:
947        String path = currentBase.getPath();
948        ElementDefinition original = currentBase;
949
950        if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item
951          // copy across the currentbase, and all of its children and siblings
952          while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path)) {
953            ElementDefinition outcome = updateURLs(url, webUrl, base.getElement().get(baseCursor).copy());
954            outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
955            if (!outcome.getPath().startsWith(resultPathBase))
956              throw new DefinitionException("Adding wrong path in profile " + profileName + ": "+outcome.getPath()+" vs " + resultPathBase);
957            result.getElement().add(outcome); // so we just copy it in
958            baseCursor++;
959          }
960        } else {
961          // first - check that the slicing is ok
962          boolean closed = currentBase.getSlicing().getRules() == SlicingRules.CLOSED;
963          int diffpos = 0;
964          boolean isExtension = cpath.endsWith(".extension") || cpath.endsWith(".modifierExtension");
965          if (diffMatches.get(0).hasSlicing()) { // it might be null if the differential doesn't want to say anything about slicing
966//            if (!isExtension)
967//              diffpos++; // if there's a slice on the first, we'll ignore any content it has
968            ElementDefinitionSlicingComponent dSlice = diffMatches.get(0).getSlicing();
969            ElementDefinitionSlicingComponent bSlice = currentBase.getSlicing();
970            if (dSlice.hasOrderedElement() && bSlice.hasOrderedElement() && !orderMatches(dSlice.getOrderedElement(), bSlice.getOrderedElement()))
971              throw new DefinitionException("Slicing rules on differential ("+summarizeSlicing(dSlice)+") do not match those on base ("+summarizeSlicing(bSlice)+") - order @ "+path+" ("+contextName+")");
972            if (!discriminatorMatches(dSlice.getDiscriminator(), bSlice.getDiscriminator()))
973             throw new DefinitionException("Slicing rules on differential ("+summarizeSlicing(dSlice)+") do not match those on base ("+summarizeSlicing(bSlice)+") - discriminator @ "+path+" ("+contextName+")");
974            if (!ruleMatches(dSlice.getRules(), bSlice.getRules()))
975             throw new DefinitionException("Slicing rules on differential ("+summarizeSlicing(dSlice)+") do not match those on base ("+summarizeSlicing(bSlice)+") - rule @ "+path+" ("+contextName+")");
976          }
977          ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy());
978          outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
979          updateFromBase(outcome, currentBase);
980          if (diffMatches.get(0).hasSlicing() || !diffMatches.get(0).hasSliceName()) {
981            updateFromSlicing(outcome.getSlicing(), diffMatches.get(0).getSlicing());
982            updateFromDefinition(outcome, diffMatches.get(0), profileName, closed, url, srcSD); // if there's no slice, we don't want to update the unsliced description
983            removeStatusExtensions(outcome);
984          } else if (!diffMatches.get(0).hasSliceName())
985            diffMatches.get(0).setUserData(GENERATED_IN_SNAPSHOT, outcome); // because of updateFromDefinition isn't called 
986          
987          result.getElement().add(outcome);
988
989          if (!diffMatches.get(0).hasSliceName()) { // it's not real content, just the slice
990            diffpos++; 
991          }
992          if (hasInnerDiffMatches(differential, cpath, diffpos, diffLimit, base.getElement(), false)) {
993            int nbl = findEndOfElement(base, baseCursor);
994            int ndc = differential.getElement().indexOf(diffMatches.get(0))+1;
995            int ndl = findEndOfElement(differential, ndc);
996            processPaths(indent+"  ", result, base, differential, baseCursor+1, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, 0), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, false, null, srcSD);
997//            throw new Error("Not done yet");
998//          } else if (currentBase.getType().get(0).getCode().equals("BackboneElement") && diffMatches.size() > 0 && diffMatches.get(0).hasSliceName()) {
999          } else if (currentBase.getType().get(0).getCode().equals("BackboneElement")) {
1000            // We need to copy children of the backbone element before we start messing around with slices
1001            int nbl = findEndOfElement(base, baseCursor);
1002            for (int i = baseCursor+1; i<=nbl; i++) {
1003              outcome = updateURLs(url, webUrl, base.getElement().get(i).copy());
1004              result.getElement().add(outcome);
1005            }
1006          }
1007
1008          // now, we have two lists, base and diff. we're going to work through base, looking for matches in diff.
1009          List<ElementDefinition> baseMatches = getSiblings(base.getElement(), currentBase);
1010          for (ElementDefinition baseItem : baseMatches) {
1011            baseCursor = base.getElement().indexOf(baseItem);
1012            outcome = updateURLs(url, webUrl, baseItem.copy());
1013            updateFromBase(outcome, currentBase);
1014            outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
1015            outcome.setSlicing(null);
1016            if (!outcome.getPath().startsWith(resultPathBase))
1017              throw new DefinitionException("Adding wrong path");
1018            if (diffpos < diffMatches.size() && diffMatches.get(diffpos).getSliceName().equals(outcome.getSliceName())) {
1019              // if there's a diff, we update the outcome with diff
1020              // no? updateFromDefinition(outcome, diffMatches.get(diffpos), profileName, closed, url);
1021              //then process any children
1022              int nbl = findEndOfElement(base, baseCursor);
1023              int ndc = differential.getElement().indexOf(diffMatches.get(diffpos));
1024              int ndl = findEndOfElement(differential, ndc);
1025              // now we process the base scope repeatedly for each instance of the item in the differential list
1026              processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, diffpos), contextPathSrc, contextPathDst, closed, contextName, resultPathBase, true, redirector, srcSD);
1027              // ok, done with that - now set the cursors for if this is the end
1028              baseCursor = nbl;
1029              diffCursor = ndl+1;
1030              diffpos++;
1031            } else {
1032              result.getElement().add(outcome);
1033              baseCursor++;
1034              // just copy any children on the base
1035              while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path) && !base.getElement().get(baseCursor).getPath().equals(path)) {
1036                outcome = updateURLs(url, webUrl, base.getElement().get(baseCursor).copy());
1037                outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
1038                if (!outcome.getPath().startsWith(resultPathBase))
1039                  throw new DefinitionException("Adding wrong path");
1040                result.getElement().add(outcome);
1041                baseCursor++;
1042              }
1043              //Lloyd - add this for test T15
1044              baseCursor--;
1045            }
1046          }
1047          // finally, we process any remaining entries in diff, which are new (and which are only allowed if the base wasn't closed
1048          if (closed && diffpos < diffMatches.size())
1049            throw new DefinitionException("The base snapshot marks a slicing as closed, but the differential tries to extend it in "+profileName+" at "+path+" ("+cpath+")");
1050          if (diffpos == diffMatches.size()) {
1051//Lloyd This was causing problems w/ Telus
1052//            diffCursor++;
1053          } else {
1054            while (diffpos < diffMatches.size()) {
1055              ElementDefinition diffItem = diffMatches.get(diffpos);
1056              for (ElementDefinition baseItem : baseMatches)
1057                if (baseItem.getSliceName().equals(diffItem.getSliceName()))
1058                  throw new DefinitionException("Named items are out of order in the slice");
1059              outcome = updateURLs(url, webUrl, currentBase.copy());
1060              //            outcome = updateURLs(url, diffItem.copy());
1061              outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
1062              updateFromBase(outcome, currentBase);
1063              outcome.setSlicing(null);
1064              if (!outcome.getPath().startsWith(resultPathBase))
1065                throw new DefinitionException("Adding wrong path");
1066              result.getElement().add(outcome);
1067              updateFromDefinition(outcome, diffItem, profileName, trimDifferential, url, srcSD);
1068              removeStatusExtensions(outcome);
1069              // --- LM Added this
1070              diffCursor = differential.getElement().indexOf(diffItem)+1;
1071              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
1072                if (!baseWalksInto(base.getElement(), baseCursor)) {
1073                  if (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".")) {
1074                    if (outcome.getType().size() > 1)
1075                      for (TypeRefComponent t : outcome.getType()) {
1076                        if (!t.getCode().equals("Reference"))
1077                          throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName);
1078                      }
1079                    TypeRefComponent t = outcome.getType().get(0);
1080                    if (t.getCode().equals("BackboneElement")) {
1081                      int baseStart = base.getElement().indexOf(currentBase)+1;
1082                      int baseMax = baseStart + 1;
1083                      while (baseMax < base.getElement().size() && base.getElement().get(baseMax).getPath().startsWith(currentBase.getPath()+"."))
1084                       baseMax++;
1085                      int start = diffCursor;
1086                      while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+"."))
1087                        diffCursor++;
1088                      processPaths(indent+"  ", result, base, differential, baseStart, start-1, baseMax-1,
1089                          diffCursor - 1, url, webUrl, profileName+pathTail(diffMatches, 0), base.getElement().get(0).getPath(), base.getElement().get(0).getPath(), trimDifferential, contextName, resultPathBase, false, redirector, srcSD);
1090                      
1091                    } else {
1092                      StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
1093                      //                if (t.getCode().equals("Extension") && t.hasProfile() && !t.getProfile().contains(":")) {
1094                      // lloydfix                  dt = 
1095                      //                }
1096                      if (dt == null)
1097                        throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") for type "+typeCode(outcome.getType())+" in profile "+profileName+", but can't find type");
1098                      contextName = dt.getUrl();
1099                      int start = diffCursor;
1100                      while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+"."))
1101                        diffCursor++;
1102                      processPaths(indent+"  ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start-1, dt.getSnapshot().getElement().size()-1,
1103                          diffCursor - 1, url, webUrl, profileName+pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false, redirector, srcSD);
1104                    }
1105                  } else if (outcome.getType().get(0).getCode().equals("Extension")) {
1106                    // Force URL to appear if we're dealing with an extension.  (This is a kludge - may need to drill down in other cases where we're slicing and the type has a profile declaration that could be setting the fixed value)
1107                    StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
1108                    for (ElementDefinition extEd : dt.getSnapshot().getElement()) {
1109                      // We only want the children that aren't the root
1110                      if (extEd.getPath().contains(".")) {
1111                        ElementDefinition extUrlEd = updateURLs(url, webUrl, extEd.copy());
1112                        extUrlEd.setPath(fixedPathDest(outcome.getPath(), extUrlEd.getPath(), redirector, null));
1113                        //                      updateFromBase(extUrlEd, currentBase);
1114                        markDerived(extUrlEd);
1115                        result.getElement().add(extUrlEd);
1116                      }
1117                    }                  
1118                  }
1119                }
1120              }
1121              // ---
1122              diffpos++;
1123            }
1124          }
1125          baseCursor++;
1126        }
1127      }
1128    }
1129    
1130    int i = 0;
1131    for (ElementDefinition e : result.getElement()) {
1132      i++;
1133      if (e.hasMinElement() && e.getMinElement().getValue()==null)
1134        throw new Error("null min");
1135    }
1136    return res;
1137  }
1138
1139
1140  private void removeStatusExtensions(ElementDefinition outcome) {
1141    outcome.removeExtension(ToolingExtensions.EXT_FMM_LEVEL);
1142    outcome.removeExtension(ToolingExtensions.EXT_STANDARDS_STATUS);
1143    outcome.removeExtension(ToolingExtensions.EXT_NORMATIVE_VERSION);
1144    outcome.removeExtension(ToolingExtensions.EXT_WORKGROUP);    
1145  }
1146
1147
1148  private String descED(List<ElementDefinition> list, int index) {
1149    return index >=0 && index < list.size() ? list.get(index).present() : "X";
1150  }
1151
1152
1153  private boolean baseHasChildren(StructureDefinitionSnapshotComponent base, ElementDefinition ed) {
1154    int index = base.getElement().indexOf(ed);
1155    if (index == -1 || index >= base.getElement().size()-1)
1156      return false;
1157    String p = base.getElement().get(index+1).getPath();
1158    return isChildOf(p, ed.getPath());
1159  }
1160
1161
1162  private boolean isChildOf(String sub, String focus) {
1163    if (focus.endsWith("[x]")) {
1164      focus = focus.substring(0, focus.length()-3);
1165      return sub.startsWith(focus);
1166    } else 
1167      return sub.startsWith(focus+".");
1168  }
1169
1170
1171  private int indexOfFirstNonChild(StructureDefinitionSnapshotComponent base, ElementDefinition currentBase, int i, int baseLimit) {
1172    return baseLimit+1;
1173  }
1174
1175
1176  private String rootName(String cpath) {
1177    String t = tail(cpath);
1178    return t.replace("[x]", "");
1179  }
1180
1181
1182  private String determineTypeSlicePath(String path, String cpath) {
1183    String headP = path.substring(0, path.lastIndexOf("."));
1184//    String tailP = path.substring(path.lastIndexOf(".")+1);
1185    String tailC = cpath.substring(cpath.lastIndexOf(".")+1);
1186    return headP+"."+tailC;
1187  }
1188
1189
1190  private boolean isImplicitSlicing(ElementDefinition ed, String path) {
1191    if (ed == null || ed.getPath() == null || path == null)
1192      return false;
1193    if (path.equals(ed.getPath()))
1194      return false;
1195    boolean ok = path.endsWith("[x]") && ed.getPath().startsWith(path.substring(0, path.length()-3));
1196    return ok;
1197  }
1198
1199
1200  private boolean diffsConstrainTypes(List<ElementDefinition> diffMatches, String cPath, List<TypeSlice> typeList) {
1201//    if (diffMatches.size() < 2)
1202//      return false;
1203    String p = diffMatches.get(0).getPath();
1204    if (!p.endsWith("[x]") && !cPath.endsWith("[x]"))
1205      return false;
1206    typeList.clear();
1207    String rn = tail(cPath);
1208    rn = rn.substring(0, rn.length()-3);
1209    for (int i = 0; i < diffMatches.size(); i++) {
1210      ElementDefinition ed = diffMatches.get(i);
1211      String n = tail(ed.getPath());
1212      if (!n.startsWith(rn))
1213        return false;
1214      String s = n.substring(rn.length());
1215      if (!s.contains(".")) {
1216        if (ed.hasSliceName() && ed.getType().size() == 1) {
1217          typeList.add(new TypeSlice(ed, ed.getTypeFirstRep().getWorkingCode()));
1218        } else if (!ed.hasSliceName() && !s.equals("[x]")) {
1219          if (isDataType(s))
1220            typeList.add(new TypeSlice(ed, s));
1221          else if (isConstrainedDataType(s))
1222            typeList.add(new TypeSlice(ed, baseType(s)));
1223          else if (isPrimitive(Utilities.uncapitalize(s)))
1224            typeList.add(new TypeSlice(ed, Utilities.uncapitalize(s)));
1225        } else if (!ed.hasSliceName() && s.equals("[x]"))
1226          typeList.add(new TypeSlice(ed, null));
1227      }
1228    }
1229    return true;
1230  }
1231
1232
1233  private List<ElementRedirection> redirectorStack(List<ElementRedirection> redirector, ElementDefinition outcome, String path) {
1234    List<ElementRedirection> result = new ArrayList<ElementRedirection>();
1235    result.addAll(redirector);
1236    result.add(new ElementRedirection(outcome, path));
1237    return result;
1238  }
1239
1240
1241  private List<TypeRefComponent> getByTypeName(List<TypeRefComponent> type, String t) {
1242    List<TypeRefComponent> res = new ArrayList<TypeRefComponent>();
1243    for (TypeRefComponent tr : type) {
1244      if (t.equals(tr.getWorkingCode()))
1245          res.add(tr);
1246    }
1247    return res;
1248  }
1249
1250
1251  private void replaceFromContentReference(ElementDefinition outcome, ElementDefinition tgt) {
1252    outcome.setContentReference(null);
1253    outcome.getType().clear(); // though it should be clear anyway
1254    outcome.getType().addAll(tgt.getType());    
1255  }
1256
1257
1258  private boolean baseWalksInto(List<ElementDefinition> elements, int cursor) {
1259    if (cursor >= elements.size())
1260      return false;
1261    String path = elements.get(cursor).getPath();
1262    String prevPath = elements.get(cursor - 1).getPath();
1263    return path.startsWith(prevPath + ".");
1264  }
1265
1266
1267  private ElementDefinition overWriteWithCurrent(ElementDefinition profile, ElementDefinition usage) throws FHIRFormatError {
1268    ElementDefinition res = profile.copy();
1269    if (usage.hasSliceName())
1270      res.setSliceName(usage.getSliceName());
1271    if (usage.hasLabel())
1272      res.setLabel(usage.getLabel());
1273    for (Coding c : usage.getCode())
1274      res.addCode(c);
1275    
1276    if (usage.hasDefinition())
1277      res.setDefinition(usage.getDefinition());
1278    if (usage.hasShort())
1279      res.setShort(usage.getShort());
1280    if (usage.hasComment())
1281      res.setComment(usage.getComment());
1282    if (usage.hasRequirements())
1283      res.setRequirements(usage.getRequirements());
1284    for (StringType c : usage.getAlias())
1285      res.addAlias(c.getValue());
1286    if (usage.hasMin())
1287      res.setMin(usage.getMin());
1288    if (usage.hasMax())
1289      res.setMax(usage.getMax());
1290     
1291    if (usage.hasFixed())
1292      res.setFixed(usage.getFixed());
1293    if (usage.hasPattern())
1294      res.setPattern(usage.getPattern());
1295    if (usage.hasExample())
1296      res.setExample(usage.getExample());
1297    if (usage.hasMinValue())
1298      res.setMinValue(usage.getMinValue());
1299    if (usage.hasMaxValue())
1300      res.setMaxValue(usage.getMaxValue());     
1301    if (usage.hasMaxLength())
1302      res.setMaxLength(usage.getMaxLength());
1303    if (usage.hasMustSupport())
1304      res.setMustSupport(usage.getMustSupport());
1305    if (usage.hasBinding())
1306      res.setBinding(usage.getBinding().copy());
1307    for (ElementDefinitionConstraintComponent c : usage.getConstraint())
1308      res.addConstraint(c);
1309    for (Extension e : usage.getExtension()) {
1310      if (!res.hasExtension(e.getUrl()))
1311        res.addExtension(e.copy());
1312    }
1313    
1314    return res;
1315  }
1316
1317
1318  private boolean checkExtensionDoco(ElementDefinition base) {
1319    // see task 3970. For an extension, there's no point copying across all the underlying definitional stuff
1320    boolean isExtension = base.getPath().equals("Extension") || base.getPath().endsWith(".extension") || base.getPath().endsWith(".modifierExtension");
1321    if (isExtension) {
1322      base.setDefinition("An Extension");
1323      base.setShort("Extension");
1324      base.setCommentElement(null);
1325      base.setRequirementsElement(null);
1326      base.getAlias().clear();
1327      base.getMapping().clear();
1328    }
1329    return isExtension;
1330  }
1331
1332
1333  private String pathTail(List<ElementDefinition> diffMatches, int i) {
1334    
1335    ElementDefinition d = diffMatches.get(i);
1336    String s = d.getPath().contains(".") ? d.getPath().substring(d.getPath().lastIndexOf(".")+1) : d.getPath();
1337    return "."+s + (d.hasType() && d.getType().get(0).hasProfile() ? "["+d.getType().get(0).getProfile()+"]" : "");
1338  }
1339
1340
1341  private void markDerived(ElementDefinition outcome) {
1342    for (ElementDefinitionConstraintComponent inv : outcome.getConstraint())
1343      inv.setUserData(IS_DERIVED, true);
1344  }
1345
1346
1347  private String summarizeSlicing(ElementDefinitionSlicingComponent slice) {
1348    StringBuilder b = new StringBuilder();
1349    boolean first = true;
1350    for (ElementDefinitionSlicingDiscriminatorComponent d : slice.getDiscriminator()) {
1351      if (first)
1352        first = false;
1353      else
1354        b.append(", ");
1355      b.append(d);
1356    }
1357    b.append("(");
1358    if (slice.hasOrdered())
1359      b.append(slice.getOrderedElement().asStringValue());
1360    b.append("/");
1361    if (slice.hasRules())
1362      b.append(slice.getRules().toCode());
1363    b.append(")");
1364    if (slice.hasDescription()) {
1365      b.append(" \"");
1366      b.append(slice.getDescription());
1367      b.append("\"");
1368    }
1369    return b.toString();
1370  }
1371
1372
1373  private void updateFromBase(ElementDefinition derived, ElementDefinition base) {
1374    if (base.hasBase()) {
1375      if (!derived.hasBase())
1376        derived.setBase(new ElementDefinitionBaseComponent());
1377      derived.getBase().setPath(base.getBase().getPath());
1378      derived.getBase().setMin(base.getBase().getMin());
1379      derived.getBase().setMax(base.getBase().getMax());
1380    } else {
1381      if (!derived.hasBase())
1382        derived.setBase(new ElementDefinitionBaseComponent());
1383      derived.getBase().setPath(base.getPath());
1384      derived.getBase().setMin(base.getMin());
1385      derived.getBase().setMax(base.getMax());
1386    }
1387  }
1388
1389
1390  private boolean pathStartsWith(String p1, String p2) {
1391    return p1.startsWith(p2);
1392  }
1393
1394  private boolean pathMatches(String p1, String p2) {
1395    return p1.equals(p2) || (p2.endsWith("[x]") && p1.startsWith(p2.substring(0, p2.length()-3)) && !p1.substring(p2.length()-3).contains("."));
1396  }
1397
1398
1399  private String fixedPathSource(String contextPath, String pathSimple, List<ElementRedirection> redirector) {
1400    if (contextPath == null)
1401      return pathSimple;
1402//    String ptail = pathSimple.substring(contextPath.length() + 1);
1403    if (redirector.size() > 0) {
1404      String ptail = pathSimple.substring(contextPath.length()+1);
1405      return redirector.get(redirector.size()-1).getPath()+"."+ptail;
1406//      return contextPath+"."+tail(redirector.getPath())+"."+ptail.substring(ptail.indexOf(".")+1);
1407    } else {
1408      String ptail = pathSimple.substring(pathSimple.indexOf(".")+1);
1409      return contextPath+"."+ptail;
1410    }
1411  }
1412  
1413  private String fixedPathDest(String contextPath, String pathSimple, List<ElementRedirection> redirector, String redirectSource) {
1414    String s;
1415    if (contextPath == null)
1416      s = pathSimple;
1417    else {
1418      if (redirector.size() > 0) {
1419        String ptail = pathSimple.substring(redirectSource.length() + 1);
1420  //      ptail = ptail.substring(ptail.indexOf(".")+1);
1421        s = contextPath+"."+/*tail(redirector.getPath())+"."+*/ptail;
1422      } else {
1423        String ptail = pathSimple.substring(pathSimple.indexOf(".")+1);
1424        s = contextPath+"."+ptail;
1425      }
1426    }
1427    return s;
1428  }  
1429
1430  private StructureDefinition getProfileForDataType(TypeRefComponent type)  {
1431    StructureDefinition sd = null;
1432    if (type.hasProfile()) {
1433      sd = context.fetchResource(StructureDefinition.class, type.getProfile().get(0).getValue());
1434      if (sd == null)
1435        System.out.println("Failed to find referenced profile: " + type.getProfile());
1436    }
1437    if (sd == null)
1438      sd = context.fetchTypeDefinition(type.getWorkingCode());
1439    if (sd == null)
1440      System.out.println("XX: failed to find profle for type: " + type.getWorkingCode()); // debug GJM
1441    return sd;
1442  }
1443
1444  private StructureDefinition getProfileForDataType(String type)  {
1445    StructureDefinition sd = context.fetchTypeDefinition(type);
1446    if (sd == null)
1447      System.out.println("XX: failed to find profle for type: " + type); // debug GJM
1448    return sd;
1449  }
1450
1451
1452  public static String typeCode(List<TypeRefComponent> types) {
1453    StringBuilder b = new StringBuilder();
1454    boolean first = true;
1455    for (TypeRefComponent type : types) {
1456      if (first) first = false; else b.append(", ");
1457      b.append(type.getWorkingCode());
1458      if (type.hasTargetProfile())
1459        b.append("{"+type.getTargetProfile()+"}");
1460      else if (type.hasProfile())
1461        b.append("{"+type.getProfile()+"}");
1462    }
1463    return b.toString();
1464  }
1465
1466
1467  private boolean isDataType(List<TypeRefComponent> types) {
1468    if (types.isEmpty())
1469      return false;
1470    for (TypeRefComponent type : types) {
1471      String t = type.getWorkingCode();
1472      if (!isDataType(t) && !isPrimitive(t))
1473        return false;
1474    }
1475    return true;
1476  }
1477
1478
1479  /**
1480   * Finds internal references in an Element's Binding and StructureDefinition references (in TypeRef) and bases them on the given url
1481   * @param url - the base url to use to turn internal references into absolute references
1482   * @param element - the Element to update
1483   * @return - the updated Element
1484   */
1485  private ElementDefinition updateURLs(String url, String webUrl, ElementDefinition element) {
1486    if (element != null) {
1487      ElementDefinition defn = element;
1488      if (defn.hasBinding() && defn.getBinding().hasValueSet() && defn.getBinding().getValueSet().startsWith("#"))
1489        defn.getBinding().setValueSet(url+defn.getBinding().getValueSet());
1490      for (TypeRefComponent t : defn.getType()) {
1491        for (UriType u : t.getProfile()) {
1492          if (u.getValue().startsWith("#"))
1493            u.setValue(url+t.getProfile());
1494        }
1495        for (UriType u : t.getTargetProfile()) {
1496          if (u.getValue().startsWith("#"))
1497            u.setValue(url+t.getTargetProfile());
1498        }
1499      }
1500      if (webUrl != null) {
1501        // also, must touch up the markdown
1502        if (element.hasDefinition())
1503          element.setDefinition(processRelativeUrls(element.getDefinition(), webUrl));
1504        if (element.hasComment())
1505          element.setComment(processRelativeUrls(element.getComment(), webUrl));
1506        if (element.hasRequirements())
1507          element.setRequirements(processRelativeUrls(element.getRequirements(), webUrl));
1508        if (element.hasMeaningWhenMissing())
1509          element.setMeaningWhenMissing(processRelativeUrls(element.getMeaningWhenMissing(), webUrl));
1510      }
1511    }
1512    return element;
1513  }
1514
1515  private String processRelativeUrls(String markdown, String webUrl) {
1516    StringBuilder b = new StringBuilder();
1517    int i = 0;
1518    while (i < markdown.length()) {
1519      if (i < markdown.length()-3 && markdown.substring(i, i+2).equals("](")) {
1520         int j = i + 2;
1521        while (j < markdown.length() && markdown.charAt(j) != ')')
1522          j++;
1523        if (j < markdown.length()) {
1524          String url = markdown.substring(i+2, j);
1525          if (!Utilities.isAbsoluteUrl(url)) {
1526            b.append("](");
1527            b.append(webUrl);
1528            i = i + 1;
1529          } else
1530            b.append(markdown.charAt(i));
1531        } else 
1532          b.append(markdown.charAt(i));
1533      } else {
1534        b.append(markdown.charAt(i));
1535      }
1536      i++;
1537    }
1538    return b.toString();
1539  }
1540
1541
1542  private List<ElementDefinition> getSiblings(List<ElementDefinition> list, ElementDefinition current) {
1543    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
1544    String path = current.getPath();
1545    int cursor = list.indexOf(current)+1;
1546    while (cursor < list.size() && list.get(cursor).getPath().length() >= path.length()) {
1547      if (pathMatches(list.get(cursor).getPath(), path))
1548        result.add(list.get(cursor));
1549      cursor++;
1550    }
1551    return result;
1552  }
1553
1554  private void updateFromSlicing(ElementDefinitionSlicingComponent dst, ElementDefinitionSlicingComponent src) {
1555    if (src.hasOrderedElement())
1556      dst.setOrderedElement(src.getOrderedElement().copy());
1557    if (src.hasDiscriminator()) {
1558      //    dst.getDiscriminator().addAll(src.getDiscriminator());  Can't use addAll because it uses object equality, not string equality
1559      for (ElementDefinitionSlicingDiscriminatorComponent s : src.getDiscriminator()) {
1560        boolean found = false;
1561        for (ElementDefinitionSlicingDiscriminatorComponent d : dst.getDiscriminator()) {
1562          if (matches(d, s)) {
1563            found = true;
1564            break;
1565          }
1566        }
1567        if (!found)
1568          dst.getDiscriminator().add(s);
1569      }
1570    }
1571    if (src.hasRulesElement())
1572      dst.setRulesElement(src.getRulesElement().copy());
1573  }
1574
1575  private boolean orderMatches(BooleanType diff, BooleanType base) {
1576    return (diff == null) || (base == null) || (diff.getValue() == base.getValue());
1577  }
1578
1579  private boolean discriminatorMatches(List<ElementDefinitionSlicingDiscriminatorComponent> diff, List<ElementDefinitionSlicingDiscriminatorComponent> base) {
1580    if (diff.isEmpty() || base.isEmpty())
1581        return true;
1582    if (diff.size() != base.size())
1583        return false;
1584    for (int i = 0; i < diff.size(); i++)
1585        if (!matches(diff.get(i), base.get(i)))
1586                return false;
1587    return true;
1588  }
1589
1590  private boolean matches(ElementDefinitionSlicingDiscriminatorComponent c1, ElementDefinitionSlicingDiscriminatorComponent c2) {
1591    return c1.getType().equals(c2.getType()) && c1.getPath().equals(c2.getPath());
1592  }
1593
1594
1595  private boolean ruleMatches(SlicingRules diff, SlicingRules base) {
1596    return (diff == null) || (base == null) || (diff == base) || (base == SlicingRules.OPEN) ||
1597        ((diff == SlicingRules.OPENATEND && base == SlicingRules.CLOSED));
1598  }
1599
1600  private boolean isSlicedToOneOnly(ElementDefinition e) {
1601    return (e.hasSlicing() && e.hasMaxElement() && e.getMax().equals("1"));
1602  }
1603
1604  private ElementDefinitionSlicingComponent makeExtensionSlicing() {
1605        ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent();
1606    slice.addDiscriminator().setPath("url").setType(DiscriminatorType.VALUE);
1607    slice.setOrdered(false);
1608    slice.setRules(SlicingRules.OPEN);
1609    return slice;
1610  }
1611
1612  private boolean isExtension(ElementDefinition currentBase) {
1613    return currentBase.getPath().endsWith(".extension") || currentBase.getPath().endsWith(".modifierExtension");
1614  }
1615
1616  private boolean hasInnerDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, List<ElementDefinition> base, boolean allowSlices) throws DefinitionException {
1617    end = Math.min(context.getElement().size(), end);
1618    start = Math.max(0,  start);
1619    
1620    for (int i = start; i <= end; i++) {
1621      String statedPath = context.getElement().get(i).getPath();
1622      if (statedPath.startsWith(path+".")) {
1623          return true;
1624      } else if (!statedPath.endsWith(path))
1625        break;
1626    }
1627    return false;
1628  }
1629
1630  private List<ElementDefinition> getDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, String profileName) throws DefinitionException {
1631    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
1632    for (int i = start; i <= end; i++) {
1633      String statedPath = context.getElement().get(i).getPath();
1634      if (statedPath.equals(path) || (path.endsWith("[x]") && statedPath.length() > path.length() - 2 && statedPath.substring(0, path.length()-3).equals(path.substring(0, path.length()-3)) && (statedPath.length() < path.length() || !statedPath.substring(path.length()).contains(".")))) {
1635        /* 
1636         * Commenting this out because it raises warnings when profiling inherited elements.  For example,
1637         * Error: unknown element 'Bundle.meta.profile' (or it is out of order) in profile ... (looking for 'Bundle.entry')
1638         * Not sure we have enough information here to do the check properly.  Might be better done when we're sorting the profile?
1639
1640        if (i != start && result.isEmpty() && !path.startsWith(context.getElement().get(start).getPath()))
1641          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));
1642
1643         */
1644        result.add(context.getElement().get(i));
1645      }
1646    }
1647    return result;
1648  }
1649
1650  private int findEndOfElement(StructureDefinitionDifferentialComponent context, int cursor) {
1651            int result = cursor;
1652            String path = context.getElement().get(cursor).getPath()+".";
1653            while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path))
1654              result++;
1655            return result;
1656          }
1657
1658  private int findEndOfElement(StructureDefinitionSnapshotComponent context, int cursor) {
1659            int result = cursor;
1660            String path = context.getElement().get(cursor).getPath()+".";
1661            while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path))
1662              result++;
1663            return result;
1664          }
1665
1666  private boolean unbounded(ElementDefinition definition) {
1667    StringType max = definition.getMaxElement();
1668    if (max == null)
1669      return false; // this is not valid
1670    if (max.getValue().equals("1"))
1671      return false;
1672    if (max.getValue().equals("0"))
1673      return false;
1674    return true;
1675  }
1676
1677  private void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, boolean trimDifferential, String purl, StructureDefinition srcSD) throws DefinitionException, FHIRException {
1678    source.setUserData(GENERATED_IN_SNAPSHOT, dest);
1679    // we start with a clone of the base profile ('dest') and we copy from the profile ('source')
1680    // over the top for anything the source has
1681    ElementDefinition base = dest;
1682    ElementDefinition derived = source;
1683    derived.setUserData(DERIVATION_POINTER, base);
1684    boolean isExtension = checkExtensionDoco(base);
1685
1686
1687    // Before applying changes, apply them to what's in the profile
1688    // TODO: follow Chris's rules - Done by Lloyd
1689    StructureDefinition profile = null;
1690    if (base.hasSliceName())
1691      profile = base.getType().size() == 1 && base.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, base.getTypeFirstRep().getProfile().get(0).getValue()) : null;
1692    if (profile==null)
1693      profile = source.getType().size() == 1 && source.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, source.getTypeFirstRep().getProfile().get(0).getValue()) : null;
1694    if (profile != null) {
1695      ElementDefinition e = profile.getSnapshot().getElement().get(0);
1696      base.setDefinition(e.getDefinition());
1697      base.setShort(e.getShort());
1698      if (e.hasCommentElement())
1699        base.setCommentElement(e.getCommentElement());
1700      if (e.hasRequirementsElement())
1701        base.setRequirementsElement(e.getRequirementsElement());
1702      base.getAlias().clear();
1703      base.getAlias().addAll(e.getAlias());
1704      base.getMapping().clear();
1705      base.getMapping().addAll(e.getMapping());
1706    } 
1707    if (derived != null) {
1708      if (derived.hasSliceName()) {
1709        base.setSliceName(derived.getSliceName());
1710      }
1711      
1712      if (derived.hasShortElement()) {
1713        if (!Base.compareDeep(derived.getShortElement(), base.getShortElement(), false))
1714          base.setShortElement(derived.getShortElement().copy());
1715        else if (trimDifferential)
1716          derived.setShortElement(null);
1717        else if (derived.hasShortElement())
1718          derived.getShortElement().setUserData(DERIVATION_EQUALS, true);
1719      }
1720
1721      if (derived.hasDefinitionElement()) {
1722        if (derived.getDefinition().startsWith("..."))
1723          base.setDefinition(Utilities.appendDerivedTextToBase(base.getDefinition(), derived.getDefinition()));
1724        else if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false))
1725          base.setDefinitionElement(derived.getDefinitionElement().copy());
1726        else if (trimDifferential)
1727          derived.setDefinitionElement(null);
1728        else if (derived.hasDefinitionElement())
1729          derived.getDefinitionElement().setUserData(DERIVATION_EQUALS, true);
1730      }
1731
1732      if (derived.hasCommentElement()) {
1733        if (derived.getComment().startsWith("..."))
1734          base.setComment(Utilities.appendDerivedTextToBase(base.getComment(), derived.getComment()));
1735        else if (derived.hasCommentElement()!= base.hasCommentElement() || !Base.compareDeep(derived.getCommentElement(), base.getCommentElement(), false))
1736          base.setCommentElement(derived.getCommentElement().copy());
1737        else if (trimDifferential)
1738          base.setCommentElement(derived.getCommentElement().copy());
1739        else if (derived.hasCommentElement())
1740          derived.getCommentElement().setUserData(DERIVATION_EQUALS, true);
1741      }
1742
1743      if (derived.hasLabelElement()) {
1744        if (derived.getLabel().startsWith("..."))
1745          base.setLabel(Utilities.appendDerivedTextToBase(base.getLabel(), derived.getLabel()));
1746        else if (!base.hasLabelElement() || !Base.compareDeep(derived.getLabelElement(), base.getLabelElement(), false))
1747          base.setLabelElement(derived.getLabelElement().copy());
1748        else if (trimDifferential)
1749          base.setLabelElement(derived.getLabelElement().copy());
1750        else if (derived.hasLabelElement())
1751          derived.getLabelElement().setUserData(DERIVATION_EQUALS, true);
1752      }
1753
1754      if (derived.hasRequirementsElement()) {
1755        if (derived.getRequirements().startsWith("..."))
1756          base.setRequirements(Utilities.appendDerivedTextToBase(base.getRequirements(), derived.getRequirements()));
1757        else if (!base.hasRequirementsElement() || !Base.compareDeep(derived.getRequirementsElement(), base.getRequirementsElement(), false))
1758          base.setRequirementsElement(derived.getRequirementsElement().copy());
1759        else if (trimDifferential)
1760          base.setRequirementsElement(derived.getRequirementsElement().copy());
1761        else if (derived.hasRequirementsElement())
1762          derived.getRequirementsElement().setUserData(DERIVATION_EQUALS, true);
1763      }
1764      // sdf-9
1765      if (derived.hasRequirements() && !base.getPath().contains("."))
1766        derived.setRequirements(null);
1767      if (base.hasRequirements() && !base.getPath().contains("."))
1768        base.setRequirements(null);
1769
1770      if (derived.hasAlias()) {
1771        if (!Base.compareDeep(derived.getAlias(), base.getAlias(), false))
1772          for (StringType s : derived.getAlias()) {
1773            if (!base.hasAlias(s.getValue()))
1774              base.getAlias().add(s.copy());
1775          }
1776        else if (trimDifferential)
1777          derived.getAlias().clear();
1778        else
1779          for (StringType t : derived.getAlias())
1780            t.setUserData(DERIVATION_EQUALS, true);
1781      }
1782
1783      if (derived.hasMinElement()) {
1784        if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) {
1785          if (derived.getMin() < base.getMin() && !derived.hasSliceName()) // in a slice, minimum cardinality rules do not apply 
1786            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));
1787          base.setMinElement(derived.getMinElement().copy());
1788        } else if (trimDifferential)
1789          derived.setMinElement(null);
1790        else
1791          derived.getMinElement().setUserData(DERIVATION_EQUALS, true);
1792      }
1793
1794      if (derived.hasMaxElement()) {
1795        if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) {
1796          if (isLargerMax(derived.getMax(), base.getMax()))
1797            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));
1798          base.setMaxElement(derived.getMaxElement().copy());
1799        } else if (trimDifferential)
1800          derived.setMaxElement(null);
1801        else
1802          derived.getMaxElement().setUserData(DERIVATION_EQUALS, true);
1803      }
1804
1805      if (derived.hasFixed()) {
1806        if (!Base.compareDeep(derived.getFixed(), base.getFixed(), true)) {
1807          base.setFixed(derived.getFixed().copy());
1808        } else if (trimDifferential)
1809          derived.setFixed(null);
1810        else
1811          derived.getFixed().setUserData(DERIVATION_EQUALS, true);
1812      }
1813
1814      if (derived.hasPattern()) {
1815        if (!Base.compareDeep(derived.getPattern(), base.getPattern(), false)) {
1816          base.setPattern(derived.getPattern().copy());
1817        } else
1818          if (trimDifferential)
1819            derived.setPattern(null);
1820          else
1821            derived.getPattern().setUserData(DERIVATION_EQUALS, true);
1822      }
1823
1824      for (ElementDefinitionExampleComponent ex : derived.getExample()) {
1825        boolean found = false;
1826        for (ElementDefinitionExampleComponent exS : base.getExample())
1827          if (Base.compareDeep(ex, exS, false))
1828            found = true;
1829        if (!found)
1830          base.addExample(ex.copy());
1831        else if (trimDifferential)
1832          derived.getExample().remove(ex);
1833        else
1834          ex.setUserData(DERIVATION_EQUALS, true);
1835      }
1836
1837      if (derived.hasMaxLengthElement()) {
1838        if (!Base.compareDeep(derived.getMaxLengthElement(), base.getMaxLengthElement(), false))
1839          base.setMaxLengthElement(derived.getMaxLengthElement().copy());
1840        else if (trimDifferential)
1841          derived.setMaxLengthElement(null);
1842        else
1843          derived.getMaxLengthElement().setUserData(DERIVATION_EQUALS, true);
1844      }
1845  
1846      if (derived.hasMaxValue()) {
1847        if (!Base.compareDeep(derived.getMaxValue(), base.getMaxValue(), false))
1848          base.setMaxValue(derived.getMaxValue().copy());
1849        else if (trimDifferential)
1850          derived.setMaxValue(null);
1851        else
1852          derived.getMaxValue().setUserData(DERIVATION_EQUALS, true);
1853      }
1854  
1855      if (derived.hasMinValue()) {
1856        if (!Base.compareDeep(derived.getMinValue(), base.getMinValue(), false))
1857          base.setMinValue(derived.getMinValue().copy());
1858        else if (trimDifferential)
1859          derived.setMinValue(null);
1860        else
1861          derived.getMinValue().setUserData(DERIVATION_EQUALS, true);
1862      }
1863
1864      // todo: what to do about conditions?
1865      // condition : id 0..*
1866
1867      if (derived.hasMustSupportElement()) {
1868        if (!(base.hasMustSupportElement() && Base.compareDeep(derived.getMustSupportElement(), base.getMustSupportElement(), false)))
1869          base.setMustSupportElement(derived.getMustSupportElement().copy());
1870        else if (trimDifferential)
1871          derived.setMustSupportElement(null);
1872        else
1873          derived.getMustSupportElement().setUserData(DERIVATION_EQUALS, true);
1874      }
1875
1876
1877      // profiles cannot change : isModifier, defaultValue, meaningWhenMissing
1878      // but extensions can change isModifier
1879      if (isExtension) {
1880        if (derived.hasIsModifierElement() && !(base.hasIsModifierElement() && Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false)))
1881          base.setIsModifierElement(derived.getIsModifierElement().copy());
1882        else if (trimDifferential)
1883          derived.setIsModifierElement(null);
1884        else if (derived.hasIsModifierElement())
1885          derived.getIsModifierElement().setUserData(DERIVATION_EQUALS, true);
1886        if (derived.hasIsModifierReasonElement() && !(base.hasIsModifierReasonElement() && Base.compareDeep(derived.getIsModifierReasonElement(), base.getIsModifierReasonElement(), false)))
1887          base.setIsModifierReasonElement(derived.getIsModifierReasonElement().copy());
1888        else if (trimDifferential)
1889          derived.setIsModifierReasonElement(null);
1890        else if (derived.hasIsModifierReasonElement())
1891          derived.getIsModifierReasonElement().setUserData(DERIVATION_EQUALS, true);
1892      }
1893
1894      if (derived.hasBinding()) {
1895        if (!base.hasBinding() || !Base.compareDeep(derived.getBinding(), base.getBinding(), false)) {
1896          if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && derived.getBinding().getStrength() != BindingStrength.REQUIRED)
1897            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));
1898//            throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode());
1899          else if (base.hasBinding() && derived.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && base.getBinding().hasValueSet() && derived.getBinding().hasValueSet()) {
1900            ValueSet baseVs = context.fetchResource(ValueSet.class, base.getBinding().getValueSet());
1901            ValueSet contextVs = context.fetchResource(ValueSet.class, derived.getBinding().getValueSet());
1902            if (baseVs == null) {
1903              messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING));
1904            } else if (contextVs == null) {
1905              messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING));
1906            } else {
1907              ValueSetExpansionOutcome expBase = context.expandVS(baseVs, true, false);
1908              ValueSetExpansionOutcome expDerived = context.expandVS(contextVs, true, false);
1909              if (expBase.getValueset() == null)
1910                messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING));
1911              else if (expDerived.getValueset() == null)
1912                messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING));
1913              else if (!isSubset(expBase.getValueset(), expDerived.getValueset()))
1914                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));
1915
1916            }
1917          }
1918          base.setBinding(derived.getBinding().copy());
1919        } else if (trimDifferential)
1920          derived.setBinding(null);
1921        else
1922          derived.getBinding().setUserData(DERIVATION_EQUALS, true);
1923      } // else if (base.hasBinding() && doesn't have bindable type )
1924        //  base
1925
1926      if (derived.hasIsSummaryElement()) {
1927        if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false)) {
1928          if (base.hasIsSummary())
1929            throw new Error("Error in profile "+pn+" at "+derived.getPath()+": Base isSummary = "+base.getIsSummaryElement().asStringValue()+", derived isSummary = "+derived.getIsSummaryElement().asStringValue());
1930          base.setIsSummaryElement(derived.getIsSummaryElement().copy());
1931        } else if (trimDifferential)
1932          derived.setIsSummaryElement(null);
1933        else
1934          derived.getIsSummaryElement().setUserData(DERIVATION_EQUALS, true);
1935      }
1936
1937      if (derived.hasType()) {
1938        if (!Base.compareDeep(derived.getType(), base.getType(), false)) {
1939          if (base.hasType()) {
1940            for (TypeRefComponent ts : derived.getType()) {
1941//              if (!ts.hasCode()) { // ommitted in the differential; copy it over....
1942//                if (base.getType().size() > 1) 
1943//                  throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": constrained type code must be present if there are multiple types ("+base.typeSummary()+")");
1944//                if (base.getType().get(0).getCode() != null)
1945//                  ts.setCode(base.getType().get(0).getCode());
1946//              }
1947              boolean ok = false;
1948              CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1949              String t = ts.getWorkingCode();
1950              for (TypeRefComponent td : base.getType()) {;
1951                String tt = td.getWorkingCode();
1952                b.append(tt);
1953                if (td.hasCode() && (tt.equals(t) || "Extension".equals(tt) || (t.equals("uri") && tt.equals("string"))  || // work around for old badly generated SDs
1954                    "Element".equals(tt) || "*".equals(tt) ||
1955                    (("Resource".equals(tt) || ("DomainResource".equals(tt)) && pkp.isResource(t)))))
1956                  ok = true;
1957              }
1958              if (!ok)
1959                throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal constrained type "+t+" from "+b.toString()+" in "+srcSD.getUrl());
1960            }
1961          }
1962          base.getType().clear();
1963          for (TypeRefComponent t : derived.getType()) {
1964            TypeRefComponent tt = t.copy();
1965//            tt.setUserData(DERIVATION_EQUALS, true);
1966            base.getType().add(tt);
1967          }
1968        }
1969        else if (trimDifferential)
1970          derived.getType().clear();
1971        else
1972          for (TypeRefComponent t : derived.getType())
1973            t.setUserData(DERIVATION_EQUALS, true);
1974      }
1975
1976      if (derived.hasMapping()) {
1977        // todo: mappings are not cumulative - one replaces another
1978        if (!Base.compareDeep(derived.getMapping(), base.getMapping(), false)) {
1979          for (ElementDefinitionMappingComponent s : derived.getMapping()) {
1980            boolean found = false;
1981            for (ElementDefinitionMappingComponent d : base.getMapping()) {
1982              found = found || (d.getIdentity().equals(s.getIdentity()) && d.getMap().equals(s.getMap()));
1983            }
1984            if (!found)
1985              base.getMapping().add(s);
1986          }
1987        }
1988        else if (trimDifferential)
1989          derived.getMapping().clear();
1990        else
1991          for (ElementDefinitionMappingComponent t : derived.getMapping())
1992            t.setUserData(DERIVATION_EQUALS, true);
1993      }
1994
1995      // todo: constraints are cumulative. there is no replacing
1996      for (ElementDefinitionConstraintComponent s : base.getConstraint()) { 
1997        s.setUserData(IS_DERIVED, true);
1998        if (!s.hasSource())
1999          s.setSource(base.getId());
2000      }
2001      if (derived.hasConstraint()) {
2002        for (ElementDefinitionConstraintComponent s : derived.getConstraint()) {
2003          ElementDefinitionConstraintComponent inv = s.copy();
2004          base.getConstraint().add(inv);
2005        }
2006      }
2007      
2008      // now, check that we still have a bindable type; if not, delete the binding - see task 8477
2009      if (dest.hasBinding() && !hasBindableType(dest))
2010        dest.setBinding(null);
2011        
2012      // finally, we copy any extensions from source to dest
2013      for (Extension ex : derived.getExtension()) {
2014        StructureDefinition sd  = context.fetchResource(StructureDefinition.class, ex.getUrl());
2015        if (sd == null || sd.getSnapshot() == null || sd.getSnapshot().getElementFirstRep().getMax().equals("1"))
2016          ToolingExtensions.removeExtension(dest, ex.getUrl());
2017        dest.addExtension(ex.copy());
2018      }
2019    }
2020  }
2021
2022  private boolean hasBindableType(ElementDefinition ed) {
2023    for (TypeRefComponent tr : ed.getType()) {
2024      if (Utilities.existsInList(tr.getWorkingCode(), "Coding", "CodeableConcept", "Quantity", "uri", "string", "code"))
2025        return true;
2026    }
2027    return false;
2028  }
2029
2030
2031  private boolean isLargerMax(String derived, String base) {
2032    if ("*".equals(base))
2033      return false;
2034    if ("*".equals(derived))
2035      return true;
2036    return Integer.parseInt(derived) > Integer.parseInt(base);
2037  }
2038
2039
2040  private boolean isSubset(ValueSet expBase, ValueSet expDerived) {
2041    return codesInExpansion(expDerived.getExpansion().getContains(), expBase.getExpansion());
2042  }
2043
2044
2045  private boolean codesInExpansion(List<ValueSetExpansionContainsComponent> contains, ValueSetExpansionComponent expansion) {
2046    for (ValueSetExpansionContainsComponent cc : contains) {
2047      if (!inExpansion(cc, expansion.getContains()))
2048        return false;
2049      if (!codesInExpansion(cc.getContains(), expansion))
2050        return false;
2051    }
2052    return true;
2053  }
2054
2055
2056  private boolean inExpansion(ValueSetExpansionContainsComponent cc, List<ValueSetExpansionContainsComponent> contains) {
2057    for (ValueSetExpansionContainsComponent cc1 : contains) {
2058      if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode()))
2059        return true;
2060      if (inExpansion(cc,  cc1.getContains()))
2061        return true;
2062    }
2063    return false;
2064  }
2065
2066  public void closeDifferential(StructureDefinition base, StructureDefinition derived) throws FHIRException {
2067    for (ElementDefinition edb : base.getSnapshot().getElement()) {
2068      if (isImmediateChild(edb) && !edb.getPath().endsWith(".id")) {
2069        ElementDefinition edm = getMatchInDerived(edb, derived.getDifferential().getElement());
2070        if (edm == null) {
2071          ElementDefinition edd = derived.getDifferential().addElement();
2072          edd.setPath(edb.getPath());
2073          edd.setMax("0");
2074        } else if (edb.hasSlicing()) {
2075          closeChildren(base, edb, derived, edm);
2076        }
2077      }
2078    }
2079    sortDifferential(base, derived, derived.getName(), new ArrayList<String>());
2080  }
2081
2082  private void closeChildren(StructureDefinition base, ElementDefinition edb, StructureDefinition derived, ElementDefinition edm) {
2083    String path = edb.getPath()+".";
2084    int baseStart = base.getSnapshot().getElement().indexOf(edb);
2085    int baseEnd = findEnd(base.getSnapshot().getElement(), edb, baseStart+1);
2086    int diffStart = derived.getDifferential().getElement().indexOf(edm);
2087    int diffEnd = findEnd(derived.getDifferential().getElement(), edm, diffStart+1);
2088    
2089    for (int cBase = baseStart; cBase < baseEnd; cBase++) {
2090      ElementDefinition edBase = base.getSnapshot().getElement().get(cBase);
2091      if (isImmediateChild(edBase, edb)) {
2092        ElementDefinition edMatch = getMatchInDerived(edBase, derived.getDifferential().getElement(), diffStart, diffEnd);
2093        if (edMatch == null) {
2094          ElementDefinition edd = derived.getDifferential().addElement();
2095          edd.setPath(edBase.getPath());
2096          edd.setMax("0");
2097        } else {
2098          closeChildren(base, edBase, derived, edMatch);
2099        }        
2100      }
2101    }
2102  }
2103
2104
2105
2106
2107  private int findEnd(List<ElementDefinition> list, ElementDefinition ed, int cursor) {
2108    String path = ed.getPath()+".";
2109    while (cursor < list.size() && list.get(cursor).getPath().startsWith(path))
2110      cursor++;
2111    return cursor;
2112  }
2113
2114
2115  private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list) {
2116    for (ElementDefinition t : list)
2117      if (t.getPath().equals(ed.getPath()))
2118        return t;
2119    return null;
2120  }
2121
2122  private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list, int start, int end) {
2123    for (int i = start; i < end; i++) {
2124      ElementDefinition t = list.get(i);
2125      if (t.getPath().equals(ed.getPath()))
2126        return t;
2127    }
2128    return null;
2129  }
2130
2131
2132  private boolean isImmediateChild(ElementDefinition ed) {
2133    String p = ed.getPath();
2134    if (!p.contains("."))
2135      return false;
2136    p = p.substring(p.indexOf(".")+1);
2137    return !p.contains(".");
2138  }
2139
2140  private boolean isImmediateChild(ElementDefinition candidate, ElementDefinition base) {
2141    String p = candidate.getPath();
2142    if (!p.contains("."))
2143      return false;
2144    if (!p.startsWith(base.getPath()+"."))
2145      return false;
2146    p = p.substring(base.getPath().length()+1);
2147    return !p.contains(".");
2148  }
2149
2150  public XhtmlNode generateExtensionTable(String defFile, StructureDefinition ed, String imageFolder, boolean inlineGraphics, boolean full, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException {
2151    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true);
2152    gen.setTranslator(getTranslator());
2153    TableModel model = gen.initNormalTable(corePath, false, true, ed.getId(), false, TableGenerationMode.XML);
2154
2155    boolean deep = false;
2156    String m = "";
2157    boolean vdeep = false;
2158    if (ed.getSnapshot().getElementFirstRep().getIsModifier())
2159      m = "modifier_";
2160    for (ElementDefinition eld : ed.getSnapshot().getElement()) {
2161      deep = deep || eld.getPath().contains("Extension.extension.");
2162      vdeep = vdeep || eld.getPath().contains("Extension.extension.extension.");
2163    }
2164    Row r = gen.new Row();
2165    model.getRows().add(r);
2166    String en;
2167    if (!full)
2168      en = ed.getName();
2169    else if (ed.getSnapshot().getElement().get(0).getIsModifier())
2170      en = "modifierExtension";
2171    else 
2172      en = "extension";
2173    
2174    r.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), en, null, null));
2175    r.getCells().add(gen.new Cell());
2176    r.getCells().add(gen.new Cell(null, null, describeCardinality(ed.getSnapshot().getElement().get(0), null, new UnusedTracker()), null, null));
2177
2178    ElementDefinition ved = null;
2179    if (full || vdeep) {
2180      r.getCells().add(gen.new Cell("", "", "Extension", null, null));
2181
2182      r.setIcon(deep ? "icon_"+m+"extension_complex.png" : "icon_extension_simple.png", deep ? HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX : HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
2183      List<ElementDefinition> children = getChildren(ed.getSnapshot().getElement(), ed.getSnapshot().getElement().get(0));
2184      for (ElementDefinition child : children)
2185        if (!child.getPath().endsWith(".id"))
2186          genElement(defFile == null ? "" : defFile+"-definitions.html#extension.", gen, r.getSubRows(), child, ed.getSnapshot().getElement(), null, true, defFile, true, full, corePath, imagePath, true, false, false, false, null);
2187    } else if (deep) {
2188      List<ElementDefinition> children = new ArrayList<ElementDefinition>();
2189      for (ElementDefinition ted : ed.getSnapshot().getElement()) {
2190        if (ted.getPath().equals("Extension.extension"))
2191          children.add(ted);
2192      }
2193
2194      r.getCells().add(gen.new Cell("", "", "Extension", null, null));
2195      r.setIcon("icon_"+m+"extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
2196      
2197      for (ElementDefinition c : children) {
2198        ved = getValueFor(ed, c);
2199        ElementDefinition ued = getUrlFor(ed, c);
2200        if (ved != null && ued != null) {
2201          Row r1 = gen.new Row();
2202          r.getSubRows().add(r1);
2203          r1.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), ((UriType) ued.getFixed()).getValue(), null, null));
2204          r1.getCells().add(gen.new Cell());
2205          r1.getCells().add(gen.new Cell(null, null, describeCardinality(c, null, new UnusedTracker()), null, null));
2206          genTypes(gen, r1, ved, defFile, ed, corePath, imagePath);
2207          Cell cell = gen.new Cell();
2208          cell.addMarkdown(c.getDefinition());
2209          r1.getCells().add(cell);
2210          r1.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);      
2211        }
2212      }
2213    } else  {
2214      for (ElementDefinition ted : ed.getSnapshot().getElement()) {
2215        if (ted.getPath().startsWith("Extension.value"))
2216          ved = ted;
2217      }
2218
2219      genTypes(gen, r, ved, defFile, ed, corePath, imagePath);
2220
2221      r.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);      
2222    }
2223    Cell c = gen.new Cell("", "", "URL = "+ed.getUrl(), null, null);
2224    Piece cc = gen.new Piece(null, ed.getName()+": ", null);
2225    c.addPiece(gen.new Piece("br")).addPiece(cc);
2226    c.addMarkdown(ed.getDescription());
2227    
2228    if (!full && !(deep || vdeep) && ved != null && ved.hasBinding()) {  
2229        c.addPiece(gen.new Piece("br"));
2230      BindingResolution br = pkp.resolveBinding(ed, ved.getBinding(), ved.getPath());
2231      c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold")));
2232      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)));
2233      if (ved.getBinding().hasStrength()) {
2234        c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, " (", null)));
2235        c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(corePath+"terminologies.html#"+ved.getBinding().getStrength().toCode(), egt(ved.getBinding().getStrengthElement()), ved.getBinding().getStrength().getDefinition())));              
2236        c.getPieces().add(gen.new Piece(null, ")", null));
2237      }
2238    }
2239    c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, describeExtensionContext(ed), null));
2240    r.getCells().add(c);
2241    
2242    try {
2243      return gen.generate(model, corePath, 0, outputTracker);
2244        } catch (org.hl7.fhir.exceptions.FHIRException e) {
2245                throw new FHIRException(e.getMessage(), e);
2246        }
2247  }
2248
2249  private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) {
2250    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
2251    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
2252      if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url"))
2253        return ed.getSnapshot().getElement().get(i);
2254      i++;
2255    }
2256    return null;
2257  }
2258
2259  private ElementDefinition getValueFor(StructureDefinition ed, ElementDefinition c) {
2260    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
2261    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
2262      if (ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".value"))
2263        return ed.getSnapshot().getElement().get(i);
2264      i++;
2265    }
2266    return null;
2267  }
2268
2269
2270  private static final int AGG_NONE = 0;
2271  private static final int AGG_IND = 1;
2272  private static final int AGG_GR = 2;
2273  private static final boolean TABLE_FORMAT_FOR_FIXED_VALUES = false;
2274  
2275  private Cell genTypes(HierarchicalTableGenerator gen, Row r, ElementDefinition e, String profileBaseFileName, StructureDefinition profile, String corePath, String imagePath) {
2276    Cell c = gen.new Cell();
2277    r.getCells().add(c);
2278    List<TypeRefComponent> types = e.getType();
2279    if (!e.hasType()) {
2280      if (e.hasContentReference()) {
2281        return c;
2282      } else {
2283      ElementDefinition d = (ElementDefinition) e.getUserData(DERIVATION_POINTER);
2284      if (d != null && d.hasType()) {
2285        types = new ArrayList<ElementDefinition.TypeRefComponent>();
2286        for (TypeRefComponent tr : d.getType()) {
2287          TypeRefComponent tt = tr.copy();
2288          tt.setUserData(DERIVATION_EQUALS, true);
2289          types.add(tt);
2290        }
2291      } else
2292        return c;
2293    }
2294    }
2295
2296    boolean first = true;
2297
2298    TypeRefComponent tl = null;
2299    for (TypeRefComponent t : types) {
2300      if (first)
2301        first = false;
2302      else
2303        c.addPiece(checkForNoChange(tl, gen.new Piece(null,", ", null)));
2304      tl = t;
2305      if (t.hasTarget()) {
2306        c.getPieces().add(gen.new Piece(corePath+"references.html", t.getWorkingCode(), null));
2307        c.getPieces().add(gen.new Piece(null, "(", null));
2308        boolean tfirst = true;
2309        for (UriType u : t.getTargetProfile()) {
2310          if (tfirst)
2311            tfirst = false;
2312          else
2313            c.addPiece(gen.new Piece(null, " | ", null));
2314          genTargetLink(gen, profileBaseFileName, corePath, c, t, u.getValue());
2315        }
2316        c.getPieces().add(gen.new Piece(null, ")", null));
2317        if (t.getAggregation().size() > 0) {
2318          c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", " {", null));
2319          boolean firstA = true;
2320          for (Enumeration<AggregationMode> a : t.getAggregation()) {
2321            if (firstA = true)
2322              firstA = false;
2323            else
2324              c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", ", ", null));
2325            c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", codeForAggregation(a.getValue()), hintForAggregation(a.getValue())));
2326          }
2327          c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", "}", null));
2328        }
2329      } else if (t.hasProfile() && (!t.getWorkingCode().equals("Extension") || isProfiledType(t.getProfile()))) { // a profiled type
2330        String ref;
2331        ref = pkp.getLinkForProfile(profile, t.getProfile().get(0).getValue());
2332        if (ref != null) {
2333          String[] parts = ref.split("\\|");
2334          if (parts[0].startsWith("http:") || parts[0].startsWith("https:")) {
2335//            c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], "<" + parts[1] + ">", t.getCode()))); Lloyd
2336            c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], parts[1], t.getWorkingCode())));
2337          } else {
2338//            c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().startsWith(corePath)? corePath: "")+parts[0], "<" + parts[1] + ">", t.getCode())));
2339            c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().get(0).getValue().startsWith(corePath+"StructureDefinition")? corePath: "")+parts[0], parts[1], t.getWorkingCode())));
2340          }
2341        } else
2342          c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().get(0).getValue().startsWith(corePath)? corePath: "")+ref, t.getWorkingCode(), null)));
2343      } else {
2344        String tc = t.getWorkingCode();
2345        if (pkp != null && pkp.hasLinkFor(tc)) {
2346          c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, tc), tc, null)));
2347      } else
2348          c.addPiece(checkForNoChange(t, gen.new Piece(null, tc, null)));
2349      }
2350    }
2351    return c;
2352  }
2353
2354
2355  public void genTargetLink(HierarchicalTableGenerator gen, String profileBaseFileName, String corePath, Cell c, TypeRefComponent t, String u) {
2356    if (u.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
2357      StructureDefinition sd = context.fetchResource(StructureDefinition.class, u);
2358      if (sd != null) {
2359        String disp = sd.hasTitle() ? sd.getTitle() : sd.getName();
2360        c.addPiece(checkForNoChange(t, gen.new Piece(checkPrepend(corePath, sd.getUserString("path")), disp, null)));
2361      } else {
2362        String rn = u.substring(40);
2363        c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, rn), rn, null)));
2364      }
2365    } else if (Utilities.isAbsoluteUrl(u)) {
2366      StructureDefinition sd = context.fetchResource(StructureDefinition.class, u);
2367      if (sd != null) {
2368        String disp = sd.hasTitle() ? sd.getTitle() : sd.getName();
2369        String ref = pkp.getLinkForProfile(null, sd.getUrl());
2370        if (ref.contains("|"))
2371          ref = ref.substring(0,  ref.indexOf("|"));
2372        c.addPiece(checkForNoChange(t, gen.new Piece(ref, disp, null)));
2373      } else
2374        c.addPiece(checkForNoChange(t, gen.new Piece(null, u, null)));        
2375    } else if (t.hasTargetProfile() && u.startsWith("#"))
2376      c.addPiece(checkForNoChange(t, gen.new Piece(corePath+profileBaseFileName+"."+u.substring(1).toLowerCase()+".html", u, null)));
2377  }
2378
2379  private boolean isProfiledType(List<CanonicalType> theProfile) {
2380    for (CanonicalType next : theProfile){
2381      if (StringUtils.defaultString(next.getValueAsString()).contains(":")) {
2382        return true;
2383      }
2384    }
2385    return false;
2386  }
2387
2388
2389  private String codeForAggregation(AggregationMode a) {
2390    switch (a) {
2391    case BUNDLED : return "b";
2392    case CONTAINED : return "c";
2393    case REFERENCED: return "r";
2394    default: return "?";
2395    }
2396  }
2397
2398  private String hintForAggregation(AggregationMode a) {
2399    if (a != null)
2400      return a.getDefinition();
2401    else 
2402      return null;
2403  }
2404
2405
2406  private String checkPrepend(String corePath, String path) {
2407    if (pkp.prependLinks() && !(path.startsWith("http:") || path.startsWith("https:")))
2408      return corePath+path;
2409    else 
2410      return path;
2411  }
2412
2413
2414  private ElementDefinition getElementByName(List<ElementDefinition> elements, String contentReference) {
2415    for (ElementDefinition ed : elements)
2416      if (ed.hasSliceName() && ("#"+ed.getSliceName()).equals(contentReference))
2417        return ed;
2418    return null;
2419  }
2420
2421  private ElementDefinition getElementById(List<ElementDefinition> elements, String contentReference) {
2422    for (ElementDefinition ed : elements)
2423      if (ed.hasId() && ("#"+ed.getId()).equals(contentReference))
2424        return ed;
2425    return null;
2426  }
2427
2428
2429  public static String describeExtensionContext(StructureDefinition ext) {
2430    StringBuilder b = new StringBuilder();
2431    b.append("Use on ");
2432    for (int i = 0; i < ext.getContext().size(); i++) {
2433      StructureDefinitionContextComponent ec = ext.getContext().get(i);
2434      if (i > 0) 
2435        b.append(i < ext.getContext().size() - 1 ? ", " : " or ");
2436      b.append(ec.getType().getDisplay());
2437      b.append(" ");
2438      b.append(ec.getExpression());
2439    }
2440    if (ext.hasContextInvariant()) {
2441      b.append(", with <a href=\"structuredefinition-definitions.html#StructureDefinition.contextInvariant\">Context Invariant</a> = ");
2442      boolean first = true;
2443      for (StringType s : ext.getContextInvariant()) {
2444        if (first)
2445          first = false;
2446        else
2447          b.append(", ");
2448        b.append("<code>"+s.getValue()+"</code>");
2449      }
2450    }
2451    return b.toString(); 
2452  }
2453
2454  private String describeCardinality(ElementDefinition definition, ElementDefinition fallback, UnusedTracker tracker) {
2455    IntegerType min = definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
2456    StringType max = definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
2457    if (min.isEmpty() && fallback != null)
2458      min = fallback.getMinElement();
2459    if (max.isEmpty() && fallback != null)
2460      max = fallback.getMaxElement();
2461
2462    tracker.used = !max.isEmpty() && !max.getValue().equals("0");
2463
2464    if (min.isEmpty() && max.isEmpty())
2465      return null;
2466    else
2467      return (!min.hasValue() ? "" : Integer.toString(min.getValue())) + ".." + (!max.hasValue() ? "" : max.getValue());
2468  }
2469
2470  private void genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef, UnusedTracker tracker, ElementDefinition fallback) {
2471    IntegerType min = !hasDef ? new IntegerType() : definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
2472    StringType max = !hasDef ? new StringType() : definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
2473    if (min.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) {
2474      ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER);
2475      if (base.hasMinElement()) {
2476        min = base.getMinElement().copy();
2477        min.setUserData(DERIVATION_EQUALS, true);
2478      }
2479    }
2480    if (max.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) {
2481      ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER);
2482      if (base.hasMaxElement()) {
2483        max = base.getMaxElement().copy();
2484        max.setUserData(DERIVATION_EQUALS, true);
2485      }
2486    }
2487    if (min.isEmpty() && fallback != null)
2488      min = fallback.getMinElement();
2489    if (max.isEmpty() && fallback != null)
2490      max = fallback.getMaxElement();
2491
2492    if (!max.isEmpty())
2493      tracker.used = !max.getValue().equals("0");
2494
2495    Cell cell = gen.new Cell(null, null, null, null, null);
2496    row.getCells().add(cell);
2497    if (!min.isEmpty() || !max.isEmpty()) {
2498      cell.addPiece(checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), null)));
2499      cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", null)));
2500      cell.addPiece(checkForNoChange(min, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), null)));
2501    }
2502  }
2503
2504
2505  private Piece checkForNoChange(Element source, Piece piece) {
2506    if (source.hasUserData(DERIVATION_EQUALS)) {
2507      piece.addStyle("opacity: 0.4");
2508    }
2509    return piece;
2510  }
2511
2512  private Piece checkForNoChange(Element src1, Element src2, Piece piece) {
2513    if (src1.hasUserData(DERIVATION_EQUALS) && src2.hasUserData(DERIVATION_EQUALS)) {
2514      piece.addStyle("opacity: 0.5");
2515    }
2516    return piece;
2517  }
2518
2519  public XhtmlNode generateTable(String defFile, StructureDefinition profile, boolean diff, String imageFolder, boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, String imagePath, boolean logicalModel, boolean allInvariants, Set<String> outputTracker) throws IOException, FHIRException {
2520    assert(diff != snapshot);// check it's ok to get rid of one of these
2521    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true);
2522    gen.setTranslator(getTranslator());
2523    TableModel model = gen.initNormalTable(corePath, false, true, profile.getId()+(diff ? "d" : "s"), false, TableGenerationMode.XML);
2524    List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement();
2525    List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
2526    profiles.add(profile);
2527    if (list.isEmpty()) {
2528      ElementDefinition root = new ElementDefinition().setPath(profile.getType());
2529      root.setId(profile.getType());
2530      list.add(root);
2531    }
2532    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);
2533    try {
2534      return gen.generate(model, imagePath, 0, outputTracker);
2535        } catch (org.hl7.fhir.exceptions.FHIRException e) {
2536                throw new FHIRException("Error generating table for profile " + profile.getUrl() + ": " + e.getMessage(), e);
2537        }
2538  }
2539
2540
2541  public XhtmlNode generateGrid(String defFile, StructureDefinition profile, String imageFolder, boolean inlineGraphics, String profileBaseFileName, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException {
2542    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true);
2543    gen.setTranslator(getTranslator());
2544    TableModel model = gen.initGridTable(corePath, profile.getId());
2545    List<ElementDefinition> list = profile.getSnapshot().getElement();
2546    List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
2547    profiles.add(profile);
2548    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));
2549    try {
2550      return gen.generate(model, imagePath, 1, outputTracker);
2551    } catch (org.hl7.fhir.exceptions.FHIRException e) {
2552      throw new FHIRException(e.getMessage(), e);
2553    }
2554  }
2555
2556
2557  private boolean usesMustSupport(List<ElementDefinition> list) {
2558    for (ElementDefinition ed : list)
2559      if (ed.hasMustSupport() && ed.getMustSupport())
2560        return true;
2561    return false;
2562  }
2563
2564
2565  private Row genElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element, List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName, Boolean extensions, boolean snapshot, String corePath, String imagePath, boolean root, boolean logicalModel, boolean isConstraintMode, boolean allInvariants, Row slicingRow) throws IOException, FHIRException {
2566    Row originalRow = slicingRow;
2567    StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1);
2568    String s = tail(element.getPath());
2569    if (element.hasSliceName())
2570      s = s +":"+element.getSliceName();
2571    Row typesRow = null;
2572    
2573    List<ElementDefinition> children = getChildren(all, element);
2574    boolean isExtension = (s.equals("extension") || s.equals("modifierExtension"));
2575//    if (!snapshot && isExtension && extensions != null && extensions != isExtension)
2576//      return;
2577
2578    if (!onlyInformationIsMapping(all, element)) {
2579      Row row = gen.new Row();
2580      row.setAnchor(element.getPath());
2581      row.setColor(getRowColor(element, isConstraintMode));
2582      if (element.hasSlicing())
2583        row.setLineColor(1);
2584      else if (element.hasSliceName())
2585        row.setLineColor(2);
2586      else
2587        row.setLineColor(0);
2588      boolean hasDef = element != null;
2589      boolean ext = false;
2590      if (tail(element.getPath()).equals("extension")) {
2591        if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue()))
2592          row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
2593        else
2594          row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
2595        ext = true;
2596      } else if (tail(element.getPath()).equals("modifierExtension")) {
2597        if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue()))
2598          row.setIcon("icon_modifier_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
2599        else
2600          row.setIcon("icon_modifier_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
2601      } else if (!hasDef || element.getType().size() == 0)
2602        row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
2603      else if (hasDef && element.getType().size() > 1) {
2604        if (allAreReference(element.getType()))
2605          row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
2606        else {
2607          row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE);
2608          typesRow = row;
2609        }
2610      } else if (hasDef && element.getType().get(0).getWorkingCode() != null && element.getType().get(0).getWorkingCode().startsWith("@"))
2611        row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE);
2612      else if (hasDef && isPrimitive(element.getType().get(0).getWorkingCode()))
2613        row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
2614      else if (hasDef && element.getType().get(0).hasTarget())
2615        row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
2616      else if (hasDef && isDataType(element.getType().get(0).getWorkingCode()))
2617        row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
2618      else
2619        row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
2620      String ref = defPath == null ? null : defPath + element.getId();
2621      UnusedTracker used = new UnusedTracker();
2622      used.used = true;
2623      if (logicalModel && element.hasRepresentation(PropertyRepresentation.XMLATTR))
2624        s = "@"+s;
2625      Cell left = gen.new Cell(null, ref, s, (element.hasSliceName() ? translate("sd.table", "Slice")+" "+element.getSliceName() : "")+(hasDef && element.hasSliceName() ? ": " : "")+(!hasDef ? null : gt(element.getDefinitionElement())), null);
2626      row.getCells().add(left);
2627      Cell gc = gen.new Cell();
2628      row.getCells().add(gc);
2629      if (element != null && element.getIsModifier())
2630        checkForNoChange(element.getIsModifierElement(), gc.addStyledText(translate("sd.table", "This element is a modifier element"), "?!", null, null, null, false));
2631      if (element != null && element.getMustSupport())
2632        checkForNoChange(element.getMustSupportElement(), gc.addStyledText(translate("sd.table", "This element must be supported"), "S", "white", "red", null, false));
2633      if (element != null && element.getIsSummary())
2634        checkForNoChange(element.getIsSummaryElement(), gc.addStyledText(translate("sd.table", "This element is included in summaries"), "\u03A3", null, null, null, false));
2635      if (element != null && (!element.getConstraint().isEmpty() || !element.getCondition().isEmpty()))
2636        gc.addStyledText(translate("sd.table", "This element has or is affected by some invariants"), "I", null, null, null, false);
2637
2638      ExtensionContext extDefn = null;
2639      if (ext) {
2640        if (element != null && element.getType().size() == 1 && element.getType().get(0).hasProfile()) {
2641          String eurl = element.getType().get(0).getProfile().get(0).getValue();
2642          extDefn = locateExtension(StructureDefinition.class, eurl);
2643          if (extDefn == null) {
2644            genCardinality(gen, element, row, hasDef, used, null);
2645            row.getCells().add(gen.new Cell(null, null, "?? "+element.getType().get(0).getProfile(), null, null));
2646            generateDescription(gen, row, element, null, used.used, profile.getUrl(), eurl, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot);
2647          } else {
2648            String name = urltail(eurl);
2649            left.getPieces().get(0).setText(name);
2650            // left.getPieces().get(0).setReference((String) extDefn.getExtensionStructure().getTag("filename"));
2651            left.getPieces().get(0).setHint(translate("sd.table", "Extension URL")+" = "+extDefn.getUrl());
2652            genCardinality(gen, element, row, hasDef, used, extDefn.getElement());
2653            ElementDefinition valueDefn = extDefn.getExtensionValueDefinition();
2654            if (valueDefn != null && !"0".equals(valueDefn.getMax()))
2655               genTypes(gen, row, valueDefn, profileBaseFileName, profile, corePath, imagePath);
2656             else // if it's complex, we just call it nothing
2657                // genTypes(gen, row, extDefn.getSnapshot().getElement().get(0), profileBaseFileName, profile);
2658              row.getCells().add(gen.new Cell(null, null, "("+translate("sd.table", "Complex")+")", null, null));
2659            generateDescription(gen, row, element, extDefn.getElement(), used.used, null, extDefn.getUrl(), profile, corePath, imagePath, root, logicalModel, allInvariants, valueDefn, snapshot);
2660          }
2661        } else {
2662          genCardinality(gen, element, row, hasDef, used, null);
2663          if ("0".equals(element.getMax()))
2664            row.getCells().add(gen.new Cell());            
2665          else
2666            genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath);
2667          generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot);
2668        }
2669      } else {
2670        genCardinality(gen, element, row, hasDef, used, null);
2671        if (element.hasSlicing())
2672          row.getCells().add(gen.new Cell(null, corePath+"profiling.html#slicing", "(Slice Definition)", null, null));
2673        else if (hasDef && !"0".equals(element.getMax()) && typesRow == null)
2674          genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath);
2675        else
2676          row.getCells().add(gen.new Cell());
2677        generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot);
2678      }
2679      if (element.hasSlicing()) {
2680        if (standardExtensionSlicing(element)) {
2681          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();
2682          showMissing = false; //?
2683        } else {
2684          row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE);
2685          slicingRow = row;
2686          for (Cell cell : row.getCells())
2687            for (Piece p : cell.getPieces()) {
2688              p.addStyle("font-style: italic");
2689            }
2690        }
2691      } else if (element.hasSliceName()) {
2692        row.setIcon("icon_slice_item.png", HierarchicalTableGenerator.TEXT_ICON_SLICE_ITEM);
2693      }
2694      if (used.used || showMissing)
2695        rows.add(row);
2696      if (!used.used && !element.hasSlicing()) {
2697        for (Cell cell : row.getCells())
2698          for (Piece p : cell.getPieces()) {
2699            p.setStyle("text-decoration:line-through");
2700            p.setReference(null);
2701          }
2702      } else{
2703        if (slicingRow != originalRow && !children.isEmpty()) {
2704          // we've entered a slice; we're going to create a holder row for the slice children
2705          Row hrow = gen.new Row();
2706          hrow.setAnchor(element.getPath());
2707          hrow.setColor(getRowColor(element, isConstraintMode));
2708          hrow.setLineColor(1);
2709          hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
2710          hrow.getCells().add(gen.new Cell(null, null, "(All Slices)", "", null));
2711          hrow.getCells().add(gen.new Cell());
2712          hrow.getCells().add(gen.new Cell());
2713          hrow.getCells().add(gen.new Cell());
2714          hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all slices", "", null));
2715          row.getSubRows().add(hrow);
2716          row = hrow;
2717        }
2718        if (typesRow != null && !children.isEmpty()) {
2719          // we've entered a typing slice; we're going to create a holder row for the all types children
2720          Row hrow = gen.new Row();
2721          hrow.setAnchor(element.getPath());
2722          hrow.setColor(getRowColor(element, isConstraintMode));
2723          hrow.setLineColor(1);
2724          hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
2725          hrow.getCells().add(gen.new Cell(null, null, "(All Types)", "", null));
2726          hrow.getCells().add(gen.new Cell());
2727          hrow.getCells().add(gen.new Cell());
2728          hrow.getCells().add(gen.new Cell());
2729          hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all Types", "", null));
2730          row.getSubRows().add(hrow);
2731          row = hrow;
2732        }
2733          
2734        Row currRow = row; 
2735        for (ElementDefinition child : children) {
2736          if (!child.hasSliceName())
2737            currRow = row; 
2738          if (logicalModel || !child.getPath().endsWith(".id") || (child.getPath().endsWith(".id") && (profile != null) && (profile.getDerivation() == TypeDerivationRule.CONSTRAINT)))  
2739            currRow = genElement(defPath, gen, currRow.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, snapshot, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants, currRow);
2740        }
2741//        if (!snapshot && (extensions == null || !extensions))
2742//          for (ElementDefinition child : children)
2743//            if (child.getPath().endsWith(".extension") || child.getPath().endsWith(".modifierExtension"))
2744//              genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, true, false, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants);
2745      }
2746      if (typesRow != null) {
2747        makeChoiceRows(typesRow.getSubRows(), element, gen, corePath, profileBaseFileName);
2748      }
2749    }
2750    return slicingRow;
2751  }
2752
2753  private void makeChoiceRows(List<Row> subRows, ElementDefinition element, HierarchicalTableGenerator gen, String corePath, String profileBaseFileName) {
2754    // create a child for each choice
2755    for (TypeRefComponent tr : element.getType()) {
2756      Row choicerow = gen.new Row();
2757      String t = tr.getWorkingCode();
2758      if (isReference(t)) {
2759        choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), null, null));
2760        choicerow.getCells().add(gen.new Cell());
2761        choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
2762        choicerow.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
2763        Cell c = gen.new Cell();
2764        choicerow.getCells().add(c);
2765        if (ADD_REFERENCE_TO_TABLE) {
2766          if (tr.getWorkingCode().equals("canonical"))
2767            c.getPieces().add(gen.new Piece(corePath+"datatypes.html#canonical", "canonical", null));
2768          else
2769            c.getPieces().add(gen.new Piece(corePath+"references.html#Reference", "Reference", null));
2770          c.getPieces().add(gen.new Piece(null, "(", null));
2771    }
2772        boolean first = true;
2773        for (CanonicalType rt : tr.getTargetProfile()) {
2774          if (!first)
2775            c.getPieces().add(gen.new Piece(null, " | ", null));
2776          genTargetLink(gen, profileBaseFileName, corePath, c, tr, rt.getValue());
2777          first = false;
2778  }
2779        if (ADD_REFERENCE_TO_TABLE) 
2780          c.getPieces().add(gen.new Piece(null, ")", null));
2781        
2782      } else {
2783        StructureDefinition sd = context.fetchTypeDefinition(t);
2784        if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) {
2785          choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]",  Utilities.capitalize(t)), sd.getDescription(), null));
2786          choicerow.getCells().add(gen.new Cell());
2787          choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
2788          choicerow.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
2789          choicerow.getCells().add(gen.new Cell(null, corePath+"datatypes.html#"+t, t, null, null));
2790          //      } else if (definitions.getConstraints().contthnsKey(t)) {
2791          //        ProfiledType pt = definitions.getConstraints().get(t);
2792          //        choicerow.getCells().add(gen.new Cell(null, null, e.getName().replace("[x]", Utilities.capitalize(pt.getBaseType())), definitions.getTypes().containsKey(t) ? definitions.getTypes().get(t).getDefinition() : null, null));
2793          //        choicerow.getCells().add(gen.new Cell());
2794          //        choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
2795          //        choicerow.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
2796          //        choicerow.getCells().add(gen.new Cell(null, definitions.getSrcFile(t)+".html#"+t.replace("*", "open"), t, null, null));
2797        } else {
2798          choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]",  Utilities.capitalize(t)), sd.getDescription(), null));
2799          choicerow.getCells().add(gen.new Cell());
2800          choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
2801          choicerow.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
2802          choicerow.getCells().add(gen.new Cell(null, pkp.getLinkFor(corePath, t), t, null, null));
2803        }
2804      }    
2805      choicerow.getCells().add(gen.new Cell());
2806      subRows.add(choicerow);
2807    }
2808  }
2809
2810  private boolean isReference(String t) {
2811    return t.equals("Reference") || t.equals("canonical"); 
2812  }  
2813
2814
2815
2816  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 {
2817    StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1);
2818    String s = tail(element.getPath());
2819    List<ElementDefinition> children = getChildren(all, element);
2820    boolean isExtension = (s.equals("extension") || s.equals("modifierExtension"));
2821
2822    if (!onlyInformationIsMapping(all, element)) {
2823      Row row = gen.new Row();
2824      row.setAnchor(element.getPath());
2825      row.setColor(getRowColor(element, isConstraintMode));
2826      if (element.hasSlicing())
2827        row.setLineColor(1);
2828      else if (element.hasSliceName())
2829        row.setLineColor(2);
2830      else
2831        row.setLineColor(0);
2832      boolean hasDef = element != null;
2833      String ref = defPath == null ? null : defPath + element.getId();
2834      UnusedTracker used = new UnusedTracker();
2835      used.used = true;
2836      Cell left = gen.new Cell();
2837      if (element.getType().size() == 1 && element.getType().get(0).isPrimitive())
2838        left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())).addStyle("font-weight:bold"));
2839      else
2840        left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())));
2841      if (element.hasSliceName()) {
2842        left.getPieces().add(gen.new Piece("br"));
2843        String indent = StringUtils.repeat('\u00A0', 1+2*(element.getPath().split("\\.").length));
2844        left.getPieces().add(gen.new Piece(null, indent + "("+element.getSliceName() + ")", null));
2845      }
2846      row.getCells().add(left);
2847
2848      ExtensionContext extDefn = null;
2849      genCardinality(gen, element, row, hasDef, used, null);
2850      if (hasDef && !"0".equals(element.getMax()))
2851        genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath);
2852      else
2853        row.getCells().add(gen.new Cell());
2854      generateGridDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, null);
2855/*      if (element.hasSlicing()) {
2856        if (standardExtensionSlicing(element)) {
2857          used.used = element.hasType() && element.getType().get(0).hasProfile();
2858          showMissing = false;
2859        } else {
2860          row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE);
2861          row.getCells().get(2).getPieces().clear();
2862          for (Cell cell : row.getCells())
2863            for (Piece p : cell.getPieces()) {
2864              p.addStyle("font-style: italic");
2865            }
2866        }
2867      }*/
2868      rows.add(row);
2869      for (ElementDefinition child : children)
2870        if (child.getMustSupport())
2871          genGridElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, corePath, imagePath, false, isConstraintMode);
2872    }
2873  }
2874
2875
2876  private ExtensionContext locateExtension(Class<StructureDefinition> class1, String value)  {
2877    if (value.contains("#")) {
2878      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
2879      if (ext == null)
2880        return null;
2881      String tail = value.substring(value.indexOf("#")+1);
2882      ElementDefinition ed = null;
2883      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
2884        if (tail.equals(ted.getSliceName())) {
2885          ed = ted;
2886          return new ExtensionContext(ext, ed);
2887        }
2888      }
2889      return null;
2890    } else {
2891      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value);
2892      if (ext == null)
2893        return null;
2894      else 
2895        return new ExtensionContext(ext, ext.getSnapshot().getElement().get(0));
2896    }
2897  }
2898
2899
2900  private boolean extensionIsComplex(String value) {
2901    if (value.contains("#")) {
2902      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
2903    if (ext == null)
2904      return false;
2905      String tail = value.substring(value.indexOf("#")+1);
2906      ElementDefinition ed = null;
2907      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
2908        if (tail.equals(ted.getSliceName())) {
2909          ed = ted;
2910          break;
2911        }
2912      }
2913      if (ed == null)
2914        return false;
2915      int i = ext.getSnapshot().getElement().indexOf(ed);
2916      int j = i+1;
2917      while (j < ext.getSnapshot().getElement().size() && !ext.getSnapshot().getElement().get(j).getPath().equals(ed.getPath()))
2918        j++;
2919      return j - i > 5;
2920    } else {
2921      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value);
2922      return ext != null && ext.getSnapshot().getElement().size() > 5;
2923    }
2924  }
2925
2926
2927  private String getRowColor(ElementDefinition element, boolean isConstraintMode) {
2928    switch (element.getUserInt(UD_ERROR_STATUS)) {
2929    case STATUS_HINT: return ROW_COLOR_HINT;
2930    case STATUS_WARNING: return ROW_COLOR_WARNING;
2931    case STATUS_ERROR: return ROW_COLOR_ERROR;
2932    case STATUS_FATAL: return ROW_COLOR_FATAL;
2933    }
2934    if (isConstraintMode && !element.getMustSupport() && !element.getIsModifier() && element.getPath().contains("."))
2935      return null; // ROW_COLOR_NOT_MUST_SUPPORT;
2936    else
2937      return null;
2938  }
2939
2940
2941  private String urltail(String path) {
2942    if (path.contains("#"))
2943      return path.substring(path.lastIndexOf('#')+1);
2944    if (path.contains("/"))
2945      return path.substring(path.lastIndexOf('/')+1);
2946    else
2947      return path;
2948
2949  }
2950
2951  private boolean standardExtensionSlicing(ElementDefinition element) {
2952    String t = tail(element.getPath());
2953    return (t.equals("extension") || t.equals("modifierExtension"))
2954          && 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);
2955  }
2956
2957  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) throws IOException, FHIRException {
2958    return generateDescription(gen, row, definition, fallback, used, baseURL, url, profile, corePath, imagePath, root, logicalModel, allInvariants, null, snapshot);
2959  }
2960  
2961  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) throws IOException, FHIRException {
2962    Cell c = gen.new Cell();
2963    row.getCells().add(c);
2964
2965    if (used) {
2966      if (logicalModel && ToolingExtensions.hasExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) {
2967        if (root) {
2968          c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold"));
2969          c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null));        
2970        } else if (!root && ToolingExtensions.hasExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace") && 
2971            !ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace").equals(ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))) {
2972          c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold"));
2973          c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null));        
2974        }
2975      }
2976      
2977      if (definition.hasContentReference()) {
2978        ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference());
2979        if (ed == null)
2980          c.getPieces().add(gen.new Piece(null, translate("sd.table", "Unknown reference to %s", definition.getContentReference()), null));
2981        else
2982          c.getPieces().add(gen.new Piece("#"+ed.getPath(), translate("sd.table", "See %s", ed.getPath()), null));
2983      }
2984      if (definition.getPath().endsWith("url") && definition.hasFixed()) {
2985        c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen")));
2986      } else {
2987        if (definition != null && definition.hasShort()) {
2988          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2989          c.addPiece(checkForNoChange(definition.getShortElement(), gen.new Piece(null, gt(definition.getShortElement()), null)));
2990        } else if (fallback != null && fallback.hasShort()) {
2991          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2992          c.addPiece(checkForNoChange(fallback.getShortElement(), gen.new Piece(null, gt(fallback.getShortElement()), null)));
2993        }
2994        if (url != null) {
2995          if (!c.getPieces().isEmpty()) 
2996            c.addPiece(gen.new Piece("br"));
2997          String fullUrl = url.startsWith("#") ? baseURL+url : url;
2998          StructureDefinition ed = context.fetchResource(StructureDefinition.class, url);
2999          String ref = null;
3000          String ref2 = null;
3001          String fixedUrl = null;
3002          if (ed != null) {
3003            String p = ed.getUserString("path");
3004            if (p != null) {
3005              ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p);
3006            }             
3007            fixedUrl = getFixedUrl(ed);
3008            if (fixedUrl != null) {// if its null, we guess that it's not a profiled extension? 
3009              if (fixedUrl.equals(url))
3010                fixedUrl = null;
3011              else {
3012                StructureDefinition ed2 = context.fetchResource(StructureDefinition.class, fixedUrl);
3013                if (ed2 != null) {
3014                  String p2 = ed2.getUserString("path");
3015                  if (p2 != null) {
3016                    ref2 = p2.startsWith("http:") || igmode ? p2 : Utilities.pathURL(corePath, p2);
3017                  }                              
3018                }
3019              }
3020            }
3021          }
3022          if (fixedUrl == null) {
3023            c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold"));
3024            c.getPieces().add(gen.new Piece(ref, fullUrl, null));
3025          } else { 
3026            // reference to a profile take on the extension show the base URL
3027            c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold"));
3028            c.getPieces().add(gen.new Piece(ref2, fixedUrl, null));
3029            c.getPieces().add(gen.new Piece(null, translate("sd.table", " profiled by ")+" ", null).addStyle("font-weight:bold"));
3030            c.getPieces().add(gen.new Piece(ref, fullUrl, null));
3031          
3032          }
3033        }
3034
3035        if (definition.hasSlicing()) {
3036          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3037          c.getPieces().add(gen.new Piece(null, translate("sd.table", "Slice")+": ", null).addStyle("font-weight:bold"));
3038          c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null));
3039        }
3040        if (definition != null) {
3041          ElementDefinitionBindingComponent binding = null;
3042          if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty())
3043            binding = valueDefn.getBinding();
3044          else if (definition.hasBinding())
3045            binding = definition.getBinding();
3046          if (binding!=null && !binding.isEmpty()) {
3047            if (!c.getPieces().isEmpty()) 
3048              c.addPiece(gen.new Piece("br"));
3049            BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath());
3050            c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold")));
3051            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)));
3052            if (binding.hasStrength()) {
3053              c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null)));
3054              c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), egt(binding.getStrengthElement()), binding.getStrength().getDefinition())));              
3055              
3056              c.getPieces().add(gen.new Piece(null, ")", null));
3057            }
3058            if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) {
3059              br = pkp.resolveBinding(profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), definition.getPath());
3060              c.addPiece(gen.new Piece("br"));
3061              c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"extension-elementdefinition-maxvalueset.html", translate("sd.table", "Max Binding")+": ", "Max Value Set Extension").addStyle("font-weight:bold")));             
3062              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)));
3063            }
3064            if (binding.hasExtension(ToolingExtensions.EXT_MIN_VALUESET)) {
3065              br = pkp.resolveBinding(profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MIN_VALUESET), definition.getPath());
3066              c.addPiece(gen.new Piece("br"));
3067              c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"extension-elementdefinition-minvalueset.html", translate("sd.table", "Min Binding")+": ", "Min Value Set Extension").addStyle("font-weight:bold")));             
3068              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)));
3069            }
3070          }
3071          for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
3072            if (!inv.hasSource() || allInvariants) {
3073              if (!c.getPieces().isEmpty()) 
3074                c.addPiece(gen.new Piece("br"));
3075              c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold")));
3076              c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, gt(inv.getHumanElement()), null)));
3077            }
3078          }
3079          if ((definition.hasBase() && definition.getBase().getMax().equals("*")) || (definition.hasMax() && definition.getMax().equals("*"))) {
3080            if (c.getPieces().size() > 0)
3081              c.addPiece(gen.new Piece("br"));
3082            if (definition.hasOrderMeaning()) {
3083              c.getPieces().add(gen.new Piece(null, "This repeating element order: "+definition.getOrderMeaning(), null));
3084            } else {
3085              // don't show this, this it's important: c.getPieces().add(gen.new Piece(null, "This repeating element has no defined order", null));
3086            }           
3087          }
3088
3089          if (definition.hasFixed()) {
3090            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3091            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, translate("sd.table", "Fixed Value")+": ", null).addStyle("font-weight:bold")));
3092            if (!useTableForFixedValues || definition.getFixed().isPrimitive()) {
3093              String s = buildJson(definition.getFixed());
3094              String link = null;
3095              if (Utilities.isAbsoluteUrl(s))
3096                link = pkp.getLinkForUrl(corePath, s);
3097              c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen")));
3098            } else {
3099              c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "As shown", null).addStyle("color: darkgreen")));
3100              genFixedValue(gen, row, definition.getFixed(), snapshot, false, corePath);              
3101            }
3102            if (isCoded(definition.getFixed()) && !hasDescription(definition.getFixed())) {
3103              Piece p = describeCoded(gen, definition.getFixed());
3104              if (p != null)
3105                c.getPieces().add(p);
3106            }
3107          } else if (definition.hasPattern()) {
3108            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3109            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, translate("sd.table", "Required Pattern")+": ", null).addStyle("font-weight:bold")));
3110            if (!useTableForFixedValues || definition.getPattern().isPrimitive())
3111            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen")));
3112            else {
3113              c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "At least the following", null).addStyle("color: darkgreen")));
3114              genFixedValue(gen, row, definition.getPattern(), snapshot, true, corePath);
3115            }
3116          } else if (definition.hasExample()) {
3117            for (ElementDefinitionExampleComponent ex : definition.getExample()) {
3118              if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3119              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, translate("sd.table", "Example")+("".equals("General")? "" : " "+ex.getLabel()+"'")+": ", null).addStyle("font-weight:bold")));
3120              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen")));
3121            }
3122          }
3123          if (definition.hasMaxLength() && definition.getMaxLength()!=0) {
3124            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3125            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold")));
3126            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen")));
3127          }
3128          if (profile != null) {
3129            for (StructureDefinitionMappingComponent md : profile.getMapping()) {
3130              if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) {
3131                ElementDefinitionMappingComponent map = null;
3132                for (ElementDefinitionMappingComponent m : definition.getMapping()) 
3133                  if (m.getIdentity().equals(md.getIdentity()))
3134                    map = m;
3135                if (map != null) {
3136                  for (int i = 0; i<definition.getMapping().size(); i++){
3137                    c.addPiece(gen.new Piece("br"));
3138                    c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null));
3139                  }
3140                }
3141              }
3142            }
3143          }
3144        }
3145      }
3146    }
3147    return c;
3148  }
3149
3150  private void genFixedValue(HierarchicalTableGenerator gen, Row erow, Type value, boolean snapshot, boolean pattern, String corePath) {
3151    String ref = pkp.getLinkFor(corePath, value.fhirType());
3152    ref = ref.substring(0, ref.indexOf(".html"))+"-definitions.html#";
3153    StructureDefinition sd = context.fetchTypeDefinition(value.fhirType());
3154    
3155    for (org.hl7.fhir.r4.model.Property t : value.children()) {
3156      if (t.getValues().size() > 0 || snapshot) {
3157        ElementDefinition ed = findElementDefinition(sd, t.getName());
3158        if (t.getValues().size() == 0 || (t.getValues().size() == 1 && t.getValues().get(0).isEmpty())) {
3159          Row row = gen.new Row();
3160          erow.getSubRows().add(row);
3161          Cell c = gen.new Cell();
3162          row.getCells().add(c);
3163          c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref+ed.getPath() : corePath+"element-definitions.html#"+ed.getBase().getPath()), t.getName(), null));
3164          c = gen.new Cell();
3165          row.getCells().add(c);
3166          c.addPiece(gen.new Piece(null, null, null));
3167          c = gen.new Cell();
3168          row.getCells().add(c);
3169          if (!pattern) {
3170            c.addPiece(gen.new Piece(null, "0..0", null));
3171            row.setIcon("icon_fixed.gif", "Fixed Value" /*HierarchicalTableGenerator.TEXT_ICON_FIXED*/);
3172          } else if (isPrimitive(t.getTypeCode())) {
3173            row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
3174            c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null));
3175          } else if (isReference(t.getTypeCode())) { 
3176            row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
3177            c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null));
3178          } else { 
3179            row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
3180            c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null));
3181          }
3182          c = gen.new Cell();
3183          row.getCells().add(c);
3184          c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, t.getTypeCode()), t.getTypeCode(), null));
3185          c = gen.new Cell();
3186          c.addPiece(gen.new Piece(null, ed.getShort(), null));
3187          row.getCells().add(c);
3188        } else {
3189          for (Base b : t.getValues()) {
3190            Row row = gen.new Row();
3191            erow.getSubRows().add(row);
3192            row.setIcon("icon_fixed.gif", "Fixed Value" /*HierarchicalTableGenerator.TEXT_ICON_FIXED*/);
3193
3194            Cell c = gen.new Cell();
3195            row.getCells().add(c);
3196            c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref+ed.getPath() : corePath+"element-definitions.html#"+ed.getBase().getPath()), t.getName(), null));
3197
3198            c = gen.new Cell();
3199            row.getCells().add(c);
3200            c.addPiece(gen.new Piece(null, null, null));
3201
3202            c = gen.new Cell();
3203            row.getCells().add(c);
3204            if (pattern)
3205              c.addPiece(gen.new Piece(null, "1.."+(t.getMaxCardinality() == 2147483647 ? "*" : Integer.toString(t.getMaxCardinality())), null));
3206            else
3207              c.addPiece(gen.new Piece(null, "1..1", null));
3208
3209            c = gen.new Cell();
3210            row.getCells().add(c);
3211            c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, b.fhirType()), b.fhirType(), null));
3212
3213            if (b.isPrimitive()) {
3214              c = gen.new Cell();
3215              row.getCells().add(c);
3216              c.addPiece(gen.new Piece(null, ed.getShort(), null));
3217              c.addPiece(gen.new Piece("br"));
3218              c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold"));
3219              String s = b.primitiveValue();
3220              // ok. let's see if we can find a relevant link for this
3221              String link = null;
3222              if (Utilities.isAbsoluteUrl(s))
3223                link = pkp.getLinkForUrl(corePath, s);
3224              c.getPieces().add(gen.new Piece(link, s, null).addStyle("color: darkgreen"));
3225            } else {
3226              c = gen.new Cell();
3227              row.getCells().add(c);
3228              c.addPiece(gen.new Piece(null, ed.getShort(), null));
3229              c.addPiece(gen.new Piece("br"));
3230              c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold"));
3231              c.getPieces().add(gen.new Piece(null, "(complex)", null).addStyle("color: darkgreen"));
3232              genFixedValue(gen, row, (Type) b, snapshot, pattern, corePath);
3233            }
3234          }
3235        }
3236      }
3237    }
3238  }
3239
3240
3241  private ElementDefinition findElementDefinition(StructureDefinition sd, String name) {
3242    String path = sd.getType()+"."+name;
3243    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
3244      if (ed.getPath().equals(path))
3245        return ed;
3246    }
3247    throw new FHIRException("Unable to find element "+path);
3248  }
3249
3250
3251  private String getFixedUrl(StructureDefinition sd) {
3252    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
3253      if (ed.getPath().equals("Extension.url")) {
3254        if (ed.hasFixed() && ed.getFixed() instanceof UriType)
3255          return ed.getFixed().primitiveValue();
3256      }
3257    }
3258    return null;
3259  }
3260
3261
3262  private Piece describeCoded(HierarchicalTableGenerator gen, Type fixed) {
3263    if (fixed instanceof Coding) {
3264      Coding c = (Coding) fixed;
3265      ValidationResult vr = context.validateCode(terminologyServiceOptions , c.getSystem(), c.getCode(), c.getDisplay());
3266      if (vr.getDisplay() != null)
3267        return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen");
3268    } else if (fixed instanceof CodeableConcept) {
3269      CodeableConcept cc = (CodeableConcept) fixed;
3270      for (Coding c : cc.getCoding()) {
3271        ValidationResult vr = context.validateCode(terminologyServiceOptions, c.getSystem(), c.getCode(), c.getDisplay());
3272        if (vr.getDisplay() != null)
3273          return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen");
3274      }
3275    }
3276    return null;
3277  }
3278
3279
3280  private boolean hasDescription(Type fixed) {
3281    if (fixed instanceof Coding) {
3282      return ((Coding) fixed).hasDisplay();
3283    } else if (fixed instanceof CodeableConcept) {
3284      CodeableConcept cc = (CodeableConcept) fixed;
3285      if (cc.hasText())
3286        return true;
3287      for (Coding c : cc.getCoding())
3288        if (c.hasDisplay())
3289         return true;
3290    } // (fixed instanceof CodeType) || (fixed instanceof Quantity);
3291    return false;
3292  }
3293
3294
3295  private boolean isCoded(Type fixed) {
3296    return (fixed instanceof Coding) || (fixed instanceof CodeableConcept) || (fixed instanceof CodeType) || (fixed instanceof Quantity);
3297  }
3298
3299
3300  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 {
3301    Cell c = gen.new Cell();
3302    row.getCells().add(c);
3303
3304    if (used) {
3305      if (definition.hasContentReference()) {
3306        ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference());
3307        if (ed == null)
3308          c.getPieces().add(gen.new Piece(null, "Unknown reference to "+definition.getContentReference(), null));
3309        else
3310          c.getPieces().add(gen.new Piece("#"+ed.getPath(), "See "+ed.getPath(), null));
3311      }
3312      if (definition.getPath().endsWith("url") && definition.hasFixed()) {
3313        c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen")));
3314      } else {
3315        if (url != null) {
3316          if (!c.getPieces().isEmpty()) 
3317            c.addPiece(gen.new Piece("br"));
3318          String fullUrl = url.startsWith("#") ? baseURL+url : url;
3319          StructureDefinition ed = context.fetchResource(StructureDefinition.class, url);
3320          String ref = null;
3321          if (ed != null) {
3322            String p = ed.getUserString("path");
3323            if (p != null) {
3324              ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p);
3325            }
3326          }
3327          c.getPieces().add(gen.new Piece(null, "URL: ", null).addStyle("font-weight:bold"));
3328          c.getPieces().add(gen.new Piece(ref, fullUrl, null));
3329        }
3330
3331        if (definition.hasSlicing()) {
3332          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3333          c.getPieces().add(gen.new Piece(null, "Slice: ", null).addStyle("font-weight:bold"));
3334          c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null));
3335        }
3336        if (definition != null) {
3337          ElementDefinitionBindingComponent binding = null;
3338          if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty())
3339            binding = valueDefn.getBinding();
3340          else if (definition.hasBinding())
3341            binding = definition.getBinding();
3342          if (binding!=null && !binding.isEmpty()) {
3343            if (!c.getPieces().isEmpty()) 
3344              c.addPiece(gen.new Piece("br"));
3345            BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath());
3346            c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, "Binding: ", null).addStyle("font-weight:bold")));
3347            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)));
3348            if (binding.hasStrength()) {
3349              c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null)));
3350              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));
3351            }
3352          }
3353          for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
3354            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3355            c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold")));
3356            c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null)));
3357          }
3358          if (definition.hasFixed()) {
3359            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3360            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight:bold")));
3361            String s = buildJson(definition.getFixed());
3362            String link = null;
3363            if (Utilities.isAbsoluteUrl(s))
3364              link = pkp.getLinkForUrl(corePath, s);
3365            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen")));
3366          } else if (definition.hasPattern()) {
3367            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3368            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "Required Pattern: ", null).addStyle("font-weight:bold")));
3369            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen")));
3370          } else if (definition.hasExample()) {
3371            for (ElementDefinitionExampleComponent ex : definition.getExample()) {
3372              if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3373              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, "Example'"+("".equals("General")? "" : " "+ex.getLabel()+"'")+": ", null).addStyle("font-weight:bold")));
3374              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen")));
3375            }
3376          }
3377          if (definition.hasMaxLength() && definition.getMaxLength()!=0) {
3378            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3379            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold")));
3380            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen")));
3381          }
3382          if (profile != null) {
3383            for (StructureDefinitionMappingComponent md : profile.getMapping()) {
3384              if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) {
3385                ElementDefinitionMappingComponent map = null;
3386                for (ElementDefinitionMappingComponent m : definition.getMapping()) 
3387                  if (m.getIdentity().equals(md.getIdentity()))
3388                    map = m;
3389                if (map != null) {
3390                  for (int i = 0; i<definition.getMapping().size(); i++){
3391                    c.addPiece(gen.new Piece("br"));
3392                    c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null));
3393                  }
3394                }
3395              }
3396            }
3397          }
3398          if (definition.hasDefinition()) {
3399            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3400            c.getPieces().add(gen.new Piece(null, "Definition: ", null).addStyle("font-weight:bold"));
3401            c.addPiece(gen.new Piece("br"));
3402            c.addMarkdown(definition.getDefinition());
3403//            c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null)));
3404          }
3405          if (definition.getComment()!=null) {
3406            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3407            c.getPieces().add(gen.new Piece(null, "Comments: ", null).addStyle("font-weight:bold"));
3408            c.addPiece(gen.new Piece("br"));
3409            c.addMarkdown(definition.getComment());
3410//            c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null)));
3411          }
3412        }
3413      }
3414    }
3415    return c;
3416  }
3417
3418
3419
3420  private String buildJson(Type value) throws IOException {
3421    if (value instanceof PrimitiveType)
3422      return ((PrimitiveType) value).asStringValue();
3423
3424    IParser json = context.newJsonParser();
3425    return json.composeString(value, null);
3426  }
3427
3428
3429  public String describeSlice(ElementDefinitionSlicingComponent slicing) {
3430    return translate("sd.table", "%s, %s by %s", slicing.getOrdered() ? translate("sd.table", "Ordered") : translate("sd.table", "Unordered"), describe(slicing.getRules()), commas(slicing.getDiscriminator()));
3431  }
3432
3433  private String commas(List<ElementDefinitionSlicingDiscriminatorComponent> list) {
3434    CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder();
3435    for (ElementDefinitionSlicingDiscriminatorComponent id : list)
3436      c.append(id.getType().toCode()+":"+id.getPath());
3437    return c.toString();
3438  }
3439
3440
3441  private String describe(SlicingRules rules) {
3442    if (rules == null)
3443      return translate("sd.table", "Unspecified");
3444    switch (rules) {
3445    case CLOSED : return translate("sd.table", "Closed");
3446    case OPEN : return translate("sd.table", "Open");
3447    case OPENATEND : return translate("sd.table", "Open At End");
3448    default:
3449      return "??";
3450    }
3451  }
3452
3453  private boolean onlyInformationIsMapping(List<ElementDefinition> list, ElementDefinition e) {
3454    return (!e.hasSliceName() && !e.hasSlicing() && (onlyInformationIsMapping(e))) &&
3455        getChildren(list, e).isEmpty();
3456  }
3457
3458  private boolean onlyInformationIsMapping(ElementDefinition d) {
3459    return !d.hasShort() && !d.hasDefinition() &&
3460        !d.hasRequirements() && !d.getAlias().isEmpty() && !d.hasMinElement() &&
3461        !d.hasMax() && !d.getType().isEmpty() && !d.hasContentReference() &&
3462        !d.hasExample() && !d.hasFixed() && !d.hasMaxLengthElement() &&
3463        !d.getCondition().isEmpty() && !d.getConstraint().isEmpty() && !d.hasMustSupportElement() &&
3464        !d.hasBinding();
3465  }
3466
3467  private boolean allAreReference(List<TypeRefComponent> types) {
3468    for (TypeRefComponent t : types) {
3469      if (!t.hasTarget())
3470        return false;
3471    }
3472    return true;
3473  }
3474
3475  private List<ElementDefinition> getChildren(List<ElementDefinition> all, ElementDefinition element) {
3476    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
3477    int i = all.indexOf(element)+1;
3478    while (i < all.size() && all.get(i).getPath().length() > element.getPath().length()) {
3479      if ((all.get(i).getPath().substring(0, element.getPath().length()+1).equals(element.getPath()+".")) && !all.get(i).getPath().substring(element.getPath().length()+1).contains("."))
3480        result.add(all.get(i));
3481      i++;
3482    }
3483    return result;
3484  }
3485
3486  private String tail(String path) {
3487    if (path.contains("."))
3488      return path.substring(path.lastIndexOf('.')+1);
3489    else
3490      return path;
3491  }
3492
3493  private boolean isDataType(String value) {
3494    StructureDefinition sd = context.fetchTypeDefinition(value);
3495    if (sd == null) // might be running before all SDs are available
3496      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", 
3497            "ContactDetail", "Contributor", "DataRequirement", "Expression", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition", "UsageContext");
3498    else 
3499      return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION;
3500  }
3501
3502  private boolean isConstrainedDataType(String value) {
3503    StructureDefinition sd = context.fetchTypeDefinition(value);
3504    if (sd == null) // might be running before all SDs are available
3505      return Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity");
3506    else 
3507      return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.CONSTRAINT;
3508  }
3509
3510  private String baseType(String value) {
3511    StructureDefinition sd = context.fetchTypeDefinition(value);
3512    if (sd != null) // might be running before all SDs are available
3513      return sd.getType();
3514    if (Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity"))
3515      return "Quantity";
3516     throw new Error("Internal error -  type not known "+value);
3517  }
3518
3519
3520  public boolean isPrimitive(String value) {
3521    StructureDefinition sd = context.fetchTypeDefinition(value);
3522    if (sd == null) // might be running before all SDs are available
3523      return Utilities.existsInList(value, "base64Binary", "boolean", "canonical", "code", "date", "dateTime", "decimal", "id", "instant", "integer", "markdown", "oid", "positiveInt", "string", "time", "unsignedInt", "uri", "url", "uuid");
3524    else 
3525      return sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
3526  }
3527
3528//  private static String listStructures(StructureDefinition p) {
3529//    StringBuilder b = new StringBuilder();
3530//    boolean first = true;
3531//    for (ProfileStructureComponent s : p.getStructure()) {
3532//      if (first)
3533//        first = false;
3534//      else
3535//        b.append(", ");
3536//      if (pkp != null && pkp.hasLinkFor(s.getType()))
3537//        b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>");
3538//      else
3539//        b.append(s.getType());
3540//    }
3541//    return b.toString();
3542//  }
3543
3544
3545  public StructureDefinition getProfile(StructureDefinition source, String url) {
3546        StructureDefinition profile = null;
3547        String code = null;
3548        if (url.startsWith("#")) {
3549                profile = source;
3550                code = url.substring(1);
3551        } else if (context != null) {
3552                String[] parts = url.split("\\#");
3553                profile = context.fetchResource(StructureDefinition.class, parts[0]);
3554      code = parts.length == 1 ? null : parts[1];
3555        }         
3556        if (profile == null)
3557                return null;
3558        if (code == null)
3559                return profile;
3560        for (Resource r : profile.getContained()) {
3561                if (r instanceof StructureDefinition && r.getId().equals(code))
3562                        return (StructureDefinition) r;
3563        }
3564        return null;
3565  }
3566
3567
3568
3569  public static class ElementDefinitionHolder {
3570    private String name;
3571    private ElementDefinition self;
3572    private int baseIndex = 0;
3573    private List<ElementDefinitionHolder> children;
3574    private boolean placeHolder = false;
3575
3576    public ElementDefinitionHolder(ElementDefinition self, boolean isPlaceholder) {
3577      super();
3578      this.self = self;
3579      this.name = self.getPath();
3580      this.placeHolder = isPlaceholder;
3581      children = new ArrayList<ElementDefinitionHolder>();      
3582    }
3583
3584    public ElementDefinitionHolder(ElementDefinition self) {
3585      this(self, false);
3586    }
3587
3588    public ElementDefinition getSelf() {
3589      return self;
3590    }
3591
3592    public List<ElementDefinitionHolder> getChildren() {
3593      return children;
3594    }
3595
3596    public int getBaseIndex() {
3597      return baseIndex;
3598    }
3599
3600    public void setBaseIndex(int baseIndex) {
3601      this.baseIndex = baseIndex;
3602    }
3603
3604    public boolean isPlaceHolder() {
3605      return this.placeHolder;
3606    }
3607
3608    @Override
3609    public String toString() {
3610      if (self.hasSliceName())
3611        return self.getPath()+"("+self.getSliceName()+")";
3612      else
3613        return self.getPath();
3614    }
3615  }
3616
3617  public static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> {
3618
3619    private boolean inExtension;
3620    private List<ElementDefinition> snapshot;
3621    private int prefixLength;
3622    private String base;
3623    private String name;
3624    private Set<String> errors = new HashSet<String>();
3625
3626    public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, int prefixLength, String name) {
3627      this.inExtension = inExtension;
3628      this.snapshot = snapshot;
3629      this.prefixLength = prefixLength;
3630      this.base = base;
3631      this.name = name;
3632    }
3633
3634    @Override
3635    public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) {
3636      if (o1.getBaseIndex() == 0)
3637        o1.setBaseIndex(find(o1.getSelf().getPath()));
3638      if (o2.getBaseIndex() == 0)
3639        o2.setBaseIndex(find(o2.getSelf().getPath()));
3640      return o1.getBaseIndex() - o2.getBaseIndex();
3641    }
3642
3643    private int find(String path) {
3644      String op = path;
3645      int lc = 0;
3646      String actual = base+path.substring(prefixLength);
3647      for (int i = 0; i < snapshot.size(); i++) {
3648        String p = snapshot.get(i).getPath();
3649        if (p.equals(actual)) {
3650          return i;
3651        }
3652        if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length()-3)) && !(actual.endsWith("[x]")) && !actual.substring(p.length()-3).contains(".")) {
3653          return i;
3654        }
3655        if (path.startsWith(p+".") && snapshot.get(i).hasContentReference()) {
3656          String ref = snapshot.get(i).getContentReference();
3657          if (ref.substring(1, 2).toUpperCase().equals(ref.substring(1,2))) {
3658            actual = base+(ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength);
3659            path = actual;
3660          } else {
3661            // Older versions of FHIR (e.g. 2016May) had reference of the style #parameter instead of #Parameters.parameter, so we have to handle that
3662            actual = base+(path.substring(0,  path.indexOf(".")+1) + ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength);
3663            path = actual;
3664          }
3665            
3666          i = 0;
3667          lc++;
3668          if (lc > MAX_RECURSION_LIMIT)
3669            throw new Error("Internal recursion detection: find() loop path recursion > "+MAX_RECURSION_LIMIT+" - check paths are valid (for path "+path+"/"+op+")");
3670        }
3671      }
3672      if (prefixLength == 0)
3673        errors.add("Differential contains path "+path+" which is not found in the base");
3674      else
3675        errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the base");
3676      return 0;
3677    }
3678
3679    public void checkForErrors(List<String> errorList) {
3680      if (errors.size() > 0) {
3681//        CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
3682//        for (String s : errors)
3683//          b.append("StructureDefinition "+name+": "+s);
3684//        throw new DefinitionException(b.toString());
3685        for (String s : errors)
3686          if (s.startsWith("!"))
3687            errorList.add("!StructureDefinition "+name+": "+s.substring(1));
3688          else
3689            errorList.add("StructureDefinition "+name+": "+s);
3690      }
3691    }
3692  }
3693
3694
3695  public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors) throws FHIRException  {
3696    final List<ElementDefinition> diffList = diff.getDifferential().getElement();
3697    int lastCount = diffList.size();
3698    // first, we move the differential elements into a tree
3699    if (diffList.isEmpty())
3700      return;
3701    
3702    ElementDefinitionHolder edh = null;
3703    int i = 0;
3704    if (diffList.get(0).getPath().contains(".")) {
3705      String newPath = diffList.get(0).getPath().split("\\.")[0];
3706      ElementDefinition e = new ElementDefinition(new StringType(newPath));
3707      edh = new ElementDefinitionHolder(e, true);
3708    } else {
3709      edh = new ElementDefinitionHolder(diffList.get(0));
3710      i = 1;
3711    }
3712
3713    boolean hasSlicing = false;
3714    List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly
3715    for(ElementDefinition elt : diffList) {
3716      if (elt.hasSlicing() || paths.contains(elt.getPath())) {
3717        hasSlicing = true;
3718        break;
3719      }
3720      paths.add(elt.getPath());
3721    }
3722    if(!hasSlicing) {
3723      // if Differential does not have slicing then safe to pre-sort the list
3724      // so elements and subcomponents are together
3725      Collections.sort(diffList, new ElementNameCompare());
3726    }
3727
3728    processElementsIntoTree(edh, i, diff.getDifferential().getElement());
3729
3730    // now, we sort the siblings throughout the tree
3731    ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name);
3732    sortElements(edh, cmp, errors);
3733
3734    // now, we serialise them back to a list
3735    diffList.clear();
3736    writeElements(edh, diffList);
3737    
3738    if (lastCount != diffList.size())
3739      errors.add("Sort failed: counts differ; at least one of the paths in the differential is illegal");
3740  }
3741
3742  private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) {
3743    String path = edh.getSelf().getPath();
3744    final String prefix = path + ".";
3745    while (i < list.size() && list.get(i).getPath().startsWith(prefix)) {
3746      if (list.get(i).getPath().substring(prefix.length()+1).contains(".")) {
3747        String newPath = prefix + list.get(i).getPath().substring(prefix.length()).split("\\.")[0];
3748        ElementDefinition e = new ElementDefinition(new StringType(newPath));
3749        ElementDefinitionHolder child = new ElementDefinitionHolder(e, true);
3750        edh.getChildren().add(child);
3751        i = processElementsIntoTree(child, i, list);
3752        
3753      } else {
3754        ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i));
3755        edh.getChildren().add(child);
3756        i = processElementsIntoTree(child, i+1, list);
3757      }
3758    }
3759    return i;
3760  }
3761
3762  private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) throws FHIRException {
3763    if (edh.getChildren().size() == 1)
3764      // 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
3765      edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath());
3766    else
3767      Collections.sort(edh.getChildren(), cmp);
3768    cmp.checkForErrors(errors);
3769
3770    for (ElementDefinitionHolder child : edh.getChildren()) {
3771      if (child.getChildren().size() > 0) {
3772        ElementDefinitionComparer ccmp = getComparer(cmp, child);
3773        if (ccmp != null)
3774        sortElements(child, ccmp, errors);
3775      }
3776    }
3777  }
3778
3779
3780  public ElementDefinitionComparer getComparer(ElementDefinitionComparer cmp, ElementDefinitionHolder child) throws FHIRException, Error {
3781    // what we have to check for here is running off the base profile into a data type profile
3782    ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex());
3783    ElementDefinitionComparer ccmp;
3784    if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getWorkingCode()) || ed.getType().get(0).getWorkingCode().equals(ed.getPath())) {
3785      ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name);
3786    } else if (ed.getType().get(0).getWorkingCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) {
3787      StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue());
3788      if (profile==null)
3789        ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case
3790      else
3791      ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
3792    } else if (ed.getType().size() == 1 && !ed.getType().get(0).getWorkingCode().equals("*")) {
3793      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
3794      if (profile==null)
3795        throw new FHIRException("Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath());
3796      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
3797    } else if (child.getSelf().getType().size() == 1) {
3798      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(child.getSelf().getType().get(0).getWorkingCode()));
3799      if (profile==null)
3800        throw new FHIRException("Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath());
3801      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), child.getSelf().getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
3802    } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) {
3803      String edLastNode = ed.getPath().replaceAll("(.*\\.)*(.*)", "$2");
3804      String childLastNode = child.getSelf().getPath().replaceAll("(.*\\.)*(.*)", "$2");
3805      String p = childLastNode.substring(edLastNode.length()-3);
3806      if (isPrimitive(Utilities.uncapitalize(p)))
3807        p = Utilities.uncapitalize(p);
3808      StructureDefinition sd = context.fetchResource(StructureDefinition.class, sdNs(p));
3809      if (sd == null)
3810        throw new Error("Unable to find profile '"+p+"' at "+ed.getId());
3811      ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name);
3812    } else if (child.getSelf().hasType() && child.getSelf().getType().get(0).getWorkingCode().equals("Reference")) {
3813      for (TypeRefComponent t: child.getSelf().getType()) {
3814        if (!t.getWorkingCode().equals("Reference")) {
3815          throw new Error("Can't have children on an element with a polymorphic type - you must slice and constrain the types first (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")");
3816        }
3817      }
3818      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
3819      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
3820    } else if (!child.getSelf().hasType() && ed.getType().get(0).getWorkingCode().equals("Reference")) {
3821      for (TypeRefComponent t: ed.getType()) {
3822        if (!t.getWorkingCode().equals("Reference")) {
3823          throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")");
3824        }
3825      }
3826      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
3827      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
3828    } else {
3829      // this is allowed if we only profile the extensions
3830      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs("Element"));
3831      if (profile==null)
3832        throw new FHIRException("Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath());
3833      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), "Element", child.getSelf().getPath().length(), cmp.name);
3834//      throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")");
3835    }
3836    return ccmp;
3837  }
3838
3839  private static String sdNs(String type) {
3840    return sdNs(type, null);
3841  }
3842  
3843  public static String sdNs(String type, String overrideVersionNs) {
3844    if (Utilities.isAbsoluteUrl(type))
3845      return type;
3846    else if (overrideVersionNs != null)
3847      return Utilities.pathURL(overrideVersionNs, type);
3848    else
3849      return "http://hl7.org/fhir/StructureDefinition/"+type;
3850  }
3851
3852
3853  private boolean isAbstract(String code) {
3854    return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") || code.equals("DomainResource");
3855  }
3856
3857
3858  private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) {
3859    if (!edh.isPlaceHolder())
3860      list.add(edh.getSelf());
3861    for (ElementDefinitionHolder child : edh.getChildren()) {
3862      writeElements(child, list);
3863    }
3864  }
3865
3866  /**
3867   * First compare element by path then by name if same
3868   */
3869  private static class ElementNameCompare implements Comparator<ElementDefinition> {
3870
3871    @Override
3872    public int compare(ElementDefinition o1, ElementDefinition o2) {
3873      String path1 = normalizePath(o1);
3874      String path2 = normalizePath(o2);
3875      int cmp = path1.compareTo(path2);
3876      if (cmp == 0) {
3877        String name1 = o1.hasSliceName() ? o1.getSliceName() : "";
3878        String name2 = o2.hasSliceName() ? o2.getSliceName() : "";
3879        cmp = name1.compareTo(name2);
3880      }
3881      return cmp;
3882    }
3883
3884    private static String normalizePath(ElementDefinition e) {
3885      if (!e.hasPath()) return "";
3886      String path = e.getPath();
3887      // if sorting element names make sure onset[x] appears before onsetAge, onsetDate, etc.
3888      // so strip off the [x] suffix when comparing the path names.
3889      if (path.endsWith("[x]")) {
3890        path = path.substring(0, path.length()-3);
3891      }
3892      return path;
3893    }
3894
3895  }
3896
3897
3898  // generate schematrons for the rules in a structure definition
3899  public void generateSchematrons(OutputStream dest, StructureDefinition structure) throws IOException, DefinitionException {
3900    if (structure.getDerivation() != TypeDerivationRule.CONSTRAINT)
3901      throw new DefinitionException("not the right kind of structure to generate schematrons for");
3902    if (!structure.hasSnapshot())
3903      throw new DefinitionException("needs a snapshot");
3904
3905        StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBaseDefinition());
3906
3907        if (base != null) {
3908          SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName());
3909
3910          ElementDefinition ed = structure.getSnapshot().getElement().get(0);
3911          generateForChildren(sch, "f:"+ed.getPath(), ed, structure, base);
3912          sch.dump();
3913        }
3914  }
3915
3916  // generate a CSV representation of the structure definition
3917  public void generateCsvs(OutputStream dest, StructureDefinition structure, boolean asXml) throws IOException, DefinitionException, Exception {
3918    if (!structure.hasSnapshot())
3919      throw new DefinitionException("needs a snapshot");
3920
3921    CSVWriter csv = new CSVWriter(dest, structure, asXml);
3922
3923    for (ElementDefinition child : structure.getSnapshot().getElement()) {
3924      csv.processElement(child);
3925    }
3926    csv.dump();
3927  }
3928  
3929  // generate an Excel representation of the structure definition
3930  public void generateXlsx(OutputStream dest, StructureDefinition structure, boolean asXml, boolean hideMustSupportFalse) throws IOException, DefinitionException, Exception {
3931    if (!structure.hasSnapshot())
3932      throw new DefinitionException("needs a snapshot");
3933
3934    XLSXWriter xlsx = new XLSXWriter(dest, structure, asXml, hideMustSupportFalse);
3935
3936    for (ElementDefinition child : structure.getSnapshot().getElement()) {
3937      xlsx.processElement(child);
3938    }
3939    xlsx.dump();
3940    xlsx.close();
3941  }
3942  
3943  private class Slicer extends ElementDefinitionSlicingComponent {
3944    String criteria = "";
3945    String name = "";   
3946    boolean check;
3947    public Slicer(boolean cantCheck) {
3948      super();
3949      this.check = cantCheck;
3950    }
3951  }
3952  
3953  private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, StructureDefinition structure) {
3954    // given a child in a structure, it's sliced. figure out the slicing xpath
3955    if (child.getPath().endsWith(".extension")) {
3956      ElementDefinition ued = getUrlFor(structure, child);
3957      if ((ued == null || !ued.hasFixed()) && !(child.hasType() && (child.getType().get(0).hasProfile())))
3958        return new Slicer(false);
3959      else {
3960      Slicer s = new Slicer(true);
3961      String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).getValue() : ((UriType) ued.getFixed()).asStringValue();
3962      s.name = " with URL = '"+url+"'";
3963      s.criteria = "[@url = '"+url+"']";
3964      return s;
3965      }
3966    } else
3967      return new Slicer(false);
3968  }
3969
3970  private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, StructureDefinition structure, StructureDefinition base) throws IOException {
3971    //    generateForChild(txt, structure, child);
3972    List<ElementDefinition> children = getChildList(structure, ed);
3973    String sliceName = null;
3974    ElementDefinitionSlicingComponent slicing = null;
3975    for (ElementDefinition child : children) {
3976      String name = tail(child.getPath());
3977      if (child.hasSlicing()) {
3978        sliceName = name;
3979        slicing = child.getSlicing();        
3980      } else if (!name.equals(sliceName))
3981        slicing = null;
3982      
3983      ElementDefinition based = getByPath(base, child.getPath());
3984      boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin()));
3985      boolean doMax = child.hasMax() && !child.getMax().equals("*") && (based == null || (!child.getMax().equals(based.getMax())));
3986      Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure);
3987      if (slicer.check) {
3988        if (doMin || doMax) {
3989          Section s = sch.section(xpath);
3990          Rule r = s.rule(xpath);
3991          if (doMin) 
3992            r.assrt("count(f:"+name+slicer.criteria+") >= "+Integer.toString(child.getMin()), name+slicer.name+": minimum cardinality of '"+name+"' is "+Integer.toString(child.getMin()));
3993          if (doMax) 
3994            r.assrt("count(f:"+name+slicer.criteria+") <= "+child.getMax(), name+slicer.name+": maximum cardinality of '"+name+"' is "+child.getMax());
3995          }
3996        }
3997      }
3998    for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
3999      if (inv.hasXpath()) {
4000        Section s = sch.section(ed.getPath());
4001        Rule r = s.rule(xpath);
4002        r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId()+": " : "")+inv.getHuman()+(inv.hasUserData(IS_DERIVED) ? " (inherited)" : ""));
4003      }
4004    }
4005    for (ElementDefinition child : children) {
4006      String name = tail(child.getPath());
4007      generateForChildren(sch, xpath+"/f:"+name, child, structure, base);
4008    }
4009  }
4010
4011
4012
4013
4014  private ElementDefinition getByPath(StructureDefinition base, String path) {
4015                for (ElementDefinition ed : base.getSnapshot().getElement()) {
4016                        if (ed.getPath().equals(path))
4017                                return ed;
4018                        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)))
4019                                return ed;
4020    }
4021          return null;
4022  }
4023
4024
4025  public void setIds(StructureDefinition sd, boolean checkFirst) throws DefinitionException  {
4026    if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) {
4027      if (!sd.hasDifferential())
4028        sd.setDifferential(new StructureDefinitionDifferentialComponent());
4029      generateIds(sd.getDifferential().getElement(), sd.getUrl());
4030    }
4031    if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) {
4032      if (!sd.hasSnapshot())
4033        sd.setSnapshot(new StructureDefinitionSnapshotComponent());
4034      generateIds(sd.getSnapshot().getElement(), sd.getUrl());
4035    }
4036  }
4037
4038
4039  private boolean hasMissingIds(List<ElementDefinition> list) {
4040    for (ElementDefinition ed : list) {
4041      if (!ed.hasId())
4042        return true;
4043    }    
4044    return false;
4045  }
4046
4047  public class SliceList {
4048
4049    private Map<String, String> slices = new HashMap<>();
4050    
4051    public void seeElement(ElementDefinition ed) {
4052      Iterator<Map.Entry<String,String>> iter = slices.entrySet().iterator();
4053      while (iter.hasNext()) {
4054        Map.Entry<String,String> entry = iter.next();
4055        if (entry.getKey().length() > ed.getPath().length() || entry.getKey().equals(ed.getPath()))
4056          iter.remove();
4057      }
4058      
4059      if (ed.hasSliceName()) 
4060        slices.put(ed.getPath(), ed.getSliceName());
4061    }
4062
4063    public String[] analyse(List<String> paths) {
4064      String s = paths.get(0);
4065      String[] res = new String[paths.size()];
4066      res[0] = null;
4067      for (int i = 1; i < paths.size(); i++) {
4068        s = s + "."+paths.get(i);
4069        if (slices.containsKey(s)) 
4070          res[i] = slices.get(s);
4071        else
4072          res[i] = null;
4073      }
4074      return res;
4075    }
4076
4077  }
4078
4079  private void generateIds(List<ElementDefinition> list, String name) throws DefinitionException  {
4080    if (list.isEmpty())
4081      return;
4082    
4083    Map<String, String> idMap = new HashMap<String, String>();
4084    Map<String, String> idList = new HashMap<String, String>();
4085    
4086    SliceList sliceInfo = new SliceList();
4087    // first pass, update the element ids
4088    for (ElementDefinition ed : list) {
4089      List<String> paths = new ArrayList<String>();
4090      if (!ed.hasPath())
4091        throw new DefinitionException("No path on element Definition "+Integer.toString(list.indexOf(ed))+" in "+name);
4092      sliceInfo.seeElement(ed);
4093      String[] pl = ed.getPath().split("\\.");
4094      for (int i = paths.size(); i < pl.length; i++) // -1 because the last path is in focus
4095        paths.add(pl[i]);
4096      String slices[] = sliceInfo.analyse(paths);
4097      
4098      StringBuilder b = new StringBuilder();
4099      b.append(paths.get(0));
4100      for (int i = 1; i < paths.size(); i++) {
4101        b.append(".");
4102        String s = paths.get(i);
4103        String p = slices[i];
4104        b.append(s);
4105        if (p != null) {
4106          b.append(":");
4107          b.append(p);
4108        }
4109      }
4110      String bs = b.toString();
4111      idMap.put(ed.hasId() ? ed.getId() : ed.getPath(), bs);
4112      ed.setId(bs);
4113      if (idList.containsKey(bs)) {
4114        if (exception || messages == null)
4115          throw new DefinitionException("Same id '"+bs+"'on multiple elements "+idList.get(bs)+"/"+ed.getPath()+" in "+name);
4116        else
4117          messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, name+"."+bs, "Duplicate Element id "+bs, ValidationMessage.IssueSeverity.ERROR));
4118      }
4119      idList.put(bs, ed.getPath());
4120      if (ed.hasContentReference()) {
4121        String s = ed.getContentReference().substring(1);
4122        if (idMap.containsKey(s))
4123          ed.setContentReference("#"+idMap.get(s));
4124        
4125      }
4126    }  
4127    // second path - fix up any broken path based id references
4128    
4129  }
4130
4131
4132//  private String describeExtension(ElementDefinition ed) {
4133//    if (!ed.hasType() || !ed.getTypeFirstRep().hasProfile())
4134//      return "";
4135//    return "$"+urlTail(ed.getTypeFirstRep().getProfile());
4136//  }
4137//
4138
4139  private String urlTail(String profile) {
4140    return profile.contains("/") ? profile.substring(profile.lastIndexOf("/")+1) : profile;
4141  }
4142
4143
4144  private String checkName(String name) {
4145//    if (name.contains("."))
4146////      throw new Exception("Illegal name "+name+": no '.'");
4147//    if (name.contains(" "))
4148//      throw new Exception("Illegal name "+name+": no spaces");
4149    StringBuilder b = new StringBuilder();
4150    for (char c : name.toCharArray()) {
4151      if (!Utilities.existsInList(c, '.', ' ', ':', '"', '\'', '(', ')', '&', '[', ']'))
4152        b.append(c);
4153    }
4154    return b.toString().toLowerCase();
4155  }
4156
4157
4158  private int charCount(String path, char t) {
4159    int res = 0;
4160    for (char ch : path.toCharArray()) {
4161      if (ch == t)
4162        res++;
4163    }
4164    return res;
4165  }
4166
4167//
4168//private void generateForChild(TextStreamWriter txt,
4169//    StructureDefinition structure, ElementDefinition child) {
4170//  // TODO Auto-generated method stub
4171//
4172//}
4173
4174  private interface ExampleValueAccessor {
4175    Type getExampleValue(ElementDefinition ed);
4176    String getId();
4177  }
4178
4179  private class BaseExampleValueAccessor implements ExampleValueAccessor {
4180    @Override
4181    public Type getExampleValue(ElementDefinition ed) {
4182      if (ed.hasFixed())
4183        return ed.getFixed();
4184      if (ed.hasExample())
4185        return ed.getExample().get(0).getValue();
4186      else
4187        return null;
4188    }
4189
4190    @Override
4191    public String getId() {
4192      return "-genexample";
4193    }
4194  }
4195  
4196  private class ExtendedExampleValueAccessor implements ExampleValueAccessor {
4197    private String index;
4198
4199    public ExtendedExampleValueAccessor(String index) {
4200      this.index = index;
4201    }
4202    @Override
4203    public Type getExampleValue(ElementDefinition ed) {
4204      if (ed.hasFixed())
4205        return ed.getFixed();
4206      for (Extension ex : ed.getExtension()) {
4207       String ndx = ToolingExtensions.readStringExtension(ex, "index");
4208       Type value = ToolingExtensions.getExtension(ex, "exValue").getValue();
4209       if (index.equals(ndx) && value != null)
4210         return value;
4211      }
4212      return null;
4213    }
4214    @Override
4215    public String getId() {
4216      return "-genexample-"+index;
4217    }
4218  }
4219  
4220  public List<org.hl7.fhir.r4.elementmodel.Element> generateExamples(StructureDefinition sd, boolean evenWhenNoExamples) throws FHIRException {
4221    List<org.hl7.fhir.r4.elementmodel.Element> examples = new ArrayList<org.hl7.fhir.r4.elementmodel.Element>();
4222    if (sd.hasSnapshot()) {
4223      if (evenWhenNoExamples || hasAnyExampleValues(sd)) 
4224        examples.add(generateExample(sd, new BaseExampleValueAccessor()));
4225      for (int i = 1; i <= 50; i++) {
4226        if (hasAnyExampleValues(sd, Integer.toString(i))) 
4227          examples.add(generateExample(sd, new ExtendedExampleValueAccessor(Integer.toString(i))));
4228      }
4229    }
4230    return examples;
4231  }
4232
4233  private org.hl7.fhir.r4.elementmodel.Element generateExample(StructureDefinition profile, ExampleValueAccessor accessor) throws FHIRException {
4234    ElementDefinition ed = profile.getSnapshot().getElementFirstRep();
4235    org.hl7.fhir.r4.elementmodel.Element r = new org.hl7.fhir.r4.elementmodel.Element(ed.getPath(), new Property(context, ed, profile));
4236    List<ElementDefinition> children = getChildMap(profile, ed);
4237    for (ElementDefinition child : children) {
4238      if (child.getPath().endsWith(".id")) {
4239        org.hl7.fhir.r4.elementmodel.Element id = new org.hl7.fhir.r4.elementmodel.Element("id", new Property(context, child, profile));
4240        id.setValue(profile.getId()+accessor.getId());
4241        r.getChildren().add(id);
4242      } else { 
4243        org.hl7.fhir.r4.elementmodel.Element e = createExampleElement(profile, child, accessor);
4244        if (e != null)
4245          r.getChildren().add(e);
4246      }
4247    }
4248    return r;
4249  }
4250
4251  private org.hl7.fhir.r4.elementmodel.Element createExampleElement(StructureDefinition profile, ElementDefinition ed, ExampleValueAccessor accessor) throws FHIRException {
4252    Type v = accessor.getExampleValue(ed);
4253    if (v != null) {
4254      return new ObjectConverter(context).convert(new Property(context, ed, profile), v);
4255    } else {
4256      org.hl7.fhir.r4.elementmodel.Element res = new org.hl7.fhir.r4.elementmodel.Element(tail(ed.getPath()), new Property(context, ed, profile));
4257      boolean hasValue = false;
4258      List<ElementDefinition> children = getChildMap(profile, ed);
4259      for (ElementDefinition child : children) {
4260        if (!child.hasContentReference()) {
4261        org.hl7.fhir.r4.elementmodel.Element e = createExampleElement(profile, child, accessor);
4262        if (e != null) {
4263          hasValue = true;
4264          res.getChildren().add(e);
4265        }
4266      }
4267      }
4268      if (hasValue)
4269        return res;
4270      else
4271        return null;
4272    }
4273  }
4274
4275  private boolean hasAnyExampleValues(StructureDefinition sd, String index) {
4276    for (ElementDefinition ed : sd.getSnapshot().getElement())
4277      for (Extension ex : ed.getExtension()) {
4278        String ndx = ToolingExtensions.readStringExtension(ex, "index");
4279        Extension exv = ToolingExtensions.getExtension(ex, "exValue");
4280        if (exv != null) {
4281          Type value = exv.getValue();
4282        if (index.equals(ndx) && value != null)
4283          return true;
4284        }
4285       }
4286    return false;
4287  }
4288
4289
4290  private boolean hasAnyExampleValues(StructureDefinition sd) {
4291    for (ElementDefinition ed : sd.getSnapshot().getElement())
4292      if (ed.hasExample())
4293        return true;
4294    return false;
4295  }
4296
4297
4298  public void populateLogicalSnapshot(StructureDefinition sd) throws FHIRException {
4299    sd.getSnapshot().getElement().add(sd.getDifferential().getElementFirstRep().copy());
4300    
4301    if (sd.hasBaseDefinition()) {
4302    StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
4303    if (base == null)
4304      throw new FHIRException("Unable to find base definition for logical model: "+sd.getBaseDefinition()+" from "+sd.getUrl());
4305    copyElements(sd, base.getSnapshot().getElement());
4306    }
4307    copyElements(sd, sd.getDifferential().getElement());
4308  }
4309
4310
4311  private void copyElements(StructureDefinition sd, List<ElementDefinition> list) {
4312    for (ElementDefinition ed : list) {
4313      if (ed.getPath().contains(".")) {
4314        ElementDefinition n = ed.copy();
4315        n.setPath(sd.getSnapshot().getElementFirstRep().getPath()+"."+ed.getPath().substring(ed.getPath().indexOf(".")+1));
4316        sd.getSnapshot().addElement(n);
4317      }
4318    }
4319  }
4320
4321    
4322  public void cleanUpDifferential(StructureDefinition sd) {
4323    if (sd.getDifferential().getElement().size() > 1)
4324      cleanUpDifferential(sd, 1);
4325  }
4326  
4327  private void cleanUpDifferential(StructureDefinition sd, int start) {
4328    int level = Utilities.charCount(sd.getDifferential().getElement().get(start).getPath(), '.');
4329    int c = start;
4330    int len = sd.getDifferential().getElement().size();
4331    HashSet<String> paths = new HashSet<String>();
4332    while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') == level) {
4333      ElementDefinition ed = sd.getDifferential().getElement().get(c);
4334      if (!paths.contains(ed.getPath())) {
4335        paths.add(ed.getPath());
4336        int ic = c+1; 
4337        while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 
4338          ic++;
4339        ElementDefinition slicer = null;
4340        List<ElementDefinition> slices = new ArrayList<ElementDefinition>();
4341        slices.add(ed);
4342        while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') == level) {
4343          ElementDefinition edi = sd.getDifferential().getElement().get(ic);
4344          if (ed.getPath().equals(edi.getPath())) {
4345            if (slicer == null) {
4346              slicer = new ElementDefinition();
4347              slicer.setPath(edi.getPath());
4348              slicer.getSlicing().setRules(SlicingRules.OPEN);
4349              sd.getDifferential().getElement().add(c, slicer);
4350              c++;
4351              ic++;
4352            }
4353            slices.add(edi);
4354          }
4355          ic++;
4356          while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 
4357            ic++;
4358        }
4359        // now we're at the end, we're going to figure out the slicing discriminator
4360        if (slicer != null)
4361          determineSlicing(slicer, slices);
4362      }
4363      c++;
4364      if (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) {
4365        cleanUpDifferential(sd, c);
4366        c++;
4367        while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) 
4368          c++;
4369      }
4370  }
4371  }
4372
4373
4374  private void determineSlicing(ElementDefinition slicer, List<ElementDefinition> slices) {
4375    // first, name them
4376    int i = 0;
4377    for (ElementDefinition ed : slices) {
4378      if (ed.hasUserData("slice-name")) {
4379        ed.setSliceName(ed.getUserString("slice-name"));
4380      } else {
4381        i++;
4382        ed.setSliceName("slice-"+Integer.toString(i));
4383      }
4384    }
4385    // now, the hard bit, how are they differentiated? 
4386    // right now, we hard code this...
4387    if (slicer.getPath().endsWith(".extension") || slicer.getPath().endsWith(".modifierExtension"))
4388      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url");
4389    else if (slicer.getPath().equals("DiagnosticReport.result"))
4390      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("reference.code");
4391    else if (slicer.getPath().equals("Observation.related"))
4392      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("target.reference.code");
4393    else if (slicer.getPath().equals("Bundle.entry"))
4394      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("resource.@profile");
4395    else  
4396      throw new Error("No slicing for "+slicer.getPath()); 
4397  }
4398
4399  public class SpanEntry {
4400    private List<SpanEntry> children = new ArrayList<SpanEntry>();
4401    private boolean profile;
4402    private String id;
4403    private String name;
4404    private String resType;
4405    private String cardinality;
4406    private String description;
4407    private String profileLink;
4408    private String resLink;
4409    private String type;
4410    
4411    public String getName() {
4412      return name;
4413    }
4414    public void setName(String name) {
4415      this.name = name;
4416    }
4417    public String getResType() {
4418      return resType;
4419    }
4420    public void setResType(String resType) {
4421      this.resType = resType;
4422    }
4423    public String getCardinality() {
4424      return cardinality;
4425    }
4426    public void setCardinality(String cardinality) {
4427      this.cardinality = cardinality;
4428    }
4429    public String getDescription() {
4430      return description;
4431    }
4432    public void setDescription(String description) {
4433      this.description = description;
4434    }
4435    public String getProfileLink() {
4436      return profileLink;
4437    }
4438    public void setProfileLink(String profileLink) {
4439      this.profileLink = profileLink;
4440    }
4441    public String getResLink() {
4442      return resLink;
4443    }
4444    public void setResLink(String resLink) {
4445      this.resLink = resLink;
4446    }
4447    public String getId() {
4448      return id;
4449    }
4450    public void setId(String id) {
4451      this.id = id;
4452    }
4453    public boolean isProfile() {
4454      return profile;
4455    }
4456    public void setProfile(boolean profile) {
4457      this.profile = profile;
4458    }
4459    public List<SpanEntry> getChildren() {
4460      return children;
4461    }
4462    public String getType() {
4463      return type;
4464    }
4465    public void setType(String type) {
4466      this.type = type;
4467    }
4468    
4469  }
4470
4471  public XhtmlNode generateSpanningTable(StructureDefinition profile, String imageFolder, boolean onlyConstraints, String constraintPrefix, Set<String> outputTracker) throws IOException, FHIRException {
4472    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, false, true);
4473    gen.setTranslator(getTranslator());
4474    TableModel model = initSpanningTable(gen, "", false, profile.getId());
4475    Set<String> processed = new HashSet<String>();
4476    SpanEntry span = buildSpanningTable("(focus)", "", profile, processed, onlyConstraints, constraintPrefix);
4477    
4478    genSpanEntry(gen, model.getRows(), span);
4479    return gen.generate(model, "", 0, outputTracker);
4480  }
4481
4482  private SpanEntry buildSpanningTable(String name, String cardinality, StructureDefinition profile, Set<String> processed, boolean onlyConstraints, String constraintPrefix) throws IOException {
4483    SpanEntry res = buildSpanEntryFromProfile(name, cardinality, profile);
4484    boolean wantProcess = !processed.contains(profile.getUrl());
4485    processed.add(profile.getUrl());
4486    if (wantProcess && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
4487      for (ElementDefinition ed : profile.getSnapshot().getElement()) {
4488        if (!"0".equals(ed.getMax()) && ed.getType().size() > 0) {
4489          String card = getCardinality(ed, profile.getSnapshot().getElement());
4490          if (!card.endsWith(".0")) {
4491            List<String> refProfiles = listReferenceProfiles(ed);
4492            if (refProfiles.size() > 0) {
4493              String uri = refProfiles.get(0);
4494              if (uri != null) {
4495                StructureDefinition sd = context.fetchResource(StructureDefinition.class, uri);
4496                if (sd != null && (!onlyConstraints || (sd.getDerivation() == TypeDerivationRule.CONSTRAINT && (constraintPrefix == null || sd.getUrl().startsWith(constraintPrefix))))) {
4497                  res.getChildren().add(buildSpanningTable(nameForElement(ed), card, sd, processed, onlyConstraints, constraintPrefix));
4498                }
4499              }
4500            }
4501          }
4502        } 
4503      }
4504    }
4505    return res;
4506  }
4507
4508
4509  private String getCardinality(ElementDefinition ed, List<ElementDefinition> list) {
4510    int min = ed.getMin();
4511    int max = !ed.hasMax() || ed.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(ed.getMax());
4512    while (ed != null && ed.getPath().contains(".")) {
4513      ed = findParent(ed, list);
4514      if (ed.getMax().equals("0"))
4515        max = 0;
4516      else if (!ed.getMax().equals("1") && !ed.hasSlicing())
4517        max = Integer.MAX_VALUE;
4518      if (ed.getMin() == 0)
4519        min = 0;
4520    }
4521    return Integer.toString(min)+".."+(max == Integer.MAX_VALUE ? "*" : Integer.toString(max));
4522  }
4523
4524
4525  private ElementDefinition findParent(ElementDefinition ed, List<ElementDefinition> list) {
4526    int i = list.indexOf(ed)-1;
4527    while (i >= 0 && !ed.getPath().startsWith(list.get(i).getPath()+"."))
4528      i--;
4529    if (i == -1)
4530      return null;
4531    else
4532      return list.get(i);
4533  }
4534
4535
4536  private List<String> listReferenceProfiles(ElementDefinition ed) {
4537    List<String> res = new ArrayList<String>();
4538    for (TypeRefComponent tr : ed.getType()) {
4539      // code is null if we're dealing with "value" and profile is null if we just have Reference()
4540      if (tr.hasTarget() && tr.hasTargetProfile())
4541        for (UriType u : tr.getTargetProfile())
4542          res.add(u.getValue());
4543    }
4544    return res;
4545  }
4546
4547
4548  private String nameForElement(ElementDefinition ed) {
4549    return ed.getPath().substring(ed.getPath().indexOf(".")+1);
4550  }
4551
4552
4553  private SpanEntry buildSpanEntryFromProfile(String name, String cardinality, StructureDefinition profile) throws IOException {
4554    SpanEntry res = new SpanEntry();
4555    res.setName(name);
4556    res.setCardinality(cardinality);
4557    res.setProfileLink(profile.getUserString("path"));
4558    res.setResType(profile.getType());
4559    StructureDefinition base = context.fetchResource(StructureDefinition.class, res.getResType());
4560    if (base != null)
4561      res.setResLink(base.getUserString("path"));
4562    res.setId(profile.getId());
4563    res.setProfile(profile.getDerivation() == TypeDerivationRule.CONSTRAINT);
4564    StringBuilder b = new StringBuilder();
4565    b.append(res.getResType());
4566    boolean first = true;
4567    boolean open = false;
4568    if (profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
4569      res.setDescription(profile.getName());
4570      for (ElementDefinition ed : profile.getSnapshot().getElement()) {
4571        if (isKeyProperty(ed.getBase().getPath()) && ed.hasFixed()) {
4572          if (first) {
4573            open = true;
4574            first = false;
4575            b.append("[");
4576          } else {
4577            b.append(", ");
4578          }
4579          b.append(tail(ed.getBase().getPath()));
4580          b.append("=");
4581          b.append(summarize(ed.getFixed()));
4582        }
4583      }
4584      if (open)
4585        b.append("]");
4586    } else
4587      res.setDescription("Base FHIR "+profile.getName());
4588    res.setType(b.toString());
4589    return res ;
4590  }
4591
4592
4593  private String summarize(Type value) throws IOException {
4594    if (value instanceof Coding)
4595      return summarizeCoding((Coding) value);
4596    else if (value instanceof CodeableConcept)
4597      return summarizeCodeableConcept((CodeableConcept) value);
4598    else
4599      return buildJson(value);
4600  }
4601
4602
4603  private String summarizeCoding(Coding value) {
4604    String uri = value.getSystem();
4605    String system = NarrativeGenerator.describeSystem(uri);
4606    if (Utilities.isURL(system)) {
4607      if (system.equals("http://cap.org/protocols"))
4608        system = "CAP Code";
4609    }
4610    return system+" "+value.getCode();
4611  }
4612
4613
4614  private String summarizeCodeableConcept(CodeableConcept value) {
4615    if (value.hasCoding())
4616      return summarizeCoding(value.getCodingFirstRep());
4617    else
4618      return value.getText();
4619  }
4620
4621
4622  private boolean isKeyProperty(String path) {
4623    return Utilities.existsInList(path, "Observation.code");
4624  }
4625
4626
4627  public TableModel initSpanningTable(HierarchicalTableGenerator gen, String prefix, boolean isLogical, String id) {
4628    TableModel model = gen.new TableModel(id, false);
4629    
4630    model.setDocoImg(prefix+"help16.png");
4631    model.setDocoRef(prefix+"formats.html#table"); // todo: change to graph definition
4632    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Property", "A profiled resource", null, 0));
4633    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));
4634    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Content", "What goes here", null, 0));
4635    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Description", "Description of the profile", null, 0));
4636    return model;
4637  }
4638
4639  private void genSpanEntry(HierarchicalTableGenerator gen, List<Row> rows, SpanEntry span) throws IOException {
4640    Row row = gen.new Row();
4641    rows.add(row);
4642    row.setAnchor(span.getId());
4643    //row.setColor(..?);
4644    if (span.isProfile()) 
4645      row.setIcon("icon_profile.png", HierarchicalTableGenerator.TEXT_ICON_PROFILE);
4646    else
4647      row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
4648    
4649    row.getCells().add(gen.new Cell(null, null, span.getName(), null, null));
4650    row.getCells().add(gen.new Cell(null, null, span.getCardinality(), null, null));
4651    row.getCells().add(gen.new Cell(null, span.getProfileLink(), span.getType(), null, null));
4652    row.getCells().add(gen.new Cell(null, null, span.getDescription(), null, null));
4653
4654    for (SpanEntry child : span.getChildren())
4655      genSpanEntry(gen, row.getSubRows(), child);
4656  }
4657
4658
4659  public static ElementDefinitionSlicingDiscriminatorComponent interpretR2Discriminator(String discriminator, boolean isExists) {
4660    if (discriminator.endsWith("@pattern"))
4661      return makeDiscriminator(DiscriminatorType.PATTERN, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 
4662    if (discriminator.endsWith("@profile"))
4663      return makeDiscriminator(DiscriminatorType.PROFILE, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 
4664    if (discriminator.endsWith("@type")) 
4665      return makeDiscriminator(DiscriminatorType.TYPE, discriminator.length() == 5 ? "" : discriminator.substring(0,discriminator.length()-6));
4666    if (discriminator.endsWith("@exists"))
4667      return makeDiscriminator(DiscriminatorType.EXISTS, discriminator.length() == 7 ? "" : discriminator.substring(0,discriminator.length()-8)); 
4668    if (isExists)
4669      return makeDiscriminator(DiscriminatorType.EXISTS, discriminator); 
4670    return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(discriminator);
4671  }
4672
4673
4674  private static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType dType, String str) {
4675    return new ElementDefinitionSlicingDiscriminatorComponent().setType(dType).setPath(Utilities.noString(str)? "$this" : str);
4676  }
4677
4678
4679  public static String buildR2Discriminator(ElementDefinitionSlicingDiscriminatorComponent t) throws FHIRException {
4680    switch (t.getType()) {
4681    case PROFILE: return t.getPath()+"/@profile";
4682    case TYPE: return t.getPath()+"/@type";
4683    case VALUE: return t.getPath();
4684    case PATTERN: return t.getPath();
4685    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
4686    default: throw new FHIRException("Unable to represent "+t.getType().toCode()+":"+t.getPath()+" in R2");    
4687    }
4688  }
4689
4690
4691  public static StructureDefinition makeExtensionForVersionedURL(IWorkerContext context, String url) {
4692    String epath = url.substring(54);
4693    if (!epath.contains("."))
4694      return null;
4695    String type = epath.substring(0, epath.indexOf("."));
4696    StructureDefinition sd = context.fetchTypeDefinition(type);
4697    if (sd == null)
4698      return null;
4699    ElementDefinition ed = null;
4700    for (ElementDefinition t : sd.getSnapshot().getElement()) {
4701      if (t.getPath().equals(epath)) {
4702        ed = t;
4703        break;
4704      }
4705    }
4706    if (ed == null)
4707      return null;
4708    if ("Element".equals(ed.typeSummary()) || "BackboneElement".equals(ed.typeSummary())) {
4709      return null;
4710    } else {
4711      StructureDefinition template = context.fetchResource(StructureDefinition.class, "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities");
4712      StructureDefinition ext = template.copy();
4713      ext.setUrl(url);
4714      ext.setId("extension-"+epath);
4715      ext.setName("Extension-"+epath);
4716      ext.setTitle("Extension for r4 "+epath);
4717      ext.setStatus(sd.getStatus());
4718      ext.setDate(sd.getDate());
4719      ext.getContact().clear();
4720      ext.getContact().addAll(sd.getContact());
4721      ext.setFhirVersion(sd.getFhirVersion());
4722      ext.setDescription(ed.getDefinition());
4723      ext.getContext().clear();
4724      ext.addContext().setType(ExtensionContextType.ELEMENT).setExpression(epath.substring(0, epath.lastIndexOf(".")));
4725      ext.getDifferential().getElement().clear();
4726      ext.getSnapshot().getElement().get(3).setFixed(new UriType(url));
4727      ext.getSnapshot().getElement().set(4, ed.copy());
4728      ext.getSnapshot().getElement().get(4).setPath("Extension.value"+Utilities.capitalize(ed.typeSummary()));
4729      return ext;      
4730    }
4731
4732  }
4733
4734
4735  public boolean isThrowException() {
4736    return exception;
4737  }
4738
4739
4740  public void setThrowException(boolean exception) {
4741    this.exception = exception;
4742  }
4743
4744
4745  public TerminologyServiceOptions getTerminologyServiceOptions() {
4746    return terminologyServiceOptions;
4747  }
4748
4749
4750  public void setTerminologyServiceOptions(TerminologyServiceOptions terminologyServiceOptions) {
4751    this.terminologyServiceOptions = terminologyServiceOptions;
4752  }
4753
4754
4755  public boolean isNewSlicingProcessing() {
4756    return newSlicingProcessing;
4757  }
4758
4759
4760  public void setNewSlicingProcessing(boolean newSlicingProcessing) {
4761    this.newSlicingProcessing = newSlicingProcessing;
4762  }
4763
4764
4765  public boolean isDebug() {
4766    return debug;
4767  }
4768
4769
4770  public void setDebug(boolean debug) {
4771    this.debug = debug;
4772  }
4773
4774
4775
4776  
4777}