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