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