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