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