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