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