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