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