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