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