001package org.hl7.fhir.dstu2.utils;
002
003/*
004  Copyright (c) 2011+, HL7, Inc.
005  All rights reserved.
006  
007  Redistribution and use in source and binary forms, with or without modification, 
008  are permitted provided that the following conditions are met:
009    
010   * Redistributions of source code must retain the above copyright notice, this 
011     list of conditions and the following disclaimer.
012   * Redistributions in binary form must reproduce the above copyright notice, 
013     this list of conditions and the following disclaimer in the documentation 
014     and/or other materials provided with the distribution.
015   * Neither the name of HL7 nor the names of its contributors may be used to 
016     endorse or promote products derived from this software without specific 
017     prior written permission.
018  
019  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
020  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
021  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
022  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
023  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
024  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
025  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
027  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
028  POSSIBILITY OF SUCH DAMAGE.
029  
030 */
031
032
033import java.io.IOException;
034import java.io.OutputStream;
035import java.util.ArrayList;
036import java.util.Collections;
037import java.util.Comparator;
038import java.util.HashSet;
039import java.util.List;
040import java.util.Set;
041
042import org.hl7.fhir.dstu2.formats.IParser;
043import org.hl7.fhir.dstu2.model.Base;
044import org.hl7.fhir.dstu2.model.BooleanType;
045import org.hl7.fhir.dstu2.model.Coding;
046import org.hl7.fhir.dstu2.model.Element;
047import org.hl7.fhir.dstu2.model.ElementDefinition;
048import org.hl7.fhir.dstu2.model.ElementDefinition.ElementDefinitionBindingComponent;
049import org.hl7.fhir.dstu2.model.ElementDefinition.ElementDefinitionConstraintComponent;
050import org.hl7.fhir.dstu2.model.ElementDefinition.ElementDefinitionMappingComponent;
051import org.hl7.fhir.dstu2.model.ElementDefinition.ElementDefinitionSlicingComponent;
052import org.hl7.fhir.dstu2.model.ElementDefinition.SlicingRules;
053import org.hl7.fhir.dstu2.model.ElementDefinition.TypeRefComponent;
054import org.hl7.fhir.dstu2.model.Enumerations.BindingStrength;
055import org.hl7.fhir.dstu2.model.IntegerType;
056import org.hl7.fhir.dstu2.model.PrimitiveType;
057import org.hl7.fhir.dstu2.model.Reference;
058import org.hl7.fhir.dstu2.model.Resource;
059import org.hl7.fhir.dstu2.model.StringType;
060import org.hl7.fhir.dstu2.model.StructureDefinition;
061import org.hl7.fhir.dstu2.model.StructureDefinition.StructureDefinitionDifferentialComponent;
062import org.hl7.fhir.dstu2.model.StructureDefinition.StructureDefinitionSnapshotComponent;
063import org.hl7.fhir.dstu2.model.Type;
064import org.hl7.fhir.dstu2.model.UriType;
065import org.hl7.fhir.dstu2.model.ValueSet;
066import org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionComponent;
067import org.hl7.fhir.dstu2.model.ValueSet.ValueSetExpansionContainsComponent;
068import org.hl7.fhir.dstu2.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
069import org.hl7.fhir.dstu2.utils.ProfileUtilities.ProfileKnowledgeProvider.BindingResolution;
070import org.hl7.fhir.exceptions.DefinitionException;
071import org.hl7.fhir.exceptions.FHIRException;
072import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
073import org.hl7.fhir.utilities.Utilities;
074import org.hl7.fhir.utilities.validation.ValidationMessage;
075import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
076import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
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 *  * generateExtensionsTable: generate the HTML for a hierarchical table presentation of the extensions
096 *  * generateTable: generate  the HTML for a hierarchical table presentation of a structure
097 *  * summarise: describe the contents of a profile
098 * @author Grahame
099 *
100 */
101public class ProfileUtilities {
102
103  public class ExtensionContext {
104
105    private ElementDefinition element;
106    private StructureDefinition defn;
107
108    public ExtensionContext(StructureDefinition ext, ElementDefinition ed) {
109      this.defn = ext;
110      this.element = ed;
111    }
112
113    public ElementDefinition getElement() {
114      return element;
115    }
116
117    public StructureDefinition getDefn() {
118      return defn;
119    }
120
121    public String getUrl() {
122      if (element == defn.getSnapshot().getElement().get(0))
123        return defn.getUrl();
124      else
125        return element.getName();
126    }
127
128    public ElementDefinition getExtensionValueDefinition() {
129      int i = defn.getSnapshot().getElement().indexOf(element)+1;
130      while (i < defn.getSnapshot().getElement().size()) {
131        ElementDefinition ed = defn.getSnapshot().getElement().get(i);
132        if (ed.getPath().equals(element.getPath()))
133          return null;
134        if (ed.getPath().startsWith(element.getPath()+".value"))
135          return ed;
136        i++;
137      }
138      return null;
139    }
140    
141  }
142
143
144
145
146  private final boolean ADD_REFERENCE_TO_TABLE = true;
147
148
149  private static final String ROW_COLOR_ERROR = "#ffcccc";
150  private static final String ROW_COLOR_FATAL = "#ff9999";
151  private static final String ROW_COLOR_WARNING = "#ffebcc";
152  private static final String ROW_COLOR_HINT = "#ebf5ff";
153  public static final int STATUS_OK = 0;
154  public static final int STATUS_HINT = 1;
155  public static final int STATUS_WARNING = 2;
156  public static final int STATUS_ERROR = 3;
157  public static final int STATUS_FATAL = 4;
158
159
160  private static final String DERIVATION_EQUALS = "derivation.equals";
161  public static final String DERIVATION_POINTER = "derived.pointer";
162  public static final String IS_DERIVED = "derived.fact";
163  public static final String UD_ERROR_STATUS = "error-status";
164
165  // note that ProfileUtilities are used re-entrantly internally, so nothing with process state can be here
166  private final IWorkerContext context;
167  private List<ValidationMessage> messages;
168  private List<String> snapshotStack = new ArrayList<String>();
169  private ProfileKnowledgeProvider pkp;
170
171  public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp) {
172    super();
173    this.context = context;
174    this.messages = messages;
175    this.pkp = pkp;
176  }
177
178  private class UnusedTracker {
179    private boolean used;
180  }
181
182  public interface ProfileKnowledgeProvider {
183    public class BindingResolution {
184      public String display;
185      public String url;
186    }
187    boolean isDatatype(String typeSimple);
188    boolean isResource(String typeSimple);
189    boolean hasLinkFor(String typeSimple);
190    String getLinkFor(String typeSimple);
191    BindingResolution resolveBinding(ElementDefinitionBindingComponent binding);
192    String getLinkForProfile(StructureDefinition profile, String url);
193  }
194
195
196/**
197 * Given a Structure, navigate to the element given by the path and return the direct children of that element
198 *
199 * @param structure The structure to navigate into
200 * @param path The path of the element within the structure to get the children for
201 * @return A Map containing the name of the element child (not the path) and the child itself (an Element)
202 * @throws DefinitionException 
203 * @throws Exception
204 */
205  public static List<ElementDefinition> getChildMap(StructureDefinition profile, String name, String path, String nameReference) throws DefinitionException {
206    List<ElementDefinition> res = new ArrayList<ElementDefinition>();
207
208    // if we have a name reference, we have to find it, and iterate it's children
209    if (nameReference != null) {
210        boolean found = false;
211      for (ElementDefinition e : profile.getSnapshot().getElement()) {
212        if (nameReference.equals(e.getName())) {
213                found = true;
214                path = e.getPath();
215        }
216      }
217      if (!found)
218        throw new DefinitionException("Unable to resolve name reference "+nameReference+" at path "+path);
219    }
220
221    for (ElementDefinition e : profile.getSnapshot().getElement())
222    {
223      String p = e.getPath();
224
225      if (path != null && !Utilities.noString(e.getNameReference()) && path.startsWith(p))
226      {
227        /* The path we are navigating to is on or below this element, but the element defers its definition to another named part of the
228         * structure.
229         */
230        if (path.length() > p.length())
231        {
232          // The path navigates further into the referenced element, so go ahead along the path over there
233          return getChildMap(profile, name, e.getNameReference()+"."+path.substring(p.length()+1), null);
234        }
235        else
236        {
237          // The path we are looking for is actually this element, but since it defers it definition, go get the referenced element
238          return getChildMap(profile, name, e.getNameReference(), null);
239        }
240      }
241      else if (p.startsWith(path+"."))
242      {
243          // The path of the element is a child of the path we're looking for (i.e. the parent),
244          // so add this element to the result.
245          String tail = p.substring(path.length()+1);
246
247          // Only add direct children, not any deeper paths
248          if (!tail.contains(".")) {
249            res.add(e);
250          }
251        }
252      }
253
254    return res;
255  }
256
257
258  public static List<ElementDefinition> getChildMap(StructureDefinition profile, ElementDefinition element) throws DefinitionException {
259                return getChildMap(profile, element.getName(), element.getPath(), null);
260  }
261
262
263  /**
264   * Given a Structure, navigate to the element given by the path and return the direct children of that element
265   *
266   * @param structure The structure to navigate into
267   * @param path The path of the element within the structure to get the children for
268   * @return A List containing the element children (all of them are Elements)
269   */
270  public static List<ElementDefinition> getChildList(StructureDefinition profile, String path) {
271    List<ElementDefinition> res = new ArrayList<ElementDefinition>();
272
273    for (ElementDefinition e : profile.getSnapshot().getElement())
274    {
275      String p = e.getPath();
276
277      if (!Utilities.noString(e.getNameReference()) && path.startsWith(p))
278      {
279        if (path.length() > p.length())
280          return getChildList(profile, e.getNameReference()+"."+path.substring(p.length()+1));
281        else
282          return getChildList(profile, e.getNameReference());
283      }
284      else if (p.startsWith(path+".") && !p.equals(path))
285      {
286          String tail = p.substring(path.length()+1);
287          if (!tail.contains(".")) {
288            res.add(e);
289          }
290        }
291
292      }
293
294    return res;
295  }
296
297
298  public static List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element) {
299                return getChildList(structure, element.getPath());
300          }
301
302  /**
303   * Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
304   *
305   * @param base - the base structure on which the differential will be applied
306   * @param differential - the differential to apply to the base
307   * @param url - where the base has relative urls for profile references, these need to be converted to absolutes by prepending this URL
308   * @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
309   * @return
310   * @throws FHIRException 
311   * @throws DefinitionException 
312   * @throws Exception
313   */
314  public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String profileName) throws DefinitionException, FHIRException {
315    if (base == null)
316      throw new DefinitionException("no base profile provided");
317    if (derived == null)
318      throw new DefinitionException("no derived structure provided");
319
320    if (snapshotStack.contains(derived.getUrl()))
321      throw new DefinitionException("Circular snapshot references detected; cannot generate snapshot (stack = "+snapshotStack.toString()+")");
322    snapshotStack.add(derived.getUrl());
323    
324//    System.out.println("Generate Snapshot for "+derived.getUrl());
325
326    derived.setSnapshot(new StructureDefinitionSnapshotComponent());
327
328    // so we have two lists - the base list, and the differential list
329    // the differential list is only allowed to include things that are in the base list, but
330    // is allowed to include them multiple times - thereby slicing them
331
332    // our approach is to walk through the base list, and see whether the differential
333    // says anything about them.
334    int baseCursor = 0;
335    int diffCursor = 0; // we need a diff cursor because we can only look ahead, in the bound scoped by longer paths
336
337    // we actually delegate the work to a subroutine so we can re-enter it with a different cursors
338    processPaths(derived.getSnapshot(), base.getSnapshot(), derived.getDifferential(), baseCursor, diffCursor, base.getSnapshot().getElement().size()-1, derived.getDifferential().getElement().size()-1, url, derived.getId(), null, false, base.getUrl(), null, false);
339  }
340
341  /**
342   * @param trimDifferential
343   * @throws DefinitionException, FHIRException 
344   * @throws Exception
345   */
346  private void processPaths(StructureDefinitionSnapshotComponent result, StructureDefinitionSnapshotComponent base, StructureDefinitionDifferentialComponent differential, int baseCursor, int diffCursor, int baseLimit,
347      int diffLimit, String url, String profileName, String contextPath, boolean trimDifferential, String contextName, String resultPathBase, boolean slicingDone) throws DefinitionException, FHIRException {
348
349    // just repeat processing entries until we run out of our allowed scope (1st entry, the allowed scope is all the entries)
350    while (baseCursor <= baseLimit) {
351      // get the current focus of the base, and decide what to do
352      ElementDefinition currentBase = base.getElement().get(baseCursor);
353      String cpath = fixedPath(contextPath, currentBase.getPath());
354      List<ElementDefinition> diffMatches = getDiffMatches(differential, cpath, diffCursor, diffLimit, profileName); // get a list of matching elements in scope
355
356      // in the simple case, source is not sliced.
357      if (!currentBase.hasSlicing()) {
358        if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item
359          // so we just copy it in
360          ElementDefinition outcome = updateURLs(url, currentBase.copy());
361          outcome.setPath(fixedPath(contextPath, outcome.getPath()));
362          updateFromBase(outcome, currentBase);
363          markDerived(outcome);
364          if (resultPathBase == null)
365            resultPathBase = outcome.getPath();
366          else if (!outcome.getPath().startsWith(resultPathBase))
367            throw new DefinitionException("Adding wrong path");
368          result.getElement().add(outcome);
369          baseCursor++;
370        } else if (diffMatches.size() == 1 && (!diffMatches.get(0).hasSlicing() || slicingDone)) {// one matching element in the differential
371          ElementDefinition template = null;
372          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")) {
373            String p = diffMatches.get(0).getType().get(0).getProfile().get(0).asStringValue();
374            StructureDefinition sd = context.fetchResource(StructureDefinition.class, p);
375            if (sd != null) {
376              if (!sd.hasSnapshot()) {
377                StructureDefinition sdb = context.fetchResource(StructureDefinition.class, sd.getBase());
378                if (sdb == null)
379                  throw new DefinitionException("no base for "+sd.getBase());
380                generateSnapshot(sdb, sd, sd.getUrl(), sd.getName());
381              }
382              template = sd.getSnapshot().getElement().get(0).copy().setPath(currentBase.getPath());
383              // temporary work around
384              if (!diffMatches.get(0).getType().get(0).getCode().equals("Extension")) {
385                template.setMin(currentBase.getMin());
386                template.setMax(currentBase.getMax());
387              }
388            }
389          } 
390          if (template == null)
391            template = currentBase.copy();
392          else
393            // some of what's in currentBase overrides template
394            template = overWriteWithCurrent(template, currentBase);
395          ElementDefinition outcome = updateURLs(url, template);
396          outcome.setPath(fixedPath(contextPath, outcome.getPath()));
397          updateFromBase(outcome, currentBase);
398          if (diffMatches.get(0).hasName())
399          outcome.setName(diffMatches.get(0).getName());
400          outcome.setSlicing(null);
401          updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url);
402          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
403            outcome.setPath(outcome.getPath().substring(0, outcome.getPath().length()-3)+Utilities.capitalize(outcome.getType().get(0).getCode()));
404          if (resultPathBase == null)
405            resultPathBase = outcome.getPath();
406          else if (!outcome.getPath().startsWith(resultPathBase))
407            throw new DefinitionException("Adding wrong path");
408          result.getElement().add(outcome);
409          baseCursor++;
410          diffCursor = differential.getElement().indexOf(diffMatches.get(0))+1;
411          if (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
412            if (pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".")) {
413              if (outcome.getType().size() > 1)
414                throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName);
415              StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
416              if (dt == null)
417                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");
418              contextName = dt.getUrl();
419              int start = diffCursor;
420              while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+"."))
421                diffCursor++;
422              processPaths(result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start-1, dt.getSnapshot().getElement().size()-1,
423                  diffCursor - 1, url, profileName+pathTail(diffMatches, 0), diffMatches.get(0).getPath(), trimDifferential, contextName, resultPathBase, false);
424            }
425          }
426        } else {
427          // ok, the differential slices the item. Let's check our pre-conditions to ensure that this is correct
428          if (!unbounded(currentBase) && !isSlicedToOneOnly(diffMatches.get(0)))
429            // you can only slice an element that doesn't repeat if the sum total of your slices is limited to 1
430            // (but you might do that in order to split up constraints by type)
431            throw new DefinitionException("Attempt to a slice an element that does not repeat: "+currentBase.getPath()+"/"+currentBase.getName()+" from "+contextName);
432          if (!diffMatches.get(0).hasSlicing() && !isExtension(currentBase)) // well, the diff has set up a slice, but hasn't defined it. this is an error
433            throw new DefinitionException("differential does not have a slice: "+currentBase.getPath());
434
435          // well, if it passed those preconditions then we slice the dest.
436          // we're just going to accept the differential slicing at face value
437          ElementDefinition outcome = updateURLs(url, currentBase.copy());
438          outcome.setPath(fixedPath(contextPath, outcome.getPath()));
439          updateFromBase(outcome, currentBase);
440
441          if (!diffMatches.get(0).hasSlicing())
442            outcome.setSlicing(makeExtensionSlicing());
443          else
444            outcome.setSlicing(diffMatches.get(0).getSlicing().copy());
445          if (!outcome.getPath().startsWith(resultPathBase))
446            throw new DefinitionException("Adding wrong path");
447          result.getElement().add(outcome);
448
449          // 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.
450          int start = 0;
451          if (!diffMatches.get(0).hasName()) {
452            updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url);
453            if (!outcome.hasType()) {
454              throw new DefinitionException("not done yet");
455            }
456            start = 1;
457          } else 
458            checkExtensionDoco(outcome);
459
460          // now, for each entry in the diff matches, we're going to process the base item
461          // our processing scope for base is all the children of the current path
462          int nbl = findEndOfElement(base, baseCursor);
463          int ndc = diffCursor;
464          int ndl = diffCursor;
465          for (int i = start; i < diffMatches.size(); i++) {
466            // our processing scope for the differential is the item in the list, and all the items before the next one in the list
467            ndc = differential.getElement().indexOf(diffMatches.get(i));
468            ndl = findEndOfElement(differential, ndc);
469            // now we process the base scope repeatedly for each instance of the item in the differential list
470            processPaths(result, base, differential, baseCursor, ndc, nbl, ndl, url, profileName+pathTail(diffMatches, i), contextPath, trimDifferential, contextName, resultPathBase, true);
471          }
472          // ok, done with that - next in the base list
473          baseCursor = nbl+1;
474          diffCursor = ndl+1;
475        }
476      } else {
477        // the item is already sliced in the base profile.
478        // here's the rules
479        //  1. irrespective of whether the slicing is ordered or not, the definition order must be maintained
480        //  2. slice element names have to match.
481        //  3. new slices must be introduced at the end
482        // corallory: you can't re-slice existing slices. is that ok?
483
484        // we're going to need this:
485        String path = currentBase.getPath();
486        ElementDefinition original = currentBase;
487
488        if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item
489          // copy across the currentbase, and all of it's children and siblings
490          while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path)) {
491            ElementDefinition outcome = updateURLs(url, base.getElement().get(baseCursor).copy());
492            if (!outcome.getPath().startsWith(resultPathBase))
493              throw new DefinitionException("Adding wrong path");
494            result.getElement().add(outcome); // so we just copy it in
495            baseCursor++;
496          }
497        } else {
498          // first - check that the slicing is ok
499          boolean closed = currentBase.getSlicing().getRules() == SlicingRules.CLOSED;
500          int diffpos = 0;
501          boolean isExtension = cpath.endsWith(".extension") || cpath.endsWith(".modifierExtension");
502          if (diffMatches.get(0).hasSlicing()) { // it might be null if the differential doesn't want to say anything about slicing
503            if (!isExtension)
504            diffpos++; // if there's a slice on the first, we'll ignore any content it has
505            ElementDefinitionSlicingComponent dSlice = diffMatches.get(0).getSlicing();
506            ElementDefinitionSlicingComponent bSlice = currentBase.getSlicing();
507            if (!orderMatches(dSlice.getOrderedElement(), bSlice.getOrderedElement()))
508              throw new DefinitionException("Slicing rules on differential ("+summariseSlicing(dSlice)+") do not match those on base ("+summariseSlicing(bSlice)+") - order @ "+path+" ("+contextName+")");
509            if (!discriiminatorMatches(dSlice.getDiscriminator(), bSlice.getDiscriminator()))
510             throw new DefinitionException("Slicing rules on differential ("+summariseSlicing(dSlice)+") do not match those on base ("+summariseSlicing(bSlice)+") - disciminator @ "+path+" ("+contextName+")");
511            if (!ruleMatches(dSlice.getRules(), bSlice.getRules()))
512             throw new DefinitionException("Slicing rules on differential ("+summariseSlicing(dSlice)+") do not match those on base ("+summariseSlicing(bSlice)+") - rule @ "+path+" ("+contextName+")");
513          }
514          ElementDefinition outcome = updateURLs(url, currentBase.copy());
515          outcome.setPath(fixedPath(contextPath, outcome.getPath()));
516          updateFromBase(outcome, currentBase);
517          if (diffMatches.get(0).hasSlicing() && !isExtension) {
518            updateFromSlicing(outcome.getSlicing(), diffMatches.get(0).getSlicing());
519            updateFromDefinition(outcome, diffMatches.get(0), profileName, closed, url); // if there's no slice, we don't want to update the unsliced description
520          }
521          if (diffMatches.get(0).hasSlicing() && !diffMatches.get(0).hasName())
522            diffpos++;
523            
524          result.getElement().add(outcome);
525
526          // now, we have two lists, base and diff. we're going to work through base, looking for matches in diff.
527          List<ElementDefinition> baseMatches = getSiblings(base.getElement(), currentBase);
528          for (ElementDefinition baseItem : baseMatches) {
529            baseCursor = base.getElement().indexOf(baseItem);
530            outcome = updateURLs(url, baseItem.copy());
531            updateFromBase(outcome, currentBase);
532            outcome.setPath(fixedPath(contextPath, outcome.getPath()));
533            outcome.setSlicing(null);
534            if (!outcome.getPath().startsWith(resultPathBase))
535              throw new DefinitionException("Adding wrong path");
536            if (diffpos < diffMatches.size() && diffMatches.get(diffpos).getName().equals(outcome.getName())) {
537              // if there's a diff, we update the outcome with diff
538              // no? updateFromDefinition(outcome, diffMatches.get(diffpos), profileName, closed, url);
539              //then process any children
540              int nbl = findEndOfElement(base, baseCursor);
541              int ndc = differential.getElement().indexOf(diffMatches.get(diffpos));
542              int ndl = findEndOfElement(differential, ndc);
543              // now we process the base scope repeatedly for each instance of the item in the differential list
544              processPaths(result, base, differential, baseCursor, ndc, nbl, ndl, url, profileName+pathTail(diffMatches, diffpos), contextPath, closed, contextName, resultPathBase, true);
545              // ok, done with that - now set the cursors for if this is the end
546              baseCursor = nbl+1;
547              diffCursor = ndl+1;
548              diffpos++;
549            } else {
550              result.getElement().add(outcome);
551              baseCursor++;
552              // just copy any children on the base
553              while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path) && !base.getElement().get(baseCursor).getPath().equals(path)) {
554                outcome = updateURLs(url, currentBase.copy());
555                outcome.setPath(fixedPath(contextPath, outcome.getPath()));
556                if (!outcome.getPath().startsWith(resultPathBase))
557                  throw new DefinitionException("Adding wrong path");
558                result.getElement().add(outcome);
559                baseCursor++;
560              }
561            }
562          }
563          // finally, we process any remaining entries in diff, which are new (and which are only allowed if the base wasn't closed
564          if (closed && diffpos < diffMatches.size())
565            throw new DefinitionException("The base snapshot marks a slicing as closed, but the differential tries to extend it in "+profileName+" at "+path+" ("+cpath+")");
566          while (diffpos < diffMatches.size()) {
567            ElementDefinition diffItem = diffMatches.get(diffpos);
568            for (ElementDefinition baseItem : baseMatches)
569              if (baseItem.getName().equals(diffItem.getName()))
570                throw new DefinitionException("Named items are out of order in the slice");
571            outcome = updateURLs(url, original.copy());
572            outcome.setPath(fixedPath(contextPath, outcome.getPath()));
573            updateFromBase(outcome, currentBase);
574            outcome.setSlicing(null);
575            if (!outcome.getPath().startsWith(resultPathBase))
576              throw new DefinitionException("Adding wrong path");
577            result.getElement().add(outcome);
578            updateFromDefinition(outcome, diffItem, profileName, trimDifferential, url);
579            diffpos++;
580          }
581        }
582      }
583    }
584  }
585
586
587  private ElementDefinition overWriteWithCurrent(ElementDefinition profile, ElementDefinition usage) {
588    ElementDefinition res = profile.copy();
589    if (usage.hasName())
590      res.setName(usage.getName());
591    if (usage.hasLabel())
592      res.setLabel(usage.getLabel());
593    for (Coding c : usage.getCode())
594      res.addCode(c);
595    
596    if (usage.hasDefinition())
597      res.setDefinition(usage.getDefinition());
598    if (usage.hasShort())
599      res.setShort(usage.getShort());
600    if (usage.hasComments())
601      res.setComments(usage.getComments());
602    if (usage.hasRequirements())
603      res.setRequirements(usage.getRequirements());
604    for (StringType c : usage.getAlias())
605      res.addAlias(c.getValue());
606    if (usage.hasMin())
607      res.setMin(usage.getMin());
608    if (usage.hasMax())
609      res.setMax(usage.getMax());
610     
611    if (usage.hasFixed())
612      res.setFixed(usage.getFixed());
613    if (usage.hasPattern())
614      res.setPattern(usage.getPattern());
615    if (usage.hasExample())
616      res.setExample(usage.getExample());
617    if (usage.hasMinValue())
618      res.setMinValue(usage.getMinValue());
619    if (usage.hasMaxValue())
620      res.setMaxValue(usage.getMaxValue());     
621    if (usage.hasMaxLength())
622      res.setMaxLength(usage.getMaxLength());
623    if (usage.hasMustSupport())
624      res.setMustSupport(usage.getMustSupport());
625    if (usage.hasBinding())
626      res.setBinding(usage.getBinding().copy());
627    for (ElementDefinitionConstraintComponent c : usage.getConstraint())
628      res.addConstraint(c);
629    
630    return res;
631  }
632
633
634  private boolean checkExtensionDoco(ElementDefinition base) {
635    // see task 3970. For an extension, there's no point copying across all the underlying definitional stuff
636    boolean isExtension = base.getPath().equals("Extension") || base.getPath().endsWith(".extension") || base.getPath().endsWith(".modifierExtension");
637    if (isExtension) {
638      base.setDefinition("An Extension");
639      base.setShort("Extension");
640      base.setCommentsElement(null);
641      base.setRequirementsElement(null);
642      base.getAlias().clear();
643      base.getMapping().clear();
644    }
645    return isExtension;
646  }
647
648
649  private String pathTail(List<ElementDefinition> diffMatches, int i) {
650    
651    ElementDefinition d = diffMatches.get(i);
652    String s = d.getPath().contains(".") ? d.getPath().substring(d.getPath().lastIndexOf(".")+1) : d.getPath();
653    return "."+s + (d.hasType() && d.getType().get(0).hasProfile() ? "["+d.getType().get(0).getProfile().get(0).asStringValue()+"]" : "");
654  }
655
656
657  private void markDerived(ElementDefinition outcome) {
658    for (ElementDefinitionConstraintComponent inv : outcome.getConstraint())
659      inv.setUserData(IS_DERIVED, true);
660  }
661
662
663  private String summariseSlicing(ElementDefinitionSlicingComponent slice) {
664    StringBuilder b = new StringBuilder();
665    boolean first = true;
666    for (StringType d : slice.getDiscriminator()) {
667      if (first)
668        first = false;
669      else
670        b.append(", ");
671      b.append(d);
672    }
673    b.append("(");
674    if (slice.hasOrdered())
675      b.append(slice.getOrderedElement().asStringValue());
676    b.append("/");
677    if (slice.hasRules())
678      b.append(slice.getRules().toCode());
679    b.append(")");
680    if (slice.hasDescription()) {
681      b.append(" \"");
682      b.append(slice.getDescription());
683      b.append("\"");
684    }
685    return b.toString();
686  }
687
688
689  private void updateFromBase(ElementDefinition derived, ElementDefinition base) {
690    if (base.hasBase()) {
691      derived.getBase().setPath(base.getBase().getPath());
692      derived.getBase().setMin(base.getBase().getMin());
693      derived.getBase().setMax(base.getBase().getMax());
694    } else {
695      derived.getBase().setPath(base.getPath());
696      derived.getBase().setMin(base.getMin());
697      derived.getBase().setMax(base.getMax());
698    }
699  }
700
701
702  private boolean pathStartsWith(String p1, String p2) {
703    return p1.startsWith(p2);
704  }
705
706  private boolean pathMatches(String p1, String p2) {
707    return p1.equals(p2) || (p2.endsWith("[x]") && p1.startsWith(p2.substring(0, p2.length()-3)) && !p1.substring(p2.length()-3).contains("."));
708  }
709
710
711  private String fixedPath(String contextPath, String pathSimple) {
712    if (contextPath == null)
713      return pathSimple;
714    return contextPath+"."+pathSimple.substring(pathSimple.indexOf(".")+1);
715  }
716
717
718  private StructureDefinition getProfileForDataType(TypeRefComponent type)  {
719    StructureDefinition sd = null;
720    if (type.hasProfile())  
721      sd = context.fetchResource(StructureDefinition.class, type.getProfile().get(0).asStringValue()); 
722    if (sd == null) 
723      sd = context.fetchTypeDefinition(type.getCode());
724    if (sd == null)
725      System.out.println("XX: failed to find profle for type: " + type.getCode()); // debug GJM
726    return sd;
727  }
728
729
730  public static String typeCode(List<TypeRefComponent> types) {
731    StringBuilder b = new StringBuilder();
732    boolean first = true;
733    for (TypeRefComponent type : types) {
734      if (first) first = false; else b.append(", ");
735      b.append(type.getCode());
736      if (type.hasProfile())
737        b.append("{"+type.getProfile()+"}");
738    }
739    return b.toString();
740  }
741
742
743  private boolean isDataType(List<TypeRefComponent> types) {
744    if (types.isEmpty())
745      return false;
746    for (TypeRefComponent type : types) {
747      String t = type.getCode();
748      if (!isDataType(t) && !t.equals("Reference") && !t.equals("Narrative") && !t.equals("Extension") && !t.equals("ElementDefinition") && !isPrimitive(t))
749        return false;
750    }
751    return true;
752  }
753
754
755  /**
756   * Finds internal references in an Element's Binding and StructureDefinition references (in TypeRef) and bases them on the given url
757   * @param url - the base url to use to turn internal references into absolute references
758   * @param element - the Element to update
759   * @return - the updated Element
760   */
761  private ElementDefinition updateURLs(String url, ElementDefinition element) {
762    if (element != null) {
763      ElementDefinition defn = element;
764      if (defn.hasBinding() && defn.getBinding().getValueSet() instanceof Reference && ((Reference)defn.getBinding().getValueSet()).getReference().startsWith("#"))
765        ((Reference)defn.getBinding().getValueSet()).setReference(url+((Reference)defn.getBinding().getValueSet()).getReference());
766      for (TypeRefComponent t : defn.getType()) {
767        for (UriType tp : t.getProfile()) {
768                if (tp.getValue().startsWith("#"))
769            tp.setValue(url+t.getProfile());
770        }
771      }
772    }
773    return element;
774  }
775
776  private List<ElementDefinition> getSiblings(List<ElementDefinition> list, ElementDefinition current) {
777    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
778    String path = current.getPath();
779    int cursor = list.indexOf(current)+1;
780    while (cursor < list.size() && list.get(cursor).getPath().length() >= path.length()) {
781      if (pathMatches(list.get(cursor).getPath(), path))
782        result.add(list.get(cursor));
783      cursor++;
784    }
785    return result;
786  }
787
788  private void updateFromSlicing(ElementDefinitionSlicingComponent dst, ElementDefinitionSlicingComponent src) {
789    if (src.hasOrderedElement())
790      dst.setOrderedElement(src.getOrderedElement().copy());
791    if (src.hasDiscriminator())
792      dst.getDiscriminator().addAll(src.getDiscriminator());
793    if (src.hasRulesElement())
794      dst.setRulesElement(src.getRulesElement().copy());
795  }
796
797  private boolean orderMatches(BooleanType diff, BooleanType base) {
798    return (diff == null) || (base == null) || (diff.getValue() == base.getValue());
799  }
800
801  private boolean discriiminatorMatches(List<StringType> diff, List<StringType> base) {
802    if (diff.isEmpty() || base.isEmpty())
803        return true;
804    if (diff.size() != base.size())
805        return false;
806    for (int i = 0; i < diff.size(); i++)
807        if (!diff.get(i).getValue().equals(base.get(i).getValue()))
808                return false;
809    return true;
810  }
811
812  private boolean ruleMatches(SlicingRules diff, SlicingRules base) {
813    return (diff == null) || (base == null) || (diff == base) || (diff == SlicingRules.OPEN) ||
814        ((diff == SlicingRules.OPENATEND && base == SlicingRules.CLOSED));
815  }
816
817  private boolean isSlicedToOneOnly(ElementDefinition e) {
818    return (e.hasSlicing() && e.hasMaxElement() && e.getMax().equals("1"));
819  }
820
821  private ElementDefinitionSlicingComponent makeExtensionSlicing() {
822        ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent();
823    slice.addDiscriminator("url");
824    slice.setOrdered(false);
825    slice.setRules(SlicingRules.OPEN);
826    return slice;
827  }
828
829  private boolean isExtension(ElementDefinition currentBase) {
830    return currentBase.getPath().endsWith(".extension") || currentBase.getPath().endsWith(".modifierExtension");
831  }
832
833  private List<ElementDefinition> getDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, String profileName) {
834    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
835    for (int i = start; i <= end; i++) {
836      String statedPath = context.getElement().get(i).getPath();
837      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.substring(path.length()).contains("."))) {
838        result.add(context.getElement().get(i));
839      } else if (result.isEmpty()) {
840//        System.out.println("ignoring "+statedPath+" in differential of "+profileName);
841      }
842    }
843    return result;
844  }
845
846  private int findEndOfElement(StructureDefinitionDifferentialComponent context, int cursor) {
847            int result = cursor;
848            String path = context.getElement().get(cursor).getPath()+".";
849            while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path))
850              result++;
851            return result;
852          }
853
854  private int findEndOfElement(StructureDefinitionSnapshotComponent context, int cursor) {
855            int result = cursor;
856            String path = context.getElement().get(cursor).getPath()+".";
857            while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path))
858              result++;
859            return result;
860          }
861
862  private boolean unbounded(ElementDefinition definition) {
863    StringType max = definition.getMaxElement();
864    if (max == null)
865      return false; // this is not valid
866    if (max.getValue().equals("1"))
867      return false;
868    if (max.getValue().equals("0"))
869      return false;
870    return true;
871  }
872
873  private void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, boolean trimDifferential, String purl) throws DefinitionException, FHIRException {
874    // we start with a clone of the base profile ('dest') and we copy from the profile ('source')
875    // over the top for anything the source has
876    ElementDefinition base = dest;
877    ElementDefinition derived = source;
878    derived.setUserData(DERIVATION_POINTER, base);
879
880    if (derived != null) {
881      boolean isExtension = checkExtensionDoco(base);
882
883      if (derived.hasShortElement()) {
884        if (!Base.compareDeep(derived.getShortElement(), base.getShortElement(), false))
885          base.setShortElement(derived.getShortElement().copy());
886        else if (trimDifferential)
887          derived.setShortElement(null);
888        else if (derived.hasShortElement())
889          derived.getShortElement().setUserData(DERIVATION_EQUALS, true);
890      }
891
892      if (derived.hasDefinitionElement()) {
893        if (derived.getDefinition().startsWith("..."))
894          base.setDefinition(base.getDefinition()+"\r\n"+derived.getDefinition().substring(3));
895        else if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false))
896          base.setDefinitionElement(derived.getDefinitionElement().copy());
897        else if (trimDifferential)
898          derived.setDefinitionElement(null);
899        else if (derived.hasDefinitionElement())
900          derived.getDefinitionElement().setUserData(DERIVATION_EQUALS, true);
901      }
902
903      if (derived.hasCommentsElement()) {
904        if (derived.getComments().startsWith("..."))
905          base.setComments(base.getComments()+"\r\n"+derived.getComments().substring(3));
906        else if (!Base.compareDeep(derived.getCommentsElement(), base.getCommentsElement(), false))
907          base.setCommentsElement(derived.getCommentsElement().copy());
908        else if (trimDifferential)
909          base.setCommentsElement(derived.getCommentsElement().copy());
910        else if (derived.hasCommentsElement())
911          derived.getCommentsElement().setUserData(DERIVATION_EQUALS, true);
912      }
913
914      if (derived.hasLabelElement()) {
915        if (derived.getLabel().startsWith("..."))
916          base.setLabel(base.getLabel()+"\r\n"+derived.getLabel().substring(3));
917        else if (!Base.compareDeep(derived.getLabelElement(), base.getLabelElement(), false))
918          base.setLabelElement(derived.getLabelElement().copy());
919        else if (trimDifferential)
920          base.setLabelElement(derived.getLabelElement().copy());
921        else if (derived.hasLabelElement())
922          derived.getLabelElement().setUserData(DERIVATION_EQUALS, true);
923      }
924
925      if (derived.hasRequirementsElement()) {
926        if (derived.getRequirements().startsWith("..."))
927          base.setRequirements(base.getRequirements()+"\r\n"+derived.getRequirements().substring(3));
928        else if (!Base.compareDeep(derived.getRequirementsElement(), base.getRequirementsElement(), false))
929          base.setRequirementsElement(derived.getRequirementsElement().copy());
930        else if (trimDifferential)
931          base.setRequirementsElement(derived.getRequirementsElement().copy());
932        else if (derived.hasRequirementsElement())
933          derived.getRequirementsElement().setUserData(DERIVATION_EQUALS, true);
934      }
935      // sdf-9
936      if (derived.hasRequirements() && !base.getPath().contains("."))
937        derived.setRequirements(null);
938      if (base.hasRequirements() && !base.getPath().contains("."))
939        base.setRequirements(null);
940
941      if (derived.hasAlias()) {
942        if (!Base.compareDeep(derived.getAlias(), base.getAlias(), false))
943          for (StringType s : derived.getAlias()) {
944            if (!base.hasAlias(s.getValue()))
945              base.getAlias().add(s.copy());
946          }
947        else if (trimDifferential)
948          derived.getAlias().clear();
949        else
950          for (StringType t : derived.getAlias())
951            t.setUserData(DERIVATION_EQUALS, true);
952      }
953
954      if (derived.hasMinElement()) {
955        if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) {
956          if (derived.getMin() < base.getMin())
957            messages.add(new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Derived min  ("+Integer.toString(derived.getMin())+") cannot be less than base min ("+Integer.toString(base.getMin())+")", IssueSeverity.ERROR));
958          base.setMinElement(derived.getMinElement().copy());
959        } else if (trimDifferential)
960          derived.setMinElement(null);
961        else
962          derived.getMinElement().setUserData(DERIVATION_EQUALS, true);
963      }
964
965      if (derived.hasMaxElement()) {
966        if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) {
967          if (isLargerMax(derived.getMax(), base.getMax()))
968            messages.add(new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Derived max ("+derived.getMax()+") cannot be greater than base max ("+base.getMax()+")", IssueSeverity.ERROR));
969          base.setMaxElement(derived.getMaxElement().copy());
970        } else if (trimDifferential)
971          derived.setMaxElement(null);
972        else
973          derived.getMaxElement().setUserData(DERIVATION_EQUALS, true);
974      }
975
976      if (derived.hasFixed()) {
977        if (!Base.compareDeep(derived.getFixed(), base.getFixed(), true)) {
978          base.setFixed(derived.getFixed().copy());
979        } else if (trimDifferential)
980          derived.setFixed(null);
981        else
982          derived.getFixed().setUserData(DERIVATION_EQUALS, true);
983      }
984
985      if (derived.hasPattern()) {
986        if (!Base.compareDeep(derived.getPattern(), base.getPattern(), false)) {
987          base.setPattern(derived.getPattern().copy());
988        } else
989          if (trimDifferential)
990            derived.setPattern(null);
991          else
992            derived.getPattern().setUserData(DERIVATION_EQUALS, true);
993      }
994
995      if (derived.hasExample()) {
996        if (!Base.compareDeep(derived.getExample(), base.getExample(), false))
997          base.setExample(derived.getExample().copy());
998        else if (trimDifferential)
999          derived.setExample(null);
1000        else
1001          derived.getExample().setUserData(DERIVATION_EQUALS, true);
1002      }
1003
1004      if (derived.hasMaxLengthElement()) {
1005        if (!Base.compareDeep(derived.getMaxLengthElement(), base.getMaxLengthElement(), false))
1006          base.setMaxLengthElement(derived.getMaxLengthElement().copy());
1007        else if (trimDifferential)
1008          derived.setMaxLengthElement(null);
1009        else
1010          derived.getMaxLengthElement().setUserData(DERIVATION_EQUALS, true);
1011      }
1012  
1013      if (derived.hasMaxValue()) {
1014        if (!Base.compareDeep(derived.getMaxValue(), base.getMaxValue(), false))
1015          base.setMaxValue(derived.getMaxValue().copy());
1016        else if (trimDifferential)
1017          derived.setMaxValue(null);
1018        else
1019          derived.getMaxValue().setUserData(DERIVATION_EQUALS, true);
1020      }
1021  
1022      if (derived.hasMinValue()) {
1023        if (!Base.compareDeep(derived.getMinValue(), base.getMinValue(), false))
1024          base.setMinValue(derived.getMinValue().copy());
1025        else if (trimDifferential)
1026          derived.setMinValue(null);
1027        else
1028          derived.getMinValue().setUserData(DERIVATION_EQUALS, true);
1029      }
1030
1031      // todo: what to do about conditions?
1032      // condition : id 0..*
1033
1034      if (derived.hasMustSupportElement()) {
1035        if (!Base.compareDeep(derived.getMustSupportElement(), base.getMustSupportElement(), false))
1036          base.setMustSupportElement(derived.getMustSupportElement().copy());
1037        else if (trimDifferential)
1038          derived.setMustSupportElement(null);
1039        else
1040          derived.getMustSupportElement().setUserData(DERIVATION_EQUALS, true);
1041      }
1042
1043
1044      // profiles cannot change : isModifier, defaultValue, meaningWhenMissing
1045      // but extensions can change isModifier
1046      if (isExtension) {
1047        if (!Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false))
1048          base.setIsModifierElement(derived.getIsModifierElement().copy());
1049        else if (trimDifferential)
1050          derived.setIsModifierElement(null);
1051        else
1052          derived.getIsModifierElement().setUserData(DERIVATION_EQUALS, true);
1053      }
1054
1055      if (derived.hasBinding()) {
1056        if (!Base.compareDeep(derived.getBinding(), base.getBinding(), false)) {
1057          if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && derived.getBinding().getStrength() != BindingStrength.REQUIRED)
1058            messages.add(new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode(), IssueSeverity.ERROR));
1059//            throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode());
1060          else if (base.hasBinding() && derived.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED) {
1061            ValueSetExpansionOutcome expBase = context.expandVS(context.fetchResource(ValueSet.class, base.getBinding().getValueSetReference().getReference()), true);
1062            ValueSetExpansionOutcome expDerived = context.expandVS(context.fetchResource(ValueSet.class, derived.getBinding().getValueSetReference().getReference()), true);
1063            if (expBase.getValueset() == null)
1064              messages.add(new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSetReference().getReference()+" could not be expanded", IssueSeverity.WARNING));
1065            else if (expDerived.getValueset() == null)
1066              messages.add(new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSetReference().getReference()+" could not be expanded", IssueSeverity.WARNING));
1067            else if (!isSubset(expBase.getValueset(), expDerived.getValueset()))
1068              messages.add(new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSetReference().getReference()+" is not a subset of binding "+base.getBinding().getValueSetReference().getReference(), IssueSeverity.ERROR));
1069          }
1070          base.setBinding(derived.getBinding().copy());
1071        } else if (trimDifferential)
1072          derived.setBinding(null);
1073        else
1074          derived.getBinding().setUserData(DERIVATION_EQUALS, true);
1075      } // else if (base.hasBinding() && doesn't have bindable type )
1076        //  base
1077
1078      if (derived.hasIsSummaryElement()) {
1079        if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false))
1080          base.setIsSummaryElement(derived.getIsSummaryElement().copy());
1081        else if (trimDifferential)
1082          derived.setIsSummaryElement(null);
1083        else
1084          derived.getIsSummaryElement().setUserData(DERIVATION_EQUALS, true);
1085      }
1086
1087      if (derived.hasType()) {
1088        if (!Base.compareDeep(derived.getType(), base.getType(), false)) {
1089          if (base.hasType()) {
1090            for (TypeRefComponent ts : derived.getType()) {
1091              boolean ok = false;
1092              CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1093              for (TypeRefComponent td : base.getType()) {
1094                b.append(td.getCode());
1095                if (td.hasCode() && (td.getCode().equals(ts.getCode()) || td.getCode().equals("Extension") ||
1096                    td.getCode().equals("Element") || td.getCode().equals("*") ||
1097                    ((td.getCode().equals("Resource") || (td.getCode().equals("DomainResource")) && pkp.isResource(ts.getCode())))))
1098                  ok = true;
1099              }
1100              if (!ok)
1101                throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal constrained type "+ts.getCode()+" from "+b.toString());
1102            }
1103          }
1104          base.getType().clear();
1105          for (TypeRefComponent t : derived.getType()) {
1106            TypeRefComponent tt = t.copy();
1107//            tt.setUserData(DERIVATION_EQUALS, true);
1108            base.getType().add(tt);
1109          }
1110        }
1111        else if (trimDifferential)
1112          derived.getType().clear();
1113        else
1114          for (TypeRefComponent t : derived.getType())
1115            t.setUserData(DERIVATION_EQUALS, true);
1116      }
1117
1118      if (derived.hasMapping()) {
1119        // todo: mappings are not cumulative - one replaces another
1120        if (!Base.compareDeep(derived.getMapping(), base.getMapping(), false)) {
1121          for (ElementDefinitionMappingComponent s : derived.getMapping()) {
1122            boolean found = false;
1123            for (ElementDefinitionMappingComponent d : base.getMapping()) {
1124              found = found || (d.getIdentity().equals(s.getIdentity()) && d.getMap().equals(s.getMap()));
1125            }
1126            if (!found)
1127              base.getMapping().add(s);
1128          }
1129        }
1130        else if (trimDifferential)
1131          derived.getMapping().clear();
1132        else
1133          for (ElementDefinitionMappingComponent t : derived.getMapping())
1134            t.setUserData(DERIVATION_EQUALS, true);
1135      }
1136
1137      // todo: constraints are cumulative. there is no replacing
1138      for (ElementDefinitionConstraintComponent s : base.getConstraint()) 
1139        s.setUserData(IS_DERIVED, true);
1140      if (derived.hasConstraint()) {
1141        for (ElementDefinitionConstraintComponent s : derived.getConstraint()) {
1142          base.getConstraint().add(s.copy());
1143        }
1144      }
1145    }
1146  }
1147
1148  private boolean isLargerMax(String derived, String base) {
1149    if ("*".equals(base))
1150      return false;
1151    if ("*".equals(derived))
1152      return true;
1153    return Integer.parseInt(derived) > Integer.parseInt(base);
1154  }
1155
1156
1157  private boolean isSubset(ValueSet expBase, ValueSet expDerived) {
1158    return codesInExpansion(expDerived.getExpansion().getContains(), expBase.getExpansion());
1159  }
1160
1161
1162  private boolean codesInExpansion(List<ValueSetExpansionContainsComponent> contains, ValueSetExpansionComponent expansion) {
1163    for (ValueSetExpansionContainsComponent cc : contains) {
1164      if (!inExpansion(cc, expansion.getContains()))
1165        return false;
1166      if (!codesInExpansion(cc.getContains(), expansion))
1167        return false;
1168    }
1169    return true;
1170  }
1171
1172
1173  private boolean inExpansion(ValueSetExpansionContainsComponent cc, List<ValueSetExpansionContainsComponent> contains) {
1174    for (ValueSetExpansionContainsComponent cc1 : contains) {
1175      if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode()))
1176        return true;
1177      if (inExpansion(cc,  cc1.getContains()))
1178        return true;
1179    }
1180    return false;
1181  }
1182
1183
1184  public XhtmlNode generateExtensionTable(String defFile, StructureDefinition ed, String imageFolder, boolean inlineGraphics, boolean full, String corePath, Set<String> outputTracker) throws IOException, FHIRException {
1185    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics);
1186    TableModel model = gen.initNormalTable(corePath, false, true, ed.getId(), false);
1187
1188    boolean deep = false;
1189    boolean vdeep = false;
1190    for (ElementDefinition eld : ed.getSnapshot().getElement()) {
1191      deep = deep || eld.getPath().contains("Extension.extension.");
1192      vdeep = vdeep || eld.getPath().contains("Extension.extension.extension.");
1193    }
1194    Row r = gen.new Row();
1195    model.getRows().add(r);
1196    r.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), ed.getSnapshot().getElement().get(0).getIsModifier() ? "modifierExtension" : "extension", null, null));
1197    r.getCells().add(gen.new Cell());
1198    r.getCells().add(gen.new Cell(null, null, describeCardinality(ed.getSnapshot().getElement().get(0), null, new UnusedTracker()), null, null));
1199
1200    if (full || vdeep) {
1201      r.getCells().add(gen.new Cell("", "", "Extension", null, null));
1202
1203      r.setIcon(deep ? "icon_extension_complex.png" : "icon_extension_simple.png", deep ? HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX : HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
1204      List<ElementDefinition> children = getChildren(ed.getSnapshot().getElement(), ed.getSnapshot().getElement().get(0));
1205      for (ElementDefinition child : children)
1206        if (!child.getPath().endsWith(".id"))
1207          genElement(defFile == null ? "" : defFile+"-definitions.html#extension.", gen, r.getSubRows(), child, ed.getSnapshot().getElement(), null, true, defFile, true, full, corePath);
1208    } else if (deep) {
1209      List<ElementDefinition> children = new ArrayList<ElementDefinition>();
1210      for (ElementDefinition ted : ed.getSnapshot().getElement()) {
1211        if (ted.getPath().equals("Extension.extension"))
1212          children.add(ted);
1213      }
1214
1215      r.getCells().add(gen.new Cell("", "", "Extension", null, null));
1216      r.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
1217      
1218      for (ElementDefinition c : children) {
1219        ElementDefinition ved = getValueFor(ed, c);
1220        ElementDefinition ued = getUrlFor(ed, c);
1221        if (ved != null && ued != null) {
1222          Row r1 = gen.new Row();
1223          r.getSubRows().add(r1);
1224          r1.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), ((UriType) ued.getFixed()).getValue(), null, null));
1225          r1.getCells().add(gen.new Cell());
1226          r1.getCells().add(gen.new Cell(null, null, describeCardinality(c, null, new UnusedTracker()), null, null));
1227          genTypes(gen, r1, ved, defFile, ed, corePath);
1228          r1.getCells().add(gen.new Cell(null, null, c.getDefinition(), null, null));
1229          r1.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);      
1230        }
1231      }
1232    } else  {
1233      ElementDefinition ved = null;
1234      for (ElementDefinition ted : ed.getSnapshot().getElement()) {
1235        if (ted.getPath().startsWith("Extension.value"))
1236          ved = ted;
1237      }
1238
1239      genTypes(gen, r, ved, defFile, ed, corePath);
1240
1241      r.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);      
1242    }
1243    Cell c = gen.new Cell("", "", "URL = "+ed.getUrl(), null, null);
1244    c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, ed.getName()+": "+ed.getDescription(), null));
1245    c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, describeExtensionContext(ed), null));
1246    r.getCells().add(c);
1247
1248
1249    return gen.generate(model, corePath, 0, outputTracker);
1250    }
1251
1252  private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) {
1253    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
1254    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
1255      if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url"))
1256        return ed.getSnapshot().getElement().get(i);
1257      i++;
1258    }
1259    return null;
1260  }
1261
1262  private ElementDefinition getValueFor(StructureDefinition ed, ElementDefinition c) {
1263    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
1264    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
1265      if (ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".value"))
1266        return ed.getSnapshot().getElement().get(i);
1267      i++;
1268    }
1269    return null;
1270  }
1271
1272
1273  private Cell genTypes(HierarchicalTableGenerator gen, Row r, ElementDefinition e, String profileBaseFileName, StructureDefinition profile, String corePath) {
1274    Cell c = gen.new Cell();
1275    r.getCells().add(c);
1276    List<TypeRefComponent> types = e.getType();
1277    if (!e.hasType()) {
1278      if (e.hasNameReference()) {
1279        ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), e.getNameReference());
1280        if (ed == null)
1281          c.getPieces().add(gen.new Piece(null, "Unknown reference to "+e.getNameReference(), null));
1282        else
1283          c.getPieces().add(gen.new Piece("#"+ed.getPath(), "See "+ed.getPath(), null));
1284        return c;
1285      } else {
1286      ElementDefinition d = (ElementDefinition) e.getUserData(DERIVATION_POINTER);
1287      if (d != null && d.hasType()) {
1288        types = new ArrayList<ElementDefinition.TypeRefComponent>();
1289        for (TypeRefComponent tr : d.getType()) {
1290          TypeRefComponent tt = tr.copy();
1291          tt.setUserData(DERIVATION_EQUALS, true);
1292          types.add(tt);
1293        }
1294      } else
1295        return c;
1296    }
1297    }
1298
1299    boolean first = true;
1300    Element source = types.get(0); // either all types are the same, or we don't consider any of them the same
1301
1302    boolean allReference = ADD_REFERENCE_TO_TABLE && !types.isEmpty();
1303    for (TypeRefComponent t : types) {
1304      if (!(t.getCode().equals("Reference") && t.hasProfile()))
1305        allReference = false;
1306    }
1307    if (allReference) {
1308      c.getPieces().add(gen.new Piece(corePath+"references.html", "Reference", null));
1309      c.getPieces().add(gen.new Piece(null, "(", null));
1310    }
1311    TypeRefComponent tl = null;
1312    for (TypeRefComponent t : types) {
1313      if (first)
1314        first = false;
1315      else if (allReference)
1316        c.addPiece(checkForNoChange(tl, gen.new Piece(null," | ", null)));
1317      else
1318        c.addPiece(checkForNoChange(tl, gen.new Piece(null,", ", null)));
1319      tl = t;
1320      if (t.getCode().equals("Reference") || (t.getCode().equals("Resource") && t.hasProfile())) {
1321        if (ADD_REFERENCE_TO_TABLE && !allReference) {
1322          c.getPieces().add(gen.new Piece(corePath+"references.html", "Reference", null));
1323          c.getPieces().add(gen.new Piece(null, "(", null));
1324        }
1325        if (t.hasProfile() && t.getProfile().get(0).getValue().startsWith("http://hl7.org/fhir/StructureDefinition/")) {
1326          StructureDefinition sd = context.fetchResource(StructureDefinition.class, t.getProfile().get(0).getValue());
1327          if (sd != null) {
1328            String disp = sd.hasDisplay() ? sd.getDisplay() : sd.getName();
1329            c.addPiece(checkForNoChange(t, gen.new Piece(corePath+sd.getUserString("path"), disp, null)));
1330          } else {
1331            String rn = t.getProfile().get(0).getValue().substring(40);
1332            c.addPiece(checkForNoChange(t, gen.new Piece(corePath+pkp.getLinkFor(rn), rn, null)));
1333          }
1334        } else if (t.getProfile().size() == 0) {
1335          c.addPiece(checkForNoChange(t, gen.new Piece(null, t.getCode(), null)));
1336        } else if (t.getProfile().get(0).getValue().startsWith("#"))
1337          c.addPiece(checkForNoChange(t, gen.new Piece(corePath+profileBaseFileName+"."+t.getProfile().get(0).getValue().substring(1).toLowerCase()+".html", t.getProfile().get(0).getValue(), null)));
1338        else
1339          c.addPiece(checkForNoChange(t, gen.new Piece(corePath+t.getProfile().get(0).getValue(), t.getProfile().get(0).getValue(), null)));
1340        if (ADD_REFERENCE_TO_TABLE && !allReference) {
1341          c.getPieces().add(gen.new Piece(null, ")", null));
1342        }
1343      } else if (t.hasProfile()) { // a profiled type
1344        String ref;
1345        ref = pkp.getLinkForProfile(profile, t.getProfile().get(0).getValue());
1346        if (ref != null) {
1347          String[] parts = ref.split("\\|");
1348          c.addPiece(checkForNoChange(t, gen.new Piece(corePath+parts[0], parts[1], t.getCode())));
1349        } else
1350          c.addPiece(checkForNoChange(t, gen.new Piece(corePath+ref, t.getCode(), null)));
1351      } else if (pkp.hasLinkFor(t.getCode())) {
1352        c.addPiece(checkForNoChange(t, gen.new Piece(corePath+pkp.getLinkFor(t.getCode()), t.getCode(), null)));
1353      } else
1354        c.addPiece(checkForNoChange(t, gen.new Piece(null, t.getCode(), null)));
1355    }
1356    if (allReference) {
1357      c.getPieces().add(gen.new Piece(null, ")", null));
1358    }
1359    return c;
1360  }
1361
1362  private ElementDefinition getElementByName(List<ElementDefinition> elements, String nameReference) {
1363    for (ElementDefinition ed : elements)
1364      if (ed.hasName() && ed.getName().equals(nameReference))
1365        return ed;
1366    return null;
1367  }
1368
1369
1370  public static String describeExtensionContext(StructureDefinition ext) {
1371    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1372    for (StringType t : ext.getContext())
1373      b.append(t.getValue());
1374    if (!ext.hasContextType())
1375      throw new Error("no context type on "+ext.getUrl());
1376    switch (ext.getContextType()) {
1377    case DATATYPE: return "Use on data type: "+b.toString();
1378    case EXTENSION: return "Use on extension: "+b.toString();
1379    case RESOURCE: return "Use on element: "+b.toString();
1380    case MAPPING: return "Use where element has mapping: "+b.toString();
1381    default:
1382      return "??";
1383    }
1384  }
1385
1386  private String describeCardinality(ElementDefinition definition, ElementDefinition fallback, UnusedTracker tracker) {
1387    IntegerType min = definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
1388    StringType max = definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
1389    if (min.isEmpty() && fallback != null)
1390      min = fallback.getMinElement();
1391    if (max.isEmpty() && fallback != null)
1392      max = fallback.getMaxElement();
1393
1394    tracker.used = !max.isEmpty() && !max.getValue().equals("0");
1395
1396    if (min.isEmpty() && max.isEmpty())
1397      return null;
1398    else
1399      return (!min.hasValue() ? "" : Integer.toString(min.getValue())) + ".." + (!max.hasValue() ? "" : max.getValue());
1400  }
1401
1402  private void genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef, UnusedTracker tracker, ElementDefinition fallback) {
1403    IntegerType min = !hasDef ? new IntegerType() : definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
1404    StringType max = !hasDef ? new StringType() : definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
1405    if (min.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) {
1406      ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER);
1407      min = base.getMinElement().copy();
1408      min.setUserData(DERIVATION_EQUALS, true);
1409    }
1410    if (max.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) {
1411      ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER);
1412      max = base.getMaxElement().copy();
1413      max.setUserData(DERIVATION_EQUALS, true);
1414    }
1415    if (min.isEmpty() && fallback != null)
1416      min = fallback.getMinElement();
1417    if (max.isEmpty() && fallback != null)
1418      max = fallback.getMaxElement();
1419
1420    if (!max.isEmpty())
1421      tracker.used = !max.getValue().equals("0");
1422
1423    Cell cell = gen.new Cell(null, null, null, null, null);
1424    row.getCells().add(cell);
1425    if (!min.isEmpty() || !max.isEmpty()) {
1426      cell.addPiece(checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), null)));
1427      cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", null)));
1428      cell.addPiece(checkForNoChange(min, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), null)));
1429    }
1430  }
1431
1432
1433  private Piece checkForNoChange(Element source, Piece piece) {
1434    if (source.hasUserData(DERIVATION_EQUALS)) {
1435      piece.addStyle("opacity: 0.4");
1436    }
1437    return piece;
1438  }
1439
1440  private Piece checkForNoChange(Element src1, Element src2, Piece piece) {
1441    if (src1.hasUserData(DERIVATION_EQUALS) && src2.hasUserData(DERIVATION_EQUALS)) {
1442      piece.addStyle("opacity: 0.5");
1443    }
1444    return piece;
1445  }
1446
1447  public XhtmlNode generateTable(String defFile, StructureDefinition profile, boolean diff, String imageFolder, boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, Set<String> outputTracker) throws IOException, FHIRException {
1448    assert(diff != snapshot);// check it's ok to get rid of one of these
1449    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics);
1450    TableModel model = gen.initNormalTable(corePath, false, true, profile.getId()+(diff ? "d" : "s"), false);
1451    List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement();
1452    List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
1453    profiles.add(profile);
1454    genElement(defFile == null ? null : defFile+"#"+profile.getId()+".", gen, model.getRows(), list.get(0), list, profiles, diff, profileBaseFileName, null, snapshot, corePath);
1455    return gen.generate(model, corePath, 0, outputTracker);
1456  }
1457
1458  private void genElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element, List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName, Boolean extensions, boolean snapshot, String corePath) throws IOException {
1459    StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1);
1460    String s = tail(element.getPath());
1461    List<ElementDefinition> children = getChildren(all, element);
1462    boolean isExtension = (s.equals("extension") || s.equals("modifierExtension"));
1463    if (!snapshot && extensions != null && extensions != isExtension)
1464      return;
1465
1466    if (!onlyInformationIsMapping(all, element)) {
1467      Row row = gen.new Row();
1468      row.setAnchor(element.getPath());
1469      row.setColor(getRowColor(element));
1470      boolean hasDef = element != null;
1471      boolean ext = false;
1472      if (s.equals("extension") || s.equals("modifierExtension")) {
1473        if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue()))
1474          row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
1475        else
1476          row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
1477        ext = true;
1478      } else if (!hasDef || element.getType().size() == 0)
1479        row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
1480      else if (hasDef && element.getType().size() > 1) {
1481        if (allTypesAre(element.getType(), "Reference"))
1482          row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
1483        else
1484          row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE);
1485      } else if (hasDef && element.getType().get(0).getCode() != null && element.getType().get(0).getCode().startsWith("@"))
1486        row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE);
1487      else if (hasDef && isPrimitive(element.getType().get(0).getCode()))
1488        row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
1489      else if (hasDef && isReference(element.getType().get(0).getCode()))
1490        row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
1491      else if (hasDef && isDataType(element.getType().get(0).getCode()))
1492        row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
1493      else
1494        row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
1495      String ref = defPath == null ? null : defPath + makePathLink(element);
1496      UnusedTracker used = new UnusedTracker();
1497      used.used = true;
1498      Cell left = gen.new Cell(null, ref, s, !hasDef ? null : element.getDefinition(), null);
1499      row.getCells().add(left);
1500      Cell gc = gen.new Cell();
1501      row.getCells().add(gc);
1502      if (element != null && element.getIsModifier())
1503        checkForNoChange(element.getIsModifierElement(), gc.addStyledText("This element is a modifier element", "?!", null, null, null, false));
1504      if (element != null && element.getMustSupport())
1505        checkForNoChange(element.getMustSupportElement(), gc.addStyledText("This element must be supported", "S", null, null, null, false));
1506      if (element != null && element.getIsSummary())
1507        checkForNoChange(element.getIsSummaryElement(), gc.addStyledText("This element is included in summaries", "∑", null, null, null, false));
1508      if (element != null && (!element.getConstraint().isEmpty() || !element.getCondition().isEmpty()))
1509        gc.addStyledText("This element has or is affected by some invariants", "I", null, null, null, false);
1510
1511      ExtensionContext extDefn = null;
1512      if (ext) {
1513        if (element != null && element.getType().size() == 1 && element.getType().get(0).hasProfile()) {
1514        extDefn = locateExtension(StructureDefinition.class, element.getType().get(0).getProfile().get(0).getValue());
1515          if (extDefn == null) {
1516            genCardinality(gen, element, row, hasDef, used, null);
1517            row.getCells().add(gen.new Cell(null, null, "?? "+element.getType().get(0).getProfile(), null, null));
1518            generateDescription(gen, row, element, null, used.used, profile.getUrl(), element.getType().get(0).getProfile().get(0).getValue(), profile, corePath);
1519          } else {
1520            String name = urltail(element.getType().get(0).getProfile().get(0).getValue());
1521            left.getPieces().get(0).setText(name);
1522            // left.getPieces().get(0).setReference((String) extDefn.getExtensionStructure().getTag("filename"));
1523            left.getPieces().get(0).setHint("Extension URL = "+extDefn.getUrl());
1524            genCardinality(gen, element, row, hasDef, used, extDefn.getElement());
1525            ElementDefinition valueDefn = extDefn.getExtensionValueDefinition();
1526            if (valueDefn != null && !"0".equals(valueDefn.getMax()))
1527               genTypes(gen, row, valueDefn, profileBaseFileName, profile, corePath);
1528             else // if it's complex, we just call it nothing
1529                // genTypes(gen, row, extDefn.getSnapshot().getElement().get(0), profileBaseFileName, profile);
1530              row.getCells().add(gen.new Cell(null, null, "(Complex)", null, null));
1531            generateDescription(gen, row, element, extDefn.getElement(), used.used, null, extDefn.getUrl(), profile, corePath);
1532          }
1533        } else {
1534          genCardinality(gen, element, row, hasDef, used, null);
1535          if ("0".equals(element.getMax()))
1536            row.getCells().add(gen.new Cell());            
1537          else
1538            genTypes(gen, row, element, profileBaseFileName, profile, corePath);
1539          generateDescription(gen, row, element, null, used.used, null, null, profile, corePath);
1540        }
1541      } else {
1542        genCardinality(gen, element, row, hasDef, used, null);
1543        if (hasDef && !"0".equals(element.getMax()))
1544          genTypes(gen, row, element, profileBaseFileName, profile, corePath);
1545        else
1546          row.getCells().add(gen.new Cell());
1547        generateDescription(gen, row, element, null, used.used, null, null, profile, corePath);
1548      }
1549      if (element.hasSlicing()) {
1550        if (standardExtensionSlicing(element)) {
1551          used.used = element.hasType() && element.getType().get(0).hasProfile();
1552          showMissing = false;
1553        } else {
1554          row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE);
1555          row.getCells().get(2).getPieces().clear();
1556          for (Cell cell : row.getCells())
1557            for (Piece p : cell.getPieces()) {
1558              p.addStyle("font-style: italic");
1559            }
1560        }
1561      }
1562      if (used.used || showMissing)
1563        rows.add(row);
1564      if (!used.used && !element.hasSlicing()) {
1565        for (Cell cell : row.getCells())
1566          for (Piece p : cell.getPieces()) {
1567            p.setStyle("text-decoration:line-through");
1568            p.setReference(null);
1569          }
1570      } else{
1571        for (ElementDefinition child : children)
1572          if (!child.getPath().endsWith(".id"))
1573            genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, snapshot, corePath);
1574        if (!snapshot && (extensions == null || !extensions))
1575          for (ElementDefinition child : children)
1576            if (child.getPath().endsWith(".extension"))
1577              genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, true, false, corePath);
1578      }
1579    }
1580  }
1581
1582  private ExtensionContext locateExtension(Class<StructureDefinition> class1, String value)  {
1583    if (value.contains("#")) {
1584      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
1585      if (ext == null)
1586        return null;
1587      String tail = value.substring(value.indexOf("#")+1);
1588      ElementDefinition ed = null;
1589      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
1590        if (tail.equals(ted.getName())) {
1591          ed = ted;
1592          return new ExtensionContext(ext, ed);
1593        }
1594      }
1595      return null;
1596    } else {
1597      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value);
1598      if (ext == null)
1599        return null;
1600      else 
1601        return new ExtensionContext(ext, ext.getSnapshot().getElement().get(0));
1602    }
1603  }
1604
1605
1606  private boolean extensionIsComplex(String value) {
1607    if (value.contains("#")) {
1608      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
1609    if (ext == null)
1610      return false;
1611      String tail = value.substring(value.indexOf("#")+1);
1612      ElementDefinition ed = null;
1613      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
1614        if (tail.equals(ted.getName())) {
1615          ed = ted;
1616          break;
1617        }
1618      }
1619      if (ed == null)
1620        return false;
1621      int i = ext.getSnapshot().getElement().indexOf(ed);
1622      int j = i+1;
1623      while (j < ext.getSnapshot().getElement().size() && !ext.getSnapshot().getElement().get(j).getPath().equals(ed.getPath()))
1624        j++;
1625      return j - i > 5;
1626    } else {
1627      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value);
1628      return ext != null && ext.getSnapshot().getElement().size() > 5;
1629    }
1630  }
1631
1632
1633  private String getRowColor(ElementDefinition element) {
1634    switch (element.getUserInt(UD_ERROR_STATUS)) {
1635    case STATUS_OK: return null;
1636    case STATUS_HINT: return ROW_COLOR_HINT;
1637    case STATUS_WARNING: return ROW_COLOR_WARNING;
1638    case STATUS_ERROR: return ROW_COLOR_ERROR;
1639    case STATUS_FATAL: return ROW_COLOR_FATAL;
1640    default: return null;
1641    }
1642  }
1643
1644
1645  private String urltail(String path) {
1646    if (path.contains("#"))
1647      return path.substring(path.lastIndexOf('#')+1);
1648    if (path.contains("/"))
1649      return path.substring(path.lastIndexOf('/')+1);
1650    else
1651      return path;
1652
1653  }
1654
1655  private boolean standardExtensionSlicing(ElementDefinition element) {
1656    String t = tail(element.getPath());
1657    return (t.equals("extension") || t.equals("modifierExtension"))
1658          && element.getSlicing().getRules() != SlicingRules.CLOSED && element.getSlicing().getDiscriminator().size() == 1 && element.getSlicing().getDiscriminator().get(0).getValue().equals("url");
1659  }
1660
1661
1662  private String makePathLink(ElementDefinition element) {
1663    if (!element.hasName())
1664      return element.getPath();
1665    if (!element.getPath().contains("."))
1666      return element.getName();
1667    return element.getPath().substring(0, element.getPath().lastIndexOf("."))+"."+element.getName();
1668
1669  }
1670
1671  private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath) throws IOException {
1672    Cell c = gen.new Cell();
1673    row.getCells().add(c);
1674
1675    if (used) {
1676      if (definition.getPath().endsWith("url") && definition.hasFixed()) {
1677        c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen")));
1678      } else {
1679        if (definition != null && definition.hasShort()) {
1680          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
1681          c.addPiece(checkForNoChange(definition.getShortElement(), gen.new Piece(null, definition.getShort(), null)));
1682        } else if (fallback != null && fallback != null && fallback.hasShort()) {
1683          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
1684          c.addPiece(checkForNoChange(fallback.getShortElement(), gen.new Piece(null, fallback.getShort(), null)));
1685        }
1686        if (url != null) {
1687          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
1688          String fullUrl = url.startsWith("#") ? baseURL+url : url;
1689          StructureDefinition ed = context.fetchResource(StructureDefinition.class, url);
1690          String ref = ed == null ? null : (String) corePath+ed.getUserData("path");
1691          c.getPieces().add(gen.new Piece(null, "URL: ", null).addStyle("font-weight:bold"));
1692          c.getPieces().add(gen.new Piece(ref, fullUrl, null));
1693        }
1694
1695        if (definition.hasSlicing()) {
1696          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
1697          c.getPieces().add(gen.new Piece(null, "Slice: ", null).addStyle("font-weight:bold"));
1698          c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null));
1699        }
1700        if (definition != null) {
1701          if (definition.hasBinding()) {
1702            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
1703            BindingResolution br = pkp.resolveBinding(definition.getBinding());
1704            c.getPieces().add(checkForNoChange(definition.getBinding(), gen.new Piece(null, "Binding: ", null).addStyle("font-weight:bold")));
1705            c.getPieces().add(checkForNoChange(definition.getBinding(), gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url)? br.url : corePath+br.url, br.display, null)));
1706            if (definition.getBinding().hasStrength()) {
1707              c.getPieces().add(checkForNoChange(definition.getBinding(), gen.new Piece(null, " (", null)));
1708              c.getPieces().add(checkForNoChange(definition.getBinding(), gen.new Piece(corePath+"terminologies.html#"+definition.getBinding().getStrength().toCode(), definition.getBinding().getStrength().toCode(), definition.getBinding().getStrength().getDefinition())));
1709              c.getPieces().add(gen.new Piece(null, ")", null));
1710            }
1711          }
1712          for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
1713            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
1714            c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold")));
1715            c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null)));
1716          }
1717          if (definition.hasFixed()) {
1718            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
1719            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight:bold")));
1720            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, buildJson(definition.getFixed()), null).addStyle("color: darkgreen")));
1721          } else if (definition.hasPattern()) {
1722            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
1723            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "Required Pattern: ", null).addStyle("font-weight:bold")));
1724            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen")));
1725          } else if (definition.hasExample()) {
1726            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
1727            c.getPieces().add(checkForNoChange(definition.getExample(), gen.new Piece(null, "Example: ", null).addStyle("font-weight:bold")));
1728            c.getPieces().add(checkForNoChange(definition.getExample(), gen.new Piece(null, buildJson(definition.getExample()), null).addStyle("color: darkgreen")));
1729          }
1730        }
1731      }
1732    }
1733    return c;
1734  }
1735
1736  private String buildJson(Type value) throws IOException {
1737    if (value instanceof PrimitiveType)
1738      return ((PrimitiveType) value).asStringValue();
1739
1740    IParser json = context.newJsonParser();
1741    return json.composeString(value, null);
1742  }
1743
1744
1745  public String describeSlice(ElementDefinitionSlicingComponent slicing) {
1746    return (slicing.getOrdered() ? "Ordered, " : "Unordered, ")+describe(slicing.getRules())+", by "+commas(slicing.getDiscriminator());
1747  }
1748
1749  private String commas(List<StringType> discriminator) {
1750    CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder();
1751    for (StringType id : discriminator)
1752      c.append(id.asStringValue());
1753    return c.toString();
1754  }
1755
1756
1757  private String describe(SlicingRules rules) {
1758    switch (rules) {
1759    case CLOSED : return "Closed";
1760    case OPEN : return "Open";
1761    case OPENATEND : return "Open At End";
1762    default:
1763      return "??";
1764    }
1765  }
1766
1767  private boolean onlyInformationIsMapping(List<ElementDefinition> list, ElementDefinition e) {
1768    return (!e.hasName() && !e.hasSlicing() && (onlyInformationIsMapping(e))) &&
1769        getChildren(list, e).isEmpty();
1770  }
1771
1772  private boolean onlyInformationIsMapping(ElementDefinition d) {
1773    return !d.hasShort() && !d.hasDefinition() &&
1774        !d.hasRequirements() && !d.getAlias().isEmpty() && !d.hasMinElement() &&
1775        !d.hasMax() && !d.getType().isEmpty() && !d.hasNameReference() &&
1776        !d.hasExample() && !d.hasFixed() && !d.hasMaxLengthElement() &&
1777        !d.getCondition().isEmpty() && !d.getConstraint().isEmpty() && !d.hasMustSupportElement() &&
1778        !d.hasBinding();
1779  }
1780
1781  private boolean allTypesAre(List<TypeRefComponent> types, String name) {
1782    for (TypeRefComponent t : types) {
1783      if (!t.getCode().equals(name))
1784        return false;
1785    }
1786    return true;
1787  }
1788
1789  private List<ElementDefinition> getChildren(List<ElementDefinition> all, ElementDefinition element) {
1790    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
1791    int i = all.indexOf(element)+1;
1792    while (i < all.size() && all.get(i).getPath().length() > element.getPath().length()) {
1793      if ((all.get(i).getPath().substring(0, element.getPath().length()+1).equals(element.getPath()+".")) && !all.get(i).getPath().substring(element.getPath().length()+1).contains("."))
1794        result.add(all.get(i));
1795      i++;
1796    }
1797    return result;
1798  }
1799
1800  private String tail(String path) {
1801    if (path.contains("."))
1802      return path.substring(path.lastIndexOf('.')+1);
1803    else
1804      return path;
1805  }
1806
1807  private boolean isDataType(String value) {
1808    return Utilities.existsInList(value, "Identifier", "HumanName", "Address", "ContactPoint", "Timing", "SimpleQuantity", "Quantity", "Attachment", "Range",
1809          "Period", "Ratio", "CodeableConcept", "Coding", "SampledData", "Age", "Distance", "Duration", "Count", "Money");
1810  }
1811
1812  private boolean isReference(String value) {
1813    return value.equals("Reference");
1814  }
1815
1816  public static boolean isPrimitive(String value) {
1817    return value == null || Utilities.existsInListNC(value, "boolean", "integer", "decimal", "base64Binary", "instant", "string", "date", "dateTime", "code", "oid", "uuid", "id", "uri");
1818  }
1819
1820//  private static String listStructures(StructureDefinition p) {
1821//    StringBuilder b = new StringBuilder();
1822//    boolean first = true;
1823//    for (ProfileStructureComponent s : p.getStructure()) {
1824//      if (first)
1825//        first = false;
1826//      else
1827//        b.append(", ");
1828//      if (pkp != null && pkp.hasLinkFor(s.getType()))
1829//        b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>");
1830//      else
1831//        b.append(s.getType());
1832//    }
1833//    return b.toString();
1834//  }
1835
1836
1837  public StructureDefinition getProfile(StructureDefinition source, String url) {
1838        StructureDefinition profile;
1839        String code;
1840        if (url.startsWith("#")) {
1841                profile = source;
1842                code = url.substring(1);
1843        } else {
1844                String[] parts = url.split("\\#");
1845                profile = context.fetchResource(StructureDefinition.class, parts[0]);
1846      code = parts.length == 1 ? null : parts[1];
1847        }
1848        if (profile == null)
1849                return null;
1850        if (code == null)
1851                return profile;
1852        for (Resource r : profile.getContained()) {
1853                if (r instanceof StructureDefinition && r.getId().equals(code))
1854                        return (StructureDefinition) r;
1855        }
1856        return null;
1857  }
1858
1859
1860
1861  public static class ElementDefinitionHolder {
1862    private String name;
1863    private ElementDefinition self;
1864    private int baseIndex = 0;
1865    private List<ElementDefinitionHolder> children;
1866
1867    public ElementDefinitionHolder(ElementDefinition self) {
1868      super();
1869      this.self = self;
1870      this.name = self.getPath();
1871      children = new ArrayList<ElementDefinitionHolder>();
1872    }
1873
1874    public ElementDefinition getSelf() {
1875      return self;
1876    }
1877
1878    public List<ElementDefinitionHolder> getChildren() {
1879      return children;
1880    }
1881
1882    public int getBaseIndex() {
1883      return baseIndex;
1884    }
1885
1886    public void setBaseIndex(int baseIndex) {
1887      this.baseIndex = baseIndex;
1888    }
1889
1890  }
1891
1892  public static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> {
1893
1894    private boolean inExtension;
1895    private List<ElementDefinition> snapshot;
1896    private int prefixLength;
1897    private String base;
1898    private String name;
1899    private Set<String> errors = new HashSet<String>();
1900
1901    public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, int prefixLength, String name) {
1902      this.inExtension = inExtension;
1903      this.snapshot = snapshot;
1904      this.prefixLength = prefixLength;
1905      this.base = base;
1906      this.name = name;
1907    }
1908
1909    @Override
1910    public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) {
1911      if (o1.getBaseIndex() == 0)
1912        o1.setBaseIndex(find(o1.getSelf().getPath()));
1913      if (o2.getBaseIndex() == 0)
1914        o2.setBaseIndex(find(o2.getSelf().getPath()));
1915      return o1.getBaseIndex() - o2.getBaseIndex();
1916    }
1917
1918    private int find(String path) {
1919      String actual = base+path.substring(prefixLength);
1920      for (int i = 0; i < snapshot.size(); i++) {
1921        String p = snapshot.get(i).getPath();
1922        if (p.equals(actual))
1923          return i;
1924        if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length()-3)) && !(actual.endsWith("[x]")) && !actual.substring(p.length()-3).contains("."))
1925          return i;
1926      }
1927      if (prefixLength == 0)
1928        errors.add("Differential contains path "+path+" which is not found in the base");
1929      else
1930        errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the base");
1931      return 0;
1932    }
1933
1934    public void checkForErrors(List<String> errorList) {
1935      if (errors.size() > 0) {
1936//        CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1937//        for (String s : errors)
1938//          b.append("StructureDefinition "+name+": "+s);
1939//        throw new DefinitionException(b.toString());
1940        for (String s : errors)
1941          if (s.startsWith("!"))
1942            errorList.add("!StructureDefinition "+name+": "+s.substring(1));
1943          else
1944            errorList.add("StructureDefinition "+name+": "+s);
1945      }
1946    }
1947  }
1948
1949
1950  public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors)  {
1951
1952    final List<ElementDefinition> diffList = diff.getDifferential().getElement();
1953    // first, we move the differential elements into a tree
1954    ElementDefinitionHolder edh = new ElementDefinitionHolder(diffList.get(0));
1955
1956    boolean hasSlicing = false;
1957    List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly
1958    for(ElementDefinition elt : diffList) {
1959      if (elt.hasSlicing() || paths.contains(elt.getPath())) {
1960        hasSlicing = true;
1961        break;
1962      }
1963      paths.add(elt.getPath());
1964    }
1965    if(!hasSlicing) {
1966      // if Differential does not have slicing then safe to pre-sort the list
1967      // so elements and subcomponents are together
1968      Collections.sort(diffList, new ElementNameCompare());
1969    }
1970
1971    int i = 1;
1972    processElementsIntoTree(edh, i, diff.getDifferential().getElement());
1973
1974    // now, we sort the siblings throughout the tree
1975    ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name);
1976    sortElements(edh, cmp, errors);
1977
1978    // now, we serialise them back to a list
1979    diffList.clear();
1980    writeElements(edh, diffList);
1981  }
1982
1983  private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) {
1984    String path = edh.getSelf().getPath();
1985    final String prefix = path + ".";
1986    while (i < list.size() && list.get(i).getPath().startsWith(prefix)) {
1987      ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i));
1988      edh.getChildren().add(child);
1989      i = processElementsIntoTree(child, i+1, list);
1990    }
1991    return i;
1992  }
1993
1994  private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) {
1995    if (edh.getChildren().size() == 1)
1996      // 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
1997      edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath());
1998    else
1999      Collections.sort(edh.getChildren(), cmp);
2000    cmp.checkForErrors(errors);
2001
2002    for (ElementDefinitionHolder child : edh.getChildren()) {
2003      if (child.getChildren().size() > 0) {
2004        // what we have to check for here is running off the base profile into a data type profile
2005        ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex());
2006        ElementDefinitionComparer ccmp;
2007        if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getCode()) || ed.getType().get(0).getCode().equals(ed.getPath())) {
2008          ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name);
2009        } else if (ed.getType().get(0).getCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) {
2010          ccmp = new ElementDefinitionComparer(true, context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue()).getSnapshot().getElement(), ed.getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name);
2011        } else if (ed.getType().size() == 1 && !ed.getType().get(0).getCode().equals("*")) {
2012          ccmp = new ElementDefinitionComparer(false, context.fetchTypeDefinition(ed.getType().get(0).getCode()).getSnapshot().getElement(), ed.getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name);
2013        } else if (child.getSelf().getType().size() == 1) {
2014          ccmp = new ElementDefinitionComparer(false, context.fetchTypeDefinition(child.getSelf().getType().get(0).getCode()).getSnapshot().getElement(), child.getSelf().getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name);
2015        } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) {
2016          String p = child.getSelf().getPath().substring(ed.getPath().length()-3);
2017          StructureDefinition sd = context.fetchTypeDefinition(p);
2018          if (sd == null)
2019            throw new Error("Unable to find profile "+p);
2020          ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name);
2021        } else {
2022          throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")");
2023        }
2024        sortElements(child, ccmp, errors);
2025      }
2026    }
2027  }
2028
2029  private boolean isAbstract(String code) {
2030    return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") || code.equals("DomainResource");
2031  }
2032
2033
2034  private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) {
2035    list.add(edh.getSelf());
2036    for (ElementDefinitionHolder child : edh.getChildren()) {
2037      writeElements(child, list);
2038    }
2039  }
2040
2041  /**
2042   * First compare element by path then by name if same
2043   */
2044  private static class ElementNameCompare implements Comparator<ElementDefinition> {
2045
2046    @Override
2047    public int compare(ElementDefinition o1, ElementDefinition o2) {
2048      String path1 = normalizePath(o1);
2049      String path2 = normalizePath(o2);
2050      int cmp = path1.compareTo(path2);
2051      if (cmp == 0) {
2052        String name1 = o1.hasName() ? o1.getName() : "";
2053        String name2 = o2.hasName() ? o2.getName() : "";
2054        cmp = name1.compareTo(name2);
2055      }
2056      return cmp;
2057    }
2058
2059    private static String normalizePath(ElementDefinition e) {
2060      if (!e.hasPath()) return "";
2061      String path = e.getPath();
2062      // if sorting element names make sure onset[x] appears before onsetAge, onsetDate, etc.
2063      // so strip off the [x] suffix when comparing the path names.
2064      if (path.endsWith("[x]")) {
2065        path = path.substring(0, path.length()-3);
2066      }
2067      return path;
2068    }
2069
2070  }
2071
2072  // generate schematroins for the rules in a structure definition
2073
2074  public void generateSchematrons(OutputStream dest, StructureDefinition structure) throws IOException, DefinitionException {
2075    if (!structure.hasConstrainedType())
2076      throw new DefinitionException("not the right kind of structure to generate schematrons for ("+structure.getUrl()+")");
2077    if (!structure.hasSnapshot())
2078      throw new DefinitionException("needs a snapshot for ("+structure.getUrl()+")");
2079
2080        StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBase());
2081
2082        SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName());
2083
2084    ElementDefinition ed = structure.getSnapshot().getElement().get(0);
2085    generateForChildren(sch, "f:"+ed.getPath(), ed, structure, base);
2086    sch.dump();
2087  }
2088
2089  private class Slicer extends ElementDefinitionSlicingComponent {
2090    String criteria = "";
2091    String name = "";   
2092    boolean check;
2093    public Slicer(boolean cantCheck) {
2094      super();
2095      this.check = cantCheck;
2096    }
2097  }
2098  
2099  private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, StructureDefinition structure) {
2100    // given a child in a structure, it's sliced. figure out the slicing xpath
2101    if (child.getPath().endsWith(".extension")) {
2102      ElementDefinition ued = getUrlFor(structure, child);
2103      if ((ued == null || !ued.hasFixed()) && !(child.getType().get(0).hasProfile()))
2104        return new Slicer(false);
2105      else {
2106      Slicer s = new Slicer(true);
2107      String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).asStringValue() : ((UriType) ued.getFixed()).asStringValue();
2108      s.name = " with URL = '"+url+"'";
2109      s.criteria = "[@url = '"+url+"']";
2110      return s;
2111      }
2112    } else
2113      return new Slicer(false);
2114  }
2115
2116  private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, StructureDefinition structure, StructureDefinition base) throws IOException {
2117    //    generateForChild(txt, structure, child);
2118    List<ElementDefinition> children = getChildList(structure, ed);
2119    String sliceName = null;
2120    ElementDefinitionSlicingComponent slicing = null;
2121    for (ElementDefinition child : children) {
2122      String name = tail(child.getPath());
2123      if (child.hasSlicing()) {
2124        sliceName = name;
2125        slicing = child.getSlicing();        
2126      } else if (!name.equals(sliceName))
2127        slicing = null;
2128      
2129      ElementDefinition based = getByPath(base, child.getPath());
2130      boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin()));
2131      boolean doMax =  !child.getMax().equals("*") && (based == null || (!child.getMax().equals(based.getMax())));
2132      Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure);
2133      if (slicer.check) {
2134        if (doMin || doMax) {
2135          Section s = sch.section(xpath);
2136          Rule r = s.rule(xpath);
2137          if (doMin) 
2138            r.assrt("count(f:"+name+slicer.criteria+") >= "+Integer.toString(child.getMin()), name+slicer.name+": minimum cardinality of '"+name+"' is "+Integer.toString(child.getMin()));
2139          if (doMax) 
2140            r.assrt("count(f:"+name+slicer.criteria+") <= "+child.getMax(), name+slicer.name+": maximum cardinality of '"+name+"' is "+child.getMax());
2141          }
2142        }
2143      }
2144    for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
2145      if (inv.hasXpath()) {
2146        Section s = sch.section(ed.getPath());
2147        Rule r = s.rule(xpath);
2148        r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId()+": " : "")+inv.getHuman()+(inv.hasUserData(IS_DERIVED) ? " (inherited)" : ""));
2149      }
2150    }
2151    for (ElementDefinition child : children) {
2152      String name = tail(child.getPath());
2153      generateForChildren(sch, xpath+"/f:"+name, child, structure, base);
2154    }
2155  }
2156
2157
2158
2159
2160  private ElementDefinition getByPath(StructureDefinition base, String path) {
2161                for (ElementDefinition ed : base.getSnapshot().getElement()) {
2162                        if (ed.getPath().equals(path))
2163                                return ed;
2164                        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)))
2165                                return ed;
2166    }
2167          return null;
2168  }
2169
2170//
2171//private void generateForChild(TextStreamWriter txt,
2172//    StructureDefinition structure, ElementDefinition child) {
2173//  // TODO Auto-generated method stub
2174//
2175//}
2176
2177
2178}