001package org.hl7.fhir.dstu3.conformance;
002
003import java.io.IOException;
004import java.io.OutputStream;
005import java.util.ArrayList;
006import java.util.Collections;
007import java.util.Comparator;
008import java.util.HashMap;
009import java.util.HashSet;
010import java.util.List;
011import java.util.Map;
012import java.util.Set;
013
014import org.hl7.fhir.dstu3.conformance.ProfileUtilities.ProfileKnowledgeProvider.BindingResolution;
015import org.hl7.fhir.dstu3.context.IWorkerContext;
016import org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult;
017import org.hl7.fhir.dstu3.elementmodel.ObjectConverter;
018import org.hl7.fhir.dstu3.elementmodel.Property;
019import org.hl7.fhir.dstu3.formats.IParser;
020import org.hl7.fhir.dstu3.model.Base;
021import org.hl7.fhir.dstu3.model.BooleanType;
022import org.hl7.fhir.dstu3.model.CodeType;
023import org.hl7.fhir.dstu3.model.CodeableConcept;
024import org.hl7.fhir.dstu3.model.Coding;
025import org.hl7.fhir.dstu3.model.Element;
026import org.hl7.fhir.dstu3.model.ElementDefinition;
027import org.hl7.fhir.dstu3.model.ElementDefinition.AggregationMode;
028import org.hl7.fhir.dstu3.model.ElementDefinition.DiscriminatorType;
029import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionBaseComponent;
030import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionBindingComponent;
031import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionConstraintComponent;
032import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionExampleComponent;
033import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionMappingComponent;
034import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionSlicingComponent;
035import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent;
036import org.hl7.fhir.dstu3.model.ElementDefinition.SlicingRules;
037import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent;
038import org.hl7.fhir.dstu3.model.Enumeration;
039import org.hl7.fhir.dstu3.model.Enumerations.BindingStrength;
040import org.hl7.fhir.dstu3.model.Extension;
041import org.hl7.fhir.dstu3.model.IntegerType;
042import org.hl7.fhir.dstu3.model.PrimitiveType;
043import org.hl7.fhir.dstu3.model.Quantity;
044import org.hl7.fhir.dstu3.model.Reference;
045import org.hl7.fhir.dstu3.model.Resource;
046import org.hl7.fhir.dstu3.model.StringType;
047import org.hl7.fhir.dstu3.model.StructureDefinition;
048import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionDifferentialComponent;
049import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionKind;
050import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionMappingComponent;
051import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionSnapshotComponent;
052import org.hl7.fhir.dstu3.model.StructureDefinition.TypeDerivationRule;
053import org.hl7.fhir.dstu3.model.Type;
054import org.hl7.fhir.dstu3.model.UriType;
055import org.hl7.fhir.dstu3.model.ValueSet;
056import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent;
057import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent;
058import org.hl7.fhir.dstu3.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
059import org.hl7.fhir.dstu3.utils.NarrativeGenerator;
060import org.hl7.fhir.dstu3.utils.ToolingExtensions;
061import org.hl7.fhir.dstu3.utils.TranslatingUtilities;
062import org.hl7.fhir.exceptions.DefinitionException;
063import org.hl7.fhir.exceptions.FHIRException;
064import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
065import org.hl7.fhir.utilities.Utilities;
066import org.hl7.fhir.utilities.validation.ValidationMessage;
067import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
068import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
069import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
070import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
071import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
072import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
073import org.hl7.fhir.utilities.xml.SchematronWriter;
074import org.hl7.fhir.utilities.xml.SchematronWriter.Rule;
075import org.hl7.fhir.utilities.xml.SchematronWriter.SchematronType;
076import org.hl7.fhir.utilities.xml.SchematronWriter.Section;
077
078/**
079 * This class provides a set of utility operations for working with Profiles.
080 * Key functionality:
081 *  * getChildMap --?
082 *  * getChildList
083 *  * generateSnapshot: Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
084 *  * closeDifferential: fill out a differential by excluding anything not mentioned
085 *  * generateExtensionsTable: generate the HTML for a hierarchical table presentation of the extensions
086 *  * generateTable: generate  the HTML for a hierarchical table presentation of a structure
087 *  * generateSpanningTable: generate the HTML for a table presentation of a network of structures, starting at a nominated point
088 *  * summarise: describe the contents of a profile
089 *  
090 * 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
091 *  
092 * @author Grahame
093 *
094 */
095public class ProfileUtilities extends TranslatingUtilities {
096
097  private static int nextSliceId = 0;
098  
099  public class ExtensionContext {
100
101    private ElementDefinition element;
102    private StructureDefinition defn;
103
104    public ExtensionContext(StructureDefinition ext, ElementDefinition ed) {
105      this.defn = ext;
106      this.element = ed;
107    }
108
109    public ElementDefinition getElement() {
110      return element;
111    }
112
113    public StructureDefinition getDefn() {
114      return defn;
115    }
116
117    public String getUrl() {
118      if (element == defn.getSnapshot().getElement().get(0))
119        return defn.getUrl();
120      else
121        return element.getSliceName();
122    }
123
124    public ElementDefinition getExtensionValueDefinition() {
125      int i = defn.getSnapshot().getElement().indexOf(element)+1;
126      while (i < defn.getSnapshot().getElement().size()) {
127        ElementDefinition ed = defn.getSnapshot().getElement().get(i);
128        if (ed.getPath().equals(element.getPath()))
129          return null;
130        if (ed.getPath().startsWith(element.getPath()+".value"))
131          return ed;
132        i++;
133      }
134      return null;
135    }
136    
137  }
138
139  private static final String ROW_COLOR_ERROR = "#ffcccc";
140  private static final String ROW_COLOR_FATAL = "#ff9999";
141  private static final String ROW_COLOR_WARNING = "#ffebcc";
142  private static final String ROW_COLOR_HINT = "#ebf5ff";
143  private static final String ROW_COLOR_NOT_MUST_SUPPORT = "#d6eaf8";
144  public static final int STATUS_OK = 0;
145  public static final int STATUS_HINT = 1;
146  public static final int STATUS_WARNING = 2;
147  public static final int STATUS_ERROR = 3;
148  public static final int STATUS_FATAL = 4;
149
150
151  private static final String DERIVATION_EQUALS = "derivation.equals";
152  public static final String DERIVATION_POINTER = "derived.pointer";
153  public static final String IS_DERIVED = "derived.fact";
154  public static final String UD_ERROR_STATUS = "error-status";
155  private static final String GENERATED_IN_SNAPSHOT = "profileutilities.snapshot.processed";
156
157  // note that ProfileUtilities are used re-entrantly internally, so nothing with process state can be here
158  private final IWorkerContext context;
159  private List<ValidationMessage> messages;
160  private List<String> snapshotStack = new ArrayList<String>();
161  private ProfileKnowledgeProvider pkp;
162  private boolean igmode;
163
164  public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp) {
165    super();
166    this.context = context;
167    this.messages = messages;
168    this.pkp = pkp;
169  }
170
171  private class UnusedTracker {
172    private boolean used;
173  }
174
175  public boolean isIgmode() {
176    return igmode;
177  }
178
179
180  public void setIgmode(boolean igmode) {
181    this.igmode = igmode;
182  }
183
184  public interface ProfileKnowledgeProvider {
185    public class BindingResolution {
186      public String display;
187      public String url;
188    }
189    boolean isDatatype(String typeSimple);
190    boolean isResource(String typeSimple);
191    boolean hasLinkFor(String typeSimple);
192    String getLinkFor(String corePath, String typeSimple);
193    BindingResolution resolveBinding(StructureDefinition def, ElementDefinitionBindingComponent binding, String path);
194    String getLinkForProfile(StructureDefinition profile, String url);
195    boolean prependLinks();
196  }
197
198
199
200  public static List<ElementDefinition> getChildMap(StructureDefinition profile, ElementDefinition element) throws DefinitionException {
201    if (element.getContentReference()!=null) {
202      for (ElementDefinition e : profile.getSnapshot().getElement()) {
203        if (element.getContentReference().equals("#"+e.getId()))
204          return getChildMap(profile, e);
205      }
206      throw new DefinitionException("Unable to resolve name reference "+element.getContentReference()+" at path "+element.getPath());
207
208    } else {
209      List<ElementDefinition> res = new ArrayList<ElementDefinition>();
210      List<ElementDefinition> elements = profile.getSnapshot().getElement();
211      String path = element.getPath();
212      for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) {
213        ElementDefinition e = elements.get(index);
214        if (e.getPath().startsWith(path + ".")) {
215          // We only want direct children, not all descendants
216          if (!e.getPath().substring(path.length()+1).contains("."))
217            res.add(e);
218        } else
219          break;
220      }
221      return res;
222    }
223  }
224
225
226  public static List<ElementDefinition> getSliceList(StructureDefinition profile, ElementDefinition element) throws DefinitionException {
227    if (!element.hasSlicing())
228      throw new Error("getSliceList should only be called when the element has slicing");
229
230    List<ElementDefinition> res = new ArrayList<ElementDefinition>();
231    List<ElementDefinition> elements = profile.getSnapshot().getElement();
232    String path = element.getPath();
233    for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) {
234      ElementDefinition e = elements.get(index);
235      if (e.getPath().startsWith(path + ".") || e.getPath().equals(path)) {
236        // We want elements with the same path (until we hit an element that doesn't start with the same path)
237        if (e.getPath().equals(element.getPath()))
238          res.add(e);
239      } else
240        break;
241    }
242    return res;
243  }
244
245
246  /**
247   * Given a Structure, navigate to the element given by the path and return the direct children of that element
248   *
249   * @param structure The structure to navigate into
250   * @param path The path of the element within the structure to get the children for
251   * @return A List containing the element children (all of them are Elements)
252   */
253  public static List<ElementDefinition> getChildList(StructureDefinition profile, String path) {
254    List<ElementDefinition> res = new ArrayList<ElementDefinition>();
255
256    for (ElementDefinition e : profile.getSnapshot().getElement())
257    {
258      String p = e.getPath();
259
260      if (!Utilities.noString(e.getContentReference()) && path.startsWith(p))
261      {
262        if (path.length() > p.length())
263          return getChildList(profile, e.getContentReference()+"."+path.substring(p.length()+1));
264        else
265          return getChildList(profile, e.getContentReference());
266      }
267      else if (p.startsWith(path+".") && !p.equals(path))
268      {
269          String tail = p.substring(path.length()+1);
270          if (!tail.contains(".")) {
271            res.add(e);
272          }
273        }
274
275      }
276
277    return res;
278  }
279
280
281  public static List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element) {
282    return getChildList(structure, element.getPath());
283        }
284
285  public void updateMaps(StructureDefinition base, StructureDefinition derived) throws DefinitionException {
286    if (base == null)
287        throw new DefinitionException("no base profile provided");
288    if (derived == null)
289      throw new DefinitionException("no derived structure provided");
290    
291    for (StructureDefinitionMappingComponent baseMap : base.getMapping()) {
292      boolean found = false;
293      for (StructureDefinitionMappingComponent derivedMap : derived.getMapping()) {
294        if (derivedMap.getUri().equals(baseMap.getUri())) {
295          found = true;
296          break;
297        }
298      }
299      if (!found)
300        derived.getMapping().add(baseMap);
301    }
302  }
303  
304  /**
305   * Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
306   *
307   * @param base - the base structure on which the differential will be applied
308   * @param differential - the differential to apply to the base
309   * @param url - where the base has relative urls for profile references, these need to be converted to absolutes by prepending this URL
310   * @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
311   * @return
312   * @throws FHIRException 
313   * @throws DefinitionException 
314   * @throws Exception
315   */
316  public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String profileName) throws DefinitionException, FHIRException {
317    if (base == null)
318      throw new DefinitionException("no base profile provided");
319    if (derived == null)
320      throw new DefinitionException("no derived structure provided");
321
322    if (snapshotStack.contains(derived.getUrl()))
323      throw new DefinitionException("Circular snapshot references detected; cannot generate snapshot (stack = "+snapshotStack.toString()+")");
324    snapshotStack.add(derived.getUrl());
325    
326
327    derived.setSnapshot(new StructureDefinitionSnapshotComponent());
328
329    // so we have two lists - the base list, and the differential list
330    // the differential list is only allowed to include things that are in the base list, but
331    // is allowed to include them multiple times - thereby slicing them
332
333    // our approach is to walk through the base list, and see whether the differential
334    // says anything about them.
335    int baseCursor = 0;
336    int diffCursor = 0; // we need a diff cursor because we can only look ahead, in the bound scoped by longer paths
337
338    if (derived.hasDifferential() && !derived.getDifferential().getElementFirstRep().getPath().contains(".") && !derived.getDifferential().getElementFirstRep().getType().isEmpty())
339      throw new Error("type on first differential element!");
340
341    for (ElementDefinition e : derived.getDifferential().getElement()) 
342      e.clearUserData(GENERATED_IN_SNAPSHOT);
343    
344    // we actually delegate the work to a subroutine so we can re-enter it with a different cursors
345    processPaths("", derived.getSnapshot(), base.getSnapshot(), derived.getDifferential(), baseCursor, diffCursor, base.getSnapshot().getElement().size()-1, 
346        derived.getDifferential().hasElement() ? derived.getDifferential().getElement().size()-1 : -1, url, derived.getId(), null, null, false, base.getUrl(), null, false);
347    if (!derived.getSnapshot().getElementFirstRep().getType().isEmpty())
348      throw new Error("type on first snapshot element for "+derived.getSnapshot().getElementFirstRep().getPath()+" in "+derived.getUrl()+" from "+base.getUrl());
349    updateMaps(base, derived);
350    setIds(derived, false);
351    
352    //Check that all differential elements have a corresponding snapshot element
353    for (ElementDefinition e : derived.getDifferential().getElement()) {
354      if (!e.hasUserData(GENERATED_IN_SNAPSHOT)) {
355        System.out.println("Error in snapshot generation: Snapshot for "+derived.getUrl()+" does not contain differential element with id: " + e.getId());
356        System.out.println("Differential: ");
357        for (ElementDefinition ed : derived.getDifferential().getElement())
358          System.out.println("  "+ed.getPath()+" : "+typeSummary(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  id = "+ed.getId());
359        System.out.println("Snapshot: ");
360        for (ElementDefinition ed : derived.getSnapshot().getElement())
361          System.out.println("  "+ed.getPath()+" : "+typeSummary(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  id = "+ed.getId());
362        throw new DefinitionException("Snapshot for "+derived.getUrl()+" does not contain differential element with id: " + e.getId());
363//        System.out.println("**BAD Differential element: " + profileName + ":" + e.getId());
364    }
365  }
366  }
367
368  private String sliceSummary(ElementDefinition ed) {
369    if (!ed.hasSlicing() && !ed.hasSliceName())
370      return "";
371    if (ed.hasSliceName())
372      return " (slicename = "+ed.getSliceName()+")";
373    
374    StringBuilder b = new StringBuilder();
375    boolean first = true;
376    for (ElementDefinitionSlicingDiscriminatorComponent d : ed.getSlicing().getDiscriminator()) {
377      if (first) 
378        first = false;
379      else
380        b.append("|");
381      b.append(d.getPath());
382    }
383    return " (slicing by "+b.toString()+")";
384  }
385
386
387  private String typeSummary(ElementDefinition ed) {
388    StringBuilder b = new StringBuilder();
389    boolean first = true;
390    for (TypeRefComponent tr : ed.getType()) {
391      if (first) 
392        first = false;
393      else
394        b.append("|");
395      b.append(tr.getCode());
396    }
397    return b.toString();
398  }
399
400
401  private boolean findMatchingElement(String id, List<ElementDefinition> list) {
402    for (ElementDefinition ed : list) {
403      if (ed.getId().equals(id))
404        return true;
405      if (id.endsWith("[x]")) {
406        if (ed.getId().startsWith(id.substring(0, id.length()-3)) && !ed.getId().substring(id.length()-3).contains("."))
407          return true;
408      }
409    }
410    return false;
411  }
412
413
414  /**
415   * @param trimDifferential
416   * @throws DefinitionException, FHIRException 
417   * @throws Exception
418   */
419  private ElementDefinition processPaths(String indent, StructureDefinitionSnapshotComponent result, StructureDefinitionSnapshotComponent base, StructureDefinitionDifferentialComponent differential, int baseCursor, int diffCursor, int baseLimit,
420      int diffLimit, String url, String profileName, String contextPathSrc, String contextPathDst, boolean trimDifferential, String contextName, String resultPathBase, boolean slicingDone) throws DefinitionException, FHIRException {
421
422//    System.out.println(indent+"PP @ "+resultPathBase+": base = "+baseCursor+" to "+baseLimit+", diff = "+diffCursor+" to "+diffLimit+" (slicing = "+slicingDone+")");
423    ElementDefinition res = null; 
424    // just repeat processing entries until we run out of our allowed scope (1st entry, the allowed scope is all the entries)
425    while (baseCursor <= baseLimit) {
426      // get the current focus of the base, and decide what to do
427      ElementDefinition currentBase = base.getElement().get(baseCursor);
428      String cpath = fixedPath(contextPathSrc, currentBase.getPath());
429//      System.out.println(indent+" - "+cpath+": base = "+baseCursor+" to "+baseLimit+", diff = "+diffCursor+" to "+diffLimit+" (slicingDone = "+slicingDone+")");
430      List<ElementDefinition> diffMatches = getDiffMatches(differential, cpath, diffCursor, diffLimit, profileName, url); // get a list of matching elements in scope
431
432      // in the simple case, source is not sliced.
433      if (!currentBase.hasSlicing()) {
434        if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item
435          // so we just copy it in
436          ElementDefinition outcome = updateURLs(url, currentBase.copy());
437          outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
438          updateFromBase(outcome, currentBase);
439          markDerived(outcome);
440          if (resultPathBase == null)
441            resultPathBase = outcome.getPath();
442          else if (!outcome.getPath().startsWith(resultPathBase))
443            throw new DefinitionException("Adding wrong path");
444          result.getElement().add(outcome);
445          if (hasInnerDiffMatches(differential, cpath, diffCursor, diffLimit, base.getElement())) {
446            // well, the profile walks into this, so we need to as well
447            if (outcome.getType().size() > 1) {
448              for (TypeRefComponent t : outcome.getType()) {
449                if (!t.getCode().equals("Reference"))
450                  throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName);
451              }
452            }
453            StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
454            if (dt == null)
455              throw new DefinitionException(cpath+" has children for type "+typeCode(outcome.getType())+" in profile "+profileName+", but can't find type");
456            contextName = dt.getUrl();
457            int start = diffCursor;
458            while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), cpath+"."))
459              diffCursor++;
460            processPaths(indent+"  ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1,
461                diffCursor-1, url, profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false);
462          }
463          baseCursor++;
464        } else if (diffMatches.size() == 1 && (slicingDone || !(diffMatches.get(0).hasSlicing() || (isExtension(diffMatches.get(0)) && diffMatches.get(0).hasSliceName())))) {// one matching element in the differential
465          ElementDefinition template = null;
466          if (diffMatches.get(0).hasType() && diffMatches.get(0).getType().size() == 1 && diffMatches.get(0).getType().get(0).hasProfile() && !diffMatches.get(0).getType().get(0).getCode().equals("Reference")) {
467            String p = diffMatches.get(0).getType().get(0).getProfile();
468            StructureDefinition sd = context.fetchResource(StructureDefinition.class, p);
469            if (sd != null) {
470              if (!sd.hasSnapshot()) {
471                StructureDefinition sdb = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
472                if (sdb == null)
473                  throw new DefinitionException("no base for "+sd.getBaseDefinition());
474                generateSnapshot(sdb, sd, sd.getUrl(), sd.getName());
475              }
476              template = sd.getSnapshot().getElement().get(0).copy().setPath(currentBase.getPath());
477              template.setSliceName(null);
478              // temporary work around
479              if (!diffMatches.get(0).getType().get(0).getCode().equals("Extension")) {
480                template.setMin(currentBase.getMin());
481                template.setMax(currentBase.getMax());
482              }
483            }
484          } 
485          if (template == null)
486            template = currentBase.copy();
487          else
488            // some of what's in currentBase overrides template
489            template = overWriteWithCurrent(template, currentBase);
490          
491          ElementDefinition outcome = updateURLs(url, template);
492          outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
493          res = outcome;
494          updateFromBase(outcome, currentBase);
495          if (diffMatches.get(0).hasSliceName())
496          outcome.setSliceName(diffMatches.get(0).getSliceName());
497          outcome.setSlicing(null);
498          updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url);
499          if (outcome.getPath().endsWith("[x]") && outcome.getType().size() == 1 && !outcome.getType().get(0).getCode().equals("*")) // if the base profile allows multiple types, but the profile only allows one, rename it
500            outcome.setPath(outcome.getPath().substring(0, outcome.getPath().length()-3)+Utilities.capitalize(outcome.getType().get(0).getCode()));
501          if (resultPathBase == null)
502            resultPathBase = outcome.getPath();
503          else if (!outcome.getPath().startsWith(resultPathBase))
504            throw new DefinitionException("Adding wrong path");
505          result.getElement().add(outcome);
506          baseCursor++;
507          diffCursor = differential.getElement().indexOf(diffMatches.get(0))+1;
508          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
509            if (pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".") && !baseWalksInto(base.getElement(), baseCursor)) {
510              if (outcome.getType().size() > 1) {
511                for (TypeRefComponent t : outcome.getType()) {
512                  if (!t.getCode().equals("Reference"))
513                    throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName);
514                }
515              }
516              int start = diffCursor;
517              while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+"."))
518                diffCursor++;
519              if (outcome.hasContentReference()) {
520                ElementDefinition tgt = getElementById(base.getElement(), outcome.getContentReference());
521                if (tgt == null)
522                  throw new DefinitionException("Unable to resolve reference to "+outcome.getContentReference());
523                replaceFromContentReference(outcome, tgt);
524                int nbc = base.getElement().indexOf(tgt)+1;
525                int nbl = nbc;
526                while (nbl < base.getElement().size() && base.getElement().get(nbl).getPath().startsWith(tgt.getPath()+"."))
527                  nbl++;
528                processPaths(indent+"  ", result, base, differential, nbc, start - 1, nbl-1, diffCursor - 1, url, profileName, tgt.getPath(), diffMatches.get(0).getPath(), trimDifferential, contextName, resultPathBase, false);
529              } else {
530              StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
531              if (dt == null)
532                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");
533              contextName = dt.getUrl();
534              processPaths(indent+"  ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start-1, dt.getSnapshot().getElement().size()-1,
535                  diffCursor - 1, url, profileName+pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false);
536            }
537          }
538          }
539        } else {
540          // ok, the differential slices the item. Let's check our pre-conditions to ensure that this is correct
541          if (!unbounded(currentBase) && !isSlicedToOneOnly(diffMatches.get(0)))
542            // you can only slice an element that doesn't repeat if the sum total of your slices is limited to 1
543            // (but you might do that in order to split up constraints by type)
544            throw new DefinitionException("Attempt to a slice an element that does not repeat: "+currentBase.getPath()+"/"+currentBase.getSliceName()+" from "+contextName+" in "+url);
545          if (!diffMatches.get(0).hasSlicing() && !isExtension(currentBase)) // well, the diff has set up a slice, but hasn't defined it. this is an error
546            throw new DefinitionException("differential does not have a slice: "+currentBase.getPath()+" in profile "+url);
547
548          // well, if it passed those preconditions then we slice the dest.
549          int start = 0;
550          int nbl = findEndOfElement(base, baseCursor);
551          if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && differential.getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0))+1) {
552            int ndc = differential.getElement().indexOf(diffMatches.get(0));
553            int ndl = findEndOfElement(differential, ndc);
554            processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, profileName+pathTail(diffMatches, 0), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true).setSlicing(diffMatches.get(0).getSlicing());
555            start++;
556          } else {
557          // we're just going to accept the differential slicing at face value
558          ElementDefinition outcome = updateURLs(url, currentBase.copy());
559          outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
560          updateFromBase(outcome, currentBase);
561
562          if (!diffMatches.get(0).hasSlicing())
563            outcome.setSlicing(makeExtensionSlicing());
564          else
565            outcome.setSlicing(diffMatches.get(0).getSlicing().copy());
566          if (!outcome.getPath().startsWith(resultPathBase))
567            throw new DefinitionException("Adding wrong path");
568          result.getElement().add(outcome);
569
570          // 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.
571          if (!diffMatches.get(0).hasSliceName()) {
572            updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url);
573              if (!outcome.hasContentReference() && !outcome.hasType()) {
574              throw new DefinitionException("not done yet");
575            }
576              start++;
577              // result.getElement().remove(result.getElement().size()-1);
578          } else 
579            checkExtensionDoco(outcome);
580          }
581          // now, for each entry in the diff matches, we're going to process the base item
582          // our processing scope for base is all the children of the current path
583          int ndc = diffCursor;
584          int ndl = diffCursor;
585          for (int i = start; i < diffMatches.size(); i++) {
586            // our processing scope for the differential is the item in the list, and all the items before the next one in the list
587            ndc = differential.getElement().indexOf(diffMatches.get(i));
588            ndl = findEndOfElement(differential, ndc);
589/*            if (skipSlicingElement && i == 0) {
590              ndc = ndc + 1;
591              if (ndc > ndl)
592                continue;
593            }*/
594            // now we process the base scope repeatedly for each instance of the item in the differential list
595            processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, profileName+pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true);
596          }
597          // ok, done with that - next in the base list
598          baseCursor = nbl+1;
599          diffCursor = ndl+1;
600        }
601      } else {
602        // the item is already sliced in the base profile.
603        // here's the rules
604        //  1. irrespective of whether the slicing is ordered or not, the definition order must be maintained
605        //  2. slice element names have to match.
606        //  3. new slices must be introduced at the end
607        // corallory: you can't re-slice existing slices. is that ok?
608
609        // we're going to need this:
610        String path = currentBase.getPath();
611        ElementDefinition original = currentBase;
612
613        if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item
614          // copy across the currentbase, and all of its children and siblings
615          while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path)) {
616            ElementDefinition outcome = updateURLs(url, base.getElement().get(baseCursor).copy());
617            outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
618            if (!outcome.getPath().startsWith(resultPathBase))
619              throw new DefinitionException("Adding wrong path in profile " + profileName + ": "+outcome.getPath()+" vs " + resultPathBase);
620            result.getElement().add(outcome); // so we just copy it in
621            baseCursor++;
622          }
623        } else {
624          // first - check that the slicing is ok
625          boolean closed = currentBase.getSlicing().getRules() == SlicingRules.CLOSED;
626          int diffpos = 0;
627          boolean isExtension = cpath.endsWith(".extension") || cpath.endsWith(".modifierExtension");
628          if (diffMatches.get(0).hasSlicing()) { // it might be null if the differential doesn't want to say anything about slicing
629            if (!isExtension)
630              diffpos++; // if there's a slice on the first, we'll ignore any content it has
631            ElementDefinitionSlicingComponent dSlice = diffMatches.get(0).getSlicing();
632            ElementDefinitionSlicingComponent bSlice = currentBase.getSlicing();
633            if (dSlice.hasOrderedElement() && bSlice.hasOrderedElement() && !orderMatches(dSlice.getOrderedElement(), bSlice.getOrderedElement()))
634              throw new DefinitionException("Slicing rules on differential ("+summariseSlicing(dSlice)+") do not match those on base ("+summariseSlicing(bSlice)+") - order @ "+path+" ("+contextName+")");
635            if (!discriminatorMatches(dSlice.getDiscriminator(), bSlice.getDiscriminator()))
636             throw new DefinitionException("Slicing rules on differential ("+summariseSlicing(dSlice)+") do not match those on base ("+summariseSlicing(bSlice)+") - disciminator @ "+path+" ("+contextName+")");
637            if (!ruleMatches(dSlice.getRules(), bSlice.getRules()))
638             throw new DefinitionException("Slicing rules on differential ("+summariseSlicing(dSlice)+") do not match those on base ("+summariseSlicing(bSlice)+") - rule @ "+path+" ("+contextName+")");
639          }
640          if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && differential.getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0))+1) {
641            throw new Error("Not done yet");
642          }
643          ElementDefinition outcome = updateURLs(url, currentBase.copy());
644          outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
645          updateFromBase(outcome, currentBase);
646          if (diffMatches.get(0).hasSlicing() /*&& !isExtension*/) {
647            updateFromSlicing(outcome.getSlicing(), diffMatches.get(0).getSlicing());
648            updateFromDefinition(outcome, diffMatches.get(0), profileName, closed, url); // if there's no slice, we don't want to update the unsliced description
649          } else if (!diffMatches.get(0).hasSliceName())
650            diffMatches.get(0).setUserData(GENERATED_IN_SNAPSHOT, true); // because of updateFromDefinition isn't called 
651          
652          result.getElement().add(outcome);
653
654          if (!diffMatches.get(0).hasSliceName()) { // it's not real content, just the slice
655              diffpos++;
656          }
657
658          // now, we have two lists, base and diff. we're going to work through base, looking for matches in diff.
659          List<ElementDefinition> baseMatches = getSiblings(base.getElement(), currentBase);
660          for (ElementDefinition baseItem : baseMatches) {
661            baseCursor = base.getElement().indexOf(baseItem);
662            outcome = updateURLs(url, baseItem.copy());
663            updateFromBase(outcome, currentBase);
664            outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
665            outcome.setSlicing(null);
666            if (!outcome.getPath().startsWith(resultPathBase))
667              throw new DefinitionException("Adding wrong path");
668            if (diffpos < diffMatches.size() && diffMatches.get(diffpos).getSliceName().equals(outcome.getSliceName())) {
669              // if there's a diff, we update the outcome with diff
670              // no? updateFromDefinition(outcome, diffMatches.get(diffpos), profileName, closed, url);
671              //then process any children
672              int nbl = findEndOfElement(base, baseCursor);
673              int ndc = differential.getElement().indexOf(diffMatches.get(diffpos));
674              int ndl = findEndOfElement(differential, ndc);
675              // now we process the base scope repeatedly for each instance of the item in the differential list
676              processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, profileName+pathTail(diffMatches, diffpos), contextPathSrc, contextPathDst, closed, contextName, resultPathBase, true);
677              // ok, done with that - now set the cursors for if this is the end
678              baseCursor = nbl;
679              diffCursor = ndl+1;
680              diffpos++;
681            } else {
682              result.getElement().add(outcome);
683              baseCursor++;
684              // just copy any children on the base
685              while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path) && !base.getElement().get(baseCursor).getPath().equals(path)) {
686                outcome = updateURLs(url, base.getElement().get(baseCursor).copy());
687                outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
688                if (!outcome.getPath().startsWith(resultPathBase))
689                  throw new DefinitionException("Adding wrong path");
690                result.getElement().add(outcome);
691                baseCursor++;
692              }
693            }
694          }
695          // finally, we process any remaining entries in diff, which are new (and which are only allowed if the base wasn't closed
696          if (closed && diffpos < diffMatches.size())
697            throw new DefinitionException("The base snapshot marks a slicing as closed, but the differential tries to extend it in "+profileName+" at "+path+" ("+cpath+")");
698          if (diffpos == diffMatches.size()) {
699            diffCursor++;
700          } else {
701            while (diffpos < diffMatches.size()) {
702              ElementDefinition diffItem = diffMatches.get(diffpos);
703              for (ElementDefinition baseItem : baseMatches)
704                if (baseItem.getSliceName().equals(diffItem.getSliceName()))
705                  throw new DefinitionException("Named items are out of order in the slice");
706              outcome = updateURLs(url, currentBase.copy());
707              //            outcome = updateURLs(url, diffItem.copy());
708              outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
709              updateFromBase(outcome, currentBase);
710              outcome.setSlicing(null);
711              if (!outcome.getPath().startsWith(resultPathBase))
712                throw new DefinitionException("Adding wrong path");
713              result.getElement().add(outcome);
714              updateFromDefinition(outcome, diffItem, profileName, trimDifferential, url);
715              // --- LM Added this
716              diffCursor = differential.getElement().indexOf(diffItem)+1;
717              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
718                if (!baseWalksInto(base.getElement(), baseCursor)) {
719                  if (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".")) {
720                    if (outcome.getType().size() > 1)
721                      for (TypeRefComponent t : outcome.getType()) {
722                        if (!t.getCode().equals("Reference"))
723                          throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName);
724                      }
725                    TypeRefComponent t = outcome.getType().get(0);
726                    StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
727                    //                if (t.getCode().equals("Extension") && t.hasProfile() && !t.getProfile().contains(":")) {
728                    // lloydfix                  dt = 
729                    //                }
730                    if (dt == null)
731                      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");
732                    contextName = dt.getUrl();
733                    int start = diffCursor;
734                    while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+"."))
735                      diffCursor++;
736                    processPaths(indent+"  ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start-1, dt.getSnapshot().getElement().size()-1,
737                        diffCursor - 1, url, profileName+pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false);
738                  } else if (outcome.getType().get(0).getCode().equals("Extension")) {
739                    // 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)
740                    StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
741                    for (ElementDefinition extEd : dt.getSnapshot().getElement()) {
742                      // We only want the children that aren't the root
743                      if (extEd.getPath().contains(".")) {
744                        ElementDefinition extUrlEd = updateURLs(url, extEd.copy());
745                        extUrlEd.setPath(fixedPath(outcome.getPath(), extUrlEd.getPath()));
746                        //                      updateFromBase(extUrlEd, currentBase);
747                        markDerived(extUrlEd);
748                        result.getElement().add(extUrlEd);
749                      }
750                    }                  
751                  }
752                }
753              }
754              // ---
755              diffpos++;
756            }
757          }
758          baseCursor++;
759        }
760      }
761    }
762
763    int i = 0;
764    for (ElementDefinition e : result.getElement()) {
765      i++;
766      if (e.hasMinElement() && e.getMinElement().getValue()==null)
767        throw new Error("null min");
768    }
769    return res;
770  }
771
772
773  private void replaceFromContentReference(ElementDefinition outcome, ElementDefinition tgt) {
774    outcome.setContentReference(null);
775    outcome.getType().clear(); // though it should be clear anyway
776    outcome.getType().addAll(tgt.getType());    
777  }
778
779
780  private boolean baseWalksInto(List<ElementDefinition> elements, int cursor) {
781    if (cursor >= elements.size())
782      return false;
783    String path = elements.get(cursor).getPath();
784    String prevPath = elements.get(cursor - 1).getPath();
785    return path.startsWith(prevPath + ".");
786  }
787
788
789  private ElementDefinition overWriteWithCurrent(ElementDefinition profile, ElementDefinition usage) {
790    ElementDefinition res = profile.copy();
791    if (usage.hasSliceName())
792      res.setSliceName(usage.getSliceName());
793    if (usage.hasLabel())
794      res.setLabel(usage.getLabel());
795    for (Coding c : usage.getCode())
796      res.addCode(c);
797    
798    if (usage.hasDefinition())
799      res.setDefinition(usage.getDefinition());
800    if (usage.hasShort())
801      res.setShort(usage.getShort());
802    if (usage.hasComment())
803      res.setComment(usage.getComment());
804    if (usage.hasRequirements())
805      res.setRequirements(usage.getRequirements());
806    for (StringType c : usage.getAlias())
807      res.addAlias(c.getValue());
808    if (usage.hasMin())
809      res.setMin(usage.getMin());
810    if (usage.hasMax())
811      res.setMax(usage.getMax());
812     
813    if (usage.hasFixed())
814      res.setFixed(usage.getFixed());
815    if (usage.hasPattern())
816      res.setPattern(usage.getPattern());
817    if (usage.hasExample())
818      res.setExample(usage.getExample());
819    if (usage.hasMinValue())
820      res.setMinValue(usage.getMinValue());
821    if (usage.hasMaxValue())
822      res.setMaxValue(usage.getMaxValue());     
823    if (usage.hasMaxLength())
824      res.setMaxLength(usage.getMaxLength());
825    if (usage.hasMustSupport())
826      res.setMustSupport(usage.getMustSupport());
827    if (usage.hasBinding())
828      res.setBinding(usage.getBinding().copy());
829    for (ElementDefinitionConstraintComponent c : usage.getConstraint())
830      res.addConstraint(c);
831    
832    return res;
833  }
834
835
836  private boolean checkExtensionDoco(ElementDefinition base) {
837    // see task 3970. For an extension, there's no point copying across all the underlying definitional stuff
838    boolean isExtension = base.getPath().equals("Extension") || base.getPath().endsWith(".extension") || base.getPath().endsWith(".modifierExtension");
839    if (isExtension) {
840      base.setDefinition("An Extension");
841      base.setShort("Extension");
842      base.setCommentElement(null);
843      base.setRequirementsElement(null);
844      base.getAlias().clear();
845      base.getMapping().clear();
846    }
847    return isExtension;
848  }
849
850
851  private String pathTail(List<ElementDefinition> diffMatches, int i) {
852    
853    ElementDefinition d = diffMatches.get(i);
854    String s = d.getPath().contains(".") ? d.getPath().substring(d.getPath().lastIndexOf(".")+1) : d.getPath();
855    return "."+s + (d.hasType() && d.getType().get(0).hasProfile() ? "["+d.getType().get(0).getProfile()+"]" : "");
856  }
857
858
859  private void markDerived(ElementDefinition outcome) {
860    for (ElementDefinitionConstraintComponent inv : outcome.getConstraint())
861      inv.setUserData(IS_DERIVED, true);
862  }
863
864
865  private String summariseSlicing(ElementDefinitionSlicingComponent slice) {
866    StringBuilder b = new StringBuilder();
867    boolean first = true;
868    for (ElementDefinitionSlicingDiscriminatorComponent d : slice.getDiscriminator()) {
869      if (first)
870        first = false;
871      else
872        b.append(", ");
873      b.append(d);
874    }
875    b.append("(");
876    if (slice.hasOrdered())
877      b.append(slice.getOrderedElement().asStringValue());
878    b.append("/");
879    if (slice.hasRules())
880      b.append(slice.getRules().toCode());
881    b.append(")");
882    if (slice.hasDescription()) {
883      b.append(" \"");
884      b.append(slice.getDescription());
885      b.append("\"");
886    }
887    return b.toString();
888  }
889
890
891  private void updateFromBase(ElementDefinition derived, ElementDefinition base) {
892    if (base.hasBase()) {
893      if (!derived.hasBase())
894        derived.setBase(new ElementDefinitionBaseComponent());
895      derived.getBase().setPath(base.getBase().getPath());
896      derived.getBase().setMin(base.getBase().getMin());
897      derived.getBase().setMax(base.getBase().getMax());
898    } else {
899      if (!derived.hasBase())
900        derived.setBase(new ElementDefinitionBaseComponent());
901      derived.getBase().setPath(base.getPath());
902      derived.getBase().setMin(base.getMin());
903      derived.getBase().setMax(base.getMax());
904    }
905  }
906
907
908  private boolean pathStartsWith(String p1, String p2) {
909    return p1.startsWith(p2);
910  }
911
912  private boolean pathMatches(String p1, String p2) {
913    return p1.equals(p2) || (p2.endsWith("[x]") && p1.startsWith(p2.substring(0, p2.length()-3)) && !p1.substring(p2.length()-3).contains("."));
914  }
915
916
917  private String fixedPath(String contextPath, String pathSimple) {
918    if (contextPath == null)
919      return pathSimple;
920    return contextPath+"."+pathSimple.substring(pathSimple.indexOf(".")+1);
921  }
922
923
924  private StructureDefinition getProfileForDataType(TypeRefComponent type)  {
925    StructureDefinition sd = null;
926    if (type.hasProfile() && !type.getCode().equals("Reference"))  
927      sd = context.fetchResource(StructureDefinition.class, type.getProfile()); 
928    if (sd == null)
929      sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+type.getCode());
930    if (sd == null)
931      System.out.println("XX: failed to find profle for type: " + type.getCode()); // debug GJM
932    return sd;
933  }
934
935
936  public static String typeCode(List<TypeRefComponent> types) {
937    StringBuilder b = new StringBuilder();
938    boolean first = true;
939    for (TypeRefComponent type : types) {
940      if (first) first = false; else b.append(", ");
941      b.append(type.getCode());
942      if (type.hasTargetProfile())
943        b.append("{"+type.getTargetProfile()+"}");
944      else if (type.hasProfile())
945        b.append("{"+type.getProfile()+"}");
946    }
947    return b.toString();
948  }
949
950
951  private boolean isDataType(List<TypeRefComponent> types) {
952    if (types.isEmpty())
953      return false;
954    for (TypeRefComponent type : types) {
955      String t = type.getCode();
956      if (!isDataType(t) && !isPrimitive(t))
957        return false;
958    }
959    return true;
960  }
961
962
963  /**
964   * Finds internal references in an Element's Binding and StructureDefinition references (in TypeRef) and bases them on the given url
965   * @param url - the base url to use to turn internal references into absolute references
966   * @param element - the Element to update
967   * @return - the updated Element
968   */
969  private ElementDefinition updateURLs(String url, ElementDefinition element) {
970    if (element != null) {
971      ElementDefinition defn = element;
972      if (defn.hasBinding() && defn.getBinding().getValueSet() instanceof Reference && ((Reference)defn.getBinding().getValueSet()).getReference().startsWith("#"))
973        ((Reference)defn.getBinding().getValueSet()).setReference(url+((Reference)defn.getBinding().getValueSet()).getReference());
974      for (TypeRefComponent t : defn.getType()) {
975        if (t.hasProfile()) {
976          if (t.getProfile().startsWith("#"))
977            t.setProfile(url+t.getProfile());
978        }
979        if (t.hasTargetProfile()) {
980          if (t.getTargetProfile().startsWith("#"))
981            t.setTargetProfile(url+t.getTargetProfile());
982        }
983      }
984    }
985    return element;
986  }
987
988  private List<ElementDefinition> getSiblings(List<ElementDefinition> list, ElementDefinition current) {
989    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
990    String path = current.getPath();
991    int cursor = list.indexOf(current)+1;
992    while (cursor < list.size() && list.get(cursor).getPath().length() >= path.length()) {
993      if (pathMatches(list.get(cursor).getPath(), path))
994        result.add(list.get(cursor));
995      cursor++;
996    }
997    return result;
998  }
999
1000  private void updateFromSlicing(ElementDefinitionSlicingComponent dst, ElementDefinitionSlicingComponent src) {
1001    if (src.hasOrderedElement())
1002      dst.setOrderedElement(src.getOrderedElement().copy());
1003    if (src.hasDiscriminator()) {
1004      //    dst.getDiscriminator().addAll(src.getDiscriminator());  Can't use addAll because it uses object equality, not string equality
1005      for (ElementDefinitionSlicingDiscriminatorComponent s : src.getDiscriminator()) {
1006        boolean found = false;
1007        for (ElementDefinitionSlicingDiscriminatorComponent d : dst.getDiscriminator()) {
1008          if (matches(d, s)) {
1009            found = true;
1010            break;
1011          }
1012        }
1013        if (!found)
1014          dst.getDiscriminator().add(s);
1015      }
1016    }
1017    if (src.hasRulesElement())
1018      dst.setRulesElement(src.getRulesElement().copy());
1019  }
1020
1021  private boolean orderMatches(BooleanType diff, BooleanType base) {
1022    return (diff == null) || (base == null) || (diff.getValue() == base.getValue());
1023  }
1024
1025  private boolean discriminatorMatches(List<ElementDefinitionSlicingDiscriminatorComponent> diff, List<ElementDefinitionSlicingDiscriminatorComponent> base) {
1026    if (diff.isEmpty() || base.isEmpty())
1027        return true;
1028    if (diff.size() != base.size())
1029        return false;
1030    for (int i = 0; i < diff.size(); i++)
1031        if (!matches(diff.get(i), base.get(i)))
1032                return false;
1033    return true;
1034  }
1035
1036  private boolean matches(ElementDefinitionSlicingDiscriminatorComponent c1, ElementDefinitionSlicingDiscriminatorComponent c2) {
1037    return c1.getType().equals(c2.getType()) && c1.getPath().equals(c2.getPath());
1038  }
1039
1040
1041  private boolean ruleMatches(SlicingRules diff, SlicingRules base) {
1042    return (diff == null) || (base == null) || (diff == base) || (diff == SlicingRules.OPEN) ||
1043        ((diff == SlicingRules.OPENATEND && base == SlicingRules.CLOSED));
1044  }
1045
1046  private boolean isSlicedToOneOnly(ElementDefinition e) {
1047    return (e.hasSlicing() && e.hasMaxElement() && e.getMax().equals("1"));
1048  }
1049
1050  private ElementDefinitionSlicingComponent makeExtensionSlicing() {
1051        ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent();
1052        nextSliceId++;
1053        slice.setId(Integer.toString(nextSliceId));
1054    slice.addDiscriminator().setPath("url").setType(DiscriminatorType.VALUE);
1055    slice.setOrdered(false);
1056    slice.setRules(SlicingRules.OPEN);
1057    return slice;
1058  }
1059
1060  private boolean isExtension(ElementDefinition currentBase) {
1061    return currentBase.getPath().endsWith(".extension") || currentBase.getPath().endsWith(".modifierExtension");
1062  }
1063
1064  private boolean hasInnerDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, List<ElementDefinition> base) throws DefinitionException {
1065    for (int i = start; i <= end; i++) {
1066      String statedPath = context.getElement().get(i).getPath();
1067      if (statedPath.startsWith(path+".") && !statedPath.substring(path.length()+1).contains(".")) {
1068        boolean found = false;
1069        for (ElementDefinition ed : base) {
1070          String ep = ed.getPath();
1071          if (ep.equals(statedPath) || (ep.endsWith("[x]") && statedPath.length() > ep.length() - 2 && statedPath.substring(0, ep.length()-3).equals(ep.substring(0, ep.length()-3)) && !statedPath.substring(ep.length()).contains(".")))
1072            found = true;
1073        }
1074        if (!found)
1075          return true;
1076      }
1077    }
1078    return false;
1079  }
1080
1081  private List<ElementDefinition> getDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, String profileName, String url) throws DefinitionException {
1082    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
1083    for (int i = start; i <= end; i++) {
1084      String statedPath = context.getElement().get(i).getPath();
1085      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(".")))) {
1086        /* 
1087         * Commenting this out because it raises warnings when profiling inherited elements.  For example,
1088         * Error: unknown element 'Bundle.meta.profile' (or it is out of order) in profile ... (looking for 'Bundle.entry')
1089         * Not sure we have enough information here to do the check properly.  Might be better done when we're sorting the profile?
1090
1091        if (i != start && result.isEmpty() && !path.startsWith(context.getElement().get(start).getPath()))
1092          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));
1093
1094         */
1095        result.add(context.getElement().get(i));
1096      }
1097    }
1098    return result;
1099  }
1100
1101  private int findEndOfElement(StructureDefinitionDifferentialComponent context, int cursor) {
1102            int result = cursor;
1103            String path = context.getElement().get(cursor).getPath()+".";
1104            while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path))
1105              result++;
1106            return result;
1107          }
1108
1109  private int findEndOfElement(StructureDefinitionSnapshotComponent context, int cursor) {
1110            int result = cursor;
1111            String path = context.getElement().get(cursor).getPath()+".";
1112            while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path))
1113              result++;
1114            return result;
1115          }
1116
1117  private boolean unbounded(ElementDefinition definition) {
1118    StringType max = definition.getMaxElement();
1119    if (max == null)
1120      return false; // this is not valid
1121    if (max.getValue().equals("1"))
1122      return false;
1123    if (max.getValue().equals("0"))
1124      return false;
1125    return true;
1126  }
1127
1128  private void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, boolean trimDifferential, String purl) throws DefinitionException, FHIRException {
1129    source.setUserData(GENERATED_IN_SNAPSHOT, true);
1130    // we start with a clone of the base profile ('dest') and we copy from the profile ('source')
1131    // over the top for anything the source has
1132    ElementDefinition base = dest;
1133    ElementDefinition derived = source;
1134    derived.setUserData(DERIVATION_POINTER, base);
1135
1136    // Before applying changes, apply them to what's in the profile
1137    // TODO: follow Chris's rules
1138    StructureDefinition profile = source.getType().size() == 1 && source.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, source.getTypeFirstRep().getProfile()) : null;
1139    if (profile != null) {
1140      ElementDefinition e = profile.getSnapshot().getElement().get(0);
1141      base.setDefinition(e.getDefinition());
1142      base.setShort(e.getShort());
1143      if (e.hasCommentElement())
1144        base.setCommentElement(e.getCommentElement());
1145      if (e.hasRequirementsElement())
1146        base.setRequirementsElement(e.getRequirementsElement());
1147      base.getAlias().clear();
1148      base.getAlias().addAll(e.getAlias());
1149      base.getMapping().clear();
1150      base.getMapping().addAll(e.getMapping());
1151    }
1152    
1153    if (derived != null) {
1154      boolean isExtension = checkExtensionDoco(base);
1155
1156      if (derived.hasSliceName()) {
1157        base.setSliceName(derived.getSliceName());
1158      }
1159      
1160      if (derived.hasShortElement()) {
1161        if (!Base.compareDeep(derived.getShortElement(), base.getShortElement(), false))
1162          base.setShortElement(derived.getShortElement().copy());
1163        else if (trimDifferential)
1164          derived.setShortElement(null);
1165        else if (derived.hasShortElement())
1166          derived.getShortElement().setUserData(DERIVATION_EQUALS, true);
1167      }
1168
1169      if (derived.hasDefinitionElement()) {
1170        if (derived.getDefinition().startsWith("..."))
1171          base.setDefinition(base.getDefinition()+"\r\n"+derived.getDefinition().substring(3));
1172        else if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false))
1173          base.setDefinitionElement(derived.getDefinitionElement().copy());
1174        else if (trimDifferential)
1175          derived.setDefinitionElement(null);
1176        else if (derived.hasDefinitionElement())
1177          derived.getDefinitionElement().setUserData(DERIVATION_EQUALS, true);
1178      }
1179
1180      if (derived.hasCommentElement()) {
1181        if (derived.getComment().startsWith("..."))
1182          base.setComment(base.getComment()+"\r\n"+derived.getComment().substring(3));
1183        else if (derived.hasCommentElement()!= base.hasCommentElement() || !Base.compareDeep(derived.getCommentElement(), base.getCommentElement(), false))
1184          base.setCommentElement(derived.getCommentElement().copy());
1185        else if (trimDifferential)
1186          base.setCommentElement(derived.getCommentElement().copy());
1187        else if (derived.hasCommentElement())
1188          derived.getCommentElement().setUserData(DERIVATION_EQUALS, true);
1189      }
1190
1191      if (derived.hasLabelElement()) {
1192        if (derived.getLabel().startsWith("..."))
1193          base.setLabel(base.getLabel()+"\r\n"+derived.getLabel().substring(3));
1194        else if (!base.hasLabelElement() || !Base.compareDeep(derived.getLabelElement(), base.getLabelElement(), false))
1195          base.setLabelElement(derived.getLabelElement().copy());
1196        else if (trimDifferential)
1197          base.setLabelElement(derived.getLabelElement().copy());
1198        else if (derived.hasLabelElement())
1199          derived.getLabelElement().setUserData(DERIVATION_EQUALS, true);
1200      }
1201
1202      if (derived.hasRequirementsElement()) {
1203        if (derived.getRequirements().startsWith("..."))
1204          base.setRequirements(base.getRequirements()+"\r\n"+derived.getRequirements().substring(3));
1205        else if (!base.hasRequirementsElement() || !Base.compareDeep(derived.getRequirementsElement(), base.getRequirementsElement(), false))
1206          base.setRequirementsElement(derived.getRequirementsElement().copy());
1207        else if (trimDifferential)
1208          base.setRequirementsElement(derived.getRequirementsElement().copy());
1209        else if (derived.hasRequirementsElement())
1210          derived.getRequirementsElement().setUserData(DERIVATION_EQUALS, true);
1211      }
1212      // sdf-9
1213      if (derived.hasRequirements() && !base.getPath().contains("."))
1214        derived.setRequirements(null);
1215      if (base.hasRequirements() && !base.getPath().contains("."))
1216        base.setRequirements(null);
1217
1218      if (derived.hasAlias()) {
1219        if (!Base.compareDeep(derived.getAlias(), base.getAlias(), false))
1220          for (StringType s : derived.getAlias()) {
1221            if (!base.hasAlias(s.getValue()))
1222              base.getAlias().add(s.copy());
1223          }
1224        else if (trimDifferential)
1225          derived.getAlias().clear();
1226        else
1227          for (StringType t : derived.getAlias())
1228            t.setUserData(DERIVATION_EQUALS, true);
1229      }
1230
1231      if (derived.hasMinElement()) {
1232        if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) {
1233          if (derived.getMin() < base.getMin())
1234            messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Derived min  ("+Integer.toString(derived.getMin())+") cannot be less than base min ("+Integer.toString(base.getMin())+")", ValidationMessage.IssueSeverity.ERROR));
1235          base.setMinElement(derived.getMinElement().copy());
1236        } else if (trimDifferential)
1237          derived.setMinElement(null);
1238        else
1239          derived.getMinElement().setUserData(DERIVATION_EQUALS, true);
1240      }
1241
1242      if (derived.hasMaxElement()) {
1243        if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) {
1244          if (isLargerMax(derived.getMax(), base.getMax()))
1245            messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Derived max ("+derived.getMax()+") cannot be greater than base max ("+base.getMax()+")", ValidationMessage.IssueSeverity.ERROR));
1246          base.setMaxElement(derived.getMaxElement().copy());
1247        } else if (trimDifferential)
1248          derived.setMaxElement(null);
1249        else
1250          derived.getMaxElement().setUserData(DERIVATION_EQUALS, true);
1251      }
1252
1253      if (derived.hasFixed()) {
1254        if (!Base.compareDeep(derived.getFixed(), base.getFixed(), true)) {
1255          base.setFixed(derived.getFixed().copy());
1256        } else if (trimDifferential)
1257          derived.setFixed(null);
1258        else
1259          derived.getFixed().setUserData(DERIVATION_EQUALS, true);
1260      }
1261
1262      if (derived.hasPattern()) {
1263        if (!Base.compareDeep(derived.getPattern(), base.getPattern(), false)) {
1264          base.setPattern(derived.getPattern().copy());
1265        } else
1266          if (trimDifferential)
1267            derived.setPattern(null);
1268          else
1269            derived.getPattern().setUserData(DERIVATION_EQUALS, true);
1270      }
1271
1272      for (ElementDefinitionExampleComponent ex : derived.getExample()) {
1273        boolean found = false;
1274        for (ElementDefinitionExampleComponent exS : base.getExample())
1275          if (Base.compareDeep(ex, exS, false))
1276            found = true;
1277        if (!found)
1278          base.addExample(ex.copy());
1279        else if (trimDifferential)
1280          derived.getExample().remove(ex);
1281        else
1282          ex.setUserData(DERIVATION_EQUALS, true);
1283      }
1284
1285      if (derived.hasMaxLengthElement()) {
1286        if (!Base.compareDeep(derived.getMaxLengthElement(), base.getMaxLengthElement(), false))
1287          base.setMaxLengthElement(derived.getMaxLengthElement().copy());
1288        else if (trimDifferential)
1289          derived.setMaxLengthElement(null);
1290        else
1291          derived.getMaxLengthElement().setUserData(DERIVATION_EQUALS, true);
1292      }
1293
1294      // todo: what to do about conditions?
1295      // condition : id 0..*
1296
1297      if (derived.hasMustSupportElement()) {
1298        if (!(base.hasMustSupportElement() && Base.compareDeep(derived.getMustSupportElement(), base.getMustSupportElement(), false)))
1299          base.setMustSupportElement(derived.getMustSupportElement().copy());
1300        else if (trimDifferential)
1301          derived.setMustSupportElement(null);
1302        else
1303          derived.getMustSupportElement().setUserData(DERIVATION_EQUALS, true);
1304      }
1305
1306
1307      // profiles cannot change : isModifier, defaultValue, meaningWhenMissing
1308      // but extensions can change isModifier
1309      if (isExtension) {
1310        if (derived.hasIsModifierElement() && !(base.hasIsModifierElement() && Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false)))
1311          base.setIsModifierElement(derived.getIsModifierElement().copy());
1312        else if (trimDifferential)
1313          derived.setIsModifierElement(null);
1314        else if (derived.hasIsModifierElement())
1315          derived.getIsModifierElement().setUserData(DERIVATION_EQUALS, true);
1316      }
1317
1318      if (derived.hasBinding()) {
1319        if (!base.hasBinding() || !Base.compareDeep(derived.getBinding(), base.getBinding(), false)) {
1320          if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && derived.getBinding().getStrength() != BindingStrength.REQUIRED)
1321            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));
1322//            throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode());
1323          else if (base.hasBinding() && derived.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && base.getBinding().hasValueSetReference() && derived.getBinding().hasValueSetReference()) {
1324            ValueSetExpansionOutcome expBase = context.expandVS(context.fetchResource(ValueSet.class, base.getBinding().getValueSetReference().getReference()), true, false);
1325            ValueSetExpansionOutcome expDerived = context.expandVS(context.fetchResource(ValueSet.class, derived.getBinding().getValueSetReference().getReference()), true, false);
1326            if (expBase.getValueset() == null)
1327              messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSetReference().getReference()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING));
1328            else if (expDerived.getValueset() == null)
1329              messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSetReference().getReference()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING));
1330            else if (!isSubset(expBase.getValueset(), expDerived.getValueset()))
1331              messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSetReference().getReference()+" is not a subset of binding "+base.getBinding().getValueSetReference().getReference(), ValidationMessage.IssueSeverity.ERROR));
1332          }
1333          base.setBinding(derived.getBinding().copy());
1334        } else if (trimDifferential)
1335          derived.setBinding(null);
1336        else
1337          derived.getBinding().setUserData(DERIVATION_EQUALS, true);
1338      } // else if (base.hasBinding() && doesn't have bindable type )
1339        //  base
1340
1341      if (derived.hasIsSummaryElement()) {
1342        if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false)) {
1343          if (base.hasIsSummary())
1344            throw new Error("Error in profile "+pn+" at "+derived.getPath()+": Base isSummary = "+base.getIsSummaryElement().asStringValue()+", derived isSummary = "+derived.getIsSummaryElement().asStringValue());
1345          base.setIsSummaryElement(derived.getIsSummaryElement().copy());
1346        } else if (trimDifferential)
1347          derived.setIsSummaryElement(null);
1348        else
1349          derived.getIsSummaryElement().setUserData(DERIVATION_EQUALS, true);
1350      }
1351
1352      if (derived.hasType()) {
1353        if (!Base.compareDeep(derived.getType(), base.getType(), false)) {
1354          if (base.hasType()) {
1355            for (TypeRefComponent ts : derived.getType()) {
1356              boolean ok = false;
1357              CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1358              for (TypeRefComponent td : base.getType()) {;
1359                b.append(td.getCode());
1360                if (td.hasCode() && (td.getCode().equals(ts.getCode()) || td.getCode().equals("Extension") ||
1361                    td.getCode().equals("Element") || td.getCode().equals("*") ||
1362                    ((td.getCode().equals("Resource") || (td.getCode().equals("DomainResource")) && pkp.isResource(ts.getCode())))))
1363                  ok = true;
1364              }
1365              if (!ok)
1366                throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal constrained type "+ts.getCode()+" from "+b.toString());
1367            }
1368          }
1369          base.getType().clear();
1370          for (TypeRefComponent t : derived.getType()) {
1371            TypeRefComponent tt = t.copy();
1372//            tt.setUserData(DERIVATION_EQUALS, true);
1373            base.getType().add(tt);
1374          }
1375        }
1376        else if (trimDifferential)
1377          derived.getType().clear();
1378        else
1379          for (TypeRefComponent t : derived.getType())
1380            t.setUserData(DERIVATION_EQUALS, true);
1381      }
1382
1383      if (derived.hasMapping()) {
1384        // todo: mappings are not cumulative - one replaces another
1385        if (!Base.compareDeep(derived.getMapping(), base.getMapping(), false)) {
1386          for (ElementDefinitionMappingComponent s : derived.getMapping()) {
1387            boolean found = false;
1388            for (ElementDefinitionMappingComponent d : base.getMapping()) {
1389              found = found || (d.getIdentity().equals(s.getIdentity()) && d.getMap().equals(s.getMap()));
1390            }
1391            if (!found)
1392              base.getMapping().add(s);
1393          }
1394        }
1395        else if (trimDifferential)
1396          derived.getMapping().clear();
1397        else
1398          for (ElementDefinitionMappingComponent t : derived.getMapping())
1399            t.setUserData(DERIVATION_EQUALS, true);
1400      }
1401
1402      // todo: constraints are cumulative. there is no replacing
1403      for (ElementDefinitionConstraintComponent s : base.getConstraint()) { 
1404        s.setUserData(IS_DERIVED, true);
1405        if (!s.hasSource())
1406          s.setSource(base.getId());
1407      }
1408      if (derived.hasConstraint()) {
1409        for (ElementDefinitionConstraintComponent s : derived.getConstraint()) {
1410          ElementDefinitionConstraintComponent inv = s.copy();
1411          base.getConstraint().add(inv);
1412        }
1413      }
1414      
1415      // now, check that we still have a bindable type; if not, delete the binding - see task 8477
1416      if (dest.hasBinding() && !hasBindableType(dest))
1417        dest.setBinding(null);
1418        
1419      // finally, we copy any extensions from source to dest
1420      for (Extension ex : base.getExtension()) {
1421        StructureDefinition sd  = context.fetchResource(StructureDefinition.class, ex.getUrl());
1422        if (sd == null || sd.getSnapshot() == null || sd.getSnapshot().getElementFirstRep().getMax().equals("1"))
1423          ToolingExtensions.removeExtension(dest, ex.getUrl());
1424        dest.addExtension(ex);
1425      }
1426    }
1427  }
1428
1429  private boolean hasBindableType(ElementDefinition ed) {
1430    for (TypeRefComponent tr : ed.getType()) {
1431      if (Utilities.existsInList(tr.getCode(), "Coding", "CodeableConcept", "Quantity", "url", "string", "code"))
1432        return true;
1433    }
1434    return false;
1435  }
1436
1437
1438  private boolean isLargerMax(String derived, String base) {
1439    if ("*".equals(base))
1440      return false;
1441    if ("*".equals(derived))
1442      return true;
1443    return Integer.parseInt(derived) > Integer.parseInt(base);
1444  }
1445
1446
1447  private boolean isSubset(ValueSet expBase, ValueSet expDerived) {
1448    return codesInExpansion(expDerived.getExpansion().getContains(), expBase.getExpansion());
1449  }
1450
1451
1452  private boolean codesInExpansion(List<ValueSetExpansionContainsComponent> contains, ValueSetExpansionComponent expansion) {
1453    for (ValueSetExpansionContainsComponent cc : contains) {
1454      if (!inExpansion(cc, expansion.getContains()))
1455        return false;
1456      if (!codesInExpansion(cc.getContains(), expansion))
1457        return false;
1458    }
1459    return true;
1460  }
1461
1462
1463  private boolean inExpansion(ValueSetExpansionContainsComponent cc, List<ValueSetExpansionContainsComponent> contains) {
1464    for (ValueSetExpansionContainsComponent cc1 : contains) {
1465      if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode()))
1466        return true;
1467      if (inExpansion(cc,  cc1.getContains()))
1468        return true;
1469    }
1470    return false;
1471  }
1472
1473  public void closeDifferential(StructureDefinition base, StructureDefinition derived) throws FHIRException {
1474    for (ElementDefinition edb : base.getSnapshot().getElement()) {
1475      if (isImmediateChild(edb) && !edb.getPath().endsWith(".id")) {
1476        ElementDefinition edm = getMatchInDerived(edb, derived.getDifferential().getElement());
1477        if (edm == null) {
1478          ElementDefinition edd = derived.getDifferential().addElement();
1479          edd.setPath(edb.getPath());
1480          edd.setMax("0");
1481        } else if (edb.hasSlicing()) {
1482          closeChildren(base, edb, derived, edm);
1483        }
1484      }
1485    }
1486    sortDifferential(base, derived, derived.getName(), new ArrayList<String>());
1487  }
1488
1489  private void closeChildren(StructureDefinition base, ElementDefinition edb, StructureDefinition derived, ElementDefinition edm) {
1490    String path = edb.getPath()+".";
1491    int baseStart = base.getSnapshot().getElement().indexOf(edb);
1492    int baseEnd = findEnd(base.getSnapshot().getElement(), edb, baseStart+1);
1493    int diffStart = derived.getDifferential().getElement().indexOf(edm);
1494    int diffEnd = findEnd(derived.getDifferential().getElement(), edm, diffStart+1);
1495    
1496    for (int cBase = baseStart; cBase < baseEnd; cBase++) {
1497      ElementDefinition edBase = base.getSnapshot().getElement().get(cBase);
1498      if (isImmediateChild(edBase, edb)) {
1499        ElementDefinition edMatch = getMatchInDerived(edBase, derived.getDifferential().getElement(), diffStart, diffEnd);
1500        if (edMatch == null) {
1501          ElementDefinition edd = derived.getDifferential().addElement();
1502          edd.setPath(edBase.getPath());
1503          edd.setMax("0");
1504        } else {
1505          closeChildren(base, edBase, derived, edMatch);
1506        }        
1507      }
1508    }
1509  }
1510
1511
1512
1513
1514  private int findEnd(List<ElementDefinition> list, ElementDefinition ed, int cursor) {
1515    String path = ed.getPath()+".";
1516    while (cursor < list.size() && list.get(cursor).getPath().startsWith(path))
1517      cursor++;
1518    return cursor;
1519  }
1520
1521
1522  private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list) {
1523    for (ElementDefinition t : list)
1524      if (t.getPath().equals(ed.getPath()))
1525        return t;
1526    return null;
1527  }
1528
1529  private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list, int start, int end) {
1530    for (int i = start; i < end; i++) {
1531      ElementDefinition t = list.get(i);
1532      if (t.getPath().equals(ed.getPath()))
1533        return t;
1534    }
1535    return null;
1536  }
1537
1538
1539  private boolean isImmediateChild(ElementDefinition ed) {
1540    String p = ed.getPath();
1541    if (!p.contains("."))
1542      return false;
1543    p = p.substring(p.indexOf(".")+1);
1544    return !p.contains(".");
1545  }
1546
1547  private boolean isImmediateChild(ElementDefinition candidate, ElementDefinition base) {
1548    String p = candidate.getPath();
1549    if (!p.contains("."))
1550      return false;
1551    if (!p.startsWith(base.getPath()+"."))
1552      return false;
1553    p = p.substring(base.getPath().length()+1);
1554    return !p.contains(".");
1555  }
1556
1557
1558  private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) {
1559    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
1560    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
1561      if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url"))
1562        return ed.getSnapshot().getElement().get(i);
1563      i++;
1564    }
1565    return null;
1566  }
1567
1568  private ElementDefinition getValueFor(StructureDefinition ed, ElementDefinition c) {
1569    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
1570    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
1571      if (ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".value"))
1572        return ed.getSnapshot().getElement().get(i);
1573      i++;
1574    }
1575    return null;
1576  }
1577
1578
1579  private static final int AGG_NONE = 0;
1580  private static final int AGG_IND = 1;
1581  private static final int AGG_GR = 2;
1582  private Cell genTypes(HierarchicalTableGenerator gen, Row r, ElementDefinition e, String profileBaseFileName, StructureDefinition profile, String corePath, String imagePath) {
1583    Cell c = gen.new Cell();
1584    r.getCells().add(c);
1585    List<TypeRefComponent> types = e.getType();
1586    if (!e.hasType()) {
1587      if (e.hasContentReference()) {
1588        return c;
1589      } else {
1590      ElementDefinition d = (ElementDefinition) e.getUserData(DERIVATION_POINTER);
1591      if (d != null && d.hasType()) {
1592        types = new ArrayList<ElementDefinition.TypeRefComponent>();
1593        for (TypeRefComponent tr : d.getType()) {
1594          TypeRefComponent tt = tr.copy();
1595          tt.setUserData(DERIVATION_EQUALS, true);
1596          types.add(tt);
1597        }
1598      } else
1599        return c;
1600    }
1601    }
1602
1603    boolean first = true;
1604    Element source = types.get(0); // either all types are the same, or we don't consider any of them the same
1605    int aggMode = AGG_NONE;
1606
1607    boolean allReference = !types.isEmpty();
1608    Set<AggregationMode> aggs = new HashSet<ElementDefinition.AggregationMode>();
1609    for (TypeRefComponent t : types) {
1610      if (t.getCode()!=null && t.getCode().equals("Reference") && t.hasProfile()) {
1611        for (Enumeration<AggregationMode> en : t.getAggregation())
1612          aggs.add(en.getValue());
1613      } else
1614        allReference = false;
1615      
1616    }
1617    if (allReference) {
1618      if (aggs.size() > 0) {
1619        boolean allSame = true;
1620        for (TypeRefComponent t : types) {
1621          for (AggregationMode agg : aggs) {
1622            boolean found = false;
1623            for (Enumeration<AggregationMode> en : t.getAggregation())
1624              if (en.getValue() == agg)
1625                found = true;
1626            if (!found)
1627              allSame = false;
1628          }
1629        }
1630        aggMode = allSame ? AGG_GR : AGG_IND;
1631        if (aggMode != AGG_GR)
1632          allReference = false;
1633      }
1634    } else 
1635      aggMode = aggs.size() == 0 ? AGG_NONE : AGG_IND;
1636
1637    if (allReference) {
1638      c.getPieces().add(gen.new Piece(corePath+"references.html", "Reference", null));
1639      c.getPieces().add(gen.new Piece(null, "(", null));
1640    }
1641    TypeRefComponent tl = null;
1642    for (TypeRefComponent t : types) {
1643      if (first)
1644        first = false;
1645      else if (allReference)
1646        c.addPiece(checkForNoChange(tl, gen.new Piece(null," | ", null)));
1647      else
1648        c.addPiece(checkForNoChange(tl, gen.new Piece(null,", ", null)));
1649      tl = t;
1650      if (t.getCode()!= null && t.getCode().equals("Reference")) {
1651        if (!allReference) {
1652          c.getPieces().add(gen.new Piece(corePath+"references.html", "Reference", null));
1653          c.getPieces().add(gen.new Piece(null, "(", null));
1654        }
1655        if (t.hasTargetProfile() && t.getTargetProfile().startsWith("http://hl7.org/fhir/StructureDefinition/")) {
1656          StructureDefinition sd = context.fetchResource(StructureDefinition.class, t.getTargetProfile());
1657          if (sd != null) {
1658            String disp = sd.hasTitle() ? sd.getTitle() : sd.getName();
1659            c.addPiece(checkForNoChange(t, gen.new Piece(checkPrepend(corePath, sd.getUserString("path")), disp, null)));
1660          } else {
1661            String rn = t.getTargetProfile().substring(40);
1662            c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, rn), rn, null)));
1663          }
1664        } else if (t.hasTargetProfile() && Utilities.isAbsoluteUrl(t.getTargetProfile())) {
1665          StructureDefinition sd = context.fetchResource(StructureDefinition.class, t.getTargetProfile());
1666          if (sd != null) {
1667            String disp = sd.hasTitle() ? sd.getTitle() : sd.getName();
1668            String ref = pkp.getLinkForProfile(null, sd.getUrl());
1669            if (ref.contains("|"))
1670              ref = ref.substring(0,  ref.indexOf("|"));
1671            c.addPiece(checkForNoChange(t, gen.new Piece(ref, disp, null)));
1672          } else
1673            c.addPiece(checkForNoChange(t, gen.new Piece(null, t.getTargetProfile(), null)));
1674        } else if (t.hasTargetProfile() && t.getTargetProfile().startsWith("#"))
1675          c.addPiece(checkForNoChange(t, gen.new Piece(corePath+profileBaseFileName+"."+t.getTargetProfile().substring(1).toLowerCase()+".html", t.getTargetProfile(), null)));
1676        else if (t.hasTargetProfile())
1677          c.addPiece(checkForNoChange(t, gen.new Piece(corePath+t.getTargetProfile(), t.getTargetProfile(), null)));
1678        if (!allReference) {
1679          c.getPieces().add(gen.new Piece(null, ")", null));
1680          if (t.getAggregation().size() > 0) {
1681            c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", " {", null));
1682            boolean firstA = true;
1683            for (Enumeration<AggregationMode> a : t.getAggregation()) {
1684              if (firstA = true)
1685                firstA = false;
1686              else
1687                c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", ", ", null));
1688              c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", codeForAggregation(a.getValue()), null));
1689            }
1690            c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", "}", null));
1691          }
1692        }
1693      } else if (t.hasProfile()) { // a profiled type
1694        String ref;
1695        ref = pkp.getLinkForProfile(profile, t.getProfile());
1696        if (ref != null) {
1697          String[] parts = ref.split("\\|");
1698          if (parts[0].startsWith("http:") || parts[0].startsWith("https:"))
1699            c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], parts[1], t.getCode())));
1700          else
1701            c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().startsWith(corePath)? corePath: "")+parts[0], parts[1], t.getCode())));
1702        } else
1703          c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().startsWith(corePath)? corePath: "")+ref, t.getCode(), null)));
1704      } else if (pkp.hasLinkFor(t.getCode())) {
1705        c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, t.getCode()), t.getCode(), null)));
1706      } else
1707        c.addPiece(checkForNoChange(t, gen.new Piece(null, t.getCode(), null)));
1708    }
1709    if (allReference) {
1710      c.getPieces().add(gen.new Piece(null, ")", null));
1711      if (aggs.size() > 0) {
1712        c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", " {", null));
1713        boolean firstA = true;
1714        for (AggregationMode a : aggs) {
1715          if (firstA = true)
1716            firstA = false;
1717          else
1718            c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", ", ", null));
1719          c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", codeForAggregation(a), null));
1720        }
1721        c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", "}", null));
1722      }
1723    }
1724    return c;
1725  }
1726
1727  private String codeForAggregation(AggregationMode a) {
1728    switch (a) {
1729    case BUNDLED : return "b";
1730    case CONTAINED : return "c";
1731    case REFERENCED: return "r";
1732    }
1733    return "?";
1734  }
1735
1736
1737  private String checkPrepend(String corePath, String path) {
1738    if (pkp.prependLinks() && !(path.startsWith("http:") || path.startsWith("https:")))
1739      return corePath+path;
1740    else 
1741      return path;
1742  }
1743
1744
1745  private ElementDefinition getElementByName(List<ElementDefinition> elements, String contentReference) {
1746    for (ElementDefinition ed : elements)
1747      if (ed.hasSliceName() && ("#"+ed.getSliceName()).equals(contentReference))
1748        return ed;
1749    return null;
1750  }
1751
1752  private ElementDefinition getElementById(List<ElementDefinition> elements, String contentReference) {
1753    for (ElementDefinition ed : elements)
1754      if (ed.hasId() && ("#"+ed.getId()).equals(contentReference))
1755        return ed;
1756    return null;
1757  }
1758
1759
1760  public static String describeExtensionContext(StructureDefinition ext) {
1761    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1762    for (StringType t : ext.getContext())
1763      b.append(t.getValue());
1764    if (!ext.hasContextType())
1765      throw new Error("no context type on "+ext.getUrl());
1766    switch (ext.getContextType()) {
1767    case DATATYPE: return "Use on data type: "+b.toString();
1768    case EXTENSION: return "Use on extension: "+b.toString();
1769    case RESOURCE: return "Use on element: "+b.toString();
1770    default:
1771      return "??";
1772    }
1773  }
1774
1775  private String describeCardinality(ElementDefinition definition, ElementDefinition fallback, UnusedTracker tracker) {
1776    IntegerType min = definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
1777    StringType max = definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
1778    if (min.isEmpty() && fallback != null)
1779      min = fallback.getMinElement();
1780    if (max.isEmpty() && fallback != null)
1781      max = fallback.getMaxElement();
1782
1783    tracker.used = !max.isEmpty() && !max.getValue().equals("0");
1784
1785    if (min.isEmpty() && max.isEmpty())
1786      return null;
1787    else
1788      return (!min.hasValue() ? "" : Integer.toString(min.getValue())) + ".." + (!max.hasValue() ? "" : max.getValue());
1789  }
1790
1791  private void genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef, UnusedTracker tracker, ElementDefinition fallback) {
1792    IntegerType min = !hasDef ? new IntegerType() : definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
1793    StringType max = !hasDef ? new StringType() : definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
1794    if (min.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) {
1795      ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER);
1796      if (base.hasMinElement()) {
1797        min = base.getMinElement().copy();
1798        min.setUserData(DERIVATION_EQUALS, true);
1799      }
1800    }
1801    if (max.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) {
1802      ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER);
1803      if (base.hasMaxElement()) {
1804        max = base.getMaxElement().copy();
1805        max.setUserData(DERIVATION_EQUALS, true);
1806      }
1807    }
1808    if (min.isEmpty() && fallback != null)
1809      min = fallback.getMinElement();
1810    if (max.isEmpty() && fallback != null)
1811      max = fallback.getMaxElement();
1812
1813    if (!max.isEmpty())
1814      tracker.used = !max.getValue().equals("0");
1815
1816    Cell cell = gen.new Cell(null, null, null, null, null);
1817    row.getCells().add(cell);
1818    if (!min.isEmpty() || !max.isEmpty()) {
1819      cell.addPiece(checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), null)));
1820      cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", null)));
1821      cell.addPiece(checkForNoChange(min, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), null)));
1822    }
1823  }
1824
1825
1826  private Piece checkForNoChange(Element source, Piece piece) {
1827    if (source.hasUserData(DERIVATION_EQUALS)) {
1828      piece.addStyle("opacity: 0.4");
1829    }
1830    return piece;
1831  }
1832
1833  private Piece checkForNoChange(Element src1, Element src2, Piece piece) {
1834    if (src1.hasUserData(DERIVATION_EQUALS) && src2.hasUserData(DERIVATION_EQUALS)) {
1835      piece.addStyle("opacity: 0.5");
1836    }
1837    return piece;
1838  }
1839
1840
1841
1842  private boolean usesMustSupport(List<ElementDefinition> list) {
1843    for (ElementDefinition ed : list)
1844      if (ed.hasMustSupport() && ed.getMustSupport())
1845        return true;
1846    return false;
1847  }
1848
1849
1850  private void 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) throws IOException {
1851    StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1);
1852    String s = tail(element.getPath());
1853    List<ElementDefinition> children = getChildren(all, element);
1854    boolean isExtension = (s.equals("extension") || s.equals("modifierExtension"));
1855    if (!snapshot && isExtension && extensions != null && extensions != isExtension)
1856      return;
1857
1858    if (!onlyInformationIsMapping(all, element)) {
1859      Row row = gen.new Row();
1860      row.setAnchor(element.getPath());
1861      row.setColor(getRowColor(element, isConstraintMode));
1862      boolean hasDef = element != null;
1863      boolean ext = false;
1864      if (s.equals("extension")) {
1865        if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile()))
1866          row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
1867        else
1868          row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
1869        ext = true;
1870      } else if (s.equals("modifierExtension")) {
1871        if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile()))
1872          row.setIcon("icon_modifier_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
1873        else
1874          row.setIcon("icon_modifier_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
1875      } else if (!hasDef || element.getType().size() == 0)
1876        row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
1877      else if (hasDef && element.getType().size() > 1) {
1878        if (allTypesAre(element.getType(), "Reference"))
1879          row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
1880        else
1881          row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE);
1882      } else if (hasDef && element.getType().get(0).getCode() != null && element.getType().get(0).getCode().startsWith("@"))
1883        row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE);
1884      else if (hasDef && isPrimitive(element.getType().get(0).getCode()))
1885        row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
1886      else if (hasDef && isReference(element.getType().get(0).getCode()))
1887        row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
1888      else if (hasDef && isDataType(element.getType().get(0).getCode()))
1889        row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
1890      else
1891        row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
1892      String ref = defPath == null ? null : defPath + element.getId();
1893      UnusedTracker used = new UnusedTracker();
1894      used.used = true;
1895      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);
1896      row.getCells().add(left);
1897      Cell gc = gen.new Cell();
1898      row.getCells().add(gc);
1899      if (element != null && element.getIsModifier())
1900        checkForNoChange(element.getIsModifierElement(), gc.addImage(imagePath+"modifier.png", translate("sd.table", "This element is a modifier element"), "?!", null, null));
1901      if (element != null && element.getMustSupport())
1902        checkForNoChange(element.getMustSupportElement(), gc.addImage(imagePath+"mustsupport.png", translate("sd.table", "This element must be supported"), "S", "white", "red"));
1903      if (element != null && element.getIsSummary())
1904        checkForNoChange(element.getIsSummaryElement(), gc.addImage(imagePath+"summary.png", translate("sd.table", "This element is included in summaries"), "Σ", null, null));
1905      if (element != null && (!element.getConstraint().isEmpty() || !element.getCondition().isEmpty()))
1906        gc.addImage(imagePath+"lock.png", translate("sd.table", "This element has or is affected by some invariants"), "I", null, null);
1907
1908      ExtensionContext extDefn = null;
1909      if (ext) {
1910        if (element != null && element.getType().size() == 1 && element.getType().get(0).hasProfile()) {
1911        extDefn = locateExtension(StructureDefinition.class, element.getType().get(0).getProfile());
1912          if (extDefn == null) {
1913            genCardinality(gen, element, row, hasDef, used, null);
1914            row.getCells().add(gen.new Cell(null, null, "?? "+element.getType().get(0).getProfile(), null, null));
1915            generateDescription(gen, row, element, null, used.used, profile.getUrl(), element.getType().get(0).getProfile(), profile, corePath, imagePath, root, logicalModel, allInvariants);
1916          } else {
1917            String name = urltail(element.getType().get(0).getProfile());
1918            left.getPieces().get(0).setText(name);
1919            // left.getPieces().get(0).setReference((String) extDefn.getExtensionStructure().getTag("filename"));
1920            left.getPieces().get(0).setHint(translate("sd.table", "Extension URL")+" = "+extDefn.getUrl());
1921            genCardinality(gen, element, row, hasDef, used, extDefn.getElement());
1922            ElementDefinition valueDefn = extDefn.getExtensionValueDefinition();
1923            if (valueDefn != null && !"0".equals(valueDefn.getMax()))
1924               genTypes(gen, row, valueDefn, profileBaseFileName, profile, corePath, imagePath);
1925             else // if it's complex, we just call it nothing
1926                // genTypes(gen, row, extDefn.getSnapshot().getElement().get(0), profileBaseFileName, profile);
1927              row.getCells().add(gen.new Cell(null, null, "("+translate("sd.table", "Complex")+")", null, null));
1928            generateDescription(gen, row, element, extDefn.getElement(), used.used, null, extDefn.getUrl(), profile, corePath, imagePath, root, logicalModel, allInvariants, valueDefn);
1929          }
1930        } else {
1931          genCardinality(gen, element, row, hasDef, used, null);
1932          if ("0".equals(element.getMax()))
1933            row.getCells().add(gen.new Cell());            
1934          else
1935            genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath);
1936          generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants);
1937        }
1938      } else {
1939        genCardinality(gen, element, row, hasDef, used, null);
1940        if (hasDef && !"0".equals(element.getMax()))
1941          genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath);
1942        else
1943          row.getCells().add(gen.new Cell());
1944        generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants);
1945      }
1946      if (element.hasSlicing()) {
1947        if (standardExtensionSlicing(element)) {
1948          used.used = element.hasType() && element.getType().get(0).hasProfile();
1949          showMissing = false;
1950        } else {
1951          row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE);
1952          row.getCells().get(2).getPieces().clear();
1953          for (Cell cell : row.getCells())
1954            for (Piece p : cell.getPieces()) {
1955              p.addStyle("font-style: italic");
1956            }
1957        }
1958      }
1959      if (used.used || showMissing)
1960        rows.add(row);
1961      if (!used.used && !element.hasSlicing()) {
1962        for (Cell cell : row.getCells())
1963          for (Piece p : cell.getPieces()) {
1964            p.setStyle("text-decoration:line-through");
1965            p.setReference(null);
1966          }
1967      } else{
1968        for (ElementDefinition child : children)
1969          if (logicalModel || !child.getPath().endsWith(".id") || (child.getPath().endsWith(".id") && (profile != null) && (profile.getDerivation() == TypeDerivationRule.CONSTRAINT)))  
1970            genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, snapshot, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants);
1971        if (!snapshot && (extensions == null || !extensions))
1972          for (ElementDefinition child : children)
1973            if (child.getPath().endsWith(".extension") || child.getPath().endsWith(".modifierExtension"))
1974              genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, true, false, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants);
1975      }
1976    }
1977  }
1978
1979
1980  private ExtensionContext locateExtension(Class<StructureDefinition> class1, String value)  {
1981    if (value.contains("#")) {
1982      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
1983      if (ext == null)
1984        return null;
1985      String tail = value.substring(value.indexOf("#")+1);
1986      ElementDefinition ed = null;
1987      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
1988        if (tail.equals(ted.getSliceName())) {
1989          ed = ted;
1990          return new ExtensionContext(ext, ed);
1991        }
1992      }
1993      return null;
1994    } else {
1995      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value);
1996      if (ext == null)
1997        return null;
1998      else 
1999        return new ExtensionContext(ext, ext.getSnapshot().getElement().get(0));
2000    }
2001  }
2002
2003
2004  private boolean extensionIsComplex(String value) {
2005    if (value.contains("#")) {
2006      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
2007    if (ext == null)
2008      return false;
2009      String tail = value.substring(value.indexOf("#")+1);
2010      ElementDefinition ed = null;
2011      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
2012        if (tail.equals(ted.getSliceName())) {
2013          ed = ted;
2014          break;
2015        }
2016      }
2017      if (ed == null)
2018        return false;
2019      int i = ext.getSnapshot().getElement().indexOf(ed);
2020      int j = i+1;
2021      while (j < ext.getSnapshot().getElement().size() && !ext.getSnapshot().getElement().get(j).getPath().equals(ed.getPath()))
2022        j++;
2023      return j - i > 5;
2024    } else {
2025      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value);
2026      return ext != null && ext.getSnapshot().getElement().size() > 5;
2027    }
2028  }
2029
2030
2031  private String getRowColor(ElementDefinition element, boolean isConstraintMode) {
2032    switch (element.getUserInt(UD_ERROR_STATUS)) {
2033    case STATUS_HINT: return ROW_COLOR_HINT;
2034    case STATUS_WARNING: return ROW_COLOR_WARNING;
2035    case STATUS_ERROR: return ROW_COLOR_ERROR;
2036    case STATUS_FATAL: return ROW_COLOR_FATAL;
2037    }
2038    if (isConstraintMode && !element.getMustSupport() && !element.getIsModifier() && element.getPath().contains("."))
2039      return null; // ROW_COLOR_NOT_MUST_SUPPORT;
2040    else
2041      return null;
2042  }
2043
2044
2045  private String urltail(String path) {
2046    if (path.contains("#"))
2047      return path.substring(path.lastIndexOf('#')+1);
2048    if (path.contains("/"))
2049      return path.substring(path.lastIndexOf('/')+1);
2050    else
2051      return path;
2052
2053  }
2054
2055  private boolean standardExtensionSlicing(ElementDefinition element) {
2056    String t = tail(element.getPath());
2057    return (t.equals("extension") || t.equals("modifierExtension"))
2058          && 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);
2059  }
2060
2061  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) throws IOException {
2062    return generateDescription(gen, row, definition, fallback, used, baseURL, url, profile, corePath, imagePath, root, logicalModel, allInvariants, null);
2063  }
2064  
2065  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) throws IOException {
2066    Cell c = gen.new Cell();
2067    row.getCells().add(c);
2068
2069    if (used) {
2070      if (logicalModel && ToolingExtensions.hasExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) {
2071        if (root) {
2072          c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold"));
2073          c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null));        
2074        } else if (!root && ToolingExtensions.hasExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace") && 
2075            !ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace").equals(ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))) {
2076          c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold"));
2077          c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null));        
2078        }
2079      }
2080      
2081      if (definition.hasContentReference()) {
2082        ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference());
2083        if (ed == null)
2084          c.getPieces().add(gen.new Piece(null, translate("sd.table", "Unknown reference to %s", definition.getContentReference()), null));
2085        else
2086          c.getPieces().add(gen.new Piece("#"+ed.getPath(), translate("sd.table", "See %s", ed.getPath()), null));
2087      }
2088      if (definition.getPath().endsWith("url") && definition.hasFixed()) {
2089        c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen")));
2090      } else {
2091        if (definition != null && definition.hasShort()) {
2092          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2093          c.addPiece(checkForNoChange(definition.getShortElement(), gen.new Piece(null, gt(definition.getShortElement()), null)));
2094        } else if (fallback != null && fallback != null && fallback.hasShort()) {
2095          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2096          c.addPiece(checkForNoChange(fallback.getShortElement(), gen.new Piece(null, gt(fallback.getShortElement()), null)));
2097        }
2098        if (url != null) {
2099          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2100          String fullUrl = url.startsWith("#") ? baseURL+url : url;
2101          StructureDefinition ed = context.fetchResource(StructureDefinition.class, url);
2102          String ref = null;
2103          if (ed != null) {
2104            String p = ed.getUserString("path");
2105            if (p != null) {
2106              ref = p.startsWith("http:") || igmode ? p : Utilities.pathReverse(corePath, p);
2107            }
2108          }
2109          c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold"));
2110          c.getPieces().add(gen.new Piece(ref, fullUrl, null));
2111        }
2112
2113        if (definition.hasSlicing()) {
2114          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2115          c.getPieces().add(gen.new Piece(null, translate("sd.table", "Slice")+": ", null).addStyle("font-weight:bold"));
2116          c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null));
2117        }
2118        if (definition != null) {
2119          ElementDefinitionBindingComponent binding = null;
2120          if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty())
2121            binding = valueDefn.getBinding();
2122          else if (definition.hasBinding())
2123            binding = definition.getBinding();
2124          if (binding!=null && !binding.isEmpty()) {
2125            if (!c.getPieces().isEmpty()) 
2126              c.addPiece(gen.new Piece("br"));
2127            BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath());
2128            c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold")));
2129            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)));
2130            if (binding.hasStrength()) {
2131              c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null)));
2132              c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), egt(binding.getStrengthElement()), binding.getStrength().getDefinition())));              
2133              c.getPieces().add(gen.new Piece(null, ")", null));
2134            }
2135          }
2136          for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
2137            if (!inv.hasSource() || allInvariants) {
2138              if (!c.getPieces().isEmpty()) 
2139                c.addPiece(gen.new Piece("br"));
2140              c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold")));
2141              c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, gt(inv.getHumanElement()), null)));
2142            }
2143          }
2144          if ((definition.hasBase() && definition.getBase().getMax().equals("*")) || (definition.hasMax() && definition.getMax().equals("*"))) {
2145            if (c.getPieces().size() > 0)
2146              c.addPiece(gen.new Piece("br"));
2147            if (definition.hasOrderMeaning()) {
2148              c.getPieces().add(gen.new Piece(null, "This repeating element order: "+definition.getOrderMeaning(), null));
2149            } else {
2150              // don't show this, this it's important: c.getPieces().add(gen.new Piece(null, "This repeating element has no defined order", null));
2151            }           
2152          }
2153
2154          if (definition.hasFixed()) {
2155            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2156            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, translate("sd.table", "Fixed Value")+": ", null).addStyle("font-weight:bold")));
2157            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, buildJson(definition.getFixed()), null).addStyle("color: darkgreen")));
2158            if (isCoded(definition.getFixed()) && !hasDescription(definition.getFixed())) {
2159              Piece p = describeCoded(gen, definition.getFixed());
2160              if (p != null)
2161                c.getPieces().add(p);
2162            }
2163          } else if (definition.hasPattern()) {
2164            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2165            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, translate("sd.table", "Required Pattern")+": ", null).addStyle("font-weight:bold")));
2166            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen")));
2167          } else if (definition.hasExample()) {
2168            for (ElementDefinitionExampleComponent ex : definition.getExample()) {
2169              if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2170              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, translate("sd.table", "Example")+("".equals("General")? "" : " "+ex.getLabel()+"'")+": ", null).addStyle("font-weight:bold")));
2171              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen")));
2172            }
2173          }
2174          if (definition.hasMaxLength() && definition.getMaxLength()!=0) {
2175            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2176            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold")));
2177            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen")));
2178          }
2179          if (profile != null) {
2180            for (StructureDefinitionMappingComponent md : profile.getMapping()) {
2181              if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) {
2182                ElementDefinitionMappingComponent map = null;
2183                for (ElementDefinitionMappingComponent m : definition.getMapping()) 
2184                  if (m.getIdentity().equals(md.getIdentity()))
2185                    map = m;
2186                if (map != null) {
2187                  for (int i = 0; i<definition.getMapping().size(); i++){
2188                    c.addPiece(gen.new Piece("br"));
2189                    c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null));
2190                  }
2191                }
2192              }
2193            }
2194          }
2195        }
2196      }
2197    }
2198    return c;
2199  }
2200
2201  private Piece describeCoded(HierarchicalTableGenerator gen, Type fixed) {
2202    if (fixed instanceof Coding) {
2203      Coding c = (Coding) fixed;
2204      ValidationResult vr = context.validateCode(c.getSystem(), c.getCode(), c.getDisplay());
2205      if (vr.getDisplay() != null)
2206        return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen");
2207    } else if (fixed instanceof CodeableConcept) {
2208      CodeableConcept cc = (CodeableConcept) fixed;
2209      for (Coding c : cc.getCoding()) {
2210        ValidationResult vr = context.validateCode(c.getSystem(), c.getCode(), c.getDisplay());
2211        if (vr.getDisplay() != null)
2212          return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen");
2213      }
2214    }
2215    return null;
2216  }
2217
2218
2219  private boolean hasDescription(Type fixed) {
2220    if (fixed instanceof Coding) {
2221      return ((Coding) fixed).hasDisplay();
2222    } else if (fixed instanceof CodeableConcept) {
2223      CodeableConcept cc = (CodeableConcept) fixed;
2224      if (cc.hasText())
2225        return true;
2226      for (Coding c : cc.getCoding())
2227        if (c.hasDisplay())
2228         return true;
2229    } // (fixed instanceof CodeType) || (fixed instanceof Quantity);
2230    return false;
2231  }
2232
2233
2234  private boolean isCoded(Type fixed) {
2235    return (fixed instanceof Coding) || (fixed instanceof CodeableConcept) || (fixed instanceof CodeType) || (fixed instanceof Quantity);
2236  }
2237
2238
2239  /*
2240  private List<Piece> markdownToPieces(String markdown) throws FHIRException {
2241    String htmlString = Processor.process(markdown);
2242    XhtmlParser parser = new XhtmlParser();
2243    try {
2244      XhtmlNode node = parser.parseFragment(htmlString);
2245      return htmlToPieces(node);
2246    } catch (IOException e) {
2247    }
2248    return null;
2249  }
2250
2251  private List<Piece> htmlToPieces(XhtmlNode n) {
2252    
2253  }*/
2254
2255  private String buildJson(Type value) throws IOException {
2256    if (value instanceof PrimitiveType)
2257      return ((PrimitiveType) value).asStringValue();
2258
2259    IParser json = context.newJsonParser();
2260    return json.composeString(value, null);
2261  }
2262
2263
2264  public String describeSlice(ElementDefinitionSlicingComponent slicing) {
2265    return translate("sd.table", "%s, %s by %s", slicing.getOrdered() ? translate("sd.table", "Ordered") : translate("sd.table", "Unordered"), describe(slicing.getRules()), commas(slicing.getDiscriminator()));
2266  }
2267
2268  private String commas(List<ElementDefinitionSlicingDiscriminatorComponent> list) {
2269    CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder();
2270    for (ElementDefinitionSlicingDiscriminatorComponent id : list)
2271      c.append(id.getType().toCode()+":"+id.getPath());
2272    return c.toString();
2273  }
2274
2275
2276  private String describe(SlicingRules rules) {
2277    if (rules == null)
2278      return translate("sd.table", "Unspecified");
2279    switch (rules) {
2280    case CLOSED : return translate("sd.table", "Closed");
2281    case OPEN : return translate("sd.table", "Open");
2282    case OPENATEND : return translate("sd.table", "Open At End");
2283    default:
2284      return "??";
2285    }
2286  }
2287
2288  private boolean onlyInformationIsMapping(List<ElementDefinition> list, ElementDefinition e) {
2289    return (!e.hasSliceName() && !e.hasSlicing() && (onlyInformationIsMapping(e))) &&
2290        getChildren(list, e).isEmpty();
2291  }
2292
2293  private boolean onlyInformationIsMapping(ElementDefinition d) {
2294    return !d.hasShort() && !d.hasDefinition() &&
2295        !d.hasRequirements() && !d.getAlias().isEmpty() && !d.hasMinElement() &&
2296        !d.hasMax() && !d.getType().isEmpty() && !d.hasContentReference() &&
2297        !d.hasExample() && !d.hasFixed() && !d.hasMaxLengthElement() &&
2298        !d.getCondition().isEmpty() && !d.getConstraint().isEmpty() && !d.hasMustSupportElement() &&
2299        !d.hasBinding();
2300  }
2301
2302  private boolean allTypesAre(List<TypeRefComponent> types, String name) {
2303    for (TypeRefComponent t : types) {
2304      if (!t.getCode().equals(name))
2305        return false;
2306    }
2307    return true;
2308  }
2309
2310  private List<ElementDefinition> getChildren(List<ElementDefinition> all, ElementDefinition element) {
2311    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
2312    int i = all.indexOf(element)+1;
2313    while (i < all.size() && all.get(i).getPath().length() > element.getPath().length()) {
2314      if ((all.get(i).getPath().substring(0, element.getPath().length()+1).equals(element.getPath()+".")) && !all.get(i).getPath().substring(element.getPath().length()+1).contains("."))
2315        result.add(all.get(i));
2316      i++;
2317    }
2318    return result;
2319  }
2320
2321  private String tail(String path) {
2322    if (path.contains("."))
2323      return path.substring(path.lastIndexOf('.')+1);
2324    else
2325      return path;
2326  }
2327
2328  private boolean isDataType(String value) {
2329    StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+value);
2330    return sd != null && sd.getKind() == StructureDefinitionKind.COMPLEXTYPE;
2331  }
2332
2333  private boolean isReference(String value) {
2334    return "Reference".equals(value);
2335  }
2336
2337  public boolean isPrimitive(String value) {
2338    StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+value);
2339    return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
2340  }
2341
2342//  private static String listStructures(StructureDefinition p) {
2343//    StringBuilder b = new StringBuilder();
2344//    boolean first = true;
2345//    for (ProfileStructureComponent s : p.getStructure()) {
2346//      if (first)
2347//        first = false;
2348//      else
2349//        b.append(", ");
2350//      if (pkp != null && pkp.hasLinkFor(s.getType()))
2351//        b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>");
2352//      else
2353//        b.append(s.getType());
2354//    }
2355//    return b.toString();
2356//  }
2357
2358
2359  public StructureDefinition getProfile(StructureDefinition source, String url) {
2360        StructureDefinition profile = null;
2361        String code = null;
2362        if (url.startsWith("#")) {
2363                profile = source;
2364                code = url.substring(1);
2365        } else if (context != null) {
2366                String[] parts = url.split("\\#");
2367                profile = context.fetchResource(StructureDefinition.class, parts[0]);
2368      code = parts.length == 1 ? null : parts[1];
2369        }         
2370        if (profile == null)
2371                return null;
2372        if (code == null)
2373                return profile;
2374        for (Resource r : profile.getContained()) {
2375                if (r instanceof StructureDefinition && r.getId().equals(code))
2376                        return (StructureDefinition) r;
2377        }
2378        return null;
2379  }
2380
2381
2382
2383  public static class ElementDefinitionHolder {
2384    private String name;
2385    private ElementDefinition self;
2386    private int baseIndex = 0;
2387    private List<ElementDefinitionHolder> children;
2388
2389    public ElementDefinitionHolder(ElementDefinition self) {
2390      super();
2391      this.self = self;
2392      this.name = self.getPath();
2393      children = new ArrayList<ElementDefinitionHolder>();
2394    }
2395
2396    public ElementDefinition getSelf() {
2397      return self;
2398    }
2399
2400    public List<ElementDefinitionHolder> getChildren() {
2401      return children;
2402    }
2403
2404    public int getBaseIndex() {
2405      return baseIndex;
2406    }
2407
2408    public void setBaseIndex(int baseIndex) {
2409      this.baseIndex = baseIndex;
2410    }
2411
2412    @Override
2413    public String toString() {
2414      if (self.hasSliceName())
2415        return self.getPath()+"("+self.getSliceName()+")";
2416      else
2417        return self.getPath();
2418    }
2419  }
2420
2421  public static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> {
2422
2423    private boolean inExtension;
2424    private List<ElementDefinition> snapshot;
2425    private int prefixLength;
2426    private String base;
2427    private String name;
2428    private Set<String> errors = new HashSet<String>();
2429
2430    public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, int prefixLength, String name) {
2431      this.inExtension = inExtension;
2432      this.snapshot = snapshot;
2433      this.prefixLength = prefixLength;
2434      this.base = base;
2435      this.name = name;
2436    }
2437
2438    @Override
2439    public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) {
2440      if (o1.getBaseIndex() == 0)
2441        o1.setBaseIndex(find(o1.getSelf().getPath()));
2442      if (o2.getBaseIndex() == 0)
2443        o2.setBaseIndex(find(o2.getSelf().getPath()));
2444      return o1.getBaseIndex() - o2.getBaseIndex();
2445    }
2446
2447    private int find(String path) {
2448      String actual = base+path.substring(prefixLength);
2449      for (int i = 0; i < snapshot.size(); i++) {
2450        String p = snapshot.get(i).getPath();
2451        if (p.equals(actual)) {
2452          return i;
2453        }
2454        if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length()-3)) && !(actual.endsWith("[x]")) && !actual.substring(p.length()-3).contains(".")) {
2455          return i;
2456      }
2457        if (path.startsWith(p+".") && snapshot.get(i).hasContentReference()) {
2458          actual = base+(snapshot.get(i).getContentReference().substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength);
2459          i = 0;
2460        }
2461      }
2462      if (prefixLength == 0)
2463        errors.add("Differential contains path "+path+" which is not found in the base");
2464      else
2465        errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the base");
2466      return 0;
2467    }
2468
2469    public void checkForErrors(List<String> errorList) {
2470      if (errors.size() > 0) {
2471//        CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
2472//        for (String s : errors)
2473//          b.append("StructureDefinition "+name+": "+s);
2474//        throw new DefinitionException(b.toString());
2475        for (String s : errors)
2476          if (s.startsWith("!"))
2477            errorList.add("!StructureDefinition "+name+": "+s.substring(1));
2478          else
2479            errorList.add("StructureDefinition "+name+": "+s);
2480      }
2481    }
2482  }
2483
2484
2485  public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors) throws FHIRException  {
2486
2487    final List<ElementDefinition> diffList = diff.getDifferential().getElement();
2488    // first, we move the differential elements into a tree
2489    if (diffList.isEmpty())
2490      return;
2491    ElementDefinitionHolder edh = new ElementDefinitionHolder(diffList.get(0));
2492
2493    boolean hasSlicing = false;
2494    List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly
2495    for(ElementDefinition elt : diffList) {
2496      if (elt.hasSlicing() || paths.contains(elt.getPath())) {
2497        hasSlicing = true;
2498        break;
2499      }
2500      paths.add(elt.getPath());
2501    }
2502    if(!hasSlicing) {
2503      // if Differential does not have slicing then safe to pre-sort the list
2504      // so elements and subcomponents are together
2505      Collections.sort(diffList, new ElementNameCompare());
2506    }
2507
2508    int i = 1;
2509    processElementsIntoTree(edh, i, diff.getDifferential().getElement());
2510
2511    // now, we sort the siblings throughout the tree
2512    ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name);
2513    sortElements(edh, cmp, errors);
2514
2515    // now, we serialise them back to a list
2516    diffList.clear();
2517    writeElements(edh, diffList);
2518  }
2519
2520  private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) {
2521    String path = edh.getSelf().getPath();
2522    final String prefix = path + ".";
2523    while (i < list.size() && list.get(i).getPath().startsWith(prefix)) {
2524      ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i));
2525      edh.getChildren().add(child);
2526      i = processElementsIntoTree(child, i+1, list);
2527    }
2528    return i;
2529  }
2530
2531  private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) throws FHIRException {
2532    if (edh.getChildren().size() == 1)
2533      // 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
2534      edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath());
2535    else
2536      Collections.sort(edh.getChildren(), cmp);
2537    cmp.checkForErrors(errors);
2538
2539    for (ElementDefinitionHolder child : edh.getChildren()) {
2540      if (child.getChildren().size() > 0) {
2541        // what we have to check for here is running off the base profile into a data type profile
2542        ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex());
2543        ElementDefinitionComparer ccmp;
2544        if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getCode()) || ed.getType().get(0).getCode().equals(ed.getPath())) {
2545          ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name);
2546        } else if (ed.getType().get(0).getCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) {
2547          StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile());
2548          if (profile==null)
2549            ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case
2550          else
2551          ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), ed.getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name);
2552        } else if (ed.getType().size() == 1 && !ed.getType().get(0).getCode().equals("*")) {
2553          StructureDefinition profile = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+ed.getType().get(0).getCode());
2554          if (profile==null)
2555            throw new FHIRException("Unable to resolve profile " + "http://hl7.org/fhir/StructureDefinition/"+ed.getType().get(0).getCode() + " in element " + ed.getPath());
2556          ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name);
2557        } else if (child.getSelf().getType().size() == 1) {
2558          StructureDefinition profile = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+child.getSelf().getType().get(0).getCode());
2559          if (profile==null)
2560            throw new FHIRException("Unable to resolve profile " + "http://hl7.org/fhir/StructureDefinition/"+ed.getType().get(0).getCode() + " in element " + ed.getPath());
2561          ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), child.getSelf().getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name);
2562        } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) {
2563          String edLastNode = ed.getPath().replaceAll("(.*\\.)*(.*)", "$2");
2564          String childLastNode = child.getSelf().getPath().replaceAll("(.*\\.)*(.*)", "$2");
2565          String p = childLastNode.substring(edLastNode.length()-3);
2566          StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+p);
2567          if (sd == null)
2568            throw new Error("Unable to find profile "+p);
2569          ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name);
2570        } else {
2571          throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")");
2572        }
2573        if (ccmp != null)
2574        sortElements(child, ccmp, errors);
2575      }
2576    }
2577  }
2578
2579  private boolean isAbstract(String code) {
2580    return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") || code.equals("DomainResource");
2581  }
2582
2583
2584  private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) {
2585    list.add(edh.getSelf());
2586    for (ElementDefinitionHolder child : edh.getChildren()) {
2587      writeElements(child, list);
2588    }
2589  }
2590
2591  /**
2592   * First compare element by path then by name if same
2593   */
2594  private static class ElementNameCompare implements Comparator<ElementDefinition> {
2595
2596    @Override
2597    public int compare(ElementDefinition o1, ElementDefinition o2) {
2598      String path1 = normalizePath(o1);
2599      String path2 = normalizePath(o2);
2600      int cmp = path1.compareTo(path2);
2601      if (cmp == 0) {
2602        String name1 = o1.hasSliceName() ? o1.getSliceName() : "";
2603        String name2 = o2.hasSliceName() ? o2.getSliceName() : "";
2604        cmp = name1.compareTo(name2);
2605      }
2606      return cmp;
2607    }
2608
2609    private static String normalizePath(ElementDefinition e) {
2610      if (!e.hasPath()) return "";
2611      String path = e.getPath();
2612      // if sorting element names make sure onset[x] appears before onsetAge, onsetDate, etc.
2613      // so strip off the [x] suffix when comparing the path names.
2614      if (path.endsWith("[x]")) {
2615        path = path.substring(0, path.length()-3);
2616      }
2617      return path;
2618    }
2619
2620  }
2621
2622
2623  // generate schematrons for the rules in a structure definition
2624  public void generateSchematrons(OutputStream dest, StructureDefinition structure) throws IOException, DefinitionException {
2625    if (structure.getDerivation() != TypeDerivationRule.CONSTRAINT)
2626      throw new DefinitionException("not the right kind of structure to generate schematrons for");
2627    if (!structure.hasSnapshot())
2628      throw new DefinitionException("needs a snapshot");
2629
2630        StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBaseDefinition());
2631
2632        SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName());
2633
2634    ElementDefinition ed = structure.getSnapshot().getElement().get(0);
2635    generateForChildren(sch, "f:"+ed.getPath(), ed, structure, base);
2636    sch.dump();
2637  }
2638
2639  
2640  private class Slicer extends ElementDefinitionSlicingComponent {
2641    String criteria = "";
2642    String name = "";   
2643    boolean check;
2644    public Slicer(boolean cantCheck) {
2645      super();
2646      this.check = cantCheck;
2647    }
2648  }
2649  
2650  private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, StructureDefinition structure) {
2651    // given a child in a structure, it's sliced. figure out the slicing xpath
2652    if (child.getPath().endsWith(".extension")) {
2653      ElementDefinition ued = getUrlFor(structure, child);
2654      if ((ued == null || !ued.hasFixed()) && !(child.hasType() && (child.getType().get(0).hasProfile())))
2655        return new Slicer(false);
2656      else {
2657      Slicer s = new Slicer(true);
2658      String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile() : ((UriType) ued.getFixed()).asStringValue();
2659      s.name = " with URL = '"+url+"'";
2660      s.criteria = "[@url = '"+url+"']";
2661      return s;
2662      }
2663    } else
2664      return new Slicer(false);
2665  }
2666
2667  private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, StructureDefinition structure, StructureDefinition base) throws IOException {
2668    //    generateForChild(txt, structure, child);
2669    List<ElementDefinition> children = getChildList(structure, ed);
2670    String sliceName = null;
2671    ElementDefinitionSlicingComponent slicing = null;
2672    for (ElementDefinition child : children) {
2673      String name = tail(child.getPath());
2674      if (child.hasSlicing()) {
2675        sliceName = name;
2676        slicing = child.getSlicing();        
2677      } else if (!name.equals(sliceName))
2678        slicing = null;
2679      
2680      ElementDefinition based = getByPath(base, child.getPath());
2681      boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin()));
2682      boolean doMax = child.hasMax() && !child.getMax().equals("*") && (based == null || (!child.getMax().equals(based.getMax())));
2683      Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure);
2684      if (slicer.check) {
2685        if (doMin || doMax) {
2686          Section s = sch.section(xpath);
2687          Rule r = s.rule(xpath);
2688          if (doMin) 
2689            r.assrt("count(f:"+name+slicer.criteria+") >= "+Integer.toString(child.getMin()), name+slicer.name+": minimum cardinality of '"+name+"' is "+Integer.toString(child.getMin()));
2690          if (doMax) 
2691            r.assrt("count(f:"+name+slicer.criteria+") <= "+child.getMax(), name+slicer.name+": maximum cardinality of '"+name+"' is "+child.getMax());
2692          }
2693        }
2694      }
2695    for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
2696      if (inv.hasXpath()) {
2697        Section s = sch.section(ed.getPath());
2698        Rule r = s.rule(xpath);
2699        r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId()+": " : "")+inv.getHuman()+(inv.hasUserData(IS_DERIVED) ? " (inherited)" : ""));
2700      }
2701    }
2702    for (ElementDefinition child : children) {
2703      String name = tail(child.getPath());
2704      generateForChildren(sch, xpath+"/f:"+name, child, structure, base);
2705    }
2706  }
2707
2708
2709
2710
2711  private ElementDefinition getByPath(StructureDefinition base, String path) {
2712                for (ElementDefinition ed : base.getSnapshot().getElement()) {
2713                        if (ed.getPath().equals(path))
2714                                return ed;
2715                        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)))
2716                                return ed;
2717    }
2718          return null;
2719  }
2720
2721
2722  public void setIds(StructureDefinition sd, boolean checkFirst) throws DefinitionException  {
2723    if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) {
2724      if (!sd.hasDifferential())
2725        sd.setDifferential(new StructureDefinitionDifferentialComponent());
2726      generateIds(sd.getDifferential().getElement(), sd.getName());
2727    }
2728    if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) {
2729      if (!sd.hasSnapshot())
2730        sd.setSnapshot(new StructureDefinitionSnapshotComponent());
2731      generateIds(sd.getSnapshot().getElement(), sd.getName());
2732    }
2733  }
2734
2735
2736  private boolean hasMissingIds(List<ElementDefinition> list) {
2737    for (ElementDefinition ed : list) {
2738      if (!ed.hasId())
2739        return true;
2740    }    
2741    return false;
2742  }
2743
2744
2745  private void generateIds(List<ElementDefinition> list, String name) throws DefinitionException  {
2746    if (list.isEmpty())
2747      return;
2748    
2749    Map<String, String> idMap = new HashMap<String, String>();
2750    
2751    List<String> paths = new ArrayList<String>();
2752    // first pass, update the element ids
2753    for (ElementDefinition ed : list) {
2754      if (!ed.hasPath())
2755        throw new DefinitionException("No path on element Definition "+Integer.toString(list.indexOf(ed))+" in "+name);
2756      int depth = charCount(ed.getPath(), '.');
2757      String tail = tail(ed.getPath());
2758
2759      if (depth > paths.size()) {
2760        // this means that we've jumped into a sparse thing. 
2761        String[] pl = ed.getPath().split("\\.");
2762        for (int i = paths.size(); i < pl.length-1; i++) // -1 because the last path is in focus
2763          paths.add(pl[i]);
2764      }
2765      while (depth < paths.size() && paths.size() > 0)
2766        paths.remove(paths.size() - 1);
2767      
2768      String t = ed.hasSliceName() ? tail+":"+checkName(ed.getSliceName()) : /* why do this? name != null ? tail + ":"+checkName(name) : */ tail;
2769//      if (isExtension(ed))
2770//        t = t + describeExtension(ed);
2771      name = null;
2772      StringBuilder b = new StringBuilder();
2773      for (String s : paths) {
2774        b.append(s);
2775        b.append(".");
2776      }
2777      b.append(t);
2778      String bs = b.toString();
2779      idMap.put(ed.hasId() ? ed.getId() : ed.getPath(), bs);
2780      ed.setId(bs);
2781      paths.add(t);
2782      if (ed.hasContentReference()) {
2783        String s = ed.getContentReference().substring(1);
2784        if (idMap.containsKey(s))
2785          ed.setContentReference("#"+idMap.get(s));
2786        
2787      }
2788    }  
2789    // second path - fix up any broken path based id references
2790    
2791  }
2792
2793
2794//  private String describeExtension(ElementDefinition ed) {
2795//    if (!ed.hasType() || !ed.getTypeFirstRep().hasProfile())
2796//      return "";
2797//    return "$"+urlTail(ed.getTypeFirstRep().getProfile());
2798//  }
2799//
2800
2801  private String urlTail(String profile) {
2802    return profile.contains("/") ? profile.substring(profile.lastIndexOf("/")+1) : profile;
2803  }
2804
2805
2806  private String checkName(String name) {
2807//    if (name.contains("."))
2808////      throw new Exception("Illegal name "+name+": no '.'");
2809//    if (name.contains(" "))
2810//      throw new Exception("Illegal name "+name+": no spaces");
2811    StringBuilder b = new StringBuilder();
2812    for (char c : name.toCharArray()) {
2813      if (!Utilities.existsInList(c, '.', ' ', ':', '"', '\'', '(', ')', '&', '[', ']'))
2814        b.append(c);
2815    }
2816    return b.toString().toLowerCase();
2817  }
2818
2819
2820  private int charCount(String path, char t) {
2821    int res = 0;
2822    for (char ch : path.toCharArray()) {
2823      if (ch == t)
2824        res++;
2825    }
2826    return res;
2827  }
2828
2829//
2830//private void generateForChild(TextStreamWriter txt,
2831//    StructureDefinition structure, ElementDefinition child) {
2832//  // TODO Auto-generated method stub
2833//
2834//}
2835
2836  private interface ExampleValueAccessor {
2837    Type getExampleValue(ElementDefinition ed);
2838    String getId();
2839  }
2840
2841  private class BaseExampleValueAccessor implements ExampleValueAccessor {
2842    @Override
2843    public Type getExampleValue(ElementDefinition ed) {
2844      if (ed.hasFixed())
2845        return ed.getFixed();
2846      if (ed.hasExample())
2847        return ed.getExample().get(0).getValue();
2848      else
2849        return null;
2850    }
2851
2852    @Override
2853    public String getId() {
2854      return "-genexample";
2855    }
2856  }
2857  
2858  private class ExtendedExampleValueAccessor implements ExampleValueAccessor {
2859    private String index;
2860
2861    public ExtendedExampleValueAccessor(String index) {
2862      this.index = index;
2863    }
2864    @Override
2865    public Type getExampleValue(ElementDefinition ed) {
2866      if (ed.hasFixed())
2867        return ed.getFixed();
2868      for (Extension ex : ed.getExtension()) {
2869       String ndx = ToolingExtensions.readStringExtension(ex, "index");
2870       Type value = ToolingExtensions.getExtension(ex, "exValue").getValue();
2871       if (index.equals(ndx) && value != null)
2872         return value;
2873      }
2874      return null;
2875    }
2876    @Override
2877    public String getId() {
2878      return "-genexample-"+index;
2879    }
2880  }
2881  
2882  public List<org.hl7.fhir.dstu3.elementmodel.Element> generateExamples(StructureDefinition sd, boolean evenWhenNoExamples) throws FHIRException {
2883    List<org.hl7.fhir.dstu3.elementmodel.Element> examples = new ArrayList<org.hl7.fhir.dstu3.elementmodel.Element>();
2884    if (sd.hasSnapshot()) {
2885      if (evenWhenNoExamples || hasAnyExampleValues(sd)) 
2886        examples.add(generateExample(sd, new BaseExampleValueAccessor()));
2887      for (int i = 1; i <= 50; i++) {
2888        if (hasAnyExampleValues(sd, Integer.toString(i))) 
2889          examples.add(generateExample(sd, new ExtendedExampleValueAccessor(Integer.toString(i))));
2890      }
2891    }
2892    return examples;
2893  }
2894
2895  private org.hl7.fhir.dstu3.elementmodel.Element generateExample(StructureDefinition profile, ExampleValueAccessor accessor) throws FHIRException {
2896    ElementDefinition ed = profile.getSnapshot().getElementFirstRep();
2897    org.hl7.fhir.dstu3.elementmodel.Element r = new org.hl7.fhir.dstu3.elementmodel.Element(ed.getPath(), new Property(context, ed, profile));
2898    List<ElementDefinition> children = getChildMap(profile, ed);
2899    for (ElementDefinition child : children) {
2900      if (child.getPath().endsWith(".id")) {
2901        org.hl7.fhir.dstu3.elementmodel.Element id = new org.hl7.fhir.dstu3.elementmodel.Element("id", new Property(context, child, profile));
2902        id.setValue(profile.getId()+accessor.getId());
2903        r.getChildren().add(id);
2904      } else { 
2905        org.hl7.fhir.dstu3.elementmodel.Element e = createExampleElement(profile, child, accessor);
2906        if (e != null)
2907          r.getChildren().add(e);
2908      }
2909    }
2910    return r;
2911  }
2912
2913  private org.hl7.fhir.dstu3.elementmodel.Element createExampleElement(StructureDefinition profile, ElementDefinition ed, ExampleValueAccessor accessor) throws FHIRException {
2914    Type v = accessor.getExampleValue(ed);
2915    if (v != null) {
2916      return new ObjectConverter(context).convert(new Property(context, ed, profile), v);
2917    } else {
2918      org.hl7.fhir.dstu3.elementmodel.Element res = new org.hl7.fhir.dstu3.elementmodel.Element(tail(ed.getPath()), new Property(context, ed, profile));
2919      boolean hasValue = false;
2920      List<ElementDefinition> children = getChildMap(profile, ed);
2921      for (ElementDefinition child : children) {
2922        if (!child.hasContentReference()) {
2923        org.hl7.fhir.dstu3.elementmodel.Element e = createExampleElement(profile, child, accessor);
2924        if (e != null) {
2925          hasValue = true;
2926          res.getChildren().add(e);
2927        }
2928      }
2929      }
2930      if (hasValue)
2931        return res;
2932      else
2933        return null;
2934    }
2935  }
2936
2937  private boolean hasAnyExampleValues(StructureDefinition sd, String index) {
2938    for (ElementDefinition ed : sd.getSnapshot().getElement())
2939      for (Extension ex : ed.getExtension()) {
2940        String ndx = ToolingExtensions.readStringExtension(ex, "index");
2941        Extension exv = ToolingExtensions.getExtension(ex, "exValue");
2942        if (exv != null) {
2943          Type value = exv.getValue();
2944        if (index.equals(ndx) && value != null)
2945          return true;
2946        }
2947       }
2948    return false;
2949  }
2950
2951
2952  private boolean hasAnyExampleValues(StructureDefinition sd) {
2953    for (ElementDefinition ed : sd.getSnapshot().getElement())
2954      if (ed.hasExample())
2955        return true;
2956    return false;
2957  }
2958
2959
2960  public void populateLogicalSnapshot(StructureDefinition sd) throws FHIRException {
2961    sd.getSnapshot().getElement().add(sd.getDifferential().getElementFirstRep().copy());
2962    
2963    if (sd.hasBaseDefinition()) {
2964    StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
2965    if (base == null)
2966      throw new FHIRException("Unable to find base definition for logical model: "+sd.getBaseDefinition()+" from "+sd.getUrl());
2967    copyElements(sd, base.getSnapshot().getElement());
2968    }
2969    copyElements(sd, sd.getDifferential().getElement());
2970  }
2971
2972
2973  private void copyElements(StructureDefinition sd, List<ElementDefinition> list) {
2974    for (ElementDefinition ed : list) {
2975      if (ed.getPath().contains(".")) {
2976        ElementDefinition n = ed.copy();
2977        n.setPath(sd.getSnapshot().getElementFirstRep().getPath()+"."+ed.getPath().substring(ed.getPath().indexOf(".")+1));
2978        sd.getSnapshot().addElement(n);
2979      }
2980    }
2981  }
2982
2983    
2984  public void cleanUpDifferential(StructureDefinition sd) {
2985    if (sd.getDifferential().getElement().size() > 1)
2986      cleanUpDifferential(sd, 1);
2987  }
2988  
2989  private void cleanUpDifferential(StructureDefinition sd, int start) {
2990    int level = Utilities.charCount(sd.getDifferential().getElement().get(start).getPath(), '.');
2991    int c = start;
2992    int len = sd.getDifferential().getElement().size();
2993    HashSet<String> paths = new HashSet<String>();
2994    while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') == level) {
2995      ElementDefinition ed = sd.getDifferential().getElement().get(c);
2996      if (!paths.contains(ed.getPath())) {
2997        paths.add(ed.getPath());
2998        int ic = c+1; 
2999        while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 
3000          ic++;
3001        ElementDefinition slicer = null;
3002        List<ElementDefinition> slices = new ArrayList<ElementDefinition>();
3003        slices.add(ed);
3004        while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') == level) {
3005          ElementDefinition edi = sd.getDifferential().getElement().get(ic);
3006          if (ed.getPath().equals(edi.getPath())) {
3007            if (slicer == null) {
3008              slicer = new ElementDefinition();
3009              slicer.setPath(edi.getPath());
3010              slicer.getSlicing().setRules(SlicingRules.OPEN);
3011              sd.getDifferential().getElement().add(c, slicer);
3012              c++;
3013              ic++;
3014            }
3015            slices.add(edi);
3016          }
3017          ic++;
3018          while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 
3019            ic++;
3020        }
3021        // now we're at the end, we're going to figure out the slicing discriminator
3022        if (slicer != null)
3023          determineSlicing(slicer, slices);
3024      }
3025      c++;
3026      if (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) {
3027        cleanUpDifferential(sd, c);
3028        c++;
3029        while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) 
3030          c++;
3031      }
3032  }
3033  }
3034
3035
3036  private void determineSlicing(ElementDefinition slicer, List<ElementDefinition> slices) {
3037    // first, name them
3038    int i = 0;
3039    for (ElementDefinition ed : slices) {
3040      if (ed.hasUserData("slice-name")) {
3041        ed.setSliceName(ed.getUserString("slice-name"));
3042      } else {
3043        i++;
3044        ed.setSliceName("slice-"+Integer.toString(i));
3045      }
3046    }
3047    // now, the hard bit, how are they differentiated? 
3048    // right now, we hard code this...
3049    if (slicer.getPath().endsWith(".extension") || slicer.getPath().endsWith(".modifierExtension"))
3050      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url");
3051    else if (slicer.getPath().equals("DiagnosticReport.result"))
3052      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("reference.code");
3053    else if (slicer.getPath().equals("Observation.related"))
3054      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("target.reference.code");
3055    else if (slicer.getPath().equals("Bundle.entry"))
3056      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("resource.@profile");
3057    else  
3058      throw new Error("No slicing for "+slicer.getPath()); 
3059  }
3060
3061  public class SpanEntry {
3062    private List<SpanEntry> children = new ArrayList<SpanEntry>();
3063    private boolean profile;
3064    private String id;
3065    private String name;
3066    private String resType;
3067    private String cardinality;
3068    private String description;
3069    private String profileLink;
3070    private String resLink;
3071    private String type;
3072    
3073    public String getName() {
3074      return name;
3075    }
3076    public void setName(String name) {
3077      this.name = name;
3078    }
3079    public String getResType() {
3080      return resType;
3081    }
3082    public void setResType(String resType) {
3083      this.resType = resType;
3084    }
3085    public String getCardinality() {
3086      return cardinality;
3087    }
3088    public void setCardinality(String cardinality) {
3089      this.cardinality = cardinality;
3090    }
3091    public String getDescription() {
3092      return description;
3093    }
3094    public void setDescription(String description) {
3095      this.description = description;
3096    }
3097    public String getProfileLink() {
3098      return profileLink;
3099    }
3100    public void setProfileLink(String profileLink) {
3101      this.profileLink = profileLink;
3102    }
3103    public String getResLink() {
3104      return resLink;
3105    }
3106    public void setResLink(String resLink) {
3107      this.resLink = resLink;
3108    }
3109    public String getId() {
3110      return id;
3111    }
3112    public void setId(String id) {
3113      this.id = id;
3114    }
3115    public boolean isProfile() {
3116      return profile;
3117    }
3118    public void setProfile(boolean profile) {
3119      this.profile = profile;
3120    }
3121    public List<SpanEntry> getChildren() {
3122      return children;
3123    }
3124    public String getType() {
3125      return type;
3126    }
3127    public void setType(String type) {
3128      this.type = type;
3129    }
3130    
3131  }
3132
3133
3134  private String getCardinality(ElementDefinition ed, List<ElementDefinition> list) {
3135    int min = ed.getMin();
3136    int max = !ed.hasMax() || ed.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(ed.getMax());
3137    while (ed != null && ed.getPath().contains(".")) {
3138      ed = findParent(ed, list);
3139      if (ed.getMax().equals("0"))
3140        max = 0;
3141      else if (!ed.getMax().equals("1") && !ed.hasSlicing())
3142        max = Integer.MAX_VALUE;
3143      if (ed.getMin() == 0)
3144        min = 0;
3145    }
3146    return Integer.toString(min)+".."+(max == Integer.MAX_VALUE ? "*" : Integer.toString(max));
3147  }
3148
3149
3150  private ElementDefinition findParent(ElementDefinition ed, List<ElementDefinition> list) {
3151    int i = list.indexOf(ed)-1;
3152    while (i >= 0 && !ed.getPath().startsWith(list.get(i).getPath()+"."))
3153      i--;
3154    if (i == -1)
3155      return null;
3156    else
3157      return list.get(i);
3158  }
3159
3160
3161  private List<String> listReferenceProfiles(ElementDefinition ed) {
3162    List<String> res = new ArrayList<String>();
3163    for (TypeRefComponent tr : ed.getType()) {
3164      // code is null if we're dealing with "value" and profile is null if we just have Reference()
3165      if (tr.getCode()!= null && "Reference".equals(tr.getCode()) && tr.getTargetProfile() != null)
3166        res.add(tr.getTargetProfile());
3167    }
3168    return res ;
3169  }
3170
3171
3172  private String nameForElement(ElementDefinition ed) {
3173    return ed.getPath().substring(ed.getPath().indexOf(".")+1);
3174  }
3175
3176
3177  private SpanEntry buildSpanEntryFromProfile(String name, String cardinality, StructureDefinition profile) throws IOException {
3178    SpanEntry res = new SpanEntry();
3179    res.setName(name);
3180    res.setCardinality(cardinality);
3181    res.setProfileLink(profile.getUserString("path"));
3182    res.setResType(profile.getType());
3183    StructureDefinition base = context.fetchResource(StructureDefinition.class, res.getResType());
3184    if (base != null)
3185      res.setResLink(base.getUserString("path"));
3186    res.setId(profile.getId());
3187    res.setProfile(profile.getDerivation() == TypeDerivationRule.CONSTRAINT);
3188    StringBuilder b = new StringBuilder();
3189    b.append(res.getResType());
3190    boolean first = true;
3191    boolean open = false;
3192    if (profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
3193      res.setDescription(profile.getName());
3194      for (ElementDefinition ed : profile.getSnapshot().getElement()) {
3195        if (isKeyProperty(ed.getBase().getPath()) && ed.hasFixed()) {
3196          if (first) {
3197            open = true;
3198            first = false;
3199            b.append("[");
3200          } else {
3201            b.append(", ");
3202          }
3203          b.append(tail(ed.getBase().getPath()));
3204          b.append("=");
3205          b.append(summarise(ed.getFixed()));
3206        }
3207      }
3208      if (open)
3209        b.append("]");
3210    } else
3211      res.setDescription("Base FHIR "+profile.getName());
3212    res.setType(b.toString());
3213    return res ;
3214  }
3215
3216
3217  private String summarise(Type value) throws IOException {
3218    if (value instanceof Coding)
3219      return summariseCoding((Coding) value);
3220    else if (value instanceof CodeableConcept)
3221      return summariseCodeableConcept((CodeableConcept) value);
3222    else
3223      return buildJson(value);
3224  }
3225
3226
3227  private String summariseCoding(Coding value) {
3228    String uri = value.getSystem();
3229    String system = NarrativeGenerator.describeSystem(uri);
3230    if (Utilities.isURL(system)) {
3231      if (system.equals("http://cap.org/protocols"))
3232        system = "CAP Code";
3233    }
3234    return system+" "+value.getCode();
3235  }
3236
3237
3238  private String summariseCodeableConcept(CodeableConcept value) {
3239    if (value.hasCoding())
3240      return summariseCoding(value.getCodingFirstRep());
3241    else
3242      return value.getText();
3243  }
3244
3245
3246  private boolean isKeyProperty(String path) {
3247    return Utilities.existsInList(path, "Observation.code");
3248  }
3249
3250
3251  public TableModel initSpanningTable(HierarchicalTableGenerator gen, String prefix, boolean isLogical) {
3252    TableModel model = gen.new TableModel();
3253    
3254    model.setDocoImg(prefix+"help16.png");
3255    model.setDocoRef(prefix+"formats.html#table"); // todo: change to graph definition
3256    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Property", "A profiled resource", null, 0));
3257    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));
3258    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Content", "What goes here", null, 0));
3259    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Description", "Description of the profile", null, 0));
3260    return model;
3261  }
3262
3263  private void genSpanEntry(HierarchicalTableGenerator gen, List<Row> rows, SpanEntry span) throws IOException {
3264    Row row = gen.new Row();
3265    rows.add(row);
3266    row.setAnchor(span.getId());
3267    //row.setColor(..?);
3268    if (span.isProfile()) 
3269      row.setIcon("icon_profile.png", HierarchicalTableGenerator.TEXT_ICON_PROFILE);
3270    else
3271      row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
3272    
3273    row.getCells().add(gen.new Cell(null, null, span.getName(), null, null));
3274    row.getCells().add(gen.new Cell(null, null, span.getCardinality(), null, null));
3275    row.getCells().add(gen.new Cell(null, span.getProfileLink(), span.getType(), null, null));
3276    row.getCells().add(gen.new Cell(null, null, span.getDescription(), null, null));
3277
3278    for (SpanEntry child : span.getChildren())
3279      genSpanEntry(gen, row.getSubRows(), child);
3280  }
3281
3282
3283  public static ElementDefinitionSlicingDiscriminatorComponent interpretR2Discriminator(String discriminator) {
3284    if (discriminator.endsWith("@profile"))
3285      return makeDiscriminator(DiscriminatorType.PROFILE, discriminator.length() == 8 ? "" : discriminator.substring(discriminator.length()-9)); 
3286    if (discriminator.endsWith("@type")) 
3287      return makeDiscriminator(DiscriminatorType.TYPE, discriminator.length() == 5 ? "" : discriminator.substring(discriminator.length()-6)); 
3288    return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(discriminator);
3289  }
3290
3291
3292  private static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType profile, String str) {
3293    return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(Utilities.noString(str)? "$this" : str);
3294  }
3295
3296
3297  public static String buildR2Discriminator(ElementDefinitionSlicingDiscriminatorComponent t) throws FHIRException {
3298    switch (t.getType()) {
3299    case PROFILE: return t.getPath()+"/@profile";
3300    case TYPE: return t.getPath()+"/@type";
3301    case VALUE: return t.getPath();
3302    default: throw new FHIRException("Unable to represent "+t.getType().toCode()+":"+t.getPath()+" in R2");    
3303    }
3304  }
3305
3306
3307
3308
3309
3310
3311}