001package org.hl7.fhir.dstu3.conformance;
002
003/*
004  Copyright (c) 2011+, HL7, Inc.
005  All rights reserved.
006  
007  Redistribution and use in source and binary forms, with or without modification, 
008  are permitted provided that the following conditions are met:
009    
010   * Redistributions of source code must retain the above copyright notice, this 
011     list of conditions and the following disclaimer.
012   * Redistributions in binary form must reproduce the above copyright notice, 
013     this list of conditions and the following disclaimer in the documentation 
014     and/or other materials provided with the distribution.
015   * Neither the name of HL7 nor the names of its contributors may be used to 
016     endorse or promote products derived from this software without specific 
017     prior written permission.
018  
019  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
020  ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
021  WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
022  IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
023  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
024  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
025  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
026  WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
027  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
028  POSSIBILITY OF SUCH DAMAGE.
029  
030 */
031
032
033import java.io.IOException;
034import java.io.OutputStream;
035import java.util.ArrayList;
036import java.util.Collections;
037import java.util.Comparator;
038import java.util.HashMap;
039import java.util.HashSet;
040import java.util.List;
041import java.util.Map;
042import java.util.Set;
043
044import org.apache.commons.lang3.StringUtils;
045import org.hl7.fhir.dstu3.conformance.ProfileUtilities.ProfileKnowledgeProvider.BindingResolution;
046import org.hl7.fhir.dstu3.context.IWorkerContext;
047import org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult;
048import org.hl7.fhir.dstu3.elementmodel.ObjectConverter;
049import org.hl7.fhir.dstu3.elementmodel.Property;
050import org.hl7.fhir.dstu3.formats.IParser;
051import org.hl7.fhir.dstu3.model.Base;
052import org.hl7.fhir.dstu3.model.BooleanType;
053import org.hl7.fhir.dstu3.model.CodeType;
054import org.hl7.fhir.dstu3.model.CodeableConcept;
055import org.hl7.fhir.dstu3.model.Coding;
056import org.hl7.fhir.dstu3.model.Element;
057import org.hl7.fhir.dstu3.model.ElementDefinition;
058import org.hl7.fhir.dstu3.model.ElementDefinition.AggregationMode;
059import org.hl7.fhir.dstu3.model.ElementDefinition.DiscriminatorType;
060import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionBaseComponent;
061import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionBindingComponent;
062import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionConstraintComponent;
063import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionExampleComponent;
064import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionMappingComponent;
065import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionSlicingComponent;
066import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent;
067import org.hl7.fhir.dstu3.model.ElementDefinition.SlicingRules;
068import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent;
069import org.hl7.fhir.dstu3.model.Enumeration;
070import org.hl7.fhir.dstu3.model.Enumerations.BindingStrength;
071import org.hl7.fhir.dstu3.model.Extension;
072import org.hl7.fhir.dstu3.model.IntegerType;
073import org.hl7.fhir.dstu3.model.PrimitiveType;
074import org.hl7.fhir.dstu3.model.Quantity;
075import org.hl7.fhir.dstu3.model.Reference;
076import org.hl7.fhir.dstu3.model.Resource;
077import org.hl7.fhir.dstu3.model.StringType;
078import org.hl7.fhir.dstu3.model.StructureDefinition;
079import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionDifferentialComponent;
080import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionKind;
081import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionMappingComponent;
082import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionSnapshotComponent;
083import org.hl7.fhir.dstu3.model.StructureDefinition.TypeDerivationRule;
084import org.hl7.fhir.dstu3.model.Type;
085import org.hl7.fhir.dstu3.model.UriType;
086import org.hl7.fhir.dstu3.model.ValueSet;
087import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent;
088import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent;
089import org.hl7.fhir.dstu3.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
090import org.hl7.fhir.dstu3.utils.NarrativeGenerator;
091import org.hl7.fhir.dstu3.utils.ToolingExtensions;
092import org.hl7.fhir.dstu3.utils.TranslatingUtilities;
093import org.hl7.fhir.dstu3.utils.formats.CSVWriter;
094import org.hl7.fhir.exceptions.DefinitionException;
095import org.hl7.fhir.exceptions.FHIRException;
096import org.hl7.fhir.exceptions.FHIRFormatError;
097import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
098import org.hl7.fhir.utilities.Utilities;
099import org.hl7.fhir.utilities.validation.ValidationMessage;
100import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
101import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
102import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
103import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
104import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
105import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
106import org.hl7.fhir.utilities.xhtml.XhtmlNode;
107import org.hl7.fhir.utilities.xml.SchematronWriter;
108import org.hl7.fhir.utilities.xml.SchematronWriter.Rule;
109import org.hl7.fhir.utilities.xml.SchematronWriter.SchematronType;
110import org.hl7.fhir.utilities.xml.SchematronWriter.Section;
111
112/**
113 * This class provides a set of utility operations for working with Profiles.
114 * Key functionality:
115 *  * getChildMap --?
116 *  * getChildList
117 *  * generateSnapshot: Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
118 *  * closeDifferential: fill out a differential by excluding anything not mentioned
119 *  * generateExtensionsTable: generate the HTML for a hierarchical table presentation of the extensions
120 *  * generateTable: generate  the HTML for a hierarchical table presentation of a structure
121 *  * generateSpanningTable: generate the HTML for a table presentation of a network of structures, starting at a nominated point
122 *  * summarise: describe the contents of a profile
123 *  
124 * note to maintainers: Do not make modifications to the snapshot generation without first changing the snapshot generation test cases to demonstrate the grounds for your change
125 *  
126 * @author Grahame
127 *
128 */
129public class ProfileUtilities extends TranslatingUtilities {
130
131  private static int nextSliceId = 0;
132  
133  public class ExtensionContext {
134
135    private ElementDefinition element;
136    private StructureDefinition defn;
137
138    public ExtensionContext(StructureDefinition ext, ElementDefinition ed) {
139      this.defn = ext;
140      this.element = ed;
141    }
142
143    public ElementDefinition getElement() {
144      return element;
145    }
146
147    public StructureDefinition getDefn() {
148      return defn;
149    }
150
151    public String getUrl() {
152      if (element == defn.getSnapshot().getElement().get(0))
153        return defn.getUrl();
154      else
155        return element.getSliceName();
156    }
157
158    public ElementDefinition getExtensionValueDefinition() {
159      int i = defn.getSnapshot().getElement().indexOf(element)+1;
160      while (i < defn.getSnapshot().getElement().size()) {
161        ElementDefinition ed = defn.getSnapshot().getElement().get(i);
162        if (ed.getPath().equals(element.getPath()))
163          return null;
164        if (ed.getPath().startsWith(element.getPath()+".value"))
165          return ed;
166        i++;
167      }
168      return null;
169    }
170    
171  }
172
173  private static final String ROW_COLOR_ERROR = "#ffcccc";
174  private static final String ROW_COLOR_FATAL = "#ff9999";
175  private static final String ROW_COLOR_WARNING = "#ffebcc";
176  private static final String ROW_COLOR_HINT = "#ebf5ff";
177  private static final String ROW_COLOR_NOT_MUST_SUPPORT = "#d6eaf8";
178  public static final int STATUS_OK = 0;
179  public static final int STATUS_HINT = 1;
180  public static final int STATUS_WARNING = 2;
181  public static final int STATUS_ERROR = 3;
182  public static final int STATUS_FATAL = 4;
183
184
185  private static final String DERIVATION_EQUALS = "derivation.equals";
186  public static final String DERIVATION_POINTER = "derived.pointer";
187  public static final String IS_DERIVED = "derived.fact";
188  public static final String UD_ERROR_STATUS = "error-status";
189  private static final String GENERATED_IN_SNAPSHOT = "profileutilities.snapshot.processed";
190
191  // note that ProfileUtilities are used re-entrantly internally, so nothing with process state can be here
192  private final IWorkerContext context;
193  private List<ValidationMessage> messages;
194  private List<String> snapshotStack = new ArrayList<String>();
195  private ProfileKnowledgeProvider pkp;
196  private boolean igmode;
197
198  public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp) {
199    super();
200    this.context = context;
201    this.messages = messages;
202    this.pkp = pkp;
203  }
204
205  private class UnusedTracker {
206    private boolean used;
207  }
208
209  public boolean isIgmode() {
210    return igmode;
211  }
212
213
214  public void setIgmode(boolean igmode) {
215    this.igmode = igmode;
216  }
217
218  public interface ProfileKnowledgeProvider {
219    public class BindingResolution {
220      public String display;
221      public String url;
222    }
223    boolean isDatatype(String typeSimple);
224    boolean isResource(String typeSimple);
225    boolean hasLinkFor(String typeSimple);
226    String getLinkFor(String corePath, String typeSimple);
227    BindingResolution resolveBinding(StructureDefinition def, ElementDefinitionBindingComponent binding, String path);
228    String getLinkForProfile(StructureDefinition profile, String url);
229    boolean prependLinks();
230  }
231
232
233
234  public static List<ElementDefinition> getChildMap(StructureDefinition profile, ElementDefinition element) throws DefinitionException {
235    if (element.getContentReference()!=null) {
236      for (ElementDefinition e : profile.getSnapshot().getElement()) {
237        if (element.getContentReference().equals("#"+e.getId()))
238          return getChildMap(profile, e);
239      }
240      throw new DefinitionException("Unable to resolve name reference "+element.getContentReference()+" at path "+element.getPath());
241
242    } else {
243      List<ElementDefinition> res = new ArrayList<ElementDefinition>();
244      List<ElementDefinition> elements = profile.getSnapshot().getElement();
245      String path = element.getPath();
246      for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) {
247        ElementDefinition e = elements.get(index);
248        if (e.getPath().startsWith(path + ".")) {
249          // We only want direct children, not all descendants
250          if (!e.getPath().substring(path.length()+1).contains("."))
251            res.add(e);
252        } else
253          break;
254      }
255      return res;
256    }
257  }
258
259
260  public static List<ElementDefinition> getSliceList(StructureDefinition profile, ElementDefinition element) throws DefinitionException {
261    if (!element.hasSlicing())
262      throw new Error("getSliceList should only be called when the element has slicing");
263
264    List<ElementDefinition> res = new ArrayList<ElementDefinition>();
265    List<ElementDefinition> elements = profile.getSnapshot().getElement();
266    String path = element.getPath();
267    for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) {
268      ElementDefinition e = elements.get(index);
269      if (e.getPath().startsWith(path + ".") || e.getPath().equals(path)) {
270        // We want elements with the same path (until we hit an element that doesn't start with the same path)
271        if (e.getPath().equals(element.getPath()))
272          res.add(e);
273      } else
274        break;
275    }
276    return res;
277  }
278
279
280  /**
281   * Given a Structure, navigate to the element given by the path and return the direct children of that element
282   *
283   * @param structure The structure to navigate into
284   * @param path The path of the element within the structure to get the children for
285   * @return A List containing the element children (all of them are Elements)
286   */
287  public static List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id) {
288    List<ElementDefinition> res = new ArrayList<ElementDefinition>();
289
290    boolean capturing = id==null;
291    if (id==null && !path.contains("."))
292      capturing = true;
293    
294    for (ElementDefinition e : profile.getSnapshot().getElement()) {
295      if (!capturing && id!=null && e.getId().equals(id)) {
296        capturing = true;
297      }
298      
299      // If our element is a slice, stop capturing children as soon as we see the next slice
300      if (capturing && e.hasId() && id!= null && !e.getId().equals(id) && e.getPath().equals(path))
301        break;
302      
303      if (capturing) {
304        String p = e.getPath();
305  
306        if (!Utilities.noString(e.getContentReference()) && path.startsWith(p)) {
307          if (path.length() > p.length())
308            return getChildList(profile, e.getContentReference()+"."+path.substring(p.length()+1), null);
309          else
310            return getChildList(profile, e.getContentReference(), null);
311          
312        } else if (p.startsWith(path+".") && !p.equals(path)) {
313          String tail = p.substring(path.length()+1);
314          if (!tail.contains(".")) {
315            res.add(e);
316          }
317        }
318      }
319    }
320
321    return res;
322  }
323
324
325  public static List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element) {
326    return getChildList(structure, element.getPath(), element.getId());
327        }
328
329  public void updateMaps(StructureDefinition base, StructureDefinition derived) throws DefinitionException {
330    if (base == null)
331        throw new DefinitionException("no base profile provided");
332    if (derived == null)
333      throw new DefinitionException("no derived structure provided");
334    
335    for (StructureDefinitionMappingComponent baseMap : base.getMapping()) {
336      boolean found = false;
337      for (StructureDefinitionMappingComponent derivedMap : derived.getMapping()) {
338        if (derivedMap.getUri().equals(baseMap.getUri())) {
339          found = true;
340          break;
341        }
342      }
343      if (!found)
344        derived.getMapping().add(baseMap);
345    }
346  }
347  
348  /**
349   * Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
350   *
351   * @param base - the base structure on which the differential will be applied
352   * @param differential - the differential to apply to the base
353   * @param url - where the base has relative urls for profile references, these need to be converted to absolutes by prepending this URL
354   * @param trimDifferential - if this is true, then the snap short generator will remove any material in the element definitions that is not different to the base
355   * @return
356   * @throws FHIRException 
357   * @throws DefinitionException 
358   * @throws Exception
359   */
360  public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String profileName) throws DefinitionException, FHIRException {
361    if (base == null)
362      throw new DefinitionException("no base profile provided");
363    if (derived == null)
364      throw new DefinitionException("no derived structure provided");
365
366    if (snapshotStack.contains(derived.getUrl()))
367      throw new DefinitionException("Circular snapshot references detected; cannot generate snapshot (stack = "+snapshotStack.toString()+")");
368    snapshotStack.add(derived.getUrl());
369    
370
371    derived.setSnapshot(new StructureDefinitionSnapshotComponent());
372
373    // so we have two lists - the base list, and the differential list
374    // the differential list is only allowed to include things that are in the base list, but
375    // is allowed to include them multiple times - thereby slicing them
376
377    // our approach is to walk through the base list, and see whether the differential
378    // says anything about them.
379    int baseCursor = 0;
380    int diffCursor = 0; // we need a diff cursor because we can only look ahead, in the bound scoped by longer paths
381
382    if (derived.hasDifferential() && !derived.getDifferential().getElementFirstRep().getPath().contains(".") && !derived.getDifferential().getElementFirstRep().getType().isEmpty())
383      throw new Error("type on first differential element!");
384
385    for (ElementDefinition e : derived.getDifferential().getElement()) 
386      e.clearUserData(GENERATED_IN_SNAPSHOT);
387    
388    // we actually delegate the work to a subroutine so we can re-enter it with a different cursors
389    processPaths("", derived.getSnapshot(), base.getSnapshot(), derived.getDifferential(), baseCursor, diffCursor, base.getSnapshot().getElement().size()-1, 
390        derived.getDifferential().hasElement() ? derived.getDifferential().getElement().size()-1 : -1, url, derived.getId(), null, null, false, base.getUrl(), null, false);
391    if (!derived.getSnapshot().getElementFirstRep().getType().isEmpty())
392      throw new Error("type on first snapshot element for "+derived.getSnapshot().getElementFirstRep().getPath()+" in "+derived.getUrl()+" from "+base.getUrl());
393    updateMaps(base, derived);
394    setIds(derived, false);
395    
396    //Check that all differential elements have a corresponding snapshot element
397    for (ElementDefinition e : derived.getDifferential().getElement()) {
398      if (!e.hasUserData(GENERATED_IN_SNAPSHOT)) {
399        System.out.println("Error in snapshot generation: Snapshot for "+derived.getUrl()+" does not contain differential element with id: " + e.getId());
400        System.out.println("Differential: ");
401        for (ElementDefinition ed : derived.getDifferential().getElement())
402          System.out.println("  "+ed.getPath()+" : "+typeSummary(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  id = "+ed.getId());
403        System.out.println("Snapshot: ");
404        for (ElementDefinition ed : derived.getSnapshot().getElement())
405          System.out.println("  "+ed.getPath()+" : "+typeSummary(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  id = "+ed.getId());
406        throw new DefinitionException("Snapshot for "+derived.getUrl()+" does not contain differential element with id: " + e.getId());
407//        System.out.println("**BAD Differential element: " + profileName + ":" + e.getId());
408      }
409    }
410  }
411
412  private String sliceSummary(ElementDefinition ed) {
413    if (!ed.hasSlicing() && !ed.hasSliceName())
414      return "";
415    if (ed.hasSliceName())
416      return " (slicename = "+ed.getSliceName()+")";
417    
418    StringBuilder b = new StringBuilder();
419    boolean first = true;
420    for (ElementDefinitionSlicingDiscriminatorComponent d : ed.getSlicing().getDiscriminator()) {
421      if (first) 
422        first = false;
423      else
424        b.append("|");
425      b.append(d.getPath());
426    }
427    return " (slicing by "+b.toString()+")";
428  }
429
430
431  private String typeSummary(ElementDefinition ed) {
432    StringBuilder b = new StringBuilder();
433    boolean first = true;
434    for (TypeRefComponent tr : ed.getType()) {
435      if (first) 
436        first = false;
437      else
438        b.append("|");
439      b.append(tr.getCode());
440    }
441    return b.toString();
442  }
443
444
445  private boolean findMatchingElement(String id, List<ElementDefinition> list) {
446    for (ElementDefinition ed : list) {
447      if (ed.getId().equals(id))
448        return true;
449      if (id.endsWith("[x]")) {
450        if (ed.getId().startsWith(id.substring(0, id.length()-3)) && !ed.getId().substring(id.length()-3).contains("."))
451          return true;
452      }
453    }
454    return false;
455  }
456
457
458  /**
459   * @param trimDifferential
460   * @throws DefinitionException, FHIRException 
461   * @throws Exception
462   */
463  private ElementDefinition processPaths(String indent, StructureDefinitionSnapshotComponent result, StructureDefinitionSnapshotComponent base, StructureDefinitionDifferentialComponent differential, int baseCursor, int diffCursor, int baseLimit,
464      int diffLimit, String url, String profileName, String contextPathSrc, String contextPathDst, boolean trimDifferential, String contextName, String resultPathBase, boolean slicingDone) throws DefinitionException, FHIRException {
465
466//    System.out.println(indent+"PP @ "+resultPathBase+": base = "+baseCursor+" to "+baseLimit+", diff = "+diffCursor+" to "+diffLimit+" (slicing = "+slicingDone+")");
467    ElementDefinition res = null; 
468    // just repeat processing entries until we run out of our allowed scope (1st entry, the allowed scope is all the entries)
469    while (baseCursor <= baseLimit) {
470      // get the current focus of the base, and decide what to do
471      ElementDefinition currentBase = base.getElement().get(baseCursor);
472      String cpath = fixedPath(contextPathSrc, currentBase.getPath());
473//      System.out.println(indent+" - "+cpath+": base = "+baseCursor+" to "+baseLimit+", diff = "+diffCursor+" to "+diffLimit+" (slicingDone = "+slicingDone+")");
474      List<ElementDefinition> diffMatches = getDiffMatches(differential, cpath, diffCursor, diffLimit, profileName, url); // get a list of matching elements in scope
475
476      // in the simple case, source is not sliced.
477      if (!currentBase.hasSlicing()) {
478        if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item
479          // so we just copy it in
480          ElementDefinition outcome = updateURLs(url, currentBase.copy());
481          outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
482          updateFromBase(outcome, currentBase);
483          markDerived(outcome);
484          if (resultPathBase == null)
485            resultPathBase = outcome.getPath();
486          else if (!outcome.getPath().startsWith(resultPathBase))
487            throw new DefinitionException("Adding wrong path");
488          result.getElement().add(outcome);
489          if (hasInnerDiffMatches(differential, cpath, diffCursor, diffLimit, base.getElement())) {
490            // well, the profile walks into this, so we need to as well
491            if (outcome.getType().size() > 1) {
492              for (TypeRefComponent t : outcome.getType()) {
493                if (!t.getCode().equals("Reference"))
494                  throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName);
495              }
496            }
497            StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
498            if (dt == null)
499              throw new DefinitionException(cpath+" has children for type "+typeCode(outcome.getType())+" in profile "+profileName+", but can't find type");
500            contextName = dt.getUrl();
501            int start = diffCursor;
502            while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), cpath+"."))
503              diffCursor++;
504            processPaths(indent+"  ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1,
505                diffCursor-1, url, profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false);
506          }
507          baseCursor++;
508        } else if (diffMatches.size() == 1 && (slicingDone || !(diffMatches.get(0).hasSlicing() || (isExtension(diffMatches.get(0)) && diffMatches.get(0).hasSliceName())))) {// one matching element in the differential
509          ElementDefinition template = null;
510          if (diffMatches.get(0).hasType() && diffMatches.get(0).getType().size() == 1 && diffMatches.get(0).getType().get(0).hasProfile() && !diffMatches.get(0).getType().get(0).getCode().equals("Reference")) {
511            String p = diffMatches.get(0).getType().get(0).getProfile();
512            StructureDefinition sd = context.fetchResource(StructureDefinition.class, p);
513            if (sd != null) {
514              if (!sd.hasSnapshot()) {
515                StructureDefinition sdb = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
516                if (sdb == null)
517                  throw new DefinitionException("no base for "+sd.getBaseDefinition());
518                generateSnapshot(sdb, sd, sd.getUrl(), sd.getName());
519              }
520              template = sd.getSnapshot().getElement().get(0).copy().setPath(currentBase.getPath());
521              template.setSliceName(null);
522              // temporary work around
523              if (!diffMatches.get(0).getType().get(0).getCode().equals("Extension")) {
524                template.setMin(currentBase.getMin());
525                template.setMax(currentBase.getMax());
526              }
527            }
528          } 
529          if (template == null)
530            template = currentBase.copy();
531          else
532            // some of what's in currentBase overrides template
533            template = overWriteWithCurrent(template, currentBase);
534          
535          ElementDefinition outcome = updateURLs(url, template);
536          outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
537          res = outcome;
538          updateFromBase(outcome, currentBase);
539          if (diffMatches.get(0).hasSliceName())
540            outcome.setSliceName(diffMatches.get(0).getSliceName());
541          outcome.setSlicing(null);
542          updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url);
543          if (outcome.getPath().endsWith("[x]") && outcome.getType().size() == 1 && !outcome.getType().get(0).getCode().equals("*")) // if the base profile allows multiple types, but the profile only allows one, rename it
544            outcome.setPath(outcome.getPath().substring(0, outcome.getPath().length()-3)+Utilities.capitalize(outcome.getType().get(0).getCode()));
545          if (resultPathBase == null)
546            resultPathBase = outcome.getPath();
547          else if (!outcome.getPath().startsWith(resultPathBase))
548            throw new DefinitionException("Adding wrong path");
549          result.getElement().add(outcome);
550          baseCursor++;
551          diffCursor = differential.getElement().indexOf(diffMatches.get(0))+1;
552          if (differential.getElement().size() > diffCursor && outcome.getPath().contains(".") && (isDataType(outcome.getType()) || outcome.hasContentReference())) {  // don't want to do this for the root, since that's base, and we're already processing it
553            if (pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".") && !baseWalksInto(base.getElement(), baseCursor)) {
554              if (outcome.getType().size() > 1) {
555                for (TypeRefComponent t : outcome.getType()) {
556                  if (!t.getCode().equals("Reference"))
557                    throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName);
558                }
559              }
560              int start = diffCursor;
561              while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+"."))
562                diffCursor++;
563              if (outcome.hasContentReference()) {
564                ElementDefinition tgt = getElementById(base.getElement(), outcome.getContentReference());
565                if (tgt == null)
566                  throw new DefinitionException("Unable to resolve reference to "+outcome.getContentReference());
567                replaceFromContentReference(outcome, tgt);
568                int nbc = base.getElement().indexOf(tgt)+1;
569                int nbl = nbc;
570                while (nbl < base.getElement().size() && base.getElement().get(nbl).getPath().startsWith(tgt.getPath()+"."))
571                  nbl++;
572                processPaths(indent+"  ", result, base, differential, nbc, start - 1, nbl-1, diffCursor - 1, url, profileName, tgt.getPath(), diffMatches.get(0).getPath(), trimDifferential, contextName, resultPathBase, false);
573              } else {
574                StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
575                if (dt == null)
576                  throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") for type "+typeCode(outcome.getType())+" in profile "+profileName+", but can't find type");
577                contextName = dt.getUrl();
578                processPaths(indent+"  ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start-1, dt.getSnapshot().getElement().size()-1,
579                    diffCursor - 1, url, profileName+pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false);
580              }
581            }
582          }
583        } else {
584          // ok, the differential slices the item. Let's check our pre-conditions to ensure that this is correct
585          if (!unbounded(currentBase) && !isSlicedToOneOnly(diffMatches.get(0)))
586            // you can only slice an element that doesn't repeat if the sum total of your slices is limited to 1
587            // (but you might do that in order to split up constraints by type)
588            throw new DefinitionException("Attempt to a slice an element that does not repeat: "+currentBase.getPath()+"/"+currentBase.getSliceName()+" from "+contextName+" in "+url);
589          if (!diffMatches.get(0).hasSlicing() && !isExtension(currentBase)) // well, the diff has set up a slice, but hasn't defined it. this is an error
590            throw new DefinitionException("differential does not have a slice: "+currentBase.getPath()+" in profile "+url);
591
592          // well, if it passed those preconditions then we slice the dest.
593          int start = 0;
594          int nbl = findEndOfElement(base, baseCursor);
595          if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && differential.getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0))+1) {
596            int ndc = differential.getElement().indexOf(diffMatches.get(0));
597            int ndl = findEndOfElement(differential, ndc);
598            processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, profileName+pathTail(diffMatches, 0), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true).setSlicing(diffMatches.get(0).getSlicing());
599            start++;
600          } else {
601            // we're just going to accept the differential slicing at face value
602            ElementDefinition outcome = updateURLs(url, currentBase.copy());
603            outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
604            updateFromBase(outcome, currentBase);
605
606            if (!diffMatches.get(0).hasSlicing())
607              outcome.setSlicing(makeExtensionSlicing());
608            else
609              outcome.setSlicing(diffMatches.get(0).getSlicing().copy());
610            if (!outcome.getPath().startsWith(resultPathBase))
611              throw new DefinitionException("Adding wrong path");
612            result.getElement().add(outcome);
613
614            // differential - if the first one in the list has a name, we'll process it. Else we'll treat it as the base definition of the slice.
615            if (!diffMatches.get(0).hasSliceName()) {
616              updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url);
617              if (!outcome.hasContentReference() && !outcome.hasType()) {
618                throw new DefinitionException("not done yet");
619              }
620              start++;
621              // result.getElement().remove(result.getElement().size()-1);
622            } else 
623              checkExtensionDoco(outcome);
624          }
625          // now, for each entry in the diff matches, we're going to process the base item
626          // our processing scope for base is all the children of the current path
627          int ndc = diffCursor;
628          int ndl = diffCursor;
629          for (int i = start; i < diffMatches.size(); i++) {
630            // our processing scope for the differential is the item in the list, and all the items before the next one in the list
631            ndc = differential.getElement().indexOf(diffMatches.get(i));
632            ndl = findEndOfElement(differential, ndc);
633/*            if (skipSlicingElement && i == 0) {
634              ndc = ndc + 1;
635              if (ndc > ndl)
636                continue;
637            }*/
638            // now we process the base scope repeatedly for each instance of the item in the differential list
639            processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, profileName+pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true);
640          }
641          // ok, done with that - next in the base list
642          baseCursor = nbl+1;
643          diffCursor = ndl+1;
644        }
645      } else {
646        // the item is already sliced in the base profile.
647        // here's the rules
648        //  1. irrespective of whether the slicing is ordered or not, the definition order must be maintained
649        //  2. slice element names have to match.
650        //  3. new slices must be introduced at the end
651        // corallory: you can't re-slice existing slices. is that ok?
652
653        // we're going to need this:
654        String path = currentBase.getPath();
655        ElementDefinition original = currentBase;
656
657        if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item
658          // copy across the currentbase, and all of its children and siblings
659          while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path)) {
660            ElementDefinition outcome = updateURLs(url, base.getElement().get(baseCursor).copy());
661            outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
662            if (!outcome.getPath().startsWith(resultPathBase))
663              throw new DefinitionException("Adding wrong path in profile " + profileName + ": "+outcome.getPath()+" vs " + resultPathBase);
664            result.getElement().add(outcome); // so we just copy it in
665            baseCursor++;
666          }
667        } else {
668          // first - check that the slicing is ok
669          boolean closed = currentBase.getSlicing().getRules() == SlicingRules.CLOSED;
670          int diffpos = 0;
671          boolean isExtension = cpath.endsWith(".extension") || cpath.endsWith(".modifierExtension");
672          if (diffMatches.get(0).hasSlicing()) { // it might be null if the differential doesn't want to say anything about slicing
673            if (!isExtension)
674              diffpos++; // if there's a slice on the first, we'll ignore any content it has
675            ElementDefinitionSlicingComponent dSlice = diffMatches.get(0).getSlicing();
676            ElementDefinitionSlicingComponent bSlice = currentBase.getSlicing();
677            if (dSlice.hasOrderedElement() && bSlice.hasOrderedElement() && !orderMatches(dSlice.getOrderedElement(), bSlice.getOrderedElement()))
678              throw new DefinitionException("Slicing rules on differential ("+summariseSlicing(dSlice)+") do not match those on base ("+summariseSlicing(bSlice)+") - order @ "+path+" ("+contextName+")");
679            if (!discriminatorMatches(dSlice.getDiscriminator(), bSlice.getDiscriminator()))
680             throw new DefinitionException("Slicing rules on differential ("+summariseSlicing(dSlice)+") do not match those on base ("+summariseSlicing(bSlice)+") - disciminator @ "+path+" ("+contextName+")");
681            if (!ruleMatches(dSlice.getRules(), bSlice.getRules()))
682             throw new DefinitionException("Slicing rules on differential ("+summariseSlicing(dSlice)+") do not match those on base ("+summariseSlicing(bSlice)+") - rule @ "+path+" ("+contextName+")");
683          }
684          if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && differential.getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0))+1) {
685            throw new Error("Not done yet");
686          }
687          ElementDefinition outcome = updateURLs(url, currentBase.copy());
688          outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
689          updateFromBase(outcome, currentBase);
690          if (diffMatches.get(0).hasSlicing() /*&& !isExtension*/) {
691            updateFromSlicing(outcome.getSlicing(), diffMatches.get(0).getSlicing());
692            updateFromDefinition(outcome, diffMatches.get(0), profileName, closed, url); // if there's no slice, we don't want to update the unsliced description
693          } else if (!diffMatches.get(0).hasSliceName())
694            diffMatches.get(0).setUserData(GENERATED_IN_SNAPSHOT, true); // because of updateFromDefinition isn't called 
695          
696          result.getElement().add(outcome);
697
698          if (!diffMatches.get(0).hasSliceName()) { // it's not real content, just the slice
699            diffpos++; 
700          }
701
702          // now, we have two lists, base and diff. we're going to work through base, looking for matches in diff.
703          List<ElementDefinition> baseMatches = getSiblings(base.getElement(), currentBase);
704          for (ElementDefinition baseItem : baseMatches) {
705            baseCursor = base.getElement().indexOf(baseItem);
706            outcome = updateURLs(url, baseItem.copy());
707            updateFromBase(outcome, currentBase);
708            outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
709            outcome.setSlicing(null);
710            if (!outcome.getPath().startsWith(resultPathBase))
711              throw new DefinitionException("Adding wrong path");
712            if (diffpos < diffMatches.size() && diffMatches.get(diffpos).getSliceName().equals(outcome.getSliceName())) {
713              // if there's a diff, we update the outcome with diff
714              // no? updateFromDefinition(outcome, diffMatches.get(diffpos), profileName, closed, url);
715              //then process any children
716              int nbl = findEndOfElement(base, baseCursor);
717              int ndc = differential.getElement().indexOf(diffMatches.get(diffpos));
718              int ndl = findEndOfElement(differential, ndc);
719              // now we process the base scope repeatedly for each instance of the item in the differential list
720              processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, profileName+pathTail(diffMatches, diffpos), contextPathSrc, contextPathDst, closed, contextName, resultPathBase, true);
721              // ok, done with that - now set the cursors for if this is the end
722              baseCursor = nbl;
723              diffCursor = ndl+1;
724              diffpos++;
725            } else {
726              result.getElement().add(outcome);
727              baseCursor++;
728              // just copy any children on the base
729              while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path) && !base.getElement().get(baseCursor).getPath().equals(path)) {
730                outcome = updateURLs(url, base.getElement().get(baseCursor).copy());
731                outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
732                if (!outcome.getPath().startsWith(resultPathBase))
733                  throw new DefinitionException("Adding wrong path");
734                result.getElement().add(outcome);
735                baseCursor++;
736              }
737              //Lloyd - add this for test T15
738              baseCursor--;
739            }
740          }
741          // finally, we process any remaining entries in diff, which are new (and which are only allowed if the base wasn't closed
742          if (closed && diffpos < diffMatches.size())
743            throw new DefinitionException("The base snapshot marks a slicing as closed, but the differential tries to extend it in "+profileName+" at "+path+" ("+cpath+")");
744          if (diffpos == diffMatches.size()) {
745            diffCursor++;
746          } else {
747            while (diffpos < diffMatches.size()) {
748              ElementDefinition diffItem = diffMatches.get(diffpos);
749              for (ElementDefinition baseItem : baseMatches)
750                if (baseItem.getSliceName().equals(diffItem.getSliceName()))
751                  throw new DefinitionException("Named items are out of order in the slice");
752              outcome = updateURLs(url, currentBase.copy());
753              //            outcome = updateURLs(url, diffItem.copy());
754              outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
755              updateFromBase(outcome, currentBase);
756              outcome.setSlicing(null);
757              if (!outcome.getPath().startsWith(resultPathBase))
758                throw new DefinitionException("Adding wrong path");
759              result.getElement().add(outcome);
760              updateFromDefinition(outcome, diffItem, profileName, trimDifferential, url);
761              // --- LM Added this
762              diffCursor = differential.getElement().indexOf(diffItem)+1;
763              if (!outcome.getType().isEmpty() && (/*outcome.getType().get(0).getCode().equals("Extension") || */differential.getElement().size() > diffCursor) && outcome.getPath().contains(".") && isDataType(outcome.getType())) {  // don't want to do this for the root, since that's base, and we're already processing it
764                if (!baseWalksInto(base.getElement(), baseCursor)) {
765                  if (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".")) {
766                    if (outcome.getType().size() > 1)
767                      for (TypeRefComponent t : outcome.getType()) {
768                        if (!t.getCode().equals("Reference"))
769                          throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName);
770                      }
771                    TypeRefComponent t = outcome.getType().get(0);
772                    StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
773                    //                if (t.getCode().equals("Extension") && t.hasProfile() && !t.getProfile().contains(":")) {
774                    // lloydfix                  dt = 
775                    //                }
776                    if (dt == null)
777                      throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") for type "+typeCode(outcome.getType())+" in profile "+profileName+", but can't find type");
778                    contextName = dt.getUrl();
779                    int start = diffCursor;
780                    while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+"."))
781                      diffCursor++;
782                    processPaths(indent+"  ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start-1, dt.getSnapshot().getElement().size()-1,
783                        diffCursor - 1, url, profileName+pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false);
784                  } else if (outcome.getType().get(0).getCode().equals("Extension")) {
785                    // Force URL to appear if we're dealing with an extension.  (This is a kludge - may need to drill down in other cases where we're slicing and the type has a profile declaration that could be setting the fixed value)
786                    StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
787                    for (ElementDefinition extEd : dt.getSnapshot().getElement()) {
788                      // We only want the children that aren't the root
789                      if (extEd.getPath().contains(".")) {
790                        ElementDefinition extUrlEd = updateURLs(url, extEd.copy());
791                        extUrlEd.setPath(fixedPath(outcome.getPath(), extUrlEd.getPath()));
792                        //                      updateFromBase(extUrlEd, currentBase);
793                        markDerived(extUrlEd);
794                        result.getElement().add(extUrlEd);
795                      }
796                    }                  
797                  }
798                }
799              }
800              // ---
801              diffpos++;
802            }
803          }
804          baseCursor++;
805        }
806      }
807    }
808    
809    int i = 0;
810    for (ElementDefinition e : result.getElement()) {
811      i++;
812      if (e.hasMinElement() && e.getMinElement().getValue()==null)
813        throw new Error("null min");
814    }
815    return res;
816  }
817
818
819  private void replaceFromContentReference(ElementDefinition outcome, ElementDefinition tgt) {
820    outcome.setContentReference(null);
821    outcome.getType().clear(); // though it should be clear anyway
822    outcome.getType().addAll(tgt.getType());    
823  }
824
825
826  private boolean baseWalksInto(List<ElementDefinition> elements, int cursor) {
827    if (cursor >= elements.size())
828      return false;
829    String path = elements.get(cursor).getPath();
830    String prevPath = elements.get(cursor - 1).getPath();
831    return path.startsWith(prevPath + ".");
832  }
833
834
835  private ElementDefinition overWriteWithCurrent(ElementDefinition profile, ElementDefinition usage) throws FHIRFormatError {
836    ElementDefinition res = profile.copy();
837    if (usage.hasSliceName())
838      res.setSliceName(usage.getSliceName());
839    if (usage.hasLabel())
840      res.setLabel(usage.getLabel());
841    for (Coding c : usage.getCode())
842      res.addCode(c);
843    
844    if (usage.hasDefinition())
845      res.setDefinition(usage.getDefinition());
846    if (usage.hasShort())
847      res.setShort(usage.getShort());
848    if (usage.hasComment())
849      res.setComment(usage.getComment());
850    if (usage.hasRequirements())
851      res.setRequirements(usage.getRequirements());
852    for (StringType c : usage.getAlias())
853      res.addAlias(c.getValue());
854    if (usage.hasMin())
855      res.setMin(usage.getMin());
856    if (usage.hasMax())
857      res.setMax(usage.getMax());
858     
859    if (usage.hasFixed())
860      res.setFixed(usage.getFixed());
861    if (usage.hasPattern())
862      res.setPattern(usage.getPattern());
863    if (usage.hasExample())
864      res.setExample(usage.getExample());
865    if (usage.hasMinValue())
866      res.setMinValue(usage.getMinValue());
867    if (usage.hasMaxValue())
868      res.setMaxValue(usage.getMaxValue());     
869    if (usage.hasMaxLength())
870      res.setMaxLength(usage.getMaxLength());
871    if (usage.hasMustSupport())
872      res.setMustSupport(usage.getMustSupport());
873    if (usage.hasBinding())
874      res.setBinding(usage.getBinding().copy());
875    for (ElementDefinitionConstraintComponent c : usage.getConstraint())
876      res.addConstraint(c);
877    
878    return res;
879  }
880
881
882  private boolean checkExtensionDoco(ElementDefinition base) {
883    // see task 3970. For an extension, there's no point copying across all the underlying definitional stuff
884    boolean isExtension = base.getPath().equals("Extension") || base.getPath().endsWith(".extension") || base.getPath().endsWith(".modifierExtension");
885    if (isExtension) {
886      base.setDefinition("An Extension");
887      base.setShort("Extension");
888      base.setCommentElement(null);
889      base.setRequirementsElement(null);
890      base.getAlias().clear();
891      base.getMapping().clear();
892    }
893    return isExtension;
894  }
895
896
897  private String pathTail(List<ElementDefinition> diffMatches, int i) {
898    
899    ElementDefinition d = diffMatches.get(i);
900    String s = d.getPath().contains(".") ? d.getPath().substring(d.getPath().lastIndexOf(".")+1) : d.getPath();
901    return "."+s + (d.hasType() && d.getType().get(0).hasProfile() ? "["+d.getType().get(0).getProfile()+"]" : "");
902  }
903
904
905  private void markDerived(ElementDefinition outcome) {
906    for (ElementDefinitionConstraintComponent inv : outcome.getConstraint())
907      inv.setUserData(IS_DERIVED, true);
908  }
909
910
911  private String summariseSlicing(ElementDefinitionSlicingComponent slice) {
912    StringBuilder b = new StringBuilder();
913    boolean first = true;
914    for (ElementDefinitionSlicingDiscriminatorComponent d : slice.getDiscriminator()) {
915      if (first)
916        first = false;
917      else
918        b.append(", ");
919      b.append(d);
920    }
921    b.append("(");
922    if (slice.hasOrdered())
923      b.append(slice.getOrderedElement().asStringValue());
924    b.append("/");
925    if (slice.hasRules())
926      b.append(slice.getRules().toCode());
927    b.append(")");
928    if (slice.hasDescription()) {
929      b.append(" \"");
930      b.append(slice.getDescription());
931      b.append("\"");
932    }
933    return b.toString();
934  }
935
936
937  private void updateFromBase(ElementDefinition derived, ElementDefinition base) {
938    if (base.hasBase()) {
939      if (!derived.hasBase())
940        derived.setBase(new ElementDefinitionBaseComponent());
941      derived.getBase().setPath(base.getBase().getPath());
942      derived.getBase().setMin(base.getBase().getMin());
943      derived.getBase().setMax(base.getBase().getMax());
944    } else {
945      if (!derived.hasBase())
946        derived.setBase(new ElementDefinitionBaseComponent());
947      derived.getBase().setPath(base.getPath());
948      derived.getBase().setMin(base.getMin());
949      derived.getBase().setMax(base.getMax());
950    }
951  }
952
953
954  private boolean pathStartsWith(String p1, String p2) {
955    return p1.startsWith(p2);
956  }
957
958  private boolean pathMatches(String p1, String p2) {
959    return p1.equals(p2) || (p2.endsWith("[x]") && p1.startsWith(p2.substring(0, p2.length()-3)) && !p1.substring(p2.length()-3).contains("."));
960  }
961
962
963  private String fixedPath(String contextPath, String pathSimple) {
964    if (contextPath == null)
965      return pathSimple;
966    return contextPath+"."+pathSimple.substring(pathSimple.indexOf(".")+1);
967  }
968
969
970  private StructureDefinition getProfileForDataType(TypeRefComponent type)  {
971    StructureDefinition sd = null;
972    if (type.hasProfile() && !type.getCode().equals("Reference"))  
973      sd = context.fetchResource(StructureDefinition.class, type.getProfile()); 
974    if (sd == null)
975      sd = context.fetchTypeDefinition(type.getCode());
976    if (sd == null)
977      System.out.println("XX: failed to find profle for type: " + type.getCode()); // debug GJM
978    return sd;
979  }
980
981
982  public static String typeCode(List<TypeRefComponent> types) {
983    StringBuilder b = new StringBuilder();
984    boolean first = true;
985    for (TypeRefComponent type : types) {
986      if (first) first = false; else b.append(", ");
987      b.append(type.getCode());
988      if (type.hasTargetProfile())
989        b.append("{"+type.getTargetProfile()+"}");
990      else if (type.hasProfile())
991        b.append("{"+type.getProfile()+"}");
992    }
993    return b.toString();
994  }
995
996
997  private boolean isDataType(List<TypeRefComponent> types) {
998    if (types.isEmpty())
999      return false;
1000    for (TypeRefComponent type : types) {
1001      String t = type.getCode();
1002      if (!isDataType(t) && !isPrimitive(t))
1003        return false;
1004    }
1005    return true;
1006  }
1007
1008
1009  /**
1010   * Finds internal references in an Element's Binding and StructureDefinition references (in TypeRef) and bases them on the given url
1011   * @param url - the base url to use to turn internal references into absolute references
1012   * @param element - the Element to update
1013   * @return - the updated Element
1014   */
1015  private ElementDefinition updateURLs(String url, ElementDefinition element) {
1016    if (element != null) {
1017      ElementDefinition defn = element;
1018      if (defn.hasBinding() && defn.getBinding().getValueSet() instanceof Reference && ((Reference)defn.getBinding().getValueSet()).getReference().startsWith("#"))
1019        ((Reference)defn.getBinding().getValueSet()).setReference(url+((Reference)defn.getBinding().getValueSet()).getReference());
1020      for (TypeRefComponent t : defn.getType()) {
1021        if (t.hasProfile()) {
1022          if (t.getProfile().startsWith("#"))
1023            t.setProfile(url+t.getProfile());
1024        }
1025        if (t.hasTargetProfile()) {
1026          if (t.getTargetProfile().startsWith("#"))
1027            t.setTargetProfile(url+t.getTargetProfile());
1028        }
1029      }
1030    }
1031    return element;
1032  }
1033
1034  private List<ElementDefinition> getSiblings(List<ElementDefinition> list, ElementDefinition current) {
1035    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
1036    String path = current.getPath();
1037    int cursor = list.indexOf(current)+1;
1038    while (cursor < list.size() && list.get(cursor).getPath().length() >= path.length()) {
1039      if (pathMatches(list.get(cursor).getPath(), path))
1040        result.add(list.get(cursor));
1041      cursor++;
1042    }
1043    return result;
1044  }
1045
1046  private void updateFromSlicing(ElementDefinitionSlicingComponent dst, ElementDefinitionSlicingComponent src) {
1047    if (src.hasOrderedElement())
1048      dst.setOrderedElement(src.getOrderedElement().copy());
1049    if (src.hasDiscriminator()) {
1050      //    dst.getDiscriminator().addAll(src.getDiscriminator());  Can't use addAll because it uses object equality, not string equality
1051      for (ElementDefinitionSlicingDiscriminatorComponent s : src.getDiscriminator()) {
1052        boolean found = false;
1053        for (ElementDefinitionSlicingDiscriminatorComponent d : dst.getDiscriminator()) {
1054          if (matches(d, s)) {
1055            found = true;
1056            break;
1057          }
1058        }
1059        if (!found)
1060          dst.getDiscriminator().add(s);
1061      }
1062    }
1063    if (src.hasRulesElement())
1064      dst.setRulesElement(src.getRulesElement().copy());
1065  }
1066
1067  private boolean orderMatches(BooleanType diff, BooleanType base) {
1068    return (diff == null) || (base == null) || (diff.getValue() == base.getValue());
1069  }
1070
1071  private boolean discriminatorMatches(List<ElementDefinitionSlicingDiscriminatorComponent> diff, List<ElementDefinitionSlicingDiscriminatorComponent> base) {
1072    if (diff.isEmpty() || base.isEmpty())
1073        return true;
1074    if (diff.size() != base.size())
1075        return false;
1076    for (int i = 0; i < diff.size(); i++)
1077        if (!matches(diff.get(i), base.get(i)))
1078                return false;
1079    return true;
1080  }
1081
1082  private boolean matches(ElementDefinitionSlicingDiscriminatorComponent c1, ElementDefinitionSlicingDiscriminatorComponent c2) {
1083    return c1.getType().equals(c2.getType()) && c1.getPath().equals(c2.getPath());
1084  }
1085
1086
1087  private boolean ruleMatches(SlicingRules diff, SlicingRules base) {
1088    return (diff == null) || (base == null) || (diff == base) || (diff == SlicingRules.OPEN) ||
1089        ((diff == SlicingRules.OPENATEND && base == SlicingRules.CLOSED));
1090  }
1091
1092  private boolean isSlicedToOneOnly(ElementDefinition e) {
1093    return (e.hasSlicing() && e.hasMaxElement() && e.getMax().equals("1"));
1094  }
1095
1096  private ElementDefinitionSlicingComponent makeExtensionSlicing() {
1097        ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent();
1098        nextSliceId++;
1099        slice.setId(Integer.toString(nextSliceId));
1100    slice.addDiscriminator().setPath("url").setType(DiscriminatorType.VALUE);
1101    slice.setOrdered(false);
1102    slice.setRules(SlicingRules.OPEN);
1103    return slice;
1104  }
1105
1106  private boolean isExtension(ElementDefinition currentBase) {
1107    return currentBase.getPath().endsWith(".extension") || currentBase.getPath().endsWith(".modifierExtension");
1108  }
1109
1110  private boolean hasInnerDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, List<ElementDefinition> base) throws DefinitionException {
1111    for (int i = start; i <= end; i++) {
1112      String statedPath = context.getElement().get(i).getPath();
1113      if (statedPath.startsWith(path+".") && !statedPath.substring(path.length()+1).contains(".")) {
1114        boolean found = false;
1115        for (ElementDefinition ed : base) {
1116          String ep = ed.getPath();
1117          if (ep.equals(statedPath) || (ep.endsWith("[x]") && statedPath.length() > ep.length() - 2 && statedPath.substring(0, ep.length()-3).equals(ep.substring(0, ep.length()-3)) && !statedPath.substring(ep.length()).contains(".")))
1118            found = true;
1119        }
1120        if (!found)
1121          return true;
1122      }
1123    }
1124    return false;
1125  }
1126
1127  private List<ElementDefinition> getDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, String profileName, String url) throws DefinitionException {
1128    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
1129    for (int i = start; i <= end; i++) {
1130      String statedPath = context.getElement().get(i).getPath();
1131      if (statedPath.equals(path) || (path.endsWith("[x]") && statedPath.length() > path.length() - 2 && statedPath.substring(0, path.length()-3).equals(path.substring(0, path.length()-3)) && (statedPath.length() < path.length() || !statedPath.substring(path.length()).contains(".")))) {
1132        /* 
1133         * Commenting this out because it raises warnings when profiling inherited elements.  For example,
1134         * Error: unknown element 'Bundle.meta.profile' (or it is out of order) in profile ... (looking for 'Bundle.entry')
1135         * Not sure we have enough information here to do the check properly.  Might be better done when we're sorting the profile?
1136
1137        if (i != start && result.isEmpty() && !path.startsWith(context.getElement().get(start).getPath()))
1138          messages.add(new ValidationMessage(Source.ProfileValidator, IssueType.VALUE, "StructureDefinition.differential.element["+Integer.toString(start)+"]", "Error: unknown element '"+context.getElement().get(start).getPath()+"' (or it is out of order) in profile '"+url+"' (looking for '"+path+"')", IssueSeverity.WARNING));
1139
1140         */
1141        result.add(context.getElement().get(i));
1142      }
1143    }
1144    return result;
1145  }
1146
1147  private int findEndOfElement(StructureDefinitionDifferentialComponent context, int cursor) {
1148            int result = cursor;
1149            String path = context.getElement().get(cursor).getPath()+".";
1150            while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path))
1151              result++;
1152            return result;
1153          }
1154
1155  private int findEndOfElement(StructureDefinitionSnapshotComponent context, int cursor) {
1156            int result = cursor;
1157            String path = context.getElement().get(cursor).getPath()+".";
1158            while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path))
1159              result++;
1160            return result;
1161          }
1162
1163  private boolean unbounded(ElementDefinition definition) {
1164    StringType max = definition.getMaxElement();
1165    if (max == null)
1166      return false; // this is not valid
1167    if (max.getValue().equals("1"))
1168      return false;
1169    if (max.getValue().equals("0"))
1170      return false;
1171    return true;
1172  }
1173
1174  private void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, boolean trimDifferential, String purl) throws DefinitionException, FHIRException {
1175    source.setUserData(GENERATED_IN_SNAPSHOT, true);
1176    // we start with a clone of the base profile ('dest') and we copy from the profile ('source')
1177    // over the top for anything the source has
1178    ElementDefinition base = dest;
1179    ElementDefinition derived = source;
1180    derived.setUserData(DERIVATION_POINTER, base);
1181
1182    // Before applying changes, apply them to what's in the profile
1183    // TODO: follow Chris's rules
1184    StructureDefinition profile = source.getType().size() == 1 && source.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, source.getTypeFirstRep().getProfile()) : null;
1185    if (profile != null) {
1186      ElementDefinition e = profile.getSnapshot().getElement().get(0);
1187      base.setDefinition(e.getDefinition());
1188      base.setShort(e.getShort());
1189      if (e.hasCommentElement())
1190        base.setCommentElement(e.getCommentElement());
1191      if (e.hasRequirementsElement())
1192        base.setRequirementsElement(e.getRequirementsElement());
1193      base.getAlias().clear();
1194      base.getAlias().addAll(e.getAlias());
1195      base.getMapping().clear();
1196      base.getMapping().addAll(e.getMapping());
1197    }
1198    
1199    if (derived != null) {
1200      boolean isExtension = checkExtensionDoco(base);
1201
1202      if (derived.hasSliceName()) {
1203        base.setSliceName(derived.getSliceName());
1204      }
1205      
1206      if (derived.hasShortElement()) {
1207        if (!Base.compareDeep(derived.getShortElement(), base.getShortElement(), false))
1208          base.setShortElement(derived.getShortElement().copy());
1209        else if (trimDifferential)
1210          derived.setShortElement(null);
1211        else if (derived.hasShortElement())
1212          derived.getShortElement().setUserData(DERIVATION_EQUALS, true);
1213      }
1214
1215      if (derived.hasDefinitionElement()) {
1216        if (derived.getDefinition().startsWith("..."))
1217          base.setDefinition(base.getDefinition()+"\r\n"+derived.getDefinition().substring(3));
1218        else if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false))
1219          base.setDefinitionElement(derived.getDefinitionElement().copy());
1220        else if (trimDifferential)
1221          derived.setDefinitionElement(null);
1222        else if (derived.hasDefinitionElement())
1223          derived.getDefinitionElement().setUserData(DERIVATION_EQUALS, true);
1224      }
1225
1226      if (derived.hasCommentElement()) {
1227        if (derived.getComment().startsWith("..."))
1228          base.setComment(base.getComment()+"\r\n"+derived.getComment().substring(3));
1229        else if (derived.hasCommentElement()!= base.hasCommentElement() || !Base.compareDeep(derived.getCommentElement(), base.getCommentElement(), false))
1230          base.setCommentElement(derived.getCommentElement().copy());
1231        else if (trimDifferential)
1232          base.setCommentElement(derived.getCommentElement().copy());
1233        else if (derived.hasCommentElement())
1234          derived.getCommentElement().setUserData(DERIVATION_EQUALS, true);
1235      }
1236
1237      if (derived.hasLabelElement()) {
1238        if (derived.getLabel().startsWith("..."))
1239          base.setLabel(base.getLabel()+"\r\n"+derived.getLabel().substring(3));
1240        else if (!base.hasLabelElement() || !Base.compareDeep(derived.getLabelElement(), base.getLabelElement(), false))
1241          base.setLabelElement(derived.getLabelElement().copy());
1242        else if (trimDifferential)
1243          base.setLabelElement(derived.getLabelElement().copy());
1244        else if (derived.hasLabelElement())
1245          derived.getLabelElement().setUserData(DERIVATION_EQUALS, true);
1246      }
1247
1248      if (derived.hasRequirementsElement()) {
1249        if (derived.getRequirements().startsWith("..."))
1250          base.setRequirements(base.getRequirements()+"\r\n"+derived.getRequirements().substring(3));
1251        else if (!base.hasRequirementsElement() || !Base.compareDeep(derived.getRequirementsElement(), base.getRequirementsElement(), false))
1252          base.setRequirementsElement(derived.getRequirementsElement().copy());
1253        else if (trimDifferential)
1254          base.setRequirementsElement(derived.getRequirementsElement().copy());
1255        else if (derived.hasRequirementsElement())
1256          derived.getRequirementsElement().setUserData(DERIVATION_EQUALS, true);
1257      }
1258      // sdf-9
1259      if (derived.hasRequirements() && !base.getPath().contains("."))
1260        derived.setRequirements(null);
1261      if (base.hasRequirements() && !base.getPath().contains("."))
1262        base.setRequirements(null);
1263
1264      if (derived.hasAlias()) {
1265        if (!Base.compareDeep(derived.getAlias(), base.getAlias(), false))
1266          for (StringType s : derived.getAlias()) {
1267            if (!base.hasAlias(s.getValue()))
1268              base.getAlias().add(s.copy());
1269          }
1270        else if (trimDifferential)
1271          derived.getAlias().clear();
1272        else
1273          for (StringType t : derived.getAlias())
1274            t.setUserData(DERIVATION_EQUALS, true);
1275      }
1276
1277      if (derived.hasMinElement()) {
1278        if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) {
1279          if (derived.getMin() < base.getMin())
1280            messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Derived min  ("+Integer.toString(derived.getMin())+") cannot be less than base min ("+Integer.toString(base.getMin())+")", ValidationMessage.IssueSeverity.ERROR));
1281          base.setMinElement(derived.getMinElement().copy());
1282        } else if (trimDifferential)
1283          derived.setMinElement(null);
1284        else
1285          derived.getMinElement().setUserData(DERIVATION_EQUALS, true);
1286      }
1287
1288      if (derived.hasMaxElement()) {
1289        if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) {
1290          if (isLargerMax(derived.getMax(), base.getMax()))
1291            messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Derived max ("+derived.getMax()+") cannot be greater than base max ("+base.getMax()+")", ValidationMessage.IssueSeverity.ERROR));
1292          base.setMaxElement(derived.getMaxElement().copy());
1293        } else if (trimDifferential)
1294          derived.setMaxElement(null);
1295        else
1296          derived.getMaxElement().setUserData(DERIVATION_EQUALS, true);
1297      }
1298
1299      if (derived.hasFixed()) {
1300        if (!Base.compareDeep(derived.getFixed(), base.getFixed(), true)) {
1301          base.setFixed(derived.getFixed().copy());
1302        } else if (trimDifferential)
1303          derived.setFixed(null);
1304        else
1305          derived.getFixed().setUserData(DERIVATION_EQUALS, true);
1306      }
1307
1308      if (derived.hasPattern()) {
1309        if (!Base.compareDeep(derived.getPattern(), base.getPattern(), false)) {
1310          base.setPattern(derived.getPattern().copy());
1311        } else
1312          if (trimDifferential)
1313            derived.setPattern(null);
1314          else
1315            derived.getPattern().setUserData(DERIVATION_EQUALS, true);
1316      }
1317
1318      for (ElementDefinitionExampleComponent ex : derived.getExample()) {
1319        boolean found = false;
1320        for (ElementDefinitionExampleComponent exS : base.getExample())
1321          if (Base.compareDeep(ex, exS, false))
1322            found = true;
1323        if (!found)
1324          base.addExample(ex.copy());
1325        else if (trimDifferential)
1326          derived.getExample().remove(ex);
1327        else
1328          ex.setUserData(DERIVATION_EQUALS, true);
1329      }
1330
1331      if (derived.hasMaxLengthElement()) {
1332        if (!Base.compareDeep(derived.getMaxLengthElement(), base.getMaxLengthElement(), false))
1333          base.setMaxLengthElement(derived.getMaxLengthElement().copy());
1334        else if (trimDifferential)
1335          derived.setMaxLengthElement(null);
1336        else
1337          derived.getMaxLengthElement().setUserData(DERIVATION_EQUALS, true);
1338      }
1339  
1340      if (derived.hasMaxValue()) {
1341        if (!Base.compareDeep(derived.getMaxValue(), base.getMaxValue(), false))
1342          base.setMaxValue(derived.getMaxValue().copy());
1343        else if (trimDifferential)
1344          derived.setMaxValue(null);
1345        else
1346          derived.getMaxValue().setUserData(DERIVATION_EQUALS, true);
1347      }
1348  
1349      if (derived.hasMinValue()) {
1350        if (!Base.compareDeep(derived.getMinValue(), base.getMinValue(), false))
1351          base.setMinValue(derived.getMinValue().copy());
1352        else if (trimDifferential)
1353          derived.setMinValue(null);
1354        else
1355          derived.getMinValue().setUserData(DERIVATION_EQUALS, true);
1356      }
1357
1358      // todo: what to do about conditions?
1359      // condition : id 0..*
1360
1361      if (derived.hasMustSupportElement()) {
1362        if (!(base.hasMustSupportElement() && Base.compareDeep(derived.getMustSupportElement(), base.getMustSupportElement(), false)))
1363          base.setMustSupportElement(derived.getMustSupportElement().copy());
1364        else if (trimDifferential)
1365          derived.setMustSupportElement(null);
1366        else
1367          derived.getMustSupportElement().setUserData(DERIVATION_EQUALS, true);
1368      }
1369
1370
1371      // profiles cannot change : isModifier, defaultValue, meaningWhenMissing
1372      // but extensions can change isModifier
1373      if (isExtension) {
1374        if (derived.hasIsModifierElement() && !(base.hasIsModifierElement() && Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false)))
1375          base.setIsModifierElement(derived.getIsModifierElement().copy());
1376        else if (trimDifferential)
1377          derived.setIsModifierElement(null);
1378        else if (derived.hasIsModifierElement())
1379          derived.getIsModifierElement().setUserData(DERIVATION_EQUALS, true);
1380      }
1381
1382      if (derived.hasBinding()) {
1383        if (!base.hasBinding() || !Base.compareDeep(derived.getBinding(), base.getBinding(), false)) {
1384          if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && derived.getBinding().getStrength() != BindingStrength.REQUIRED)
1385            messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "illegal attempt to change the binding on "+derived.getPath()+" from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode(), ValidationMessage.IssueSeverity.ERROR));
1386//            throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode());
1387          else if (base.hasBinding() && derived.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && base.getBinding().hasValueSetReference() && derived.getBinding().hasValueSetReference()) {
1388            ValueSetExpansionOutcome expBase = context.expandVS(context.fetchResource(ValueSet.class, base.getBinding().getValueSetReference().getReference()), true, false);
1389            ValueSetExpansionOutcome expDerived = context.expandVS(context.fetchResource(ValueSet.class, derived.getBinding().getValueSetReference().getReference()), true, false);
1390            if (expBase.getValueset() == null)
1391              messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSetReference().getReference()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING));
1392            else if (expDerived.getValueset() == null)
1393              messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSetReference().getReference()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING));
1394            else if (!isSubset(expBase.getValueset(), expDerived.getValueset()))
1395              messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSetReference().getReference()+" is not a subset of binding "+base.getBinding().getValueSetReference().getReference(), ValidationMessage.IssueSeverity.ERROR));
1396          }
1397          base.setBinding(derived.getBinding().copy());
1398        } else if (trimDifferential)
1399          derived.setBinding(null);
1400        else
1401          derived.getBinding().setUserData(DERIVATION_EQUALS, true);
1402      } // else if (base.hasBinding() && doesn't have bindable type )
1403        //  base
1404
1405      if (derived.hasIsSummaryElement()) {
1406        if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false)) {
1407          if (base.hasIsSummary())
1408            throw new Error("Error in profile "+pn+" at "+derived.getPath()+": Base isSummary = "+base.getIsSummaryElement().asStringValue()+", derived isSummary = "+derived.getIsSummaryElement().asStringValue());
1409          base.setIsSummaryElement(derived.getIsSummaryElement().copy());
1410        } else if (trimDifferential)
1411          derived.setIsSummaryElement(null);
1412        else
1413          derived.getIsSummaryElement().setUserData(DERIVATION_EQUALS, true);
1414      }
1415
1416      if (derived.hasType()) {
1417        if (!Base.compareDeep(derived.getType(), base.getType(), false)) {
1418          if (base.hasType()) {
1419            for (TypeRefComponent ts : derived.getType()) {
1420              boolean ok = false;
1421              CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1422              for (TypeRefComponent td : base.getType()) {;
1423                b.append(td.getCode());
1424                if (td.hasCode() && (td.getCode().equals(ts.getCode()) || td.getCode().equals("Extension") ||
1425                    td.getCode().equals("Element") || td.getCode().equals("*") ||
1426                    ((td.getCode().equals("Resource") || (td.getCode().equals("DomainResource")) && pkp.isResource(ts.getCode())))))
1427                  ok = true;
1428              }
1429              if (!ok)
1430                throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal constrained type "+ts.getCode()+" from "+b.toString());
1431            }
1432          }
1433          base.getType().clear();
1434          for (TypeRefComponent t : derived.getType()) {
1435            TypeRefComponent tt = t.copy();
1436//            tt.setUserData(DERIVATION_EQUALS, true);
1437            base.getType().add(tt);
1438          }
1439        }
1440        else if (trimDifferential)
1441          derived.getType().clear();
1442        else
1443          for (TypeRefComponent t : derived.getType())
1444            t.setUserData(DERIVATION_EQUALS, true);
1445      }
1446
1447      if (derived.hasMapping()) {
1448        // todo: mappings are not cumulative - one replaces another
1449        if (!Base.compareDeep(derived.getMapping(), base.getMapping(), false)) {
1450          for (ElementDefinitionMappingComponent s : derived.getMapping()) {
1451            boolean found = false;
1452            for (ElementDefinitionMappingComponent d : base.getMapping()) {
1453              found = found || (d.getIdentity().equals(s.getIdentity()) && d.getMap().equals(s.getMap()));
1454            }
1455            if (!found)
1456              base.getMapping().add(s);
1457          }
1458        }
1459        else if (trimDifferential)
1460          derived.getMapping().clear();
1461        else
1462          for (ElementDefinitionMappingComponent t : derived.getMapping())
1463            t.setUserData(DERIVATION_EQUALS, true);
1464      }
1465
1466      // todo: constraints are cumulative. there is no replacing
1467      for (ElementDefinitionConstraintComponent s : base.getConstraint()) { 
1468        s.setUserData(IS_DERIVED, true);
1469        if (!s.hasSource())
1470          s.setSource(base.getId());
1471      }
1472      if (derived.hasConstraint()) {
1473        for (ElementDefinitionConstraintComponent s : derived.getConstraint()) {
1474          ElementDefinitionConstraintComponent inv = s.copy();
1475          base.getConstraint().add(inv);
1476        }
1477      }
1478      
1479      // now, check that we still have a bindable type; if not, delete the binding - see task 8477
1480      if (dest.hasBinding() && !hasBindableType(dest))
1481        dest.setBinding(null);
1482        
1483      // finally, we copy any extensions from source to dest
1484      for (Extension ex : base.getExtension()) {
1485        StructureDefinition sd  = context.fetchResource(StructureDefinition.class, ex.getUrl());
1486        if (sd == null || sd.getSnapshot() == null || sd.getSnapshot().getElementFirstRep().getMax().equals("1"))
1487          ToolingExtensions.removeExtension(dest, ex.getUrl());
1488        dest.addExtension(ex);
1489      }
1490    }
1491  }
1492
1493  private boolean hasBindableType(ElementDefinition ed) {
1494    for (TypeRefComponent tr : ed.getType()) {
1495      if (Utilities.existsInList(tr.getCode(), "Coding", "CodeableConcept", "Quantity", "url", "string", "code"))
1496        return true;
1497    }
1498    return false;
1499  }
1500
1501
1502  private boolean isLargerMax(String derived, String base) {
1503    if ("*".equals(base))
1504      return false;
1505    if ("*".equals(derived))
1506      return true;
1507    return Integer.parseInt(derived) > Integer.parseInt(base);
1508  }
1509
1510
1511  private boolean isSubset(ValueSet expBase, ValueSet expDerived) {
1512    return codesInExpansion(expDerived.getExpansion().getContains(), expBase.getExpansion());
1513  }
1514
1515
1516  private boolean codesInExpansion(List<ValueSetExpansionContainsComponent> contains, ValueSetExpansionComponent expansion) {
1517    for (ValueSetExpansionContainsComponent cc : contains) {
1518      if (!inExpansion(cc, expansion.getContains()))
1519        return false;
1520      if (!codesInExpansion(cc.getContains(), expansion))
1521        return false;
1522    }
1523    return true;
1524  }
1525
1526
1527  private boolean inExpansion(ValueSetExpansionContainsComponent cc, List<ValueSetExpansionContainsComponent> contains) {
1528    for (ValueSetExpansionContainsComponent cc1 : contains) {
1529      if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode()))
1530        return true;
1531      if (inExpansion(cc,  cc1.getContains()))
1532        return true;
1533    }
1534    return false;
1535  }
1536
1537  public void closeDifferential(StructureDefinition base, StructureDefinition derived) throws FHIRException {
1538    for (ElementDefinition edb : base.getSnapshot().getElement()) {
1539      if (isImmediateChild(edb) && !edb.getPath().endsWith(".id")) {
1540        ElementDefinition edm = getMatchInDerived(edb, derived.getDifferential().getElement());
1541        if (edm == null) {
1542          ElementDefinition edd = derived.getDifferential().addElement();
1543          edd.setPath(edb.getPath());
1544          edd.setMax("0");
1545        } else if (edb.hasSlicing()) {
1546          closeChildren(base, edb, derived, edm);
1547        }
1548      }
1549    }
1550    sortDifferential(base, derived, derived.getName(), new ArrayList<String>());
1551  }
1552
1553  private void closeChildren(StructureDefinition base, ElementDefinition edb, StructureDefinition derived, ElementDefinition edm) {
1554    String path = edb.getPath()+".";
1555    int baseStart = base.getSnapshot().getElement().indexOf(edb);
1556    int baseEnd = findEnd(base.getSnapshot().getElement(), edb, baseStart+1);
1557    int diffStart = derived.getDifferential().getElement().indexOf(edm);
1558    int diffEnd = findEnd(derived.getDifferential().getElement(), edm, diffStart+1);
1559    
1560    for (int cBase = baseStart; cBase < baseEnd; cBase++) {
1561      ElementDefinition edBase = base.getSnapshot().getElement().get(cBase);
1562      if (isImmediateChild(edBase, edb)) {
1563        ElementDefinition edMatch = getMatchInDerived(edBase, derived.getDifferential().getElement(), diffStart, diffEnd);
1564        if (edMatch == null) {
1565          ElementDefinition edd = derived.getDifferential().addElement();
1566          edd.setPath(edBase.getPath());
1567          edd.setMax("0");
1568        } else {
1569          closeChildren(base, edBase, derived, edMatch);
1570        }        
1571      }
1572    }
1573  }
1574
1575
1576
1577
1578  private int findEnd(List<ElementDefinition> list, ElementDefinition ed, int cursor) {
1579    String path = ed.getPath()+".";
1580    while (cursor < list.size() && list.get(cursor).getPath().startsWith(path))
1581      cursor++;
1582    return cursor;
1583  }
1584
1585
1586  private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list) {
1587    for (ElementDefinition t : list)
1588      if (t.getPath().equals(ed.getPath()))
1589        return t;
1590    return null;
1591  }
1592
1593  private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list, int start, int end) {
1594    for (int i = start; i < end; i++) {
1595      ElementDefinition t = list.get(i);
1596      if (t.getPath().equals(ed.getPath()))
1597        return t;
1598    }
1599    return null;
1600  }
1601
1602
1603  private boolean isImmediateChild(ElementDefinition ed) {
1604    String p = ed.getPath();
1605    if (!p.contains("."))
1606      return false;
1607    p = p.substring(p.indexOf(".")+1);
1608    return !p.contains(".");
1609  }
1610
1611  private boolean isImmediateChild(ElementDefinition candidate, ElementDefinition base) {
1612    String p = candidate.getPath();
1613    if (!p.contains("."))
1614      return false;
1615    if (!p.startsWith(base.getPath()+"."))
1616      return false;
1617    p = p.substring(base.getPath().length()+1);
1618    return !p.contains(".");
1619  }
1620
1621  public XhtmlNode generateExtensionTable(String defFile, StructureDefinition ed, String imageFolder, boolean inlineGraphics, boolean full, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException {
1622    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics);
1623    gen.setTranslator(getTranslator());
1624    TableModel model = gen.initNormalTable(corePath, false, true, ed.getId(), false);
1625
1626    boolean deep = false;
1627    String m = "";
1628    boolean vdeep = false;
1629    if (ed.getSnapshot().getElementFirstRep().getIsModifier())
1630      m = "modifier_";
1631    for (ElementDefinition eld : ed.getSnapshot().getElement()) {
1632      deep = deep || eld.getPath().contains("Extension.extension.");
1633      vdeep = vdeep || eld.getPath().contains("Extension.extension.extension.");
1634    }
1635    Row r = gen.new Row();
1636    model.getRows().add(r);
1637    r.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), ed.getSnapshot().getElement().get(0).getIsModifier() ? "modifierExtension" : "extension", null, null));
1638    r.getCells().add(gen.new Cell());
1639    r.getCells().add(gen.new Cell(null, null, describeCardinality(ed.getSnapshot().getElement().get(0), null, new UnusedTracker()), null, null));
1640
1641    ElementDefinition ved = null;
1642    if (full || vdeep) {
1643      r.getCells().add(gen.new Cell("", "", "Extension", null, null));
1644
1645      r.setIcon(deep ? "icon_"+m+"extension_complex.png" : "icon_extension_simple.png", deep ? HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX : HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
1646      List<ElementDefinition> children = getChildren(ed.getSnapshot().getElement(), ed.getSnapshot().getElement().get(0));
1647      for (ElementDefinition child : children)
1648        if (!child.getPath().endsWith(".id"))
1649          genElement(defFile == null ? "" : defFile+"-definitions.html#extension.", gen, r.getSubRows(), child, ed.getSnapshot().getElement(), null, true, defFile, true, full, corePath, imagePath, true, false, false, false);
1650    } else if (deep) {
1651      List<ElementDefinition> children = new ArrayList<ElementDefinition>();
1652      for (ElementDefinition ted : ed.getSnapshot().getElement()) {
1653        if (ted.getPath().equals("Extension.extension"))
1654          children.add(ted);
1655      }
1656
1657      r.getCells().add(gen.new Cell("", "", "Extension", null, null));
1658      r.setIcon("icon_"+m+"extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
1659      
1660      for (ElementDefinition c : children) {
1661        ved = getValueFor(ed, c);
1662        ElementDefinition ued = getUrlFor(ed, c);
1663        if (ved != null && ued != null) {
1664          Row r1 = gen.new Row();
1665          r.getSubRows().add(r1);
1666          r1.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), ((UriType) ued.getFixed()).getValue(), null, null));
1667          r1.getCells().add(gen.new Cell());
1668          r1.getCells().add(gen.new Cell(null, null, describeCardinality(c, null, new UnusedTracker()), null, null));
1669          genTypes(gen, r1, ved, defFile, ed, corePath, imagePath);
1670          r1.getCells().add(gen.new Cell(null, null, c.getDefinition(), null, null));
1671          r1.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);      
1672        }
1673      }
1674    } else  {
1675      for (ElementDefinition ted : ed.getSnapshot().getElement()) {
1676        if (ted.getPath().startsWith("Extension.value"))
1677          ved = ted;
1678      }
1679
1680      genTypes(gen, r, ved, defFile, ed, corePath, imagePath);
1681
1682      r.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);      
1683    }
1684    Cell c = gen.new Cell("", "", "URL = "+ed.getUrl(), null, null);
1685    c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, ed.getName()+": "+ed.getDescription(), null));
1686    if (!full && !(deep || vdeep) && ved != null && ved.hasBinding()) {  
1687        c.addPiece(gen.new Piece("br"));
1688      BindingResolution br = pkp.resolveBinding(ed, ved.getBinding(), ved.getPath());
1689      c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold")));
1690      c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath+br.url, br.display, null)));
1691      if (ved.getBinding().hasStrength()) {
1692        c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, " (", null)));
1693        c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(corePath+"terminologies.html#"+ved.getBinding().getStrength().toCode(), egt(ved.getBinding().getStrengthElement()), ved.getBinding().getStrength().getDefinition())));              
1694        c.getPieces().add(gen.new Piece(null, ")", null));
1695      }
1696    }
1697    c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, describeExtensionContext(ed), null));
1698    r.getCells().add(c);
1699    
1700    try {
1701      return gen.generate(model, corePath, 0, outputTracker);
1702        } catch (org.hl7.fhir.exceptions.FHIRException e) {
1703                throw new FHIRException(e.getMessage(), e);
1704        }
1705  }
1706
1707  private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) {
1708    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
1709    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
1710      if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url"))
1711        return ed.getSnapshot().getElement().get(i);
1712      i++;
1713    }
1714    return null;
1715  }
1716
1717  private ElementDefinition getValueFor(StructureDefinition ed, ElementDefinition c) {
1718    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
1719    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
1720      if (ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".value"))
1721        return ed.getSnapshot().getElement().get(i);
1722      i++;
1723    }
1724    return null;
1725  }
1726
1727
1728  private static final int AGG_NONE = 0;
1729  private static final int AGG_IND = 1;
1730  private static final int AGG_GR = 2;
1731  private Cell genTypes(HierarchicalTableGenerator gen, Row r, ElementDefinition e, String profileBaseFileName, StructureDefinition profile, String corePath, String imagePath) {
1732    Cell c = gen.new Cell();
1733    r.getCells().add(c);
1734    List<TypeRefComponent> types = e.getType();
1735    if (!e.hasType()) {
1736      if (e.hasContentReference()) {
1737        return c;
1738      } else {
1739      ElementDefinition d = (ElementDefinition) e.getUserData(DERIVATION_POINTER);
1740      if (d != null && d.hasType()) {
1741        types = new ArrayList<ElementDefinition.TypeRefComponent>();
1742        for (TypeRefComponent tr : d.getType()) {
1743          TypeRefComponent tt = tr.copy();
1744          tt.setUserData(DERIVATION_EQUALS, true);
1745          types.add(tt);
1746        }
1747      } else
1748        return c;
1749    }
1750    }
1751
1752    boolean first = true;
1753    Element source = types.get(0); // either all types are the same, or we don't consider any of them the same
1754    int aggMode = AGG_NONE;
1755
1756    boolean allReference = !types.isEmpty();
1757    Set<AggregationMode> aggs = new HashSet<ElementDefinition.AggregationMode>();
1758    for (TypeRefComponent t : types) {
1759      if (t.getCode()!=null && t.getCode().equals("Reference") && t.hasProfile()) {
1760        for (Enumeration<AggregationMode> en : t.getAggregation())
1761          aggs.add(en.getValue());
1762      } else
1763        allReference = false;
1764      
1765    }
1766    if (allReference) {
1767      if (aggs.size() > 0) {
1768        boolean allSame = true;
1769        for (TypeRefComponent t : types) {
1770          for (AggregationMode agg : aggs) {
1771            boolean found = false;
1772            for (Enumeration<AggregationMode> en : t.getAggregation())
1773              if (en.getValue() == agg)
1774                found = true;
1775            if (!found)
1776              allSame = false;
1777          }
1778        }
1779        aggMode = allSame ? AGG_GR : AGG_IND;
1780        if (aggMode != AGG_GR)
1781          allReference = false;
1782      }
1783    } else 
1784      aggMode = aggs.size() == 0 ? AGG_NONE : AGG_IND;
1785
1786    if (allReference) {
1787      c.getPieces().add(gen.new Piece(corePath+"references.html", "Reference", null));
1788      c.getPieces().add(gen.new Piece(null, "(", null));
1789    }
1790    TypeRefComponent tl = null;
1791    for (TypeRefComponent t : types) {
1792      if (first)
1793        first = false;
1794      else if (allReference)
1795        c.addPiece(checkForNoChange(tl, gen.new Piece(null," | ", null)));
1796      else
1797        c.addPiece(checkForNoChange(tl, gen.new Piece(null,", ", null)));
1798      tl = t;
1799      if (t.getCode()!= null && t.getCode().equals("Reference")) {
1800        if (!allReference) {
1801          c.getPieces().add(gen.new Piece(corePath+"references.html", "Reference", null));
1802          c.getPieces().add(gen.new Piece(null, "(", null));
1803        }
1804        if (t.hasTargetProfile() && t.getTargetProfile().startsWith("http://hl7.org/fhir/StructureDefinition/")) {
1805          StructureDefinition sd = context.fetchResource(StructureDefinition.class, t.getTargetProfile());
1806          if (sd != null) {
1807            String disp = sd.hasTitle() ? sd.getTitle() : sd.getName();
1808            c.addPiece(checkForNoChange(t, gen.new Piece(checkPrepend(corePath, sd.getUserString("path")), disp, null)));
1809          } else {
1810            String rn = t.getTargetProfile().substring(40);
1811            c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, rn), rn, null)));
1812          }
1813        } else if (t.hasTargetProfile() && Utilities.isAbsoluteUrl(t.getTargetProfile())) {
1814          StructureDefinition sd = context.fetchResource(StructureDefinition.class, t.getTargetProfile());
1815          if (sd != null) {
1816            String disp = sd.hasTitle() ? sd.getTitle() : sd.getName();
1817            String ref = pkp.getLinkForProfile(null, sd.getUrl());
1818            if (ref.contains("|"))
1819              ref = ref.substring(0,  ref.indexOf("|"));
1820            c.addPiece(checkForNoChange(t, gen.new Piece(ref, disp, null)));
1821          } else
1822            c.addPiece(checkForNoChange(t, gen.new Piece(null, t.getTargetProfile(), null)));
1823        } else if (t.hasTargetProfile() && t.getTargetProfile().startsWith("#"))
1824          c.addPiece(checkForNoChange(t, gen.new Piece(corePath+profileBaseFileName+"."+t.getTargetProfile().substring(1).toLowerCase()+".html", t.getTargetProfile(), null)));
1825        else if (t.hasTargetProfile())
1826          c.addPiece(checkForNoChange(t, gen.new Piece(corePath+t.getTargetProfile(), t.getTargetProfile(), null)));
1827        if (!allReference) {
1828          c.getPieces().add(gen.new Piece(null, ")", null));
1829          if (t.getAggregation().size() > 0) {
1830            c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", " {", null));
1831            boolean firstA = true;
1832            for (Enumeration<AggregationMode> a : t.getAggregation()) {
1833              if (firstA = true)
1834                firstA = false;
1835              else
1836                c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", ", ", null));
1837              c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", codeForAggregation(a.getValue()), null));
1838            }
1839            c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", "}", null));
1840          }
1841        }
1842      } else if (t.hasProfile() && (!t.getCode().equals("Extension") || t.getProfile().contains(":"))) { // a profiled type
1843        String ref;
1844        ref = pkp.getLinkForProfile(profile, t.getProfile());
1845        if (ref != null) {
1846          String[] parts = ref.split("\\|");
1847          if (parts[0].startsWith("http:") || parts[0].startsWith("https:"))
1848            c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], parts[1], t.getCode())));
1849          else
1850            c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().startsWith(corePath)? corePath: "")+parts[0], parts[1], t.getCode())));
1851        } else
1852          c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().startsWith(corePath)? corePath: "")+ref, t.getCode(), null)));
1853      } else if (pkp.hasLinkFor(t.getCode())) {
1854        c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, t.getCode()), t.getCode(), null)));
1855      } else
1856        c.addPiece(checkForNoChange(t, gen.new Piece(null, t.getCode(), null)));
1857    }
1858    if (allReference) {
1859      c.getPieces().add(gen.new Piece(null, ")", null));
1860      if (aggs.size() > 0) {
1861        c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", " {", null));
1862        boolean firstA = true;
1863        for (AggregationMode a : aggs) {
1864          if (firstA = true)
1865            firstA = false;
1866          else
1867            c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", ", ", null));
1868          c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", codeForAggregation(a), null));
1869        }
1870        c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", "}", null));
1871      }
1872    }
1873    return c;
1874  }
1875
1876  private String codeForAggregation(AggregationMode a) {
1877    switch (a) {
1878    case BUNDLED : return "b";
1879    case CONTAINED : return "c";
1880    case REFERENCED: return "r";
1881         default: return "?";
1882    }
1883  }
1884
1885
1886  private String checkPrepend(String corePath, String path) {
1887    if (pkp.prependLinks() && !(path.startsWith("http:") || path.startsWith("https:")))
1888      return corePath+path;
1889    else 
1890      return path;
1891  }
1892
1893
1894  private ElementDefinition getElementByName(List<ElementDefinition> elements, String contentReference) {
1895    for (ElementDefinition ed : elements)
1896      if (ed.hasSliceName() && ("#"+ed.getSliceName()).equals(contentReference))
1897        return ed;
1898    return null;
1899  }
1900
1901  private ElementDefinition getElementById(List<ElementDefinition> elements, String contentReference) {
1902    for (ElementDefinition ed : elements)
1903      if (ed.hasId() && ("#"+ed.getId()).equals(contentReference))
1904        return ed;
1905    return null;
1906  }
1907
1908
1909  public static String describeExtensionContext(StructureDefinition ext) {
1910    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1911    for (StringType t : ext.getContext())
1912      b.append(t.getValue());
1913    if (!ext.hasContextType())
1914      throw new Error("no context type on "+ext.getUrl());
1915    switch (ext.getContextType()) {
1916    case DATATYPE: return "Use on data type: "+b.toString();
1917    case EXTENSION: return "Use on extension: "+b.toString();
1918    case RESOURCE: return "Use on element: "+b.toString();
1919    default:
1920      return "??";
1921    }
1922  }
1923
1924  private String describeCardinality(ElementDefinition definition, ElementDefinition fallback, UnusedTracker tracker) {
1925    IntegerType min = definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
1926    StringType max = definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
1927    if (min.isEmpty() && fallback != null)
1928      min = fallback.getMinElement();
1929    if (max.isEmpty() && fallback != null)
1930      max = fallback.getMaxElement();
1931
1932    tracker.used = !max.isEmpty() && !max.getValue().equals("0");
1933
1934    if (min.isEmpty() && max.isEmpty())
1935      return null;
1936    else
1937      return (!min.hasValue() ? "" : Integer.toString(min.getValue())) + ".." + (!max.hasValue() ? "" : max.getValue());
1938  }
1939
1940  private void genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef, UnusedTracker tracker, ElementDefinition fallback) {
1941    IntegerType min = !hasDef ? new IntegerType() : definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
1942    StringType max = !hasDef ? new StringType() : definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
1943    if (min.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) {
1944      ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER);
1945      if (base.hasMinElement()) {
1946        min = base.getMinElement().copy();
1947        min.setUserData(DERIVATION_EQUALS, true);
1948      }
1949    }
1950    if (max.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) {
1951      ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER);
1952      if (base.hasMaxElement()) {
1953        max = base.getMaxElement().copy();
1954        max.setUserData(DERIVATION_EQUALS, true);
1955      }
1956    }
1957    if (min.isEmpty() && fallback != null)
1958      min = fallback.getMinElement();
1959    if (max.isEmpty() && fallback != null)
1960      max = fallback.getMaxElement();
1961
1962    if (!max.isEmpty())
1963      tracker.used = !max.getValue().equals("0");
1964
1965    Cell cell = gen.new Cell(null, null, null, null, null);
1966    row.getCells().add(cell);
1967    if (!min.isEmpty() || !max.isEmpty()) {
1968      cell.addPiece(checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), null)));
1969      cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", null)));
1970      cell.addPiece(checkForNoChange(min, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), null)));
1971    }
1972  }
1973
1974
1975  private Piece checkForNoChange(Element source, Piece piece) {
1976    if (source.hasUserData(DERIVATION_EQUALS)) {
1977      piece.addStyle("opacity: 0.4");
1978    }
1979    return piece;
1980  }
1981
1982  private Piece checkForNoChange(Element src1, Element src2, Piece piece) {
1983    if (src1.hasUserData(DERIVATION_EQUALS) && src2.hasUserData(DERIVATION_EQUALS)) {
1984      piece.addStyle("opacity: 0.5");
1985    }
1986    return piece;
1987  }
1988
1989  public XhtmlNode generateTable(String defFile, StructureDefinition profile, boolean diff, String imageFolder, boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, String imagePath, boolean logicalModel, boolean allInvariants, Set<String> outputTracker) throws IOException, FHIRException {
1990    assert(diff != snapshot);// check it's ok to get rid of one of these
1991    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics);
1992    gen.setTranslator(getTranslator());
1993    TableModel model = gen.initNormalTable(corePath, false, true, profile.getId()+(diff ? "d" : "s"), false);
1994    List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement();
1995    List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
1996    profiles.add(profile);
1997    genElement(defFile == null ? null : defFile+"#", gen, model.getRows(), list.get(0), list, profiles, diff, profileBaseFileName, null, snapshot, corePath, imagePath, true, logicalModel, profile.getDerivation() == TypeDerivationRule.CONSTRAINT && usesMustSupport(list), allInvariants);
1998    try {
1999      return gen.generate(model, imagePath, 0, outputTracker);
2000        } catch (org.hl7.fhir.exceptions.FHIRException e) {
2001                throw new FHIRException(e.getMessage(), e);
2002        }
2003  }
2004
2005
2006  public XhtmlNode generateGrid(String defFile, StructureDefinition profile, String imageFolder, boolean inlineGraphics, String profileBaseFileName, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException {
2007    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics);
2008    gen.setTranslator(getTranslator());
2009    TableModel model = gen.initGridTable(corePath, profile.getId());
2010    List<ElementDefinition> list = profile.getSnapshot().getElement();
2011    List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
2012    profiles.add(profile);
2013    genGridElement(defFile == null ? null : defFile+"#", gen, model.getRows(), list.get(0), list, profiles, true, profileBaseFileName, null, corePath, imagePath, true, profile.getDerivation() == TypeDerivationRule.CONSTRAINT && usesMustSupport(list));
2014    try {
2015      return gen.generate(model, imagePath, 1, outputTracker);
2016    } catch (org.hl7.fhir.exceptions.FHIRException e) {
2017      throw new FHIRException(e.getMessage(), e);
2018    }
2019  }
2020
2021
2022  private boolean usesMustSupport(List<ElementDefinition> list) {
2023    for (ElementDefinition ed : list)
2024      if (ed.hasMustSupport() && ed.getMustSupport())
2025        return true;
2026    return false;
2027  }
2028
2029
2030  private void genElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element, List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName, Boolean extensions, boolean snapshot, String corePath, String imagePath, boolean root, boolean logicalModel, boolean isConstraintMode, boolean allInvariants) throws IOException {
2031    StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1);
2032    String s = tail(element.getPath());
2033    List<ElementDefinition> children = getChildren(all, element);
2034    boolean isExtension = (s.equals("extension") || s.equals("modifierExtension"));
2035    if (!snapshot && isExtension && extensions != null && extensions != isExtension)
2036      return;
2037
2038    if (!onlyInformationIsMapping(all, element)) {
2039      Row row = gen.new Row();
2040      row.setAnchor(element.getPath());
2041      row.setColor(getRowColor(element, isConstraintMode));
2042      boolean hasDef = element != null;
2043      boolean ext = false;
2044      if (s.equals("extension")) {
2045        if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile()))
2046          row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
2047        else
2048          row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
2049        ext = true;
2050      } else if (s.equals("modifierExtension")) {
2051        if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile()))
2052          row.setIcon("icon_modifier_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
2053        else
2054          row.setIcon("icon_modifier_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
2055      } else if (!hasDef || element.getType().size() == 0)
2056        row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
2057      else if (hasDef && element.getType().size() > 1) {
2058        if (allTypesAre(element.getType(), "Reference"))
2059          row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
2060        else
2061          row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE);
2062      } else if (hasDef && element.getType().get(0).getCode() != null && element.getType().get(0).getCode().startsWith("@"))
2063        row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE);
2064      else if (hasDef && isPrimitive(element.getType().get(0).getCode()))
2065        row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
2066      else if (hasDef && isReference(element.getType().get(0).getCode()))
2067        row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
2068      else if (hasDef && isDataType(element.getType().get(0).getCode()))
2069        row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
2070      else
2071        row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
2072      String ref = defPath == null ? null : defPath + element.getId();
2073      UnusedTracker used = new UnusedTracker();
2074      used.used = true;
2075      Cell left = gen.new Cell(null, ref, s, (element.hasSliceName() ? translate("sd.table", "Slice")+" "+element.getSliceName() : "")+(hasDef && element.hasSliceName() ? ": " : "")+(!hasDef ? null : gt(element.getDefinitionElement())), null);
2076      row.getCells().add(left);
2077      Cell gc = gen.new Cell();
2078      row.getCells().add(gc);
2079      if (element != null && element.getIsModifier())
2080        checkForNoChange(element.getIsModifierElement(), gc.addStyledText(translate("sd.table", "This element is a modifier element"), "?!", null, null, null, false));
2081      if (element != null && element.getMustSupport())
2082        checkForNoChange(element.getMustSupportElement(), gc.addStyledText(translate("sd.table", "This element must be supported"), "S", "white", "red", null, false));
2083      if (element != null && element.getIsSummary())
2084        checkForNoChange(element.getIsSummaryElement(), gc.addStyledText(translate("sd.table", "This element is included in summaries"), "\u03A3", null, null, null, false));
2085      if (element != null && (!element.getConstraint().isEmpty() || !element.getCondition().isEmpty()))
2086        gc.addStyledText(translate("sd.table", "This element has or is affected by some invariants"), "I", null, null, null, false);
2087
2088      ExtensionContext extDefn = null;
2089      if (ext) {
2090        if (element != null && element.getType().size() == 1 && element.getType().get(0).hasProfile()) {
2091        extDefn = locateExtension(StructureDefinition.class, element.getType().get(0).getProfile());
2092          if (extDefn == null) {
2093            genCardinality(gen, element, row, hasDef, used, null);
2094            row.getCells().add(gen.new Cell(null, null, "?? "+element.getType().get(0).getProfile(), null, null));
2095            generateDescription(gen, row, element, null, used.used, profile.getUrl(), element.getType().get(0).getProfile(), profile, corePath, imagePath, root, logicalModel, allInvariants);
2096          } else {
2097            String name = urltail(element.getType().get(0).getProfile());
2098            left.getPieces().get(0).setText(name);
2099            // left.getPieces().get(0).setReference((String) extDefn.getExtensionStructure().getTag("filename"));
2100            left.getPieces().get(0).setHint(translate("sd.table", "Extension URL")+" = "+extDefn.getUrl());
2101            genCardinality(gen, element, row, hasDef, used, extDefn.getElement());
2102            ElementDefinition valueDefn = extDefn.getExtensionValueDefinition();
2103            if (valueDefn != null && !"0".equals(valueDefn.getMax()))
2104               genTypes(gen, row, valueDefn, profileBaseFileName, profile, corePath, imagePath);
2105             else // if it's complex, we just call it nothing
2106                // genTypes(gen, row, extDefn.getSnapshot().getElement().get(0), profileBaseFileName, profile);
2107              row.getCells().add(gen.new Cell(null, null, "("+translate("sd.table", "Complex")+")", null, null));
2108            generateDescription(gen, row, element, extDefn.getElement(), used.used, null, extDefn.getUrl(), profile, corePath, imagePath, root, logicalModel, allInvariants, valueDefn);
2109          }
2110        } else {
2111          genCardinality(gen, element, row, hasDef, used, null);
2112          if ("0".equals(element.getMax()))
2113            row.getCells().add(gen.new Cell());            
2114          else
2115            genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath);
2116          generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants);
2117        }
2118      } else {
2119        genCardinality(gen, element, row, hasDef, used, null);
2120        if (hasDef && !"0".equals(element.getMax()))
2121          genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath);
2122        else
2123          row.getCells().add(gen.new Cell());
2124        generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants);
2125      }
2126      if (element.hasSlicing()) {
2127        if (standardExtensionSlicing(element)) {
2128          used.used = element.hasType() && element.getType().get(0).hasProfile();
2129          showMissing = false;
2130        } else {
2131          row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE);
2132          row.getCells().get(2).getPieces().clear();
2133          for (Cell cell : row.getCells())
2134            for (Piece p : cell.getPieces()) {
2135              p.addStyle("font-style: italic");
2136            }
2137        }
2138      }
2139      if (used.used || showMissing)
2140        rows.add(row);
2141      if (!used.used && !element.hasSlicing()) {
2142        for (Cell cell : row.getCells())
2143          for (Piece p : cell.getPieces()) {
2144            p.setStyle("text-decoration:line-through");
2145            p.setReference(null);
2146          }
2147      } else{
2148        for (ElementDefinition child : children)
2149          if (logicalModel || !child.getPath().endsWith(".id") || (child.getPath().endsWith(".id") && (profile != null) && (profile.getDerivation() == TypeDerivationRule.CONSTRAINT)))  
2150            genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, snapshot, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants);
2151        if (!snapshot && (extensions == null || !extensions))
2152          for (ElementDefinition child : children)
2153            if (child.getPath().endsWith(".extension") || child.getPath().endsWith(".modifierExtension"))
2154              genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, true, false, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants);
2155      }
2156    } 
2157  }
2158
2159  private void genGridElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element, List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName, Boolean extensions, String corePath, String imagePath, boolean root, boolean isConstraintMode) throws IOException {
2160    StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1);
2161    String s = tail(element.getPath());
2162    List<ElementDefinition> children = getChildren(all, element);
2163    boolean isExtension = (s.equals("extension") || s.equals("modifierExtension"));
2164
2165    if (!onlyInformationIsMapping(all, element)) {
2166      Row row = gen.new Row();
2167      row.setAnchor(element.getPath());
2168      row.setColor(getRowColor(element, isConstraintMode));
2169      boolean hasDef = element != null;
2170      String ref = defPath == null ? null : defPath + element.getId();
2171      UnusedTracker used = new UnusedTracker();
2172      used.used = true;
2173      Cell left = gen.new Cell();
2174      if (element.getType().size() == 1 && element.getType().get(0).isPrimitive())
2175        left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())).addStyle("font-weight:bold"));
2176      else
2177        left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())));
2178      if (element.hasSliceName()) {
2179        left.getPieces().add(gen.new Piece("br"));
2180        String indent = StringUtils.repeat('\u00A0', 1+2*(element.getPath().split("\\.").length));
2181        left.getPieces().add(gen.new Piece(null, indent + "("+element.getSliceName() + ")", null));
2182      }
2183      row.getCells().add(left);
2184
2185      ExtensionContext extDefn = null;
2186      genCardinality(gen, element, row, hasDef, used, null);
2187      if (hasDef && !"0".equals(element.getMax()))
2188        genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath);
2189      else
2190        row.getCells().add(gen.new Cell());
2191      generateGridDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, null);
2192/*      if (element.hasSlicing()) {
2193        if (standardExtensionSlicing(element)) {
2194          used.used = element.hasType() && element.getType().get(0).hasProfile();
2195          showMissing = false;
2196        } else {
2197          row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE);
2198          row.getCells().get(2).getPieces().clear();
2199          for (Cell cell : row.getCells())
2200            for (Piece p : cell.getPieces()) {
2201              p.addStyle("font-style: italic");
2202            }
2203        }
2204      }*/
2205      rows.add(row);
2206      for (ElementDefinition child : children)
2207        if (child.getMustSupport())
2208          genGridElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, corePath, imagePath, false, isConstraintMode);
2209    }
2210  }
2211
2212
2213  private ExtensionContext locateExtension(Class<StructureDefinition> class1, String value)  {
2214    if (value.contains("#")) {
2215      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
2216      if (ext == null)
2217        return null;
2218      String tail = value.substring(value.indexOf("#")+1);
2219      ElementDefinition ed = null;
2220      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
2221        if (tail.equals(ted.getSliceName())) {
2222          ed = ted;
2223          return new ExtensionContext(ext, ed);
2224        }
2225      }
2226      return null;
2227    } else {
2228      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value);
2229      if (ext == null)
2230        return null;
2231      else 
2232        return new ExtensionContext(ext, ext.getSnapshot().getElement().get(0));
2233    }
2234  }
2235
2236
2237  private boolean extensionIsComplex(String value) {
2238    if (value.contains("#")) {
2239      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
2240    if (ext == null)
2241      return false;
2242      String tail = value.substring(value.indexOf("#")+1);
2243      ElementDefinition ed = null;
2244      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
2245        if (tail.equals(ted.getSliceName())) {
2246          ed = ted;
2247          break;
2248        }
2249      }
2250      if (ed == null)
2251        return false;
2252      int i = ext.getSnapshot().getElement().indexOf(ed);
2253      int j = i+1;
2254      while (j < ext.getSnapshot().getElement().size() && !ext.getSnapshot().getElement().get(j).getPath().equals(ed.getPath()))
2255        j++;
2256      return j - i > 5;
2257    } else {
2258      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value);
2259      return ext != null && ext.getSnapshot().getElement().size() > 5;
2260    }
2261  }
2262
2263
2264  private String getRowColor(ElementDefinition element, boolean isConstraintMode) {
2265    switch (element.getUserInt(UD_ERROR_STATUS)) {
2266    case STATUS_HINT: return ROW_COLOR_HINT;
2267    case STATUS_WARNING: return ROW_COLOR_WARNING;
2268    case STATUS_ERROR: return ROW_COLOR_ERROR;
2269    case STATUS_FATAL: return ROW_COLOR_FATAL;
2270    }
2271    if (isConstraintMode && !element.getMustSupport() && !element.getIsModifier() && element.getPath().contains("."))
2272      return null; // ROW_COLOR_NOT_MUST_SUPPORT;
2273    else
2274      return null;
2275  }
2276
2277
2278  private String urltail(String path) {
2279    if (path.contains("#"))
2280      return path.substring(path.lastIndexOf('#')+1);
2281    if (path.contains("/"))
2282      return path.substring(path.lastIndexOf('/')+1);
2283    else
2284      return path;
2285
2286  }
2287
2288  private boolean standardExtensionSlicing(ElementDefinition element) {
2289    String t = tail(element.getPath());
2290    return (t.equals("extension") || t.equals("modifierExtension"))
2291          && element.getSlicing().getRules() != SlicingRules.CLOSED && element.getSlicing().getDiscriminator().size() == 1 && element.getSlicing().getDiscriminator().get(0).getPath().equals("url") && element.getSlicing().getDiscriminator().get(0).getType().equals(DiscriminatorType.VALUE);
2292  }
2293
2294  private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants) throws IOException {
2295    return generateDescription(gen, row, definition, fallback, used, baseURL, url, profile, corePath, imagePath, root, logicalModel, allInvariants, null);
2296  }
2297  
2298  private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, ElementDefinition valueDefn) throws IOException {
2299    Cell c = gen.new Cell();
2300    row.getCells().add(c);
2301
2302    if (used) {
2303      if (logicalModel && ToolingExtensions.hasExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) {
2304        if (root) {
2305          c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold"));
2306          c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null));        
2307        } else if (!root && ToolingExtensions.hasExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace") && 
2308            !ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace").equals(ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))) {
2309          c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold"));
2310          c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null));        
2311        }
2312      }
2313      
2314      if (definition.hasContentReference()) {
2315        ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference());
2316        if (ed == null)
2317          c.getPieces().add(gen.new Piece(null, translate("sd.table", "Unknown reference to %s", definition.getContentReference()), null));
2318        else
2319          c.getPieces().add(gen.new Piece("#"+ed.getPath(), translate("sd.table", "See %s", ed.getPath()), null));
2320      }
2321      if (definition.getPath().endsWith("url") && definition.hasFixed()) {
2322        c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen")));
2323      } else {
2324        if (definition != null && definition.hasShort()) {
2325          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2326          c.addPiece(checkForNoChange(definition.getShortElement(), gen.new Piece(null, gt(definition.getShortElement()), null)));
2327        } else if (fallback != null && fallback.hasShort()) {
2328          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2329          c.addPiece(checkForNoChange(fallback.getShortElement(), gen.new Piece(null, gt(fallback.getShortElement()), null)));
2330        }
2331        if (url != null) {
2332          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2333          String fullUrl = url.startsWith("#") ? baseURL+url : url;
2334          StructureDefinition ed = context.fetchResource(StructureDefinition.class, url);
2335          String ref = null;
2336          if (ed != null) {
2337            String p = ed.getUserString("path");
2338            if (p != null) {
2339              ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p);
2340            }
2341          }
2342          c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold"));
2343          c.getPieces().add(gen.new Piece(ref, fullUrl, null));
2344        }
2345
2346        if (definition.hasSlicing()) {
2347          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2348          c.getPieces().add(gen.new Piece(null, translate("sd.table", "Slice")+": ", null).addStyle("font-weight:bold"));
2349          c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null));
2350        }
2351        if (definition != null) {
2352          ElementDefinitionBindingComponent binding = null;
2353          if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty())
2354            binding = valueDefn.getBinding();
2355          else if (definition.hasBinding())
2356            binding = definition.getBinding();
2357          if (binding!=null && !binding.isEmpty()) {
2358            if (!c.getPieces().isEmpty()) 
2359              c.addPiece(gen.new Piece("br"));
2360            BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath());
2361            c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold")));
2362            c.getPieces().add(checkForNoChange(binding, gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath+br.url, br.display, null)));
2363            if (binding.hasStrength()) {
2364              c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null)));
2365              c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), egt(binding.getStrengthElement()), binding.getStrength().getDefinition())));              
2366              c.getPieces().add(gen.new Piece(null, ")", null));
2367            }
2368          }
2369          for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
2370            if (!inv.hasSource() || allInvariants) {
2371              if (!c.getPieces().isEmpty()) 
2372                c.addPiece(gen.new Piece("br"));
2373              c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold")));
2374              c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, gt(inv.getHumanElement()), null)));
2375            }
2376          }
2377          if ((definition.hasBase() && definition.getBase().getMax().equals("*")) || (definition.hasMax() && definition.getMax().equals("*"))) {
2378            if (c.getPieces().size() > 0)
2379              c.addPiece(gen.new Piece("br"));
2380            if (definition.hasOrderMeaning()) {
2381              c.getPieces().add(gen.new Piece(null, "This repeating element order: "+definition.getOrderMeaning(), null));
2382            } else {
2383              // don't show this, this it's important: c.getPieces().add(gen.new Piece(null, "This repeating element has no defined order", null));
2384            }           
2385          }
2386
2387          if (definition.hasFixed()) {
2388            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2389            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, translate("sd.table", "Fixed Value")+": ", null).addStyle("font-weight:bold")));
2390            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, buildJson(definition.getFixed()), null).addStyle("color: darkgreen")));
2391            if (isCoded(definition.getFixed()) && !hasDescription(definition.getFixed())) {
2392              Piece p = describeCoded(gen, definition.getFixed());
2393              if (p != null)
2394                c.getPieces().add(p);
2395            }
2396          } else if (definition.hasPattern()) {
2397            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2398            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, translate("sd.table", "Required Pattern")+": ", null).addStyle("font-weight:bold")));
2399            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen")));
2400          } else if (definition.hasExample()) {
2401            for (ElementDefinitionExampleComponent ex : definition.getExample()) {
2402              if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2403              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, translate("sd.table", "Example")+("".equals("General")? "" : " "+ex.getLabel()+"'")+": ", null).addStyle("font-weight:bold")));
2404              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen")));
2405            }
2406          }
2407          if (definition.hasMaxLength() && definition.getMaxLength()!=0) {
2408            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2409            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold")));
2410            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen")));
2411          }
2412          if (profile != null) {
2413            for (StructureDefinitionMappingComponent md : profile.getMapping()) {
2414              if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) {
2415                ElementDefinitionMappingComponent map = null;
2416                for (ElementDefinitionMappingComponent m : definition.getMapping()) 
2417                  if (m.getIdentity().equals(md.getIdentity()))
2418                    map = m;
2419                if (map != null) {
2420                  for (int i = 0; i<definition.getMapping().size(); i++){
2421                    c.addPiece(gen.new Piece("br"));
2422                    c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null));
2423                  }
2424                }
2425              }
2426            }
2427          }
2428        }
2429      }
2430    }
2431    return c;
2432  }
2433
2434  private Piece describeCoded(HierarchicalTableGenerator gen, Type fixed) {
2435    if (fixed instanceof Coding) {
2436      Coding c = (Coding) fixed;
2437      ValidationResult vr = context.validateCode(c.getSystem(), c.getCode(), c.getDisplay());
2438      if (vr.getDisplay() != null)
2439        return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen");
2440    } else if (fixed instanceof CodeableConcept) {
2441      CodeableConcept cc = (CodeableConcept) fixed;
2442      for (Coding c : cc.getCoding()) {
2443        ValidationResult vr = context.validateCode(c.getSystem(), c.getCode(), c.getDisplay());
2444        if (vr.getDisplay() != null)
2445          return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen");
2446      }
2447    }
2448    return null;
2449  }
2450
2451
2452  private boolean hasDescription(Type fixed) {
2453    if (fixed instanceof Coding) {
2454      return ((Coding) fixed).hasDisplay();
2455    } else if (fixed instanceof CodeableConcept) {
2456      CodeableConcept cc = (CodeableConcept) fixed;
2457      if (cc.hasText())
2458        return true;
2459      for (Coding c : cc.getCoding())
2460        if (c.hasDisplay())
2461         return true;
2462    } // (fixed instanceof CodeType) || (fixed instanceof Quantity);
2463    return false;
2464  }
2465
2466
2467  private boolean isCoded(Type fixed) {
2468    return (fixed instanceof Coding) || (fixed instanceof CodeableConcept) || (fixed instanceof CodeType) || (fixed instanceof Quantity);
2469  }
2470
2471
2472  private Cell generateGridDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, ElementDefinition valueDefn) throws IOException {
2473    Cell c = gen.new Cell();
2474    row.getCells().add(c);
2475
2476    if (used) {
2477      if (definition.hasContentReference()) {
2478        ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference());
2479        if (ed == null)
2480          c.getPieces().add(gen.new Piece(null, "Unknown reference to "+definition.getContentReference(), null));
2481        else
2482          c.getPieces().add(gen.new Piece("#"+ed.getPath(), "See "+ed.getPath(), null));
2483      }
2484      if (definition.getPath().endsWith("url") && definition.hasFixed()) {
2485        c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen")));
2486      } else {
2487        if (url != null) {
2488          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2489          String fullUrl = url.startsWith("#") ? baseURL+url : url;
2490          StructureDefinition ed = context.fetchResource(StructureDefinition.class, url);
2491          String ref = null;
2492          if (ed != null) {
2493            String p = ed.getUserString("path");
2494            if (p != null) {
2495              ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p);
2496            }
2497          }
2498          c.getPieces().add(gen.new Piece(null, "URL: ", null).addStyle("font-weight:bold"));
2499          c.getPieces().add(gen.new Piece(ref, fullUrl, null));
2500        }
2501
2502        if (definition.hasSlicing()) {
2503          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2504          c.getPieces().add(gen.new Piece(null, "Slice: ", null).addStyle("font-weight:bold"));
2505          c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null));
2506        }
2507        if (definition != null) {
2508          ElementDefinitionBindingComponent binding = null;
2509          if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty())
2510            binding = valueDefn.getBinding();
2511          else if (definition.hasBinding())
2512            binding = definition.getBinding();
2513          if (binding!=null && !binding.isEmpty()) {
2514            if (!c.getPieces().isEmpty()) 
2515              c.addPiece(gen.new Piece("br"));
2516            BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath());
2517            c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, "Binding: ", null).addStyle("font-weight:bold")));
2518            c.getPieces().add(checkForNoChange(binding, gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath+br.url, br.display, null)));
2519            if (binding.hasStrength()) {
2520              c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null)));
2521              c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), binding.getStrength().toCode(), binding.getStrength().getDefinition())));              c.getPieces().add(gen.new Piece(null, ")", null));
2522            }
2523          }
2524          for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
2525            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2526            c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold")));
2527            c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null)));
2528          }
2529          if (definition.hasFixed()) {
2530            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2531            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight:bold")));
2532            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, buildJson(definition.getFixed()), null).addStyle("color: darkgreen")));
2533          } else if (definition.hasPattern()) {
2534            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2535            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "Required Pattern: ", null).addStyle("font-weight:bold")));
2536            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen")));
2537          } else if (definition.hasExample()) {
2538            for (ElementDefinitionExampleComponent ex : definition.getExample()) {
2539              if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2540              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, "Example'"+("".equals("General")? "" : " "+ex.getLabel()+"'")+": ", null).addStyle("font-weight:bold")));
2541              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen")));
2542            }
2543          }
2544          if (definition.hasMaxLength() && definition.getMaxLength()!=0) {
2545            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2546            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold")));
2547            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen")));
2548          }
2549          if (profile != null) {
2550            for (StructureDefinitionMappingComponent md : profile.getMapping()) {
2551              if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) {
2552                ElementDefinitionMappingComponent map = null;
2553                for (ElementDefinitionMappingComponent m : definition.getMapping()) 
2554                  if (m.getIdentity().equals(md.getIdentity()))
2555                    map = m;
2556                if (map != null) {
2557                  for (int i = 0; i<definition.getMapping().size(); i++){
2558                    c.addPiece(gen.new Piece("br"));
2559                    c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null));
2560                  }
2561                }
2562              }
2563            }
2564          }
2565          if (definition.getComment()!=null) {
2566            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2567            c.getPieces().add(gen.new Piece(null, "Comments: ", null).addStyle("font-weight:bold"));
2568            c.addPiece(gen.new Piece("br"));
2569            c.addMarkdown(definition.getComment());
2570//            c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null)));
2571          }
2572        }
2573      }
2574    }
2575    return c;
2576  }
2577  /*
2578  private List<Piece> markdownToPieces(String markdown) throws FHIRException {
2579    String htmlString = Processor.process(markdown);
2580    XhtmlParser parser = new XhtmlParser();
2581    try {
2582      XhtmlNode node = parser.parseFragment(htmlString);
2583      return htmlToPieces(node);
2584    } catch (IOException e) {
2585    }
2586    return null;
2587  }
2588
2589  private List<Piece> htmlToPieces(XhtmlNode n) {
2590    
2591  }*/
2592
2593  private String buildJson(Type value) throws IOException {
2594    if (value instanceof PrimitiveType)
2595      return ((PrimitiveType) value).asStringValue();
2596
2597    IParser json = context.newJsonParser();
2598    return json.composeString(value, null);
2599  }
2600
2601
2602  public String describeSlice(ElementDefinitionSlicingComponent slicing) {
2603    return translate("sd.table", "%s, %s by %s", slicing.getOrdered() ? translate("sd.table", "Ordered") : translate("sd.table", "Unordered"), describe(slicing.getRules()), commas(slicing.getDiscriminator()));
2604  }
2605
2606  private String commas(List<ElementDefinitionSlicingDiscriminatorComponent> list) {
2607    CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder();
2608    for (ElementDefinitionSlicingDiscriminatorComponent id : list)
2609      c.append(id.getType().toCode()+":"+id.getPath());
2610    return c.toString();
2611  }
2612
2613
2614  private String describe(SlicingRules rules) {
2615    if (rules == null)
2616      return translate("sd.table", "Unspecified");
2617    switch (rules) {
2618    case CLOSED : return translate("sd.table", "Closed");
2619    case OPEN : return translate("sd.table", "Open");
2620    case OPENATEND : return translate("sd.table", "Open At End");
2621    default:
2622      return "??";
2623    }
2624  }
2625
2626  private boolean onlyInformationIsMapping(List<ElementDefinition> list, ElementDefinition e) {
2627    return (!e.hasSliceName() && !e.hasSlicing() && (onlyInformationIsMapping(e))) &&
2628        getChildren(list, e).isEmpty();
2629  }
2630
2631  private boolean onlyInformationIsMapping(ElementDefinition d) {
2632    return !d.hasShort() && !d.hasDefinition() &&
2633        !d.hasRequirements() && !d.getAlias().isEmpty() && !d.hasMinElement() &&
2634        !d.hasMax() && !d.getType().isEmpty() && !d.hasContentReference() &&
2635        !d.hasExample() && !d.hasFixed() && !d.hasMaxLengthElement() &&
2636        !d.getCondition().isEmpty() && !d.getConstraint().isEmpty() && !d.hasMustSupportElement() &&
2637        !d.hasBinding();
2638  }
2639
2640  private boolean allTypesAre(List<TypeRefComponent> types, String name) {
2641    for (TypeRefComponent t : types) {
2642      if (!t.getCode().equals(name))
2643        return false;
2644    }
2645    return true;
2646  }
2647
2648  private List<ElementDefinition> getChildren(List<ElementDefinition> all, ElementDefinition element) {
2649    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
2650    int i = all.indexOf(element)+1;
2651    while (i < all.size() && all.get(i).getPath().length() > element.getPath().length()) {
2652      if ((all.get(i).getPath().substring(0, element.getPath().length()+1).equals(element.getPath()+".")) && !all.get(i).getPath().substring(element.getPath().length()+1).contains("."))
2653        result.add(all.get(i));
2654      i++;
2655    }
2656    return result;
2657  }
2658
2659  private String tail(String path) {
2660    if (path.contains("."))
2661      return path.substring(path.lastIndexOf('.')+1);
2662    else
2663      return path;
2664  }
2665
2666  private boolean isDataType(String value) {
2667    StructureDefinition sd = context.fetchTypeDefinition(value);
2668    return sd != null && sd.getKind() == StructureDefinitionKind.COMPLEXTYPE;
2669  }
2670
2671  private boolean isReference(String value) {
2672    return "Reference".equals(value);
2673  }
2674
2675  public boolean isPrimitive(String value) {
2676    StructureDefinition sd = context.fetchTypeDefinition(value);
2677    return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
2678  }
2679
2680//  private static String listStructures(StructureDefinition p) {
2681//    StringBuilder b = new StringBuilder();
2682//    boolean first = true;
2683//    for (ProfileStructureComponent s : p.getStructure()) {
2684//      if (first)
2685//        first = false;
2686//      else
2687//        b.append(", ");
2688//      if (pkp != null && pkp.hasLinkFor(s.getType()))
2689//        b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>");
2690//      else
2691//        b.append(s.getType());
2692//    }
2693//    return b.toString();
2694//  }
2695
2696
2697  public StructureDefinition getProfile(StructureDefinition source, String url) {
2698        StructureDefinition profile = null;
2699        String code = null;
2700        if (url.startsWith("#")) {
2701                profile = source;
2702                code = url.substring(1);
2703        } else if (context != null) {
2704                String[] parts = url.split("\\#");
2705                profile = context.fetchResource(StructureDefinition.class, parts[0]);
2706      code = parts.length == 1 ? null : parts[1];
2707        }         
2708        if (profile == null)
2709                return null;
2710        if (code == null)
2711                return profile;
2712        for (Resource r : profile.getContained()) {
2713                if (r instanceof StructureDefinition && r.getId().equals(code))
2714                        return (StructureDefinition) r;
2715        }
2716        return null;
2717  }
2718
2719
2720
2721  public static class ElementDefinitionHolder {
2722    private String name;
2723    private ElementDefinition self;
2724    private int baseIndex = 0;
2725    private List<ElementDefinitionHolder> children;
2726
2727    public ElementDefinitionHolder(ElementDefinition self) {
2728      super();
2729      this.self = self;
2730      this.name = self.getPath();
2731      children = new ArrayList<ElementDefinitionHolder>();
2732    }
2733
2734    public ElementDefinition getSelf() {
2735      return self;
2736    }
2737
2738    public List<ElementDefinitionHolder> getChildren() {
2739      return children;
2740    }
2741
2742    public int getBaseIndex() {
2743      return baseIndex;
2744    }
2745
2746    public void setBaseIndex(int baseIndex) {
2747      this.baseIndex = baseIndex;
2748    }
2749
2750    @Override
2751    public String toString() {
2752      if (self.hasSliceName())
2753        return self.getPath()+"("+self.getSliceName()+")";
2754      else
2755        return self.getPath();
2756    }
2757  }
2758
2759  public static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> {
2760
2761    private boolean inExtension;
2762    private List<ElementDefinition> snapshot;
2763    private int prefixLength;
2764    private String base;
2765    private String name;
2766    private Set<String> errors = new HashSet<String>();
2767
2768    public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, int prefixLength, String name) {
2769      this.inExtension = inExtension;
2770      this.snapshot = snapshot;
2771      this.prefixLength = prefixLength;
2772      this.base = base;
2773      this.name = name;
2774    }
2775
2776    @Override
2777    public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) {
2778      if (o1.getBaseIndex() == 0)
2779        o1.setBaseIndex(find(o1.getSelf().getPath()));
2780      if (o2.getBaseIndex() == 0)
2781        o2.setBaseIndex(find(o2.getSelf().getPath()));
2782      return o1.getBaseIndex() - o2.getBaseIndex();
2783    }
2784
2785    private int find(String path) {
2786      String actual = base+path.substring(prefixLength);
2787      for (int i = 0; i < snapshot.size(); i++) {
2788        String p = snapshot.get(i).getPath();
2789        if (p.equals(actual)) {
2790          return i;
2791        }
2792        if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length()-3)) && !(actual.endsWith("[x]")) && !actual.substring(p.length()-3).contains(".")) {
2793          return i;
2794        }
2795        if (path.startsWith(p+".") && snapshot.get(i).hasContentReference()) {
2796          actual = base+(snapshot.get(i).getContentReference().substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength);
2797          i = 0;
2798        }
2799      }
2800      if (prefixLength == 0)
2801        errors.add("Differential contains path "+path+" which is not found in the base");
2802      else
2803        errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the base");
2804      return 0;
2805    }
2806
2807    public void checkForErrors(List<String> errorList) {
2808      if (errors.size() > 0) {
2809//        CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
2810//        for (String s : errors)
2811//          b.append("StructureDefinition "+name+": "+s);
2812//        throw new DefinitionException(b.toString());
2813        for (String s : errors)
2814          if (s.startsWith("!"))
2815            errorList.add("!StructureDefinition "+name+": "+s.substring(1));
2816          else
2817            errorList.add("StructureDefinition "+name+": "+s);
2818      }
2819    }
2820  }
2821
2822
2823  public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors) throws FHIRException  {
2824
2825    final List<ElementDefinition> diffList = diff.getDifferential().getElement();
2826    // first, we move the differential elements into a tree
2827    if (diffList.isEmpty())
2828      return;
2829    ElementDefinitionHolder edh = new ElementDefinitionHolder(diffList.get(0));
2830
2831    boolean hasSlicing = false;
2832    List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly
2833    for(ElementDefinition elt : diffList) {
2834      if (elt.hasSlicing() || paths.contains(elt.getPath())) {
2835        hasSlicing = true;
2836        break;
2837      }
2838      paths.add(elt.getPath());
2839    }
2840    if(!hasSlicing) {
2841      // if Differential does not have slicing then safe to pre-sort the list
2842      // so elements and subcomponents are together
2843      Collections.sort(diffList, new ElementNameCompare());
2844    }
2845
2846    int i = 1;
2847    processElementsIntoTree(edh, i, diff.getDifferential().getElement());
2848
2849    // now, we sort the siblings throughout the tree
2850    ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name);
2851    sortElements(edh, cmp, errors);
2852
2853    // now, we serialise them back to a list
2854    diffList.clear();
2855    writeElements(edh, diffList);
2856  }
2857
2858  private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) {
2859    String path = edh.getSelf().getPath();
2860    final String prefix = path + ".";
2861    while (i < list.size() && list.get(i).getPath().startsWith(prefix)) {
2862      ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i));
2863      edh.getChildren().add(child);
2864      i = processElementsIntoTree(child, i+1, list);
2865    }
2866    return i;
2867  }
2868
2869  private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) throws FHIRException {
2870    if (edh.getChildren().size() == 1)
2871      // special case - sort needsto allocate base numbers, but there'll be no sort if there's only 1 child. So in that case, we just go ahead and allocated base number directly
2872      edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath());
2873    else
2874      Collections.sort(edh.getChildren(), cmp);
2875    cmp.checkForErrors(errors);
2876
2877    for (ElementDefinitionHolder child : edh.getChildren()) {
2878      if (child.getChildren().size() > 0) {
2879        // what we have to check for here is running off the base profile into a data type profile
2880        ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex());
2881        ElementDefinitionComparer ccmp;
2882        if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getCode()) || ed.getType().get(0).getCode().equals(ed.getPath())) {
2883          ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name);
2884        } else if (ed.getType().get(0).getCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) {
2885          StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile());
2886          if (profile==null)
2887            ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case
2888          else
2889          ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), ed.getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name);
2890        } else if (ed.getType().size() == 1 && !ed.getType().get(0).getCode().equals("*")) {
2891          StructureDefinition profile = context.fetchTypeDefinition(ed.getType().get(0).getCode());
2892          if (profile==null)
2893            throw new FHIRException("Unable to resolve profile " + "http://hl7.org/fhir/StructureDefinition/"+ed.getType().get(0).getCode() + " in element " + ed.getPath());
2894          ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name);
2895        } else if (child.getSelf().getType().size() == 1) {
2896          StructureDefinition profile = context.fetchTypeDefinition(child.getSelf().getType().get(0).getCode());
2897          if (profile==null)
2898            throw new FHIRException("Unable to resolve profile " + "http://hl7.org/fhir/StructureDefinition/"+ed.getType().get(0).getCode() + " in element " + ed.getPath());
2899          ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), child.getSelf().getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name);
2900        } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) {
2901          String edLastNode = ed.getPath().replaceAll("(.*\\.)*(.*)", "$2");
2902          String childLastNode = child.getSelf().getPath().replaceAll("(.*\\.)*(.*)", "$2");
2903          String p = childLastNode.substring(edLastNode.length()-3);
2904          StructureDefinition sd = context.fetchTypeDefinition(p);
2905          if (sd == null)
2906            throw new Error("Unable to find profile "+p);
2907          ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name);
2908        } else {
2909          throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")");
2910        }
2911        if (ccmp != null)
2912        sortElements(child, ccmp, errors);
2913      }
2914    }
2915  }
2916
2917  private boolean isAbstract(String code) {
2918    return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") || code.equals("DomainResource");
2919  }
2920
2921
2922  private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) {
2923    list.add(edh.getSelf());
2924    for (ElementDefinitionHolder child : edh.getChildren()) {
2925      writeElements(child, list);
2926    }
2927  }
2928
2929  /**
2930   * First compare element by path then by name if same
2931   */
2932  private static class ElementNameCompare implements Comparator<ElementDefinition> {
2933
2934    @Override
2935    public int compare(ElementDefinition o1, ElementDefinition o2) {
2936      String path1 = normalizePath(o1);
2937      String path2 = normalizePath(o2);
2938      int cmp = path1.compareTo(path2);
2939      if (cmp == 0) {
2940        String name1 = o1.hasSliceName() ? o1.getSliceName() : "";
2941        String name2 = o2.hasSliceName() ? o2.getSliceName() : "";
2942        cmp = name1.compareTo(name2);
2943      }
2944      return cmp;
2945    }
2946
2947    private static String normalizePath(ElementDefinition e) {
2948      if (!e.hasPath()) return "";
2949      String path = e.getPath();
2950      // if sorting element names make sure onset[x] appears before onsetAge, onsetDate, etc.
2951      // so strip off the [x] suffix when comparing the path names.
2952      if (path.endsWith("[x]")) {
2953        path = path.substring(0, path.length()-3);
2954      }
2955      return path;
2956    }
2957
2958  }
2959
2960
2961  // generate schematrons for the rules in a structure definition
2962  public void generateSchematrons(OutputStream dest, StructureDefinition structure) throws IOException, DefinitionException {
2963    if (structure.getDerivation() != TypeDerivationRule.CONSTRAINT)
2964      throw new DefinitionException("not the right kind of structure to generate schematrons for");
2965    if (!structure.hasSnapshot())
2966      throw new DefinitionException("needs a snapshot");
2967
2968        StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBaseDefinition());
2969
2970        SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName());
2971
2972    ElementDefinition ed = structure.getSnapshot().getElement().get(0);
2973    generateForChildren(sch, "f:"+ed.getPath(), ed, structure, base);
2974    sch.dump();
2975  }
2976
2977  // generate a CSV representation of the structure definition
2978  public void generateCsvs(OutputStream dest, StructureDefinition structure, boolean asXml) throws IOException, DefinitionException, Exception {
2979    if (!structure.hasSnapshot())
2980      throw new DefinitionException("needs a snapshot");
2981
2982    CSVWriter csv = new CSVWriter(dest, structure, asXml);
2983
2984    for (ElementDefinition child : structure.getSnapshot().getElement()) {
2985      csv.processElement(child);
2986    }
2987    csv.dump();
2988  }
2989  
2990  private class Slicer extends ElementDefinitionSlicingComponent {
2991    String criteria = "";
2992    String name = "";   
2993    boolean check;
2994    public Slicer(boolean cantCheck) {
2995      super();
2996      this.check = cantCheck;
2997    }
2998  }
2999  
3000  private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, StructureDefinition structure) {
3001    // given a child in a structure, it's sliced. figure out the slicing xpath
3002    if (child.getPath().endsWith(".extension")) {
3003      ElementDefinition ued = getUrlFor(structure, child);
3004      if ((ued == null || !ued.hasFixed()) && !(child.hasType() && (child.getType().get(0).hasProfile())))
3005        return new Slicer(false);
3006      else {
3007      Slicer s = new Slicer(true);
3008      String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile() : ((UriType) ued.getFixed()).asStringValue();
3009      s.name = " with URL = '"+url+"'";
3010      s.criteria = "[@url = '"+url+"']";
3011      return s;
3012      }
3013    } else
3014      return new Slicer(false);
3015  }
3016
3017  private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, StructureDefinition structure, StructureDefinition base) throws IOException {
3018    //    generateForChild(txt, structure, child);
3019    List<ElementDefinition> children = getChildList(structure, ed);
3020    String sliceName = null;
3021    ElementDefinitionSlicingComponent slicing = null;
3022    for (ElementDefinition child : children) {
3023      String name = tail(child.getPath());
3024      if (child.hasSlicing()) {
3025        sliceName = name;
3026        slicing = child.getSlicing();        
3027      } else if (!name.equals(sliceName))
3028        slicing = null;
3029      
3030      ElementDefinition based = getByPath(base, child.getPath());
3031      boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin()));
3032      boolean doMax = child.hasMax() && !child.getMax().equals("*") && (based == null || (!child.getMax().equals(based.getMax())));
3033      Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure);
3034      if (slicer.check) {
3035        if (doMin || doMax) {
3036          Section s = sch.section(xpath);
3037          Rule r = s.rule(xpath);
3038          if (doMin) 
3039            r.assrt("count(f:"+name+slicer.criteria+") >= "+Integer.toString(child.getMin()), name+slicer.name+": minimum cardinality of '"+name+"' is "+Integer.toString(child.getMin()));
3040          if (doMax) 
3041            r.assrt("count(f:"+name+slicer.criteria+") <= "+child.getMax(), name+slicer.name+": maximum cardinality of '"+name+"' is "+child.getMax());
3042          }
3043        }
3044      }
3045    for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
3046      if (inv.hasXpath()) {
3047        Section s = sch.section(ed.getPath());
3048        Rule r = s.rule(xpath);
3049        r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId()+": " : "")+inv.getHuman()+(inv.hasUserData(IS_DERIVED) ? " (inherited)" : ""));
3050      }
3051    }
3052    for (ElementDefinition child : children) {
3053      String name = tail(child.getPath());
3054      generateForChildren(sch, xpath+"/f:"+name, child, structure, base);
3055    }
3056  }
3057
3058
3059
3060
3061  private ElementDefinition getByPath(StructureDefinition base, String path) {
3062                for (ElementDefinition ed : base.getSnapshot().getElement()) {
3063                        if (ed.getPath().equals(path))
3064                                return ed;
3065                        if (ed.getPath().endsWith("[x]") && ed.getPath().length() <= path.length()-3 &&  ed.getPath().substring(0, ed.getPath().length()-3).equals(path.substring(0, ed.getPath().length()-3)))
3066                                return ed;
3067    }
3068          return null;
3069  }
3070
3071
3072  public void setIds(StructureDefinition sd, boolean checkFirst) throws DefinitionException  {
3073    if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) {
3074      if (!sd.hasDifferential())
3075        sd.setDifferential(new StructureDefinitionDifferentialComponent());
3076      generateIds(sd.getDifferential().getElement(), sd.getName());
3077    }
3078    if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) {
3079      if (!sd.hasSnapshot())
3080        sd.setSnapshot(new StructureDefinitionSnapshotComponent());
3081      generateIds(sd.getSnapshot().getElement(), sd.getName());
3082    }
3083  }
3084
3085
3086  private boolean hasMissingIds(List<ElementDefinition> list) {
3087    for (ElementDefinition ed : list) {
3088      if (!ed.hasId())
3089        return true;
3090    }    
3091    return false;
3092  }
3093
3094
3095  private void generateIds(List<ElementDefinition> list, String name) throws DefinitionException  {
3096    if (list.isEmpty())
3097      return;
3098    
3099    Map<String, String> idMap = new HashMap<String, String>();
3100    
3101    List<String> paths = new ArrayList<String>();
3102    // first pass, update the element ids
3103    for (ElementDefinition ed : list) {
3104      if (!ed.hasPath())
3105        throw new DefinitionException("No path on element Definition "+Integer.toString(list.indexOf(ed))+" in "+name);
3106      int depth = charCount(ed.getPath(), '.');
3107      String tail = tail(ed.getPath());
3108
3109      if (depth > paths.size()) {
3110        // this means that we've jumped into a sparse thing. 
3111        String[] pl = ed.getPath().split("\\.");
3112        for (int i = paths.size(); i < pl.length-1; i++) // -1 because the last path is in focus
3113          paths.add(pl[i]);
3114      }
3115      while (depth < paths.size() && paths.size() > 0)
3116        paths.remove(paths.size() - 1);
3117      
3118      String t = ed.hasSliceName() ? tail+":"+checkName(ed.getSliceName()) : /* why do this? name != null ? tail + ":"+checkName(name) : */ tail;
3119//      if (isExtension(ed))
3120//        t = t + describeExtension(ed);
3121      name = null;
3122      StringBuilder b = new StringBuilder();
3123      for (String s : paths) {
3124        b.append(s);
3125        b.append(".");
3126      }
3127      b.append(t);
3128      String bs = b.toString();
3129      idMap.put(ed.hasId() ? ed.getId() : ed.getPath(), bs);
3130      ed.setId(bs);
3131      paths.add(t);
3132      if (ed.hasContentReference()) {
3133        String s = ed.getContentReference().substring(1);
3134        if (idMap.containsKey(s))
3135          ed.setContentReference("#"+idMap.get(s));
3136        
3137      }
3138    }  
3139    // second path - fix up any broken path based id references
3140    
3141  }
3142
3143
3144//  private String describeExtension(ElementDefinition ed) {
3145//    if (!ed.hasType() || !ed.getTypeFirstRep().hasProfile())
3146//      return "";
3147//    return "$"+urlTail(ed.getTypeFirstRep().getProfile());
3148//  }
3149//
3150
3151  private String urlTail(String profile) {
3152    return profile.contains("/") ? profile.substring(profile.lastIndexOf("/")+1) : profile;
3153  }
3154
3155
3156  private String checkName(String name) {
3157//    if (name.contains("."))
3158////      throw new Exception("Illegal name "+name+": no '.'");
3159//    if (name.contains(" "))
3160//      throw new Exception("Illegal name "+name+": no spaces");
3161    StringBuilder b = new StringBuilder();
3162    for (char c : name.toCharArray()) {
3163      if (!Utilities.existsInList(c, '.', ' ', ':', '"', '\'', '(', ')', '&', '[', ']'))
3164        b.append(c);
3165    }
3166    return b.toString().toLowerCase();
3167  }
3168
3169
3170  private int charCount(String path, char t) {
3171    int res = 0;
3172    for (char ch : path.toCharArray()) {
3173      if (ch == t)
3174        res++;
3175    }
3176    return res;
3177  }
3178
3179//
3180//private void generateForChild(TextStreamWriter txt,
3181//    StructureDefinition structure, ElementDefinition child) {
3182//  // TODO Auto-generated method stub
3183//
3184//}
3185
3186  private interface ExampleValueAccessor {
3187    Type getExampleValue(ElementDefinition ed);
3188    String getId();
3189  }
3190
3191  private class BaseExampleValueAccessor implements ExampleValueAccessor {
3192    @Override
3193    public Type getExampleValue(ElementDefinition ed) {
3194      if (ed.hasFixed())
3195        return ed.getFixed();
3196      if (ed.hasExample())
3197        return ed.getExample().get(0).getValue();
3198      else
3199        return null;
3200    }
3201
3202    @Override
3203    public String getId() {
3204      return "-genexample";
3205    }
3206  }
3207  
3208  private class ExtendedExampleValueAccessor implements ExampleValueAccessor {
3209    private String index;
3210
3211    public ExtendedExampleValueAccessor(String index) {
3212      this.index = index;
3213    }
3214    @Override
3215    public Type getExampleValue(ElementDefinition ed) {
3216      if (ed.hasFixed())
3217        return ed.getFixed();
3218      for (Extension ex : ed.getExtension()) {
3219       String ndx = ToolingExtensions.readStringExtension(ex, "index");
3220       Type value = ToolingExtensions.getExtension(ex, "exValue").getValue();
3221       if (index.equals(ndx) && value != null)
3222         return value;
3223      }
3224      return null;
3225    }
3226    @Override
3227    public String getId() {
3228      return "-genexample-"+index;
3229    }
3230  }
3231  
3232  public List<org.hl7.fhir.dstu3.elementmodel.Element> generateExamples(StructureDefinition sd, boolean evenWhenNoExamples) throws FHIRException {
3233    List<org.hl7.fhir.dstu3.elementmodel.Element> examples = new ArrayList<org.hl7.fhir.dstu3.elementmodel.Element>();
3234    if (sd.hasSnapshot()) {
3235      if (evenWhenNoExamples || hasAnyExampleValues(sd)) 
3236        examples.add(generateExample(sd, new BaseExampleValueAccessor()));
3237      for (int i = 1; i <= 50; i++) {
3238        if (hasAnyExampleValues(sd, Integer.toString(i))) 
3239          examples.add(generateExample(sd, new ExtendedExampleValueAccessor(Integer.toString(i))));
3240      }
3241    }
3242    return examples;
3243  }
3244
3245  private org.hl7.fhir.dstu3.elementmodel.Element generateExample(StructureDefinition profile, ExampleValueAccessor accessor) throws FHIRException {
3246    ElementDefinition ed = profile.getSnapshot().getElementFirstRep();
3247    org.hl7.fhir.dstu3.elementmodel.Element r = new org.hl7.fhir.dstu3.elementmodel.Element(ed.getPath(), new Property(context, ed, profile));
3248    List<ElementDefinition> children = getChildMap(profile, ed);
3249    for (ElementDefinition child : children) {
3250      if (child.getPath().endsWith(".id")) {
3251        org.hl7.fhir.dstu3.elementmodel.Element id = new org.hl7.fhir.dstu3.elementmodel.Element("id", new Property(context, child, profile));
3252        id.setValue(profile.getId()+accessor.getId());
3253        r.getChildren().add(id);
3254      } else { 
3255        org.hl7.fhir.dstu3.elementmodel.Element e = createExampleElement(profile, child, accessor);
3256        if (e != null)
3257          r.getChildren().add(e);
3258      }
3259    }
3260    return r;
3261  }
3262
3263  private org.hl7.fhir.dstu3.elementmodel.Element createExampleElement(StructureDefinition profile, ElementDefinition ed, ExampleValueAccessor accessor) throws FHIRException {
3264    Type v = accessor.getExampleValue(ed);
3265    if (v != null) {
3266      return new ObjectConverter(context).convert(new Property(context, ed, profile), v);
3267    } else {
3268      org.hl7.fhir.dstu3.elementmodel.Element res = new org.hl7.fhir.dstu3.elementmodel.Element(tail(ed.getPath()), new Property(context, ed, profile));
3269      boolean hasValue = false;
3270      List<ElementDefinition> children = getChildMap(profile, ed);
3271      for (ElementDefinition child : children) {
3272        if (!child.hasContentReference()) {
3273        org.hl7.fhir.dstu3.elementmodel.Element e = createExampleElement(profile, child, accessor);
3274        if (e != null) {
3275          hasValue = true;
3276          res.getChildren().add(e);
3277        }
3278      }
3279      }
3280      if (hasValue)
3281        return res;
3282      else
3283        return null;
3284    }
3285  }
3286
3287  private boolean hasAnyExampleValues(StructureDefinition sd, String index) {
3288    for (ElementDefinition ed : sd.getSnapshot().getElement())
3289      for (Extension ex : ed.getExtension()) {
3290        String ndx = ToolingExtensions.readStringExtension(ex, "index");
3291        Extension exv = ToolingExtensions.getExtension(ex, "exValue");
3292        if (exv != null) {
3293          Type value = exv.getValue();
3294        if (index.equals(ndx) && value != null)
3295          return true;
3296        }
3297       }
3298    return false;
3299  }
3300
3301
3302  private boolean hasAnyExampleValues(StructureDefinition sd) {
3303    for (ElementDefinition ed : sd.getSnapshot().getElement())
3304      if (ed.hasExample())
3305        return true;
3306    return false;
3307  }
3308
3309
3310  public void populateLogicalSnapshot(StructureDefinition sd) throws FHIRException {
3311    sd.getSnapshot().getElement().add(sd.getDifferential().getElementFirstRep().copy());
3312    
3313    if (sd.hasBaseDefinition()) {
3314    StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
3315    if (base == null)
3316      throw new FHIRException("Unable to find base definition for logical model: "+sd.getBaseDefinition()+" from "+sd.getUrl());
3317    copyElements(sd, base.getSnapshot().getElement());
3318    }
3319    copyElements(sd, sd.getDifferential().getElement());
3320  }
3321
3322
3323  private void copyElements(StructureDefinition sd, List<ElementDefinition> list) {
3324    for (ElementDefinition ed : list) {
3325      if (ed.getPath().contains(".")) {
3326        ElementDefinition n = ed.copy();
3327        n.setPath(sd.getSnapshot().getElementFirstRep().getPath()+"."+ed.getPath().substring(ed.getPath().indexOf(".")+1));
3328        sd.getSnapshot().addElement(n);
3329      }
3330    }
3331  }
3332
3333    
3334  public void cleanUpDifferential(StructureDefinition sd) {
3335    if (sd.getDifferential().getElement().size() > 1)
3336      cleanUpDifferential(sd, 1);
3337  }
3338  
3339  private void cleanUpDifferential(StructureDefinition sd, int start) {
3340    int level = Utilities.charCount(sd.getDifferential().getElement().get(start).getPath(), '.');
3341    int c = start;
3342    int len = sd.getDifferential().getElement().size();
3343    HashSet<String> paths = new HashSet<String>();
3344    while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') == level) {
3345      ElementDefinition ed = sd.getDifferential().getElement().get(c);
3346      if (!paths.contains(ed.getPath())) {
3347        paths.add(ed.getPath());
3348        int ic = c+1; 
3349        while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 
3350          ic++;
3351        ElementDefinition slicer = null;
3352        List<ElementDefinition> slices = new ArrayList<ElementDefinition>();
3353        slices.add(ed);
3354        while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') == level) {
3355          ElementDefinition edi = sd.getDifferential().getElement().get(ic);
3356          if (ed.getPath().equals(edi.getPath())) {
3357            if (slicer == null) {
3358              slicer = new ElementDefinition();
3359              slicer.setPath(edi.getPath());
3360              slicer.getSlicing().setRules(SlicingRules.OPEN);
3361              sd.getDifferential().getElement().add(c, slicer);
3362              c++;
3363              ic++;
3364            }
3365            slices.add(edi);
3366          }
3367          ic++;
3368          while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 
3369            ic++;
3370        }
3371        // now we're at the end, we're going to figure out the slicing discriminator
3372        if (slicer != null)
3373          determineSlicing(slicer, slices);
3374      }
3375      c++;
3376      if (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) {
3377        cleanUpDifferential(sd, c);
3378        c++;
3379        while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) 
3380          c++;
3381      }
3382  }
3383  }
3384
3385
3386  private void determineSlicing(ElementDefinition slicer, List<ElementDefinition> slices) {
3387    // first, name them
3388    int i = 0;
3389    for (ElementDefinition ed : slices) {
3390      if (ed.hasUserData("slice-name")) {
3391        ed.setSliceName(ed.getUserString("slice-name"));
3392      } else {
3393        i++;
3394        ed.setSliceName("slice-"+Integer.toString(i));
3395      }
3396    }
3397    // now, the hard bit, how are they differentiated? 
3398    // right now, we hard code this...
3399    if (slicer.getPath().endsWith(".extension") || slicer.getPath().endsWith(".modifierExtension"))
3400      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url");
3401    else if (slicer.getPath().equals("DiagnosticReport.result"))
3402      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("reference.code");
3403    else if (slicer.getPath().equals("Observation.related"))
3404      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("target.reference.code");
3405    else if (slicer.getPath().equals("Bundle.entry"))
3406      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("resource.@profile");
3407    else  
3408      throw new Error("No slicing for "+slicer.getPath()); 
3409  }
3410
3411  public class SpanEntry {
3412    private List<SpanEntry> children = new ArrayList<SpanEntry>();
3413    private boolean profile;
3414    private String id;
3415    private String name;
3416    private String resType;
3417    private String cardinality;
3418    private String description;
3419    private String profileLink;
3420    private String resLink;
3421    private String type;
3422    
3423    public String getName() {
3424      return name;
3425    }
3426    public void setName(String name) {
3427      this.name = name;
3428    }
3429    public String getResType() {
3430      return resType;
3431    }
3432    public void setResType(String resType) {
3433      this.resType = resType;
3434    }
3435    public String getCardinality() {
3436      return cardinality;
3437    }
3438    public void setCardinality(String cardinality) {
3439      this.cardinality = cardinality;
3440    }
3441    public String getDescription() {
3442      return description;
3443    }
3444    public void setDescription(String description) {
3445      this.description = description;
3446    }
3447    public String getProfileLink() {
3448      return profileLink;
3449    }
3450    public void setProfileLink(String profileLink) {
3451      this.profileLink = profileLink;
3452    }
3453    public String getResLink() {
3454      return resLink;
3455    }
3456    public void setResLink(String resLink) {
3457      this.resLink = resLink;
3458    }
3459    public String getId() {
3460      return id;
3461    }
3462    public void setId(String id) {
3463      this.id = id;
3464    }
3465    public boolean isProfile() {
3466      return profile;
3467    }
3468    public void setProfile(boolean profile) {
3469      this.profile = profile;
3470    }
3471    public List<SpanEntry> getChildren() {
3472      return children;
3473    }
3474    public String getType() {
3475      return type;
3476    }
3477    public void setType(String type) {
3478      this.type = type;
3479    }
3480    
3481  }
3482
3483  public XhtmlNode generateSpanningTable(StructureDefinition profile, String imageFolder, boolean onlyConstraints, String constraintPrefix, Set<String> outputTracker) throws IOException, FHIRException {
3484    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, false);
3485    gen.setTranslator(getTranslator());
3486    TableModel model = initSpanningTable(gen, "", false, profile.getId());
3487    Set<String> processed = new HashSet<String>();
3488    SpanEntry span = buildSpanningTable("(focus)", "", profile, processed, onlyConstraints, constraintPrefix);
3489    
3490    genSpanEntry(gen, model.getRows(), span);
3491    return gen.generate(model, "", 0, outputTracker);
3492  }
3493
3494  private SpanEntry buildSpanningTable(String name, String cardinality, StructureDefinition profile, Set<String> processed, boolean onlyConstraints, String constraintPrefix) throws IOException {
3495    SpanEntry res = buildSpanEntryFromProfile(name, cardinality, profile);
3496    boolean wantProcess = !processed.contains(profile.getUrl());
3497    processed.add(profile.getUrl());
3498    if (wantProcess && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
3499      for (ElementDefinition ed : profile.getSnapshot().getElement()) {
3500        if (!"0".equals(ed.getMax()) && ed.getType().size() > 0) {
3501          String card = getCardinality(ed, profile.getSnapshot().getElement());
3502          if (!card.endsWith(".0")) {
3503            List<String> refProfiles = listReferenceProfiles(ed);
3504            if (refProfiles.size() > 0) {
3505              String uri = refProfiles.get(0);
3506              if (uri != null) {
3507                StructureDefinition sd = context.fetchResource(StructureDefinition.class, uri);
3508                if (sd != null && (!onlyConstraints || (sd.getDerivation() == TypeDerivationRule.CONSTRAINT && (constraintPrefix == null || sd.getUrl().startsWith(constraintPrefix))))) {
3509                  res.getChildren().add(buildSpanningTable(nameForElement(ed), card, sd, processed, onlyConstraints, constraintPrefix));
3510                }
3511              }
3512            }
3513          }
3514        } 
3515      }
3516    }
3517    return res;
3518  }
3519
3520
3521  private String getCardinality(ElementDefinition ed, List<ElementDefinition> list) {
3522    int min = ed.getMin();
3523    int max = !ed.hasMax() || ed.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(ed.getMax());
3524    while (ed != null && ed.getPath().contains(".")) {
3525      ed = findParent(ed, list);
3526      if (ed.getMax().equals("0"))
3527        max = 0;
3528      else if (!ed.getMax().equals("1") && !ed.hasSlicing())
3529        max = Integer.MAX_VALUE;
3530      if (ed.getMin() == 0)
3531        min = 0;
3532    }
3533    return Integer.toString(min)+".."+(max == Integer.MAX_VALUE ? "*" : Integer.toString(max));
3534  }
3535
3536
3537  private ElementDefinition findParent(ElementDefinition ed, List<ElementDefinition> list) {
3538    int i = list.indexOf(ed)-1;
3539    while (i >= 0 && !ed.getPath().startsWith(list.get(i).getPath()+"."))
3540      i--;
3541    if (i == -1)
3542      return null;
3543    else
3544      return list.get(i);
3545  }
3546
3547
3548  private List<String> listReferenceProfiles(ElementDefinition ed) {
3549    List<String> res = new ArrayList<String>();
3550    for (TypeRefComponent tr : ed.getType()) {
3551      // code is null if we're dealing with "value" and profile is null if we just have Reference()
3552      if (tr.getCode()!= null && "Reference".equals(tr.getCode()) && tr.getTargetProfile() != null)
3553        res.add(tr.getTargetProfile());
3554    }
3555    return res ;
3556  }
3557
3558
3559  private String nameForElement(ElementDefinition ed) {
3560    return ed.getPath().substring(ed.getPath().indexOf(".")+1);
3561  }
3562
3563
3564  private SpanEntry buildSpanEntryFromProfile(String name, String cardinality, StructureDefinition profile) throws IOException {
3565    SpanEntry res = new SpanEntry();
3566    res.setName(name);
3567    res.setCardinality(cardinality);
3568    res.setProfileLink(profile.getUserString("path"));
3569    res.setResType(profile.getType());
3570    StructureDefinition base = context.fetchResource(StructureDefinition.class, res.getResType());
3571    if (base != null)
3572      res.setResLink(base.getUserString("path"));
3573    res.setId(profile.getId());
3574    res.setProfile(profile.getDerivation() == TypeDerivationRule.CONSTRAINT);
3575    StringBuilder b = new StringBuilder();
3576    b.append(res.getResType());
3577    boolean first = true;
3578    boolean open = false;
3579    if (profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
3580      res.setDescription(profile.getName());
3581      for (ElementDefinition ed : profile.getSnapshot().getElement()) {
3582        if (isKeyProperty(ed.getBase().getPath()) && ed.hasFixed()) {
3583          if (first) {
3584            open = true;
3585            first = false;
3586            b.append("[");
3587          } else {
3588            b.append(", ");
3589          }
3590          b.append(tail(ed.getBase().getPath()));
3591          b.append("=");
3592          b.append(summarise(ed.getFixed()));
3593        }
3594      }
3595      if (open)
3596        b.append("]");
3597    } else
3598      res.setDescription("Base FHIR "+profile.getName());
3599    res.setType(b.toString());
3600    return res ;
3601  }
3602
3603
3604  private String summarise(Type value) throws IOException {
3605    if (value instanceof Coding)
3606      return summariseCoding((Coding) value);
3607    else if (value instanceof CodeableConcept)
3608      return summariseCodeableConcept((CodeableConcept) value);
3609    else
3610      return buildJson(value);
3611  }
3612
3613
3614  private String summariseCoding(Coding value) {
3615    String uri = value.getSystem();
3616    String system = NarrativeGenerator.describeSystem(uri);
3617    if (Utilities.isURL(system)) {
3618      if (system.equals("http://cap.org/protocols"))
3619        system = "CAP Code";
3620    }
3621    return system+" "+value.getCode();
3622  }
3623
3624
3625  private String summariseCodeableConcept(CodeableConcept value) {
3626    if (value.hasCoding())
3627      return summariseCoding(value.getCodingFirstRep());
3628    else
3629      return value.getText();
3630  }
3631
3632
3633  private boolean isKeyProperty(String path) {
3634    return Utilities.existsInList(path, "Observation.code");
3635  }
3636
3637
3638  public TableModel initSpanningTable(HierarchicalTableGenerator gen, String prefix, boolean isLogical, String id) {
3639    TableModel model = gen.new TableModel(id, false);
3640    
3641    model.setDocoImg(prefix+"help16.png");
3642    model.setDocoRef(prefix+"formats.html#table"); // todo: change to graph definition
3643    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Property", "A profiled resource", null, 0));
3644    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Card.", "Minimum and Maximum # of times the the element can appear in the instance", null, 0));
3645    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Content", "What goes here", null, 0));
3646    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Description", "Description of the profile", null, 0));
3647    return model;
3648  }
3649
3650  private void genSpanEntry(HierarchicalTableGenerator gen, List<Row> rows, SpanEntry span) throws IOException {
3651    Row row = gen.new Row();
3652    rows.add(row);
3653    row.setAnchor(span.getId());
3654    //row.setColor(..?);
3655    if (span.isProfile()) 
3656      row.setIcon("icon_profile.png", HierarchicalTableGenerator.TEXT_ICON_PROFILE);
3657    else
3658      row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
3659    
3660    row.getCells().add(gen.new Cell(null, null, span.getName(), null, null));
3661    row.getCells().add(gen.new Cell(null, null, span.getCardinality(), null, null));
3662    row.getCells().add(gen.new Cell(null, span.getProfileLink(), span.getType(), null, null));
3663    row.getCells().add(gen.new Cell(null, null, span.getDescription(), null, null));
3664
3665    for (SpanEntry child : span.getChildren())
3666      genSpanEntry(gen, row.getSubRows(), child);
3667  }
3668
3669
3670  public static ElementDefinitionSlicingDiscriminatorComponent interpretR2Discriminator(String discriminator) {
3671    if (discriminator.endsWith("@pattern"))
3672      return makeDiscriminator(DiscriminatorType.PATTERN, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 
3673    if (discriminator.endsWith("@profile"))
3674      return makeDiscriminator(DiscriminatorType.PROFILE, discriminator.length() == 8 ? "" : discriminator.substring(discriminator.length()-9)); 
3675    if (discriminator.endsWith("@type")) 
3676      return makeDiscriminator(DiscriminatorType.TYPE, discriminator.length() == 5 ? "" : discriminator.substring(discriminator.length()-6)); 
3677    return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(discriminator);
3678  }
3679
3680
3681  public static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType profile, String str) {
3682    return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(Utilities.noString(str)? "$this" : str);
3683  }
3684
3685
3686  public static String buildR2Discriminator(ElementDefinitionSlicingDiscriminatorComponent t) throws FHIRException {
3687    switch (t.getType()) {
3688    case PROFILE: return t.getPath()+"/@profile";
3689    case PATTERN: return t.getPath()+"/@pattern";
3690    case TYPE: return t.getPath()+"/@type";
3691    case VALUE: return t.getPath();
3692    case EXISTS: return t.getPath(); // determination of value vs. exists is based on whether there's only 2 slices - one with minOccurs=1 and other with maxOccur=0
3693    default: throw new FHIRException("Unable to represent "+t.getType().toCode()+":"+t.getPath()+" in R2");    
3694    }
3695  }
3696
3697
3698
3699
3700
3701
3702}