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      // todo: what to do about conditions?
1014      // condition : id 0..*
1015
1016      if (derived.hasMustSupportElement()) {
1017        if (!Base.compareDeep(derived.getMustSupportElement(), base.getMustSupportElement(), false))
1018          base.setMustSupportElement(derived.getMustSupportElement().copy());
1019        else if (trimDifferential)
1020          derived.setMustSupportElement(null);
1021        else
1022          derived.getMustSupportElement().setUserData(DERIVATION_EQUALS, true);
1023      }
1024
1025
1026      // profiles cannot change : isModifier, defaultValue, meaningWhenMissing
1027      // but extensions can change isModifier
1028      if (isExtension) {
1029        if (!Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false))
1030          base.setIsModifierElement(derived.getIsModifierElement().copy());
1031        else if (trimDifferential)
1032          derived.setIsModifierElement(null);
1033        else
1034          derived.getIsModifierElement().setUserData(DERIVATION_EQUALS, true);
1035      }
1036
1037      if (derived.hasBinding()) {
1038        if (!Base.compareDeep(derived.getBinding(), base.getBinding(), false)) {
1039          if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && derived.getBinding().getStrength() != BindingStrength.REQUIRED)
1040            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));
1041//            throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode());
1042          else if (base.hasBinding() && derived.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED) {
1043            ValueSetExpansionOutcome expBase = context.expandVS(context.fetchResource(ValueSet.class, base.getBinding().getValueSetReference().getReference()), true);
1044            ValueSetExpansionOutcome expDerived = context.expandVS(context.fetchResource(ValueSet.class, derived.getBinding().getValueSetReference().getReference()), true);
1045            if (expBase.getValueset() == null)
1046              messages.add(new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSetReference().getReference()+" could not be expanded", IssueSeverity.WARNING));
1047            else if (expDerived.getValueset() == null)
1048              messages.add(new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSetReference().getReference()+" could not be expanded", IssueSeverity.WARNING));
1049            else if (!isSubset(expBase.getValueset(), expDerived.getValueset()))
1050              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));
1051          }
1052          base.setBinding(derived.getBinding().copy());
1053        } else if (trimDifferential)
1054          derived.setBinding(null);
1055        else
1056          derived.getBinding().setUserData(DERIVATION_EQUALS, true);
1057      } // else if (base.hasBinding() && doesn't have bindable type )
1058        //  base
1059
1060      if (derived.hasIsSummaryElement()) {
1061        if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false))
1062          base.setIsSummaryElement(derived.getIsSummaryElement().copy());
1063        else if (trimDifferential)
1064          derived.setIsSummaryElement(null);
1065        else
1066          derived.getIsSummaryElement().setUserData(DERIVATION_EQUALS, true);
1067      }
1068
1069      if (derived.hasType()) {
1070        if (!Base.compareDeep(derived.getType(), base.getType(), false)) {
1071          if (base.hasType()) {
1072            for (TypeRefComponent ts : derived.getType()) {
1073              boolean ok = false;
1074              CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1075              for (TypeRefComponent td : base.getType()) {
1076                b.append(td.getCode());
1077                if (td.hasCode() && (td.getCode().equals(ts.getCode()) || td.getCode().equals("Extension") ||
1078                    td.getCode().equals("Element") || td.getCode().equals("*") ||
1079                    ((td.getCode().equals("Resource") || (td.getCode().equals("DomainResource")) && pkp.isResource(ts.getCode())))))
1080                  ok = true;
1081              }
1082              if (!ok)
1083                throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal constrained type "+ts.getCode()+" from "+b.toString());
1084            }
1085          }
1086          base.getType().clear();
1087          for (TypeRefComponent t : derived.getType()) {
1088            TypeRefComponent tt = t.copy();
1089//            tt.setUserData(DERIVATION_EQUALS, true);
1090            base.getType().add(tt);
1091          }
1092        }
1093        else if (trimDifferential)
1094          derived.getType().clear();
1095        else
1096          for (TypeRefComponent t : derived.getType())
1097            t.setUserData(DERIVATION_EQUALS, true);
1098      }
1099
1100      if (derived.hasMapping()) {
1101        // todo: mappings are not cumulative - one replaces another
1102        if (!Base.compareDeep(derived.getMapping(), base.getMapping(), false)) {
1103          for (ElementDefinitionMappingComponent s : derived.getMapping()) {
1104            boolean found = false;
1105            for (ElementDefinitionMappingComponent d : base.getMapping()) {
1106              found = found || (d.getIdentity().equals(s.getIdentity()) && d.getMap().equals(s.getMap()));
1107            }
1108            if (!found)
1109              base.getMapping().add(s);
1110          }
1111        }
1112        else if (trimDifferential)
1113          derived.getMapping().clear();
1114        else
1115          for (ElementDefinitionMappingComponent t : derived.getMapping())
1116            t.setUserData(DERIVATION_EQUALS, true);
1117      }
1118
1119      // todo: constraints are cumulative. there is no replacing
1120      for (ElementDefinitionConstraintComponent s : base.getConstraint()) 
1121        s.setUserData(IS_DERIVED, true);
1122      if (derived.hasConstraint()) {
1123        for (ElementDefinitionConstraintComponent s : derived.getConstraint()) {
1124          base.getConstraint().add(s.copy());
1125        }
1126      }
1127    }
1128  }
1129
1130  private boolean isLargerMax(String derived, String base) {
1131    if ("*".equals(base))
1132      return false;
1133    if ("*".equals(derived))
1134      return true;
1135    return Integer.parseInt(derived) > Integer.parseInt(base);
1136  }
1137
1138
1139  private boolean isSubset(ValueSet expBase, ValueSet expDerived) {
1140    return codesInExpansion(expDerived.getExpansion().getContains(), expBase.getExpansion());
1141  }
1142
1143
1144  private boolean codesInExpansion(List<ValueSetExpansionContainsComponent> contains, ValueSetExpansionComponent expansion) {
1145    for (ValueSetExpansionContainsComponent cc : contains) {
1146      if (!inExpansion(cc, expansion.getContains()))
1147        return false;
1148      if (!codesInExpansion(cc.getContains(), expansion))
1149        return false;
1150    }
1151    return true;
1152  }
1153
1154
1155  private boolean inExpansion(ValueSetExpansionContainsComponent cc, List<ValueSetExpansionContainsComponent> contains) {
1156    for (ValueSetExpansionContainsComponent cc1 : contains) {
1157      if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode()))
1158        return true;
1159      if (inExpansion(cc,  cc1.getContains()))
1160        return true;
1161    }
1162    return false;
1163  }
1164
1165
1166  public XhtmlNode generateExtensionTable(String defFile, StructureDefinition ed, String imageFolder, boolean inlineGraphics, boolean full, String corePath, Set<String> outputTracker) throws IOException, FHIRException {
1167    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics);
1168    TableModel model = gen.initNormalTable(corePath, false, true, ed.getId(), false);
1169
1170    boolean deep = false;
1171    boolean vdeep = false;
1172    for (ElementDefinition eld : ed.getSnapshot().getElement()) {
1173      deep = deep || eld.getPath().contains("Extension.extension.");
1174      vdeep = vdeep || eld.getPath().contains("Extension.extension.extension.");
1175    }
1176    Row r = gen.new Row();
1177    model.getRows().add(r);
1178    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));
1179    r.getCells().add(gen.new Cell());
1180    r.getCells().add(gen.new Cell(null, null, describeCardinality(ed.getSnapshot().getElement().get(0), null, new UnusedTracker()), null, null));
1181
1182    if (full || vdeep) {
1183      r.getCells().add(gen.new Cell("", "", "Extension", null, null));
1184
1185      r.setIcon(deep ? "icon_extension_complex.png" : "icon_extension_simple.png", deep ? HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX : HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
1186      List<ElementDefinition> children = getChildren(ed.getSnapshot().getElement(), ed.getSnapshot().getElement().get(0));
1187      for (ElementDefinition child : children)
1188        if (!child.getPath().endsWith(".id"))
1189          genElement(defFile == null ? "" : defFile+"-definitions.html#extension.", gen, r.getSubRows(), child, ed.getSnapshot().getElement(), null, true, defFile, true, full, corePath);
1190    } else if (deep) {
1191      List<ElementDefinition> children = new ArrayList<ElementDefinition>();
1192      for (ElementDefinition ted : ed.getSnapshot().getElement()) {
1193        if (ted.getPath().equals("Extension.extension"))
1194          children.add(ted);
1195      }
1196
1197      r.getCells().add(gen.new Cell("", "", "Extension", null, null));
1198      r.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
1199      
1200      for (ElementDefinition c : children) {
1201        ElementDefinition ved = getValueFor(ed, c);
1202        ElementDefinition ued = getUrlFor(ed, c);
1203        if (ved != null && ued != null) {
1204          Row r1 = gen.new Row();
1205          r.getSubRows().add(r1);
1206          r1.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), ((UriType) ued.getFixed()).getValue(), null, null));
1207          r1.getCells().add(gen.new Cell());
1208          r1.getCells().add(gen.new Cell(null, null, describeCardinality(c, null, new UnusedTracker()), null, null));
1209          genTypes(gen, r1, ved, defFile, ed, corePath);
1210          r1.getCells().add(gen.new Cell(null, null, c.getDefinition(), null, null));
1211          r1.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);      
1212        }
1213      }
1214    } else  {
1215      ElementDefinition ved = null;
1216      for (ElementDefinition ted : ed.getSnapshot().getElement()) {
1217        if (ted.getPath().startsWith("Extension.value"))
1218          ved = ted;
1219      }
1220
1221      genTypes(gen, r, ved, defFile, ed, corePath);
1222
1223      r.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);      
1224    }
1225    Cell c = gen.new Cell("", "", "URL = "+ed.getUrl(), null, null);
1226    c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, ed.getName()+": "+ed.getDescription(), null));
1227    c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, describeExtensionContext(ed), null));
1228    r.getCells().add(c);
1229
1230
1231    return gen.generate(model, corePath, 0, outputTracker);
1232    }
1233
1234  private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) {
1235    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
1236    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
1237      if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url"))
1238        return ed.getSnapshot().getElement().get(i);
1239      i++;
1240    }
1241    return null;
1242  }
1243
1244  private ElementDefinition getValueFor(StructureDefinition ed, ElementDefinition c) {
1245    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
1246    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
1247      if (ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".value"))
1248        return ed.getSnapshot().getElement().get(i);
1249      i++;
1250    }
1251    return null;
1252  }
1253
1254
1255  private Cell genTypes(HierarchicalTableGenerator gen, Row r, ElementDefinition e, String profileBaseFileName, StructureDefinition profile, String corePath) {
1256    Cell c = gen.new Cell();
1257    r.getCells().add(c);
1258    List<TypeRefComponent> types = e.getType();
1259    if (!e.hasType()) {
1260      if (e.hasNameReference()) {
1261        ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), e.getNameReference());
1262        if (ed == null)
1263          c.getPieces().add(gen.new Piece(null, "Unknown reference to "+e.getNameReference(), null));
1264        else
1265          c.getPieces().add(gen.new Piece("#"+ed.getPath(), "See "+ed.getPath(), null));
1266        return c;
1267      } else {
1268      ElementDefinition d = (ElementDefinition) e.getUserData(DERIVATION_POINTER);
1269      if (d != null && d.hasType()) {
1270        types = new ArrayList<ElementDefinition.TypeRefComponent>();
1271        for (TypeRefComponent tr : d.getType()) {
1272          TypeRefComponent tt = tr.copy();
1273          tt.setUserData(DERIVATION_EQUALS, true);
1274          types.add(tt);
1275        }
1276      } else
1277        return c;
1278    }
1279    }
1280
1281    boolean first = true;
1282    Element source = types.get(0); // either all types are the same, or we don't consider any of them the same
1283
1284    boolean allReference = ADD_REFERENCE_TO_TABLE && !types.isEmpty();
1285    for (TypeRefComponent t : types) {
1286      if (!(t.getCode().equals("Reference") && t.hasProfile()))
1287        allReference = false;
1288    }
1289    if (allReference) {
1290      c.getPieces().add(gen.new Piece(corePath+"references.html", "Reference", null));
1291      c.getPieces().add(gen.new Piece(null, "(", null));
1292    }
1293    TypeRefComponent tl = null;
1294    for (TypeRefComponent t : types) {
1295      if (first)
1296        first = false;
1297      else if (allReference)
1298        c.addPiece(checkForNoChange(tl, gen.new Piece(null," | ", null)));
1299      else
1300        c.addPiece(checkForNoChange(tl, gen.new Piece(null,", ", null)));
1301      tl = t;
1302      if (t.getCode().equals("Reference") || (t.getCode().equals("Resource") && t.hasProfile())) {
1303        if (ADD_REFERENCE_TO_TABLE && !allReference) {
1304          c.getPieces().add(gen.new Piece(corePath+"references.html", "Reference", null));
1305          c.getPieces().add(gen.new Piece(null, "(", null));
1306        }
1307        if (t.hasProfile() && t.getProfile().get(0).getValue().startsWith("http://hl7.org/fhir/StructureDefinition/")) {
1308          StructureDefinition sd = context.fetchResource(StructureDefinition.class, t.getProfile().get(0).getValue());
1309          if (sd != null) {
1310            String disp = sd.hasDisplay() ? sd.getDisplay() : sd.getName();
1311            c.addPiece(checkForNoChange(t, gen.new Piece(corePath+sd.getUserString("path"), disp, null)));
1312          } else {
1313            String rn = t.getProfile().get(0).getValue().substring(40);
1314            c.addPiece(checkForNoChange(t, gen.new Piece(corePath+pkp.getLinkFor(rn), rn, null)));
1315          }
1316        } else if (t.getProfile().size() == 0) {
1317          c.addPiece(checkForNoChange(t, gen.new Piece(null, t.getCode(), null)));
1318        } else if (t.getProfile().get(0).getValue().startsWith("#"))
1319          c.addPiece(checkForNoChange(t, gen.new Piece(corePath+profileBaseFileName+"."+t.getProfile().get(0).getValue().substring(1).toLowerCase()+".html", t.getProfile().get(0).getValue(), null)));
1320        else
1321          c.addPiece(checkForNoChange(t, gen.new Piece(corePath+t.getProfile().get(0).getValue(), t.getProfile().get(0).getValue(), null)));
1322        if (ADD_REFERENCE_TO_TABLE && !allReference) {
1323          c.getPieces().add(gen.new Piece(null, ")", null));
1324        }
1325      } else if (t.hasProfile()) { // a profiled type
1326        String ref;
1327        ref = pkp.getLinkForProfile(profile, t.getProfile().get(0).getValue());
1328        if (ref != null) {
1329          String[] parts = ref.split("\\|");
1330          c.addPiece(checkForNoChange(t, gen.new Piece(corePath+parts[0], parts[1], t.getCode())));
1331        } else
1332          c.addPiece(checkForNoChange(t, gen.new Piece(corePath+ref, t.getCode(), null)));
1333      } else if (pkp.hasLinkFor(t.getCode())) {
1334        c.addPiece(checkForNoChange(t, gen.new Piece(corePath+pkp.getLinkFor(t.getCode()), t.getCode(), null)));
1335      } else
1336        c.addPiece(checkForNoChange(t, gen.new Piece(null, t.getCode(), null)));
1337    }
1338    if (allReference) {
1339      c.getPieces().add(gen.new Piece(null, ")", null));
1340    }
1341    return c;
1342  }
1343
1344  private ElementDefinition getElementByName(List<ElementDefinition> elements, String nameReference) {
1345    for (ElementDefinition ed : elements)
1346      if (ed.hasName() && ed.getName().equals(nameReference))
1347        return ed;
1348    return null;
1349  }
1350
1351
1352  public static String describeExtensionContext(StructureDefinition ext) {
1353    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1354    for (StringType t : ext.getContext())
1355      b.append(t.getValue());
1356    if (!ext.hasContextType())
1357      throw new Error("no context type on "+ext.getUrl());
1358    switch (ext.getContextType()) {
1359    case DATATYPE: return "Use on data type: "+b.toString();
1360    case EXTENSION: return "Use on extension: "+b.toString();
1361    case RESOURCE: return "Use on element: "+b.toString();
1362    case MAPPING: return "Use where element has mapping: "+b.toString();
1363    default:
1364      return "??";
1365    }
1366  }
1367
1368  private String describeCardinality(ElementDefinition definition, ElementDefinition fallback, UnusedTracker tracker) {
1369    IntegerType min = definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
1370    StringType max = definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
1371    if (min.isEmpty() && fallback != null)
1372      min = fallback.getMinElement();
1373    if (max.isEmpty() && fallback != null)
1374      max = fallback.getMaxElement();
1375
1376    tracker.used = !max.isEmpty() && !max.getValue().equals("0");
1377
1378    if (min.isEmpty() && max.isEmpty())
1379      return null;
1380    else
1381      return (!min.hasValue() ? "" : Integer.toString(min.getValue())) + ".." + (!max.hasValue() ? "" : max.getValue());
1382  }
1383
1384  private void genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef, UnusedTracker tracker, ElementDefinition fallback) {
1385    IntegerType min = !hasDef ? new IntegerType() : definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
1386    StringType max = !hasDef ? new StringType() : definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
1387    if (min.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) {
1388      ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER);
1389      min = base.getMinElement().copy();
1390      min.setUserData(DERIVATION_EQUALS, true);
1391    }
1392    if (max.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) {
1393      ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER);
1394      max = base.getMaxElement().copy();
1395      max.setUserData(DERIVATION_EQUALS, true);
1396    }
1397    if (min.isEmpty() && fallback != null)
1398      min = fallback.getMinElement();
1399    if (max.isEmpty() && fallback != null)
1400      max = fallback.getMaxElement();
1401
1402    if (!max.isEmpty())
1403      tracker.used = !max.getValue().equals("0");
1404
1405    Cell cell = gen.new Cell(null, null, null, null, null);
1406    row.getCells().add(cell);
1407    if (!min.isEmpty() || !max.isEmpty()) {
1408      cell.addPiece(checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), null)));
1409      cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", null)));
1410      cell.addPiece(checkForNoChange(min, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), null)));
1411    }
1412  }
1413
1414
1415  private Piece checkForNoChange(Element source, Piece piece) {
1416    if (source.hasUserData(DERIVATION_EQUALS)) {
1417      piece.addStyle("opacity: 0.4");
1418    }
1419    return piece;
1420  }
1421
1422  private Piece checkForNoChange(Element src1, Element src2, Piece piece) {
1423    if (src1.hasUserData(DERIVATION_EQUALS) && src2.hasUserData(DERIVATION_EQUALS)) {
1424      piece.addStyle("opacity: 0.5");
1425    }
1426    return piece;
1427  }
1428
1429  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 {
1430    assert(diff != snapshot);// check it's ok to get rid of one of these
1431    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics);
1432    TableModel model = gen.initNormalTable(corePath, false, true, profile.getId()+(diff ? "d" : "s"), false);
1433    List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement();
1434    List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
1435    profiles.add(profile);
1436    genElement(defFile == null ? null : defFile+"#"+profile.getId()+".", gen, model.getRows(), list.get(0), list, profiles, diff, profileBaseFileName, null, snapshot, corePath);
1437    return gen.generate(model, corePath, 0, outputTracker);
1438  }
1439
1440  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 {
1441    StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1);
1442    String s = tail(element.getPath());
1443    List<ElementDefinition> children = getChildren(all, element);
1444    boolean isExtension = (s.equals("extension") || s.equals("modifierExtension"));
1445    if (!snapshot && extensions != null && extensions != isExtension)
1446      return;
1447
1448    if (!onlyInformationIsMapping(all, element)) {
1449      Row row = gen.new Row();
1450      row.setAnchor(element.getPath());
1451      row.setColor(getRowColor(element));
1452      boolean hasDef = element != null;
1453      boolean ext = false;
1454      if (s.equals("extension") || s.equals("modifierExtension")) {
1455        if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue()))
1456          row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
1457        else
1458          row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
1459        ext = true;
1460      } else if (!hasDef || element.getType().size() == 0)
1461        row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
1462      else if (hasDef && element.getType().size() > 1) {
1463        if (allTypesAre(element.getType(), "Reference"))
1464          row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
1465        else
1466          row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE);
1467      } else if (hasDef && element.getType().get(0).getCode() != null && element.getType().get(0).getCode().startsWith("@"))
1468        row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE);
1469      else if (hasDef && isPrimitive(element.getType().get(0).getCode()))
1470        row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
1471      else if (hasDef && isReference(element.getType().get(0).getCode()))
1472        row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
1473      else if (hasDef && isDataType(element.getType().get(0).getCode()))
1474        row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
1475      else
1476        row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
1477      String ref = defPath == null ? null : defPath + makePathLink(element);
1478      UnusedTracker used = new UnusedTracker();
1479      used.used = true;
1480      Cell left = gen.new Cell(null, ref, s, !hasDef ? null : element.getDefinition(), null);
1481      row.getCells().add(left);
1482      Cell gc = gen.new Cell();
1483      row.getCells().add(gc);
1484      if (element != null && element.getIsModifier())
1485        checkForNoChange(element.getIsModifierElement(), gc.addStyledText("This element is a modifier element", "?!", null, null, null, false));
1486      if (element != null && element.getMustSupport())
1487        checkForNoChange(element.getMustSupportElement(), gc.addStyledText("This element must be supported", "S", null, null, null, false));
1488      if (element != null && element.getIsSummary())
1489        checkForNoChange(element.getIsSummaryElement(), gc.addStyledText("This element is included in summaries", "∑", null, null, null, false));
1490      if (element != null && (!element.getConstraint().isEmpty() || !element.getCondition().isEmpty()))
1491        gc.addStyledText("This element has or is affected by some invariants", "I", null, null, null, false);
1492
1493      ExtensionContext extDefn = null;
1494      if (ext) {
1495        if (element != null && element.getType().size() == 1 && element.getType().get(0).hasProfile()) {
1496        extDefn = locateExtension(StructureDefinition.class, element.getType().get(0).getProfile().get(0).getValue());
1497          if (extDefn == null) {
1498            genCardinality(gen, element, row, hasDef, used, null);
1499            row.getCells().add(gen.new Cell(null, null, "?? "+element.getType().get(0).getProfile(), null, null));
1500            generateDescription(gen, row, element, null, used.used, profile.getUrl(), element.getType().get(0).getProfile().get(0).getValue(), profile, corePath);
1501          } else {
1502            String name = urltail(element.getType().get(0).getProfile().get(0).getValue());
1503            left.getPieces().get(0).setText(name);
1504            // left.getPieces().get(0).setReference((String) extDefn.getExtensionStructure().getTag("filename"));
1505            left.getPieces().get(0).setHint("Extension URL = "+extDefn.getUrl());
1506            genCardinality(gen, element, row, hasDef, used, extDefn.getElement());
1507            ElementDefinition valueDefn = extDefn.getExtensionValueDefinition();
1508            if (valueDefn != null && !"0".equals(valueDefn.getMax()))
1509               genTypes(gen, row, valueDefn, profileBaseFileName, profile, corePath);
1510             else // if it's complex, we just call it nothing
1511                // genTypes(gen, row, extDefn.getSnapshot().getElement().get(0), profileBaseFileName, profile);
1512              row.getCells().add(gen.new Cell(null, null, "(Complex)", null, null));
1513            generateDescription(gen, row, element, extDefn.getElement(), used.used, null, extDefn.getUrl(), profile, corePath);
1514          }
1515        } else {
1516          genCardinality(gen, element, row, hasDef, used, null);
1517          if ("0".equals(element.getMax()))
1518            row.getCells().add(gen.new Cell());            
1519          else
1520            genTypes(gen, row, element, profileBaseFileName, profile, corePath);
1521          generateDescription(gen, row, element, null, used.used, null, null, profile, corePath);
1522        }
1523      } else {
1524        genCardinality(gen, element, row, hasDef, used, null);
1525        if (hasDef && !"0".equals(element.getMax()))
1526          genTypes(gen, row, element, profileBaseFileName, profile, corePath);
1527        else
1528          row.getCells().add(gen.new Cell());
1529        generateDescription(gen, row, element, null, used.used, null, null, profile, corePath);
1530      }
1531      if (element.hasSlicing()) {
1532        if (standardExtensionSlicing(element)) {
1533          used.used = element.hasType() && element.getType().get(0).hasProfile();
1534          showMissing = false;
1535        } else {
1536          row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE);
1537          row.getCells().get(2).getPieces().clear();
1538          for (Cell cell : row.getCells())
1539            for (Piece p : cell.getPieces()) {
1540              p.addStyle("font-style: italic");
1541            }
1542        }
1543      }
1544      if (used.used || showMissing)
1545        rows.add(row);
1546      if (!used.used && !element.hasSlicing()) {
1547        for (Cell cell : row.getCells())
1548          for (Piece p : cell.getPieces()) {
1549            p.setStyle("text-decoration:line-through");
1550            p.setReference(null);
1551          }
1552      } else{
1553        for (ElementDefinition child : children)
1554          if (!child.getPath().endsWith(".id"))
1555            genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, snapshot, corePath);
1556        if (!snapshot && (extensions == null || !extensions))
1557          for (ElementDefinition child : children)
1558            if (child.getPath().endsWith(".extension"))
1559              genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, true, false, corePath);
1560      }
1561    }
1562  }
1563
1564  private ExtensionContext locateExtension(Class<StructureDefinition> class1, String value)  {
1565    if (value.contains("#")) {
1566      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
1567      if (ext == null)
1568        return null;
1569      String tail = value.substring(value.indexOf("#")+1);
1570      ElementDefinition ed = null;
1571      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
1572        if (tail.equals(ted.getName())) {
1573          ed = ted;
1574          return new ExtensionContext(ext, ed);
1575        }
1576      }
1577      return null;
1578    } else {
1579      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value);
1580      if (ext == null)
1581        return null;
1582      else 
1583        return new ExtensionContext(ext, ext.getSnapshot().getElement().get(0));
1584    }
1585  }
1586
1587
1588  private boolean extensionIsComplex(String value) {
1589    if (value.contains("#")) {
1590      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
1591    if (ext == null)
1592      return false;
1593      String tail = value.substring(value.indexOf("#")+1);
1594      ElementDefinition ed = null;
1595      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
1596        if (tail.equals(ted.getName())) {
1597          ed = ted;
1598          break;
1599        }
1600      }
1601      if (ed == null)
1602        return false;
1603      int i = ext.getSnapshot().getElement().indexOf(ed);
1604      int j = i+1;
1605      while (j < ext.getSnapshot().getElement().size() && !ext.getSnapshot().getElement().get(j).getPath().equals(ed.getPath()))
1606        j++;
1607      return j - i > 5;
1608    } else {
1609      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value);
1610      return ext != null && ext.getSnapshot().getElement().size() > 5;
1611    }
1612  }
1613
1614
1615  private String getRowColor(ElementDefinition element) {
1616    switch (element.getUserInt(UD_ERROR_STATUS)) {
1617    case STATUS_OK: return null;
1618    case STATUS_HINT: return ROW_COLOR_HINT;
1619    case STATUS_WARNING: return ROW_COLOR_WARNING;
1620    case STATUS_ERROR: return ROW_COLOR_ERROR;
1621    case STATUS_FATAL: return ROW_COLOR_FATAL;
1622    default: return null;
1623    }
1624  }
1625
1626
1627  private String urltail(String path) {
1628    if (path.contains("#"))
1629      return path.substring(path.lastIndexOf('#')+1);
1630    if (path.contains("/"))
1631      return path.substring(path.lastIndexOf('/')+1);
1632    else
1633      return path;
1634
1635  }
1636
1637  private boolean standardExtensionSlicing(ElementDefinition element) {
1638    String t = tail(element.getPath());
1639    return (t.equals("extension") || t.equals("modifierExtension"))
1640          && element.getSlicing().getRules() != SlicingRules.CLOSED && element.getSlicing().getDiscriminator().size() == 1 && element.getSlicing().getDiscriminator().get(0).getValue().equals("url");
1641  }
1642
1643
1644  private String makePathLink(ElementDefinition element) {
1645    if (!element.hasName())
1646      return element.getPath();
1647    if (!element.getPath().contains("."))
1648      return element.getName();
1649    return element.getPath().substring(0, element.getPath().lastIndexOf("."))+"."+element.getName();
1650
1651  }
1652
1653  private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath) throws IOException {
1654    Cell c = gen.new Cell();
1655    row.getCells().add(c);
1656
1657    if (used) {
1658      if (definition.getPath().endsWith("url") && definition.hasFixed()) {
1659        c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen")));
1660      } else {
1661        if (definition != null && definition.hasShort()) {
1662          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
1663          c.addPiece(checkForNoChange(definition.getShortElement(), gen.new Piece(null, definition.getShort(), null)));
1664        } else if (fallback != null && fallback != null && fallback.hasShort()) {
1665          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
1666          c.addPiece(checkForNoChange(fallback.getShortElement(), gen.new Piece(null, fallback.getShort(), null)));
1667        }
1668        if (url != null) {
1669          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
1670          String fullUrl = url.startsWith("#") ? baseURL+url : url;
1671          StructureDefinition ed = context.fetchResource(StructureDefinition.class, url);
1672          String ref = ed == null ? null : (String) corePath+ed.getUserData("path");
1673          c.getPieces().add(gen.new Piece(null, "URL: ", null).addStyle("font-weight:bold"));
1674          c.getPieces().add(gen.new Piece(ref, fullUrl, null));
1675        }
1676
1677        if (definition.hasSlicing()) {
1678          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
1679          c.getPieces().add(gen.new Piece(null, "Slice: ", null).addStyle("font-weight:bold"));
1680          c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null));
1681        }
1682        if (definition != null) {
1683          if (definition.hasBinding()) {
1684            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
1685            BindingResolution br = pkp.resolveBinding(definition.getBinding());
1686            c.getPieces().add(checkForNoChange(definition.getBinding(), gen.new Piece(null, "Binding: ", null).addStyle("font-weight:bold")));
1687            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)));
1688            if (definition.getBinding().hasStrength()) {
1689              c.getPieces().add(checkForNoChange(definition.getBinding(), gen.new Piece(null, " (", null)));
1690              c.getPieces().add(checkForNoChange(definition.getBinding(), gen.new Piece(corePath+"terminologies.html#"+definition.getBinding().getStrength().toCode(), definition.getBinding().getStrength().toCode(), definition.getBinding().getStrength().getDefinition())));
1691              c.getPieces().add(gen.new Piece(null, ")", null));
1692            }
1693          }
1694          for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
1695            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
1696            c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold")));
1697            c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null)));
1698          }
1699          if (definition.hasFixed()) {
1700            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
1701            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight:bold")));
1702            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, buildJson(definition.getFixed()), null).addStyle("color: darkgreen")));
1703          } else if (definition.hasPattern()) {
1704            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
1705            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "Required Pattern: ", null).addStyle("font-weight:bold")));
1706            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen")));
1707          } else if (definition.hasExample()) {
1708            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
1709            c.getPieces().add(checkForNoChange(definition.getExample(), gen.new Piece(null, "Example: ", null).addStyle("font-weight:bold")));
1710            c.getPieces().add(checkForNoChange(definition.getExample(), gen.new Piece(null, buildJson(definition.getExample()), null).addStyle("color: darkgreen")));
1711          }
1712        }
1713      }
1714    }
1715    return c;
1716  }
1717
1718  private String buildJson(Type value) throws IOException {
1719    if (value instanceof PrimitiveType)
1720      return ((PrimitiveType) value).asStringValue();
1721
1722    IParser json = context.newJsonParser();
1723    return json.composeString(value, null);
1724  }
1725
1726
1727  public String describeSlice(ElementDefinitionSlicingComponent slicing) {
1728    return (slicing.getOrdered() ? "Ordered, " : "Unordered, ")+describe(slicing.getRules())+", by "+commas(slicing.getDiscriminator());
1729  }
1730
1731  private String commas(List<StringType> discriminator) {
1732    CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder();
1733    for (StringType id : discriminator)
1734      c.append(id.asStringValue());
1735    return c.toString();
1736  }
1737
1738
1739  private String describe(SlicingRules rules) {
1740    switch (rules) {
1741    case CLOSED : return "Closed";
1742    case OPEN : return "Open";
1743    case OPENATEND : return "Open At End";
1744    default:
1745      return "??";
1746    }
1747  }
1748
1749  private boolean onlyInformationIsMapping(List<ElementDefinition> list, ElementDefinition e) {
1750    return (!e.hasName() && !e.hasSlicing() && (onlyInformationIsMapping(e))) &&
1751        getChildren(list, e).isEmpty();
1752  }
1753
1754  private boolean onlyInformationIsMapping(ElementDefinition d) {
1755    return !d.hasShort() && !d.hasDefinition() &&
1756        !d.hasRequirements() && !d.getAlias().isEmpty() && !d.hasMinElement() &&
1757        !d.hasMax() && !d.getType().isEmpty() && !d.hasNameReference() &&
1758        !d.hasExample() && !d.hasFixed() && !d.hasMaxLengthElement() &&
1759        !d.getCondition().isEmpty() && !d.getConstraint().isEmpty() && !d.hasMustSupportElement() &&
1760        !d.hasBinding();
1761  }
1762
1763  private boolean allTypesAre(List<TypeRefComponent> types, String name) {
1764    for (TypeRefComponent t : types) {
1765      if (!t.getCode().equals(name))
1766        return false;
1767    }
1768    return true;
1769  }
1770
1771  private List<ElementDefinition> getChildren(List<ElementDefinition> all, ElementDefinition element) {
1772    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
1773    int i = all.indexOf(element)+1;
1774    while (i < all.size() && all.get(i).getPath().length() > element.getPath().length()) {
1775      if ((all.get(i).getPath().substring(0, element.getPath().length()+1).equals(element.getPath()+".")) && !all.get(i).getPath().substring(element.getPath().length()+1).contains("."))
1776        result.add(all.get(i));
1777      i++;
1778    }
1779    return result;
1780  }
1781
1782  private String tail(String path) {
1783    if (path.contains("."))
1784      return path.substring(path.lastIndexOf('.')+1);
1785    else
1786      return path;
1787  }
1788
1789  private boolean isDataType(String value) {
1790    return Utilities.existsInList(value, "Identifier", "HumanName", "Address", "ContactPoint", "Timing", "SimpleQuantity", "Quantity", "Attachment", "Range",
1791          "Period", "Ratio", "CodeableConcept", "Coding", "SampledData", "Age", "Distance", "Duration", "Count", "Money");
1792  }
1793
1794  private boolean isReference(String value) {
1795    return value.equals("Reference");
1796  }
1797
1798  public static boolean isPrimitive(String value) {
1799    return value == null || Utilities.existsInListNC(value, "boolean", "integer", "decimal", "base64Binary", "instant", "string", "date", "dateTime", "code", "oid", "uuid", "id", "uri");
1800  }
1801
1802//  private static String listStructures(StructureDefinition p) {
1803//    StringBuilder b = new StringBuilder();
1804//    boolean first = true;
1805//    for (ProfileStructureComponent s : p.getStructure()) {
1806//      if (first)
1807//        first = false;
1808//      else
1809//        b.append(", ");
1810//      if (pkp != null && pkp.hasLinkFor(s.getType()))
1811//        b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>");
1812//      else
1813//        b.append(s.getType());
1814//    }
1815//    return b.toString();
1816//  }
1817
1818
1819  public StructureDefinition getProfile(StructureDefinition source, String url) {
1820        StructureDefinition profile;
1821        String code;
1822        if (url.startsWith("#")) {
1823                profile = source;
1824                code = url.substring(1);
1825        } else {
1826                String[] parts = url.split("\\#");
1827                profile = context.fetchResource(StructureDefinition.class, parts[0]);
1828      code = parts.length == 1 ? null : parts[1];
1829        }
1830        if (profile == null)
1831                return null;
1832        if (code == null)
1833                return profile;
1834        for (Resource r : profile.getContained()) {
1835                if (r instanceof StructureDefinition && r.getId().equals(code))
1836                        return (StructureDefinition) r;
1837        }
1838        return null;
1839  }
1840
1841
1842
1843  public static class ElementDefinitionHolder {
1844    private String name;
1845    private ElementDefinition self;
1846    private int baseIndex = 0;
1847    private List<ElementDefinitionHolder> children;
1848
1849    public ElementDefinitionHolder(ElementDefinition self) {
1850      super();
1851      this.self = self;
1852      this.name = self.getPath();
1853      children = new ArrayList<ElementDefinitionHolder>();
1854    }
1855
1856    public ElementDefinition getSelf() {
1857      return self;
1858    }
1859
1860    public List<ElementDefinitionHolder> getChildren() {
1861      return children;
1862    }
1863
1864    public int getBaseIndex() {
1865      return baseIndex;
1866    }
1867
1868    public void setBaseIndex(int baseIndex) {
1869      this.baseIndex = baseIndex;
1870    }
1871
1872  }
1873
1874  public static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> {
1875
1876    private boolean inExtension;
1877    private List<ElementDefinition> snapshot;
1878    private int prefixLength;
1879    private String base;
1880    private String name;
1881    private Set<String> errors = new HashSet<String>();
1882
1883    public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, int prefixLength, String name) {
1884      this.inExtension = inExtension;
1885      this.snapshot = snapshot;
1886      this.prefixLength = prefixLength;
1887      this.base = base;
1888      this.name = name;
1889    }
1890
1891    @Override
1892    public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) {
1893      if (o1.getBaseIndex() == 0)
1894        o1.setBaseIndex(find(o1.getSelf().getPath()));
1895      if (o2.getBaseIndex() == 0)
1896        o2.setBaseIndex(find(o2.getSelf().getPath()));
1897      return o1.getBaseIndex() - o2.getBaseIndex();
1898    }
1899
1900    private int find(String path) {
1901      String actual = base+path.substring(prefixLength);
1902      for (int i = 0; i < snapshot.size(); i++) {
1903        String p = snapshot.get(i).getPath();
1904        if (p.equals(actual))
1905          return i;
1906        if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length()-3)) && !(actual.endsWith("[x]")) && !actual.substring(p.length()-3).contains("."))
1907          return i;
1908      }
1909      if (prefixLength == 0)
1910        errors.add("Differential contains path "+path+" which is not found in the base");
1911      else
1912        errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the base");
1913      return 0;
1914    }
1915
1916    public void checkForErrors(List<String> errorList) {
1917      if (errors.size() > 0) {
1918//        CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1919//        for (String s : errors)
1920//          b.append("StructureDefinition "+name+": "+s);
1921//        throw new DefinitionException(b.toString());
1922        for (String s : errors)
1923          if (s.startsWith("!"))
1924            errorList.add("!StructureDefinition "+name+": "+s.substring(1));
1925          else
1926            errorList.add("StructureDefinition "+name+": "+s);
1927      }
1928    }
1929  }
1930
1931
1932  public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors)  {
1933
1934    final List<ElementDefinition> diffList = diff.getDifferential().getElement();
1935    // first, we move the differential elements into a tree
1936    ElementDefinitionHolder edh = new ElementDefinitionHolder(diffList.get(0));
1937
1938    boolean hasSlicing = false;
1939    List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly
1940    for(ElementDefinition elt : diffList) {
1941      if (elt.hasSlicing() || paths.contains(elt.getPath())) {
1942        hasSlicing = true;
1943        break;
1944      }
1945      paths.add(elt.getPath());
1946    }
1947    if(!hasSlicing) {
1948      // if Differential does not have slicing then safe to pre-sort the list
1949      // so elements and subcomponents are together
1950      Collections.sort(diffList, new ElementNameCompare());
1951    }
1952
1953    int i = 1;
1954    processElementsIntoTree(edh, i, diff.getDifferential().getElement());
1955
1956    // now, we sort the siblings throughout the tree
1957    ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name);
1958    sortElements(edh, cmp, errors);
1959
1960    // now, we serialise them back to a list
1961    diffList.clear();
1962    writeElements(edh, diffList);
1963  }
1964
1965  private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) {
1966    String path = edh.getSelf().getPath();
1967    final String prefix = path + ".";
1968    while (i < list.size() && list.get(i).getPath().startsWith(prefix)) {
1969      ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i));
1970      edh.getChildren().add(child);
1971      i = processElementsIntoTree(child, i+1, list);
1972    }
1973    return i;
1974  }
1975
1976  private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) {
1977    if (edh.getChildren().size() == 1)
1978      // 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
1979      edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath());
1980    else
1981      Collections.sort(edh.getChildren(), cmp);
1982    cmp.checkForErrors(errors);
1983
1984    for (ElementDefinitionHolder child : edh.getChildren()) {
1985      if (child.getChildren().size() > 0) {
1986        // what we have to check for here is running off the base profile into a data type profile
1987        ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex());
1988        ElementDefinitionComparer ccmp;
1989        if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getCode()) || ed.getType().get(0).getCode().equals(ed.getPath())) {
1990          ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name);
1991        } else if (ed.getType().get(0).getCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) {
1992          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);
1993        } else if (ed.getType().size() == 1 && !ed.getType().get(0).getCode().equals("*")) {
1994          ccmp = new ElementDefinitionComparer(false, context.fetchTypeDefinition(ed.getType().get(0).getCode()).getSnapshot().getElement(), ed.getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name);
1995        } else if (child.getSelf().getType().size() == 1) {
1996          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);
1997        } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) {
1998          String p = child.getSelf().getPath().substring(ed.getPath().length()-3);
1999          StructureDefinition sd = context.fetchTypeDefinition(p);
2000          if (sd == null)
2001            throw new Error("Unable to find profile "+p);
2002          ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name);
2003        } else {
2004          throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")");
2005        }
2006        sortElements(child, ccmp, errors);
2007      }
2008    }
2009  }
2010
2011  private boolean isAbstract(String code) {
2012    return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") || code.equals("DomainResource");
2013  }
2014
2015
2016  private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) {
2017    list.add(edh.getSelf());
2018    for (ElementDefinitionHolder child : edh.getChildren()) {
2019      writeElements(child, list);
2020    }
2021  }
2022
2023  /**
2024   * First compare element by path then by name if same
2025   */
2026  private static class ElementNameCompare implements Comparator<ElementDefinition> {
2027
2028    @Override
2029    public int compare(ElementDefinition o1, ElementDefinition o2) {
2030      String path1 = normalizePath(o1);
2031      String path2 = normalizePath(o2);
2032      int cmp = path1.compareTo(path2);
2033      if (cmp == 0) {
2034        String name1 = o1.hasName() ? o1.getName() : "";
2035        String name2 = o2.hasName() ? o2.getName() : "";
2036        cmp = name1.compareTo(name2);
2037      }
2038      return cmp;
2039    }
2040
2041    private static String normalizePath(ElementDefinition e) {
2042      if (!e.hasPath()) return "";
2043      String path = e.getPath();
2044      // if sorting element names make sure onset[x] appears before onsetAge, onsetDate, etc.
2045      // so strip off the [x] suffix when comparing the path names.
2046      if (path.endsWith("[x]")) {
2047        path = path.substring(0, path.length()-3);
2048      }
2049      return path;
2050    }
2051
2052  }
2053
2054  // generate schematroins for the rules in a structure definition
2055
2056  public void generateSchematrons(OutputStream dest, StructureDefinition structure) throws IOException, DefinitionException {
2057    if (!structure.hasConstrainedType())
2058      throw new DefinitionException("not the right kind of structure to generate schematrons for ("+structure.getUrl()+")");
2059    if (!structure.hasSnapshot())
2060      throw new DefinitionException("needs a snapshot for ("+structure.getUrl()+")");
2061
2062        StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBase());
2063
2064        SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName());
2065
2066    ElementDefinition ed = structure.getSnapshot().getElement().get(0);
2067    generateForChildren(sch, "f:"+ed.getPath(), ed, structure, base);
2068    sch.dump();
2069  }
2070
2071  private class Slicer extends ElementDefinitionSlicingComponent {
2072    String criteria = "";
2073    String name = "";   
2074    boolean check;
2075    public Slicer(boolean cantCheck) {
2076      super();
2077      this.check = cantCheck;
2078    }
2079  }
2080  
2081  private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, StructureDefinition structure) {
2082    // given a child in a structure, it's sliced. figure out the slicing xpath
2083    if (child.getPath().endsWith(".extension")) {
2084      ElementDefinition ued = getUrlFor(structure, child);
2085      if ((ued == null || !ued.hasFixed()) && !(child.getType().get(0).hasProfile()))
2086        return new Slicer(false);
2087      else {
2088      Slicer s = new Slicer(true);
2089      String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).asStringValue() : ((UriType) ued.getFixed()).asStringValue();
2090      s.name = " with URL = '"+url+"'";
2091      s.criteria = "[@url = '"+url+"']";
2092      return s;
2093      }
2094    } else
2095      return new Slicer(false);
2096  }
2097
2098  private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, StructureDefinition structure, StructureDefinition base) throws IOException {
2099    //    generateForChild(txt, structure, child);
2100    List<ElementDefinition> children = getChildList(structure, ed);
2101    String sliceName = null;
2102    ElementDefinitionSlicingComponent slicing = null;
2103    for (ElementDefinition child : children) {
2104      String name = tail(child.getPath());
2105      if (child.hasSlicing()) {
2106        sliceName = name;
2107        slicing = child.getSlicing();        
2108      } else if (!name.equals(sliceName))
2109        slicing = null;
2110      
2111      ElementDefinition based = getByPath(base, child.getPath());
2112      boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin()));
2113      boolean doMax =  !child.getMax().equals("*") && (based == null || (!child.getMax().equals(based.getMax())));
2114      Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure);
2115      if (slicer.check) {
2116        if (doMin || doMax) {
2117          Section s = sch.section(xpath);
2118          Rule r = s.rule(xpath);
2119          if (doMin) 
2120            r.assrt("count(f:"+name+slicer.criteria+") >= "+Integer.toString(child.getMin()), name+slicer.name+": minimum cardinality of '"+name+"' is "+Integer.toString(child.getMin()));
2121          if (doMax) 
2122            r.assrt("count(f:"+name+slicer.criteria+") <= "+child.getMax(), name+slicer.name+": maximum cardinality of '"+name+"' is "+child.getMax());
2123          }
2124        }
2125      }
2126    for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
2127      if (inv.hasXpath()) {
2128        Section s = sch.section(ed.getPath());
2129        Rule r = s.rule(xpath);
2130        r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId()+": " : "")+inv.getHuman()+(inv.hasUserData(IS_DERIVED) ? " (inherited)" : ""));
2131      }
2132    }
2133    for (ElementDefinition child : children) {
2134      String name = tail(child.getPath());
2135      generateForChildren(sch, xpath+"/f:"+name, child, structure, base);
2136    }
2137  }
2138
2139
2140
2141
2142  private ElementDefinition getByPath(StructureDefinition base, String path) {
2143                for (ElementDefinition ed : base.getSnapshot().getElement()) {
2144                        if (ed.getPath().equals(path))
2145                                return ed;
2146                        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)))
2147                                return ed;
2148    }
2149          return null;
2150  }
2151
2152//
2153//private void generateForChild(TextStreamWriter txt,
2154//    StructureDefinition structure, ElementDefinition child) {
2155//  // TODO Auto-generated method stub
2156//
2157//}
2158
2159
2160}