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