001package org.hl7.fhir.r4.conformance;
002
003/*-
004 * #%L
005 * org.hl7.fhir.r4
006 * %%
007 * Copyright (C) 2014 - 2019 Health Level 7
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 * 
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import java.io.IOException;
024import java.io.OutputStream;
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.Comparator;
028import java.util.HashMap;
029import java.util.HashSet;
030import java.util.Iterator;
031import java.util.List;
032import java.util.Map;
033import java.util.Set;
034
035import org.apache.commons.lang3.StringUtils;
036import org.hl7.fhir.exceptions.DefinitionException;
037import org.hl7.fhir.exceptions.FHIRException;
038import org.hl7.fhir.exceptions.FHIRFormatError;
039import org.hl7.fhir.r4.conformance.ProfileUtilities.ProfileKnowledgeProvider.BindingResolution;
040import org.hl7.fhir.r4.context.IWorkerContext;
041import org.hl7.fhir.r4.context.IWorkerContext.ValidationResult;
042import org.hl7.fhir.r4.elementmodel.ObjectConverter;
043import org.hl7.fhir.r4.elementmodel.Property;
044import org.hl7.fhir.r4.formats.IParser;
045import org.hl7.fhir.r4.model.Base;
046import org.hl7.fhir.r4.model.BooleanType;
047import org.hl7.fhir.r4.model.CanonicalType;
048import org.hl7.fhir.r4.model.CodeType;
049import org.hl7.fhir.r4.model.CodeableConcept;
050import org.hl7.fhir.r4.model.Coding;
051import org.hl7.fhir.r4.model.Element;
052import org.hl7.fhir.r4.model.ElementDefinition;
053import org.hl7.fhir.r4.model.ElementDefinition.AggregationMode;
054import org.hl7.fhir.r4.model.ElementDefinition.DiscriminatorType;
055import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBaseComponent;
056import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent;
057import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionConstraintComponent;
058import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionExampleComponent;
059import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionMappingComponent;
060import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingComponent;
061import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent;
062import org.hl7.fhir.r4.model.ElementDefinition.PropertyRepresentation;
063import org.hl7.fhir.r4.model.ElementDefinition.SlicingRules;
064import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent;
065import org.hl7.fhir.r4.model.Enumeration;
066import org.hl7.fhir.r4.model.Enumerations.BindingStrength;
067import org.hl7.fhir.r4.model.Enumerations.FHIRVersion;
068import org.hl7.fhir.r4.model.Extension;
069import org.hl7.fhir.r4.model.IntegerType;
070import org.hl7.fhir.r4.model.PrimitiveType;
071import org.hl7.fhir.r4.model.Quantity;
072import org.hl7.fhir.r4.model.Resource;
073import org.hl7.fhir.r4.model.StringType;
074import org.hl7.fhir.r4.model.StructureDefinition;
075import org.hl7.fhir.r4.model.StructureDefinition.ExtensionContextType;
076import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionContextComponent;
077import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionDifferentialComponent;
078import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind;
079import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionMappingComponent;
080import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionSnapshotComponent;
081import org.hl7.fhir.r4.model.StructureDefinition.TypeDerivationRule;
082import org.hl7.fhir.r4.model.Type;
083import org.hl7.fhir.r4.model.UriType;
084import org.hl7.fhir.r4.model.ValueSet;
085import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionComponent;
086import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent;
087import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
088import org.hl7.fhir.r4.utils.NarrativeGenerator;
089import org.hl7.fhir.r4.utils.ToolingExtensions;
090import org.hl7.fhir.r4.utils.TranslatingUtilities;
091import org.hl7.fhir.r4.utils.formats.CSVWriter;
092import org.hl7.fhir.r4.utils.formats.XLSXWriter;
093import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
094import org.hl7.fhir.utilities.TerminologyServiceOptions;
095import org.hl7.fhir.utilities.Utilities;
096import org.hl7.fhir.utilities.validation.ValidationMessage;
097import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
098import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
099import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
100import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
101import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
102import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
103import org.hl7.fhir.utilities.xhtml.XhtmlNode;
104import org.hl7.fhir.utilities.xml.SchematronWriter;
105import org.hl7.fhir.utilities.xml.SchematronWriter.Rule;
106import org.hl7.fhir.utilities.xml.SchematronWriter.SchematronType;
107import org.hl7.fhir.utilities.xml.SchematronWriter.Section;
108
109/** 
110 * This class provides a set of utility operations for working with Profiles.
111 * Key functionality:
112 *  * getChildMap --?
113 *  * getChildList
114 *  * generateSnapshot: Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
115 *  * closeDifferential: fill out a differential by excluding anything not mentioned
116 *  * generateExtensionsTable: generate the HTML for a hierarchical table presentation of the extensions
117 *  * generateTable: generate  the HTML for a hierarchical table presentation of a structure
118 *  * generateSpanningTable: generate the HTML for a table presentation of a network of structures, starting at a nominated point
119 *  * summarize: describe the contents of a profile
120 *  
121 * 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
122 *  
123 * @author Grahame
124 *
125 */
126public class ProfileUtilities extends TranslatingUtilities {
127
128  public class ElementRedirection {
129
130    private String path;
131    private ElementDefinition element;
132
133    public ElementRedirection(ElementDefinition element, String path) {
134      this.path = path;
135      this.element = element;
136    }
137
138    public ElementDefinition getElement() {
139      return element;
140    }
141
142    @Override
143    public String toString() {
144      return element.toString() + " : "+path;
145    }
146
147    public String getPath() {
148      return path;
149    }
150
151  }
152
153  public class TypeSlice {
154    private ElementDefinition defn;
155    private String type;
156    public TypeSlice(ElementDefinition defn, String type) {
157      super();
158      this.defn = defn;
159      this.type = type;
160    }
161    public ElementDefinition getDefn() {
162      return defn;
163    }
164    public String getType() {
165      return type;
166    }
167    
168  }
169  private static final int MAX_RECURSION_LIMIT = 10;
170  
171  public class ExtensionContext {
172
173    private ElementDefinition element;
174    private StructureDefinition defn;
175
176    public ExtensionContext(StructureDefinition ext, ElementDefinition ed) {
177      this.defn = ext;
178      this.element = ed;
179    }
180
181    public ElementDefinition getElement() {
182      return element;
183    }
184
185    public StructureDefinition getDefn() {
186      return defn;
187    }
188
189    public String getUrl() {
190      if (element == defn.getSnapshot().getElement().get(0))
191        return defn.getUrl();
192      else
193        return element.getSliceName();
194    }
195
196    public ElementDefinition getExtensionValueDefinition() {
197      int i = defn.getSnapshot().getElement().indexOf(element)+1;
198      while (i < defn.getSnapshot().getElement().size()) {
199        ElementDefinition ed = defn.getSnapshot().getElement().get(i);
200        if (ed.getPath().equals(element.getPath()))
201          return null;
202        if (ed.getPath().startsWith(element.getPath()+".value"))
203          return ed;
204        i++;
205      }
206      return null;
207    }
208  }
209
210  private static final String ROW_COLOR_ERROR = "#ffcccc";
211  private static final String ROW_COLOR_FATAL = "#ff9999";
212  private static final String ROW_COLOR_WARNING = "#ffebcc";
213  private static final String ROW_COLOR_HINT = "#ebf5ff";
214  private static final String ROW_COLOR_NOT_MUST_SUPPORT = "#d6eaf8";
215  public static final int STATUS_OK = 0;
216  public static final int STATUS_HINT = 1;
217  public static final int STATUS_WARNING = 2;
218  public static final int STATUS_ERROR = 3;
219  public static final int STATUS_FATAL = 4;
220
221
222  private static final String DERIVATION_EQUALS = "derivation.equals";
223  public static final String DERIVATION_POINTER = "derived.pointer";
224  public static final String IS_DERIVED = "derived.fact";
225  public static final String UD_ERROR_STATUS = "error-status";
226  private static final String GENERATED_IN_SNAPSHOT = "profileutilities.snapshot.processed";
227  private final boolean ADD_REFERENCE_TO_TABLE = true;
228
229  private boolean useTableForFixedValues = true;
230  private boolean debug;
231
232  // note that ProfileUtilities are used re-entrantly internally, so nothing with process state can be here
233  private final IWorkerContext context;
234  private List<ValidationMessage> messages;
235  private List<String> snapshotStack = new ArrayList<String>();
236  private ProfileKnowledgeProvider pkp;
237  private boolean igmode;
238  private boolean exception;
239  private TerminologyServiceOptions terminologyServiceOptions = new TerminologyServiceOptions();
240  private boolean newSlicingProcessing;
241
242  public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp) {
243    super();
244    this.context = context;
245    this.messages = messages;
246    this.pkp = pkp;
247  }
248
249  private class UnusedTracker {
250    private boolean used;
251  }
252
253  public boolean isIgmode() {
254    return igmode;
255  }
256
257
258  public void setIgmode(boolean igmode) {
259    this.igmode = igmode;
260  }
261
262  public interface ProfileKnowledgeProvider {
263    public class BindingResolution {
264      public String display;
265      public String url;
266    }
267    public boolean isDatatype(String typeSimple);
268    public boolean isResource(String typeSimple);
269    public boolean hasLinkFor(String typeSimple);
270    public String getLinkFor(String corePath, String typeSimple);
271    public BindingResolution resolveBinding(StructureDefinition def, ElementDefinitionBindingComponent binding, String path) throws FHIRException;
272    public BindingResolution resolveBinding(StructureDefinition def, String url, String path) throws FHIRException;
273    public String getLinkForProfile(StructureDefinition profile, String url);
274    public boolean prependLinks();
275    public String getLinkForUrl(String corePath, String s);
276  }
277
278
279
280  public static List<ElementDefinition> getChildMap(StructureDefinition profile, ElementDefinition element) throws DefinitionException {
281    if (element.getContentReference()!=null) {
282      for (ElementDefinition e : profile.getSnapshot().getElement()) {
283        if (element.getContentReference().equals("#"+e.getId()))
284          return getChildMap(profile, e);
285      }
286      throw new DefinitionException("Unable to resolve name reference "+element.getContentReference()+" at path "+element.getPath());
287
288    } else {
289      List<ElementDefinition> res = new ArrayList<ElementDefinition>();
290      List<ElementDefinition> elements = profile.getSnapshot().getElement();
291      String path = element.getPath();
292      for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) {
293        ElementDefinition e = elements.get(index);
294        if (e.getPath().startsWith(path + ".")) {
295          // We only want direct children, not all descendants
296          if (!e.getPath().substring(path.length()+1).contains("."))
297            res.add(e);
298        } else
299          break;
300      }
301      return res;
302    }
303  }
304
305
306  public static List<ElementDefinition> getSliceList(StructureDefinition profile, ElementDefinition element) throws DefinitionException {
307    if (!element.hasSlicing())
308      throw new Error("getSliceList should only be called when the element has slicing");
309
310    List<ElementDefinition> res = new ArrayList<ElementDefinition>();
311    List<ElementDefinition> elements = profile.getSnapshot().getElement();
312    String path = element.getPath();
313    for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) {
314      ElementDefinition e = elements.get(index);
315      if (e.getPath().startsWith(path + ".") || e.getPath().equals(path)) {
316        // We want elements with the same path (until we hit an element that doesn't start with the same path)
317        if (e.getPath().equals(element.getPath()))
318          res.add(e);
319      } else
320        break;
321    }
322    return res;
323  }
324
325
326  /**
327   * Given a Structure, navigate to the element given by the path and return the direct children of that element
328   *
329   * @param structure The structure to navigate into
330   * @param path The path of the element within the structure to get the children for
331   * @return A List containing the element children (all of them are Elements)
332   */
333  public static List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id) {
334    return getChildList(profile, path, id, false);
335  }
336  
337  public static List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id, boolean diff) {
338    List<ElementDefinition> res = new ArrayList<ElementDefinition>();
339
340    boolean capturing = id==null;
341    if (id==null && !path.contains("."))
342      capturing = true;
343    
344    List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement();
345    for (ElementDefinition e : list) {
346      if (e == null)
347        throw new Error("element = null: "+profile.getUrl());
348      if (e.getId() == null)
349        throw new Error("element id = null: "+e.toString()+" on "+profile.getUrl());
350      
351      if (!capturing && id!=null && e.getId().equals(id)) {
352        capturing = true;
353      }
354      
355      // If our element is a slice, stop capturing children as soon as we see the next slice
356      if (capturing && e.hasId() && id!= null && !e.getId().equals(id) && e.getPath().equals(path))
357        break;
358      
359      if (capturing) {
360        String p = e.getPath();
361  
362        if (!Utilities.noString(e.getContentReference()) && path.startsWith(p)) {
363          if (path.length() > p.length())
364            return getChildList(profile, e.getContentReference()+"."+path.substring(p.length()+1), null, diff);
365          else
366            return getChildList(profile, e.getContentReference(), null, diff);
367          
368        } else if (p.startsWith(path+".") && !p.equals(path)) {
369          String tail = p.substring(path.length()+1);
370          if (!tail.contains(".")) {
371            res.add(e);
372          }
373        }
374      }
375    }
376
377    return res;
378  }
379
380  public static List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element, boolean diff) {
381    return getChildList(structure, element.getPath(), element.getId(), diff);
382  }
383
384  public static List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element) {
385    return getChildList(structure, element.getPath(), element.getId(), false);
386        }
387
388  public void updateMaps(StructureDefinition base, StructureDefinition derived) throws DefinitionException {
389    if (base == null)
390        throw new DefinitionException("no base profile provided");
391    if (derived == null)
392      throw new DefinitionException("no derived structure provided");
393    
394    for (StructureDefinitionMappingComponent baseMap : base.getMapping()) {
395      boolean found = false;
396      for (StructureDefinitionMappingComponent derivedMap : derived.getMapping()) {
397        if (derivedMap.getUri() != null && derivedMap.getUri().equals(baseMap.getUri())) {
398          found = true;
399          break;
400        }
401      }
402      if (!found)
403        derived.getMapping().add(baseMap);
404    }
405  }
406  
407  /**
408   * Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
409   *
410   * @param base - the base structure on which the differential will be applied
411   * @param differential - the differential to apply to the base
412   * @param url - where the base has relative urls for profile references, these need to be converted to absolutes by prepending this URL (e.g. the canonical URL)
413   * @param webUrl - where the base has relative urls in markdown, these need to be converted to absolutes by prepending this URL (this is not the same as the canonical URL)
414   * @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
415   * @return
416   * @throws FHIRException 
417   * @throws DefinitionException 
418   * @throws Exception
419   */
420  public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String webUrl, String profileName) throws DefinitionException, FHIRException {
421    if (base == null)
422      throw new DefinitionException("no base profile provided");
423    if (derived == null)
424      throw new DefinitionException("no derived structure provided");
425
426    if (snapshotStack.contains(derived.getUrl()))
427      throw new DefinitionException("Circular snapshot references detected; cannot generate snapshot (stack = "+snapshotStack.toString()+")");
428    snapshotStack.add(derived.getUrl());
429    
430    if (!Utilities.noString(webUrl) && !webUrl.endsWith("/"))
431      webUrl = webUrl + '/';
432
433    derived.setSnapshot(new StructureDefinitionSnapshotComponent());
434
435    try {
436    // so we have two lists - the base list, and the differential list
437    // the differential list is only allowed to include things that are in the base list, but
438    // is allowed to include them multiple times - thereby slicing them
439
440    // our approach is to walk through the base list, and see whether the differential
441    // says anything about them.
442    int baseCursor = 0;
443    int diffCursor = 0; // we need a diff cursor because we can only look ahead, in the bound scoped by longer paths
444
445    if (derived.hasDifferential() && !derived.getDifferential().getElementFirstRep().getPath().contains(".") && !derived.getDifferential().getElementFirstRep().getType().isEmpty())
446      throw new Error("type on first differential element!");
447
448    for (ElementDefinition e : derived.getDifferential().getElement()) 
449      e.clearUserData(GENERATED_IN_SNAPSHOT);
450    
451    // we actually delegate the work to a subroutine so we can re-enter it with a different cursors
452      StructureDefinitionDifferentialComponent diff = cloneDiff(derived.getDifferential()); // we make a copy here because we're sometimes going to hack the differential while processing it. Have to migrate user data back afterwards
453
454      processPaths("", derived.getSnapshot(), base.getSnapshot(), diff, baseCursor, diffCursor, base.getSnapshot().getElement().size()-1, 
455          derived.getDifferential().hasElement() ? derived.getDifferential().getElement().size()-1 : -1, url, webUrl, derived.present(), null, null, false, base.getUrl(), null, false, new ArrayList<ElementRedirection>(), base);
456    if (!derived.getSnapshot().getElementFirstRep().getType().isEmpty())
457      throw new Error("type on first snapshot element for "+derived.getSnapshot().getElementFirstRep().getPath()+" in "+derived.getUrl()+" from "+base.getUrl());
458    updateMaps(base, derived);
459    
460      if (debug) {
461      System.out.println("Differential: ");
462      for (ElementDefinition ed : derived.getDifferential().getElement())
463        System.out.println("  "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  id = "+ed.getId()+" "+constraintSummary(ed));
464      System.out.println("Snapshot: ");
465      for (ElementDefinition ed : derived.getSnapshot().getElement())
466        System.out.println("  "+ed.getPath()+" : "+typeSummaryWithProfile(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  id = "+ed.getId()+" "+constraintSummary(ed));
467    }
468      setIds(derived, false);
469    //Check that all differential elements have a corresponding snapshot element
470      for (ElementDefinition e : diff.getElement()) {
471        if (!e.hasUserData("diff-source"))
472          throw new Error("Unxpected internal condition - no source on diff element");
473        else {
474          if (e.hasUserData(DERIVATION_EQUALS))
475            ((Base) e.getUserData("diff-source")).setUserData(DERIVATION_EQUALS, e.getUserData(DERIVATION_EQUALS));
476          if (e.hasUserData(DERIVATION_POINTER))
477            ((Base) e.getUserData("diff-source")).setUserData(DERIVATION_POINTER, e.getUserData(DERIVATION_POINTER));
478        }
479      if (!e.hasUserData(GENERATED_IN_SNAPSHOT)) {
480          System.out.println("Error in snapshot generation: Differential for "+derived.getUrl()+" with " + (e.hasId() ? "id: "+e.getId() : "path: "+e.getPath())+" has an element that is not marked with a snapshot match");
481        if (exception)
482            throw new DefinitionException("Snapshot for "+derived.getUrl()+" does not contain an element that matches an existing differential element that has "+(e.hasId() ? "id: "+e.getId() : "path: "+e.getPath()));
483        else
484          messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.VALUE, url, "Snapshot for "+derived.getUrl()+" does not contain an element that matches an existing differential element that has id: " + e.getId(), ValidationMessage.IssueSeverity.ERROR));
485      }
486    }
487    if (derived.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
488      for (ElementDefinition ed : derived.getSnapshot().getElement()) {
489        if (!ed.hasBase()) {
490          ed.getBase().setPath(ed.getPath()).setMin(ed.getMin()).setMax(ed.getMax());
491        }
492      }
493    }
494    } catch (Exception e) {
495      // if we had an exception generating the snapshot, make sure we don't leave any half generated snapshot behind
496      derived.setSnapshot(null);
497      throw e;
498    }
499  }
500
501  private StructureDefinitionDifferentialComponent cloneDiff(StructureDefinitionDifferentialComponent source) {
502    StructureDefinitionDifferentialComponent diff = new StructureDefinitionDifferentialComponent();
503    for (ElementDefinition sed : source.getElement()) {
504      ElementDefinition ted = sed.copy();
505      diff.getElement().add(ted);
506      ted.setUserData("diff-source", sed);
507    }
508    return diff;
509  }
510
511
512  private String constraintSummary(ElementDefinition ed) {
513    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
514    if (ed.hasPattern())
515      b.append("pattern="+ed.getPattern().fhirType());
516    if (ed.hasFixed())
517      b.append("fixed="+ed.getFixed().fhirType());
518    if (ed.hasConstraint())
519      b.append("constraints="+ed.getConstraint().size());
520    return b.toString();
521  }
522
523
524  private String sliceSummary(ElementDefinition ed) {
525    if (!ed.hasSlicing() && !ed.hasSliceName())
526      return "";
527    if (ed.hasSliceName())
528      return " (slicename = "+ed.getSliceName()+")";
529    
530    StringBuilder b = new StringBuilder();
531    boolean first = true;
532    for (ElementDefinitionSlicingDiscriminatorComponent d : ed.getSlicing().getDiscriminator()) {
533      if (first) 
534        first = false;
535      else
536        b.append("|");
537      b.append(d.getPath());
538    }
539    return " (slicing by "+b.toString()+")";
540  }
541
542
543  private String typeSummary(ElementDefinition ed) {
544    StringBuilder b = new StringBuilder();
545    boolean first = true;
546    for (TypeRefComponent tr : ed.getType()) {
547      if (first) 
548        first = false;
549      else
550        b.append("|");
551      b.append(tr.getWorkingCode());
552    }
553    return b.toString();
554  }
555
556  private String typeSummaryWithProfile(ElementDefinition ed) {
557    StringBuilder b = new StringBuilder();
558    boolean first = true;
559    for (TypeRefComponent tr : ed.getType()) {
560      if (first) 
561        first = false;
562      else
563        b.append("|");
564      b.append(tr.getWorkingCode());
565      if (tr.hasProfile()) {
566        b.append("(");
567        b.append(tr.getProfile());
568        b.append(")");
569        
570      }
571    }
572    return b.toString();
573  }
574
575
576  private boolean findMatchingElement(String id, List<ElementDefinition> list) {
577    for (ElementDefinition ed : list) {
578      if (ed.getId().equals(id))
579        return true;
580      if (id.endsWith("[x]")) {
581        if (ed.getId().startsWith(id.substring(0, id.length()-3)) && !ed.getId().substring(id.length()-3).contains("."))
582          return true;
583      }
584    }
585    return false;
586  }
587
588
589  /**
590   * @param trimDifferential
591   * @param srcSD 
592   * @throws DefinitionException, FHIRException 
593   * @throws Exception
594   */
595  private ElementDefinition processPaths(String indent, StructureDefinitionSnapshotComponent result, StructureDefinitionSnapshotComponent base, StructureDefinitionDifferentialComponent differential, int baseCursor, int diffCursor, int baseLimit,
596      int diffLimit, String url, String webUrl, String profileName, String contextPathSrc, String contextPathDst, boolean trimDifferential, String contextName, String resultPathBase, boolean slicingDone, List<ElementRedirection> redirector, StructureDefinition srcSD) throws DefinitionException, FHIRException {
597    if (debug) 
598      System.out.println(indent+"PP @ "+resultPathBase+" / "+contextPathSrc+" : base = "+baseCursor+" to "+baseLimit+", diff = "+diffCursor+" to "+diffLimit+" (slicing = "+slicingDone+", redirector = "+(redirector == null ? "null" : redirector.toString())+")");
599    ElementDefinition res = null; 
600    List<TypeSlice> typeList = new ArrayList<>();
601    // just repeat processing entries until we run out of our allowed scope (1st entry, the allowed scope is all the entries)
602    while (baseCursor <= baseLimit) {
603      // get the current focus of the base, and decide what to do
604      ElementDefinition currentBase = base.getElement().get(baseCursor);
605      String cpath = fixedPathSource(contextPathSrc, currentBase.getPath(), redirector);
606      if (debug) 
607        System.out.println(indent+" - "+cpath+": base = "+baseCursor+" ("+descED(base.getElement(),baseCursor)+") to "+baseLimit+" ("+descED(base.getElement(),baseLimit)+"), diff = "+diffCursor+" ("+descED(differential.getElement(),diffCursor)+") to "+diffLimit+" ("+descED(differential.getElement(),diffLimit)+") "+
608           "(slicingDone = "+slicingDone+") (diffpath= "+(differential.getElement().size() > diffCursor ? differential.getElement().get(diffCursor).getPath() : "n/a")+")");
609      List<ElementDefinition> diffMatches = getDiffMatches(differential, cpath, diffCursor, diffLimit, profileName); // get a list of matching elements in scope
610
611      // in the simple case, source is not sliced.
612      if (!currentBase.hasSlicing()) {
613        if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item
614          // so we just copy it in
615          ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy());
616          outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
617          updateFromBase(outcome, currentBase);
618          markDerived(outcome);
619          if (resultPathBase == null)
620            resultPathBase = outcome.getPath();
621          else if (!outcome.getPath().startsWith(resultPathBase))
622            throw new DefinitionException("Adding wrong path");
623          result.getElement().add(outcome);
624          if (hasInnerDiffMatches(differential, cpath, diffCursor, diffLimit, base.getElement(), true)) {
625            // well, the profile walks into this, so we need to as well
626            // did we implicitly step into a new type?
627            if (baseHasChildren(base, currentBase)) { // not a new type here
628              processPaths(indent+"  ", result, base, differential, baseCursor+1, diffCursor, baseLimit, diffLimit, url, webUrl, profileName, contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, false, redirector, srcSD);
629              baseCursor = indexOfFirstNonChild(base, currentBase, baseCursor+1, baseLimit);
630            } else {
631              if (outcome.getType().size() == 0) {
632                throw new DefinitionException(diffMatches.get(0).getPath()+" has no children ("+differential.getElement().get(diffCursor).getPath()+") and no types in profile "+profileName);
633              }
634            if (outcome.getType().size() > 1) {
635              for (TypeRefComponent t : outcome.getType()) {
636                  if (!t.getWorkingCode().equals("Reference"))
637                  throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName);
638              }
639            }
640              StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
641            if (dt == null)
642                throw new DefinitionException("Unknown type "+outcome.getType().get(0)+" at "+diffMatches.get(0).getPath());
643            contextName = dt.getUrl();
644            int start = diffCursor;
645            while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), cpath+"."))
646              diffCursor++;
647            processPaths(indent+"  ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1,
648                  diffCursor-1, url, webUrl, profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false, redirector, srcSD);
649            }
650          }
651          baseCursor++;
652        } else if (diffMatches.size() == 1 && (slicingDone || (!isImplicitSlicing(diffMatches.get(0), cpath) && !(diffMatches.get(0).hasSlicing() || (isExtension(diffMatches.get(0)) && diffMatches.get(0).hasSliceName()))))) {// one matching element in the differential
653          ElementDefinition template = null;
654          if (diffMatches.get(0).hasType() && diffMatches.get(0).getType().size() == 1 && diffMatches.get(0).getType().get(0).hasProfile() && !"Reference".equals(diffMatches.get(0).getType().get(0).getWorkingCode())) {
655            CanonicalType p = diffMatches.get(0).getType().get(0).getProfile().get(0);
656            StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getValue());
657            if (sd != null) {
658              if (!sd.hasSnapshot()) {
659                StructureDefinition sdb = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
660                if (sdb == null)
661                  throw new DefinitionException("no base for "+sd.getBaseDefinition());
662                generateSnapshot(sdb, sd, sd.getUrl(), (sdb.hasUserData("path")) ? Utilities.extractBaseUrl(sdb.getUserString("path")) : webUrl, sd.getName());
663              }
664              ElementDefinition src;
665              if (p.hasExtension(ToolingExtensions.EXT_PROFILE_ELEMENT)) {
666                 src = null;
667                 String eid = p.getExtensionString(ToolingExtensions.EXT_PROFILE_ELEMENT);
668                 for (ElementDefinition t : sd.getSnapshot().getElement()) {
669                   if (eid.equals(t.getId()))
670                     src = t;
671                 }
672                 if (src == null)
673                   throw new DefinitionException("Unable to find element "+eid+" in "+p.getValue());
674              } else 
675                src = sd.getSnapshot().getElement().get(0);
676              template = src.copy().setPath(currentBase.getPath());
677              template.setSliceName(null);
678              // temporary work around
679              if (!"Extension".equals(diffMatches.get(0).getType().get(0).getCode())) {
680                template.setMin(currentBase.getMin());
681                template.setMax(currentBase.getMax());
682              }
683            }
684          } 
685          if (template == null)
686            template = currentBase.copy();
687          else
688            // some of what's in currentBase overrides template
689            template = overWriteWithCurrent(template, currentBase);
690          
691          ElementDefinition outcome = updateURLs(url, webUrl, template);
692          outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
693          if (res == null)
694            res = outcome;
695          updateFromBase(outcome, currentBase);
696          if (diffMatches.get(0).hasSliceName())
697            outcome.setSliceName(diffMatches.get(0).getSliceName());
698          updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url, srcSD);
699          removeStatusExtensions(outcome);
700//          if (outcome.getPath().endsWith("[x]") && outcome.getType().size() == 1 && !outcome.getType().get(0).getCode().equals("*") && !diffMatches.get(0).hasSlicing()) // if the base profile allows multiple types, but the profile only allows one, rename it
701//            outcome.setPath(outcome.getPath().substring(0, outcome.getPath().length()-3)+Utilities.capitalize(outcome.getType().get(0).getCode()));
702          outcome.setSlicing(null);
703          if (resultPathBase == null)
704            resultPathBase = outcome.getPath();
705          else if (!outcome.getPath().startsWith(resultPathBase))
706            throw new DefinitionException("Adding wrong path");
707          result.getElement().add(outcome);
708          baseCursor++;
709          diffCursor = differential.getElement().indexOf(diffMatches.get(0))+1;
710          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
711            if (pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".") && !baseWalksInto(base.getElement(), baseCursor)) {
712              if (outcome.getType().size() > 1) {
713                if (outcome.getPath().endsWith("[x]") && !diffMatches.get(0).getPath().endsWith("[x]")) {
714                  String en = tail(outcome.getPath());
715                  String tn = tail(diffMatches.get(0).getPath());
716                  String t = tn.substring(en.length()-3);
717                  if (isPrimitive(Utilities.uncapitalize(t)))
718                    t = Utilities.uncapitalize(t);
719                  List<TypeRefComponent> ntr = getByTypeName(outcome.getType(), t); // keep any additional information
720                  if (ntr.isEmpty()) 
721                    ntr.add(new TypeRefComponent().setCode(t));
722                  outcome.getType().clear();
723                  outcome.getType().addAll(ntr);
724                }
725                if (outcome.getType().size() > 1)
726                  for (TypeRefComponent t : outcome.getType()) {
727                    if (!t.getCode().equals("Reference")) {
728                      boolean nonExtension = false;
729                      for (ElementDefinition ed : diffMatches)
730                        if (ed != diffMatches.get(0) && !ed.getPath().endsWith(".extension"))
731                          nonExtension = true;
732                      if (nonExtension)
733                        throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName);
734                    }
735                }
736              }
737              int start = diffCursor;
738              while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+"."))
739                diffCursor++;
740              if (outcome.hasContentReference()) {
741                ElementDefinition tgt = getElementById(base.getElement(), outcome.getContentReference());
742                if (tgt == null)
743                  throw new DefinitionException("Unable to resolve reference to "+outcome.getContentReference());
744                replaceFromContentReference(outcome, tgt);
745                int nbc = base.getElement().indexOf(tgt)+1;
746                int nbl = nbc;
747                while (nbl < base.getElement().size() && base.getElement().get(nbl).getPath().startsWith(tgt.getPath()+"."))
748                  nbl++;
749                processPaths(indent+"  ", result, base, differential, nbc, start - 1, nbl-1, diffCursor - 1, url, webUrl, profileName, tgt.getPath(), diffMatches.get(0).getPath(), trimDifferential, contextName, resultPathBase, false, redirectorStack(redirector, outcome, cpath), srcSD);
750              } else {
751                StructureDefinition dt = outcome.getType().size() == 1 ? getProfileForDataType(outcome.getType().get(0)) : getProfileForDataType("Element");
752                if (dt == null)
753                  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");
754                contextName = dt.getUrl();
755                processPaths(indent+"  ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1,
756                    diffCursor - 1, url, webUrl, profileName+pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false, new ArrayList<ElementRedirection>(), srcSD);
757              }
758            }
759          }
760        } else if (diffsConstrainTypes(diffMatches, cpath, typeList)) {
761          int start = 0;
762          int nbl = findEndOfElement(base, baseCursor);
763          int ndc = differential.getElement().indexOf(diffMatches.get(0));
764          ElementDefinition elementToRemove = null;
765          // we come here whether they are sliced in the diff, or whether the short cut is used. 
766          if (typeList.get(0).type != null) {
767            // this is the short cut method, we've just dived in and specified a type slice. 
768            // in R3 (and unpatched R4, as a workaround right now...
769            if (!FHIRVersion.isR4Plus(context.getVersion()) || !newSlicingProcessing) { // newSlicingProcessing is a work around for editorial loop dependency
770              // we insert a cloned element with the right types at the start of the diffMatches
771              ElementDefinition ed = new ElementDefinition();
772              ed.setPath(determineTypeSlicePath(diffMatches.get(0).getPath(), cpath));
773              for (TypeSlice ts : typeList) 
774                ed.addType().setCode(ts.type);
775              ed.setSlicing(new ElementDefinitionSlicingComponent());
776              ed.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
777              ed.getSlicing().setRules(SlicingRules.CLOSED);
778              ed.getSlicing().setOrdered(false);
779              diffMatches.add(0, ed);
780              differential.getElement().add(ndc, ed);
781              elementToRemove = ed;
782            } else {
783              // as of R4, this changed; if there's no slice, there's no constraint on the slice types, only one the type. 
784              // so the element we insert specifies no types (= all types) allowed in the base, not just the listed type. 
785              // see also discussion here: https://chat.fhir.org/#narrow/stream/179177-conformance/topic/Slicing.20a.20non-repeating.20element             
786              ElementDefinition ed = new ElementDefinition();
787              ed.setPath(determineTypeSlicePath(diffMatches.get(0).getPath(), cpath));
788              ed.setSlicing(new ElementDefinitionSlicingComponent());
789              ed.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
790              ed.getSlicing().setRules(SlicingRules.CLOSED);
791              ed.getSlicing().setOrdered(false);
792              diffMatches.add(0, ed);
793              differential.getElement().add(ndc, ed);
794              elementToRemove = ed;
795            }
796          }
797          int ndl = findEndOfElement(differential, ndc);
798          // the first element is setting up the slicing
799          if (diffMatches.get(0).getSlicing().hasRules())
800            if (diffMatches.get(0).getSlicing().getRules() != SlicingRules.CLOSED)
801              throw new FHIRException("Error at path "+contextPathSrc+": Type slicing with slicing.rules != closed");
802          if (diffMatches.get(0).getSlicing().hasOrdered())
803            if (diffMatches.get(0).getSlicing().getOrdered())
804              throw new FHIRException("Error at path "+contextPathSrc+": Type slicing with slicing.ordered = true");
805          if (diffMatches.get(0).getSlicing().hasDiscriminator()) {
806            if (diffMatches.get(0).getSlicing().getDiscriminator().size() != 1)
807              throw new FHIRException("Error at path "+contextPathSrc+": Type slicing with slicing.discriminator.count() > 1");
808            if (!"$this".equals(diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getPath()))
809              throw new FHIRException("Error at path "+contextPathSrc+": Type slicing with slicing.discriminator.path != '$this'");
810            if (diffMatches.get(0).getSlicing().getDiscriminatorFirstRep().getType() != DiscriminatorType.TYPE)
811              throw new FHIRException("Error at path "+contextPathSrc+": Type slicing with slicing.discriminator.type != 'type'");
812          }
813          // check the slice names too while we're at it...
814          for (TypeSlice ts : typeList)
815            if (ts.type != null) {
816              String tn = rootName(cpath)+Utilities.capitalize(ts.type);
817              if (!ts.defn.hasSliceName())
818                ts.defn.setSliceName(tn);
819              else if (!ts.defn.getSliceName().equals(tn))
820                throw new FHIRException("Error at path "+(!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath)+": Slice name must be '"+tn+"' but is '"+ts.defn.getSliceName()+"'"); 
821              if (!ts.defn.hasType())
822                ts.defn.addType().setCode(ts.type);
823              else if (ts.defn.getType().size() > 1)
824                throw new FHIRException("Error at path "+(!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath)+": Slice for type '"+tn+"' has more than one type '"+ts.defn.typeSummary()+"'"); 
825              else if (!ts.defn.getType().get(0).getCode().equals(ts.type))
826                throw new FHIRException("Error at path "+(!Utilities.noString(contextPathSrc) ? contextPathSrc : cpath)+": Slice for type '"+tn+"' has wrong type '"+ts.defn.typeSummary()+"'"); 
827            }
828
829          // ok passed the checks. 
830          // copy the root diff, and then process any children it has
831          ElementDefinition e = processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, 0), contextPathSrc, contextPathDst, 
832              trimDifferential, contextName, resultPathBase, true, redirector, srcSD);
833          if (e==null)
834            throw new FHIRException("Did not find type root: " + diffMatches.get(0).getPath());
835          // now set up slicing on the e (cause it was wiped by what we called.
836          e.setSlicing(new ElementDefinitionSlicingComponent());
837          e.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
838          e.getSlicing().setRules(SlicingRules.CLOSED);
839          e.getSlicing().setOrdered(false);
840          start++;
841          // now process the siblings, which should each be type constrained - and may also have their own children
842          // now we process the base scope repeatedly for each instance of the item in the differential list
843          for (int i = start; i < diffMatches.size(); i++) {
844            // our processing scope for the differential is the item in the list, and all the items before the next one in the list
845            ndc = differential.getElement().indexOf(diffMatches.get(i));
846            ndl = findEndOfElement(differential, ndc);
847            processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true, redirector, srcSD);
848          }
849          if (elementToRemove != null) {
850            differential.getElement().remove(elementToRemove);
851            ndl--;
852          }
853          
854          // ok, done with that - next in the base list
855          baseCursor = nbl+1;
856          diffCursor = ndl+1;
857          
858        } else {
859          // ok, the differential slices the item. Let's check our pre-conditions to ensure that this is correct
860          if (!unbounded(currentBase) && !isSlicedToOneOnly(diffMatches.get(0)))
861            // you can only slice an element that doesn't repeat if the sum total of your slices is limited to 1
862            // (but you might do that in order to split up constraints by type)
863            throw new DefinitionException("Attempt to a slice an element that does not repeat: "+currentBase.getPath()+"/"+currentBase.getPath()+" from "+contextName+" in "+url);
864          if (!diffMatches.get(0).hasSlicing() && !isExtension(currentBase)) // well, the diff has set up a slice, but hasn't defined it. this is an error
865            throw new DefinitionException("Differential does not have a slice: "+currentBase.getPath()+"/ (b:"+baseCursor+" of "+ baseLimit+" / "+ diffCursor +"/ "+diffLimit+") in profile "+url);
866
867          // well, if it passed those preconditions then we slice the dest.
868          int start = 0;
869          int nbl = findEndOfElement(base, baseCursor);
870//          if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && differential.getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0))+1) {
871          if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && (nbl > baseCursor || differential.getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0))+1)) { // there's a default set before the slices
872            int ndc = differential.getElement().indexOf(diffMatches.get(0));
873            int ndl = findEndOfElement(differential, ndc);
874            ElementDefinition e = processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, 0), contextPathSrc, contextPathDst, 
875                trimDifferential, contextName, resultPathBase, true, redirector, srcSD);
876            if (e==null)
877              throw new FHIRException("Did not find single slice: " + diffMatches.get(0).getPath());
878            e.setSlicing(diffMatches.get(0).getSlicing());
879            start++;
880          } else {
881            // we're just going to accept the differential slicing at face value
882            ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy());
883            outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
884            updateFromBase(outcome, currentBase);
885
886            if (!diffMatches.get(0).hasSlicing())
887              outcome.setSlicing(makeExtensionSlicing());
888            else
889              outcome.setSlicing(diffMatches.get(0).getSlicing().copy());
890            if (!outcome.getPath().startsWith(resultPathBase))
891              throw new DefinitionException("Adding wrong path");
892            result.getElement().add(outcome);
893
894            // 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.
895            if (!diffMatches.get(0).hasSliceName()) {
896              updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url, srcSD);
897              removeStatusExtensions(outcome);
898              if (!outcome.hasContentReference() && !outcome.hasType()) {
899                throw new DefinitionException("not done yet");
900              }
901              start++;
902              // result.getElement().remove(result.getElement().size()-1);
903            } else 
904              checkExtensionDoco(outcome);
905          }
906          // now, for each entry in the diff matches, we're going to process the base item
907          // our processing scope for base is all the children of the current path
908          int ndc = diffCursor;
909          int ndl = diffCursor;
910          for (int i = start; i < diffMatches.size(); i++) {
911            // our processing scope for the differential is the item in the list, and all the items before the next one in the list
912            ndc = differential.getElement().indexOf(diffMatches.get(i));
913            ndl = findEndOfElement(differential, ndc);
914/*            if (skipSlicingElement && i == 0) {
915              ndc = ndc + 1;
916              if (ndc > ndl)
917                continue;
918            }*/
919            // now we process the base scope repeatedly for each instance of the item in the differential list
920            processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true, redirector, srcSD);
921          }
922          // ok, done with that - next in the base list
923          baseCursor = nbl+1;
924          diffCursor = ndl+1;
925        }
926      } else {
927        // the item is already sliced in the base profile.
928        // here's the rules
929        //  1. irrespective of whether the slicing is ordered or not, the definition order must be maintained
930        //  2. slice element names have to match.
931        //  3. new slices must be introduced at the end
932        // corallory: you can't re-slice existing slices. is that ok?
933
934        // we're going to need this:
935        String path = currentBase.getPath();
936        ElementDefinition original = currentBase;
937
938        if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item
939          // copy across the currentbase, and all of its children and siblings
940          while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path)) {
941            ElementDefinition outcome = updateURLs(url, webUrl, base.getElement().get(baseCursor).copy());
942            outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
943            if (!outcome.getPath().startsWith(resultPathBase))
944              throw new DefinitionException("Adding wrong path in profile " + profileName + ": "+outcome.getPath()+" vs " + resultPathBase);
945            result.getElement().add(outcome); // so we just copy it in
946            baseCursor++;
947          }
948        } else {
949          // first - check that the slicing is ok
950          boolean closed = currentBase.getSlicing().getRules() == SlicingRules.CLOSED;
951          int diffpos = 0;
952          boolean isExtension = cpath.endsWith(".extension") || cpath.endsWith(".modifierExtension");
953          if (diffMatches.get(0).hasSlicing()) { // it might be null if the differential doesn't want to say anything about slicing
954//            if (!isExtension)
955//              diffpos++; // if there's a slice on the first, we'll ignore any content it has
956            ElementDefinitionSlicingComponent dSlice = diffMatches.get(0).getSlicing();
957            ElementDefinitionSlicingComponent bSlice = currentBase.getSlicing();
958            if (dSlice.hasOrderedElement() && bSlice.hasOrderedElement() && !orderMatches(dSlice.getOrderedElement(), bSlice.getOrderedElement()))
959              throw new DefinitionException("Slicing rules on differential ("+summarizeSlicing(dSlice)+") do not match those on base ("+summarizeSlicing(bSlice)+") - order @ "+path+" ("+contextName+")");
960            if (!discriminatorMatches(dSlice.getDiscriminator(), bSlice.getDiscriminator()))
961             throw new DefinitionException("Slicing rules on differential ("+summarizeSlicing(dSlice)+") do not match those on base ("+summarizeSlicing(bSlice)+") - disciminator @ "+path+" ("+contextName+")");
962            if (!ruleMatches(dSlice.getRules(), bSlice.getRules()))
963             throw new DefinitionException("Slicing rules on differential ("+summarizeSlicing(dSlice)+") do not match those on base ("+summarizeSlicing(bSlice)+") - rule @ "+path+" ("+contextName+")");
964          }
965          ElementDefinition outcome = updateURLs(url, webUrl, currentBase.copy());
966          outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
967          updateFromBase(outcome, currentBase);
968          if (diffMatches.get(0).hasSlicing() || !diffMatches.get(0).hasSliceName()) {
969            updateFromSlicing(outcome.getSlicing(), diffMatches.get(0).getSlicing());
970            updateFromDefinition(outcome, diffMatches.get(0), profileName, closed, url, srcSD); // if there's no slice, we don't want to update the unsliced description
971            removeStatusExtensions(outcome);
972          } else if (!diffMatches.get(0).hasSliceName())
973            diffMatches.get(0).setUserData(GENERATED_IN_SNAPSHOT, outcome); // because of updateFromDefinition isn't called 
974          
975          result.getElement().add(outcome);
976
977          if (!diffMatches.get(0).hasSliceName()) { // it's not real content, just the slice
978            diffpos++; 
979          }
980          if (hasInnerDiffMatches(differential, cpath, diffpos, diffLimit, base.getElement(), false)) {
981            int nbl = findEndOfElement(base, baseCursor);
982            int ndc = differential.getElement().indexOf(diffMatches.get(0))+1;
983            int ndl = findEndOfElement(differential, ndc);
984            processPaths(indent+"  ", result, base, differential, baseCursor+1, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, 0), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, false, null, srcSD);
985//            throw new Error("Not done yet");
986//          } else if (currentBase.getType().get(0).getCode().equals("BackboneElement") && diffMatches.size() > 0 && diffMatches.get(0).hasSliceName()) {
987          } else if (currentBase.getType().get(0).getCode().equals("BackboneElement")) {
988            // We need to copy children of the backbone element before we start messing around with slices
989            int nbl = findEndOfElement(base, baseCursor);
990            for (int i = baseCursor+1; i<=nbl; i++) {
991              outcome = updateURLs(url, webUrl, base.getElement().get(i).copy());
992              result.getElement().add(outcome);
993            }
994          }
995
996          // now, we have two lists, base and diff. we're going to work through base, looking for matches in diff.
997          List<ElementDefinition> baseMatches = getSiblings(base.getElement(), currentBase);
998          for (ElementDefinition baseItem : baseMatches) {
999            baseCursor = base.getElement().indexOf(baseItem);
1000            outcome = updateURLs(url, webUrl, baseItem.copy());
1001            updateFromBase(outcome, currentBase);
1002            outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
1003            outcome.setSlicing(null);
1004            if (!outcome.getPath().startsWith(resultPathBase))
1005              throw new DefinitionException("Adding wrong path");
1006            if (diffpos < diffMatches.size() && diffMatches.get(diffpos).getSliceName().equals(outcome.getSliceName())) {
1007              // if there's a diff, we update the outcome with diff
1008              // no? updateFromDefinition(outcome, diffMatches.get(diffpos), profileName, closed, url);
1009              //then process any children
1010              int nbl = findEndOfElement(base, baseCursor);
1011              int ndc = differential.getElement().indexOf(diffMatches.get(diffpos));
1012              int ndl = findEndOfElement(differential, ndc);
1013              // now we process the base scope repeatedly for each instance of the item in the differential list
1014              processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, webUrl, profileName+pathTail(diffMatches, diffpos), contextPathSrc, contextPathDst, closed, contextName, resultPathBase, true, redirector, srcSD);
1015              // ok, done with that - now set the cursors for if this is the end
1016              baseCursor = nbl;
1017              diffCursor = ndl+1;
1018              diffpos++;
1019            } else {
1020              result.getElement().add(outcome);
1021              baseCursor++;
1022              // just copy any children on the base
1023              while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path) && !base.getElement().get(baseCursor).getPath().equals(path)) {
1024                outcome = updateURLs(url, webUrl, base.getElement().get(baseCursor).copy());
1025                outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
1026                if (!outcome.getPath().startsWith(resultPathBase))
1027                  throw new DefinitionException("Adding wrong path");
1028                result.getElement().add(outcome);
1029                baseCursor++;
1030              }
1031              //Lloyd - add this for test T15
1032              baseCursor--;
1033            }
1034          }
1035          // finally, we process any remaining entries in diff, which are new (and which are only allowed if the base wasn't closed
1036          if (closed && diffpos < diffMatches.size())
1037            throw new DefinitionException("The base snapshot marks a slicing as closed, but the differential tries to extend it in "+profileName+" at "+path+" ("+cpath+")");
1038          if (diffpos == diffMatches.size()) {
1039//Lloyd This was causing problems w/ Telus
1040//            diffCursor++;
1041          } else {
1042            while (diffpos < diffMatches.size()) {
1043              ElementDefinition diffItem = diffMatches.get(diffpos);
1044              for (ElementDefinition baseItem : baseMatches)
1045                if (baseItem.getSliceName().equals(diffItem.getSliceName()))
1046                  throw new DefinitionException("Named items are out of order in the slice");
1047              outcome = updateURLs(url, webUrl, currentBase.copy());
1048              //            outcome = updateURLs(url, diffItem.copy());
1049              outcome.setPath(fixedPathDest(contextPathDst, outcome.getPath(), redirector, contextPathSrc));
1050              updateFromBase(outcome, currentBase);
1051              outcome.setSlicing(null);
1052              if (!outcome.getPath().startsWith(resultPathBase))
1053                throw new DefinitionException("Adding wrong path");
1054              result.getElement().add(outcome);
1055              updateFromDefinition(outcome, diffItem, profileName, trimDifferential, url, srcSD);
1056              removeStatusExtensions(outcome);
1057              // --- LM Added this
1058              diffCursor = differential.getElement().indexOf(diffItem)+1;
1059              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
1060                if (!baseWalksInto(base.getElement(), baseCursor)) {
1061                  if (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".")) {
1062                    if (outcome.getType().size() > 1)
1063                      for (TypeRefComponent t : outcome.getType()) {
1064                        if (!t.getCode().equals("Reference"))
1065                          throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName);
1066                      }
1067                    TypeRefComponent t = outcome.getType().get(0);
1068                    if (t.getCode().equals("BackboneElement")) {
1069                      int baseStart = base.getElement().indexOf(currentBase)+1;
1070                      int baseMax = baseStart + 1;
1071                      while (baseMax < base.getElement().size() && base.getElement().get(baseMax).getPath().startsWith(currentBase.getPath()+"."))
1072                       baseMax++;
1073                      int start = diffCursor;
1074                      while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+"."))
1075                        diffCursor++;
1076                      processPaths(indent+"  ", result, base, differential, baseStart, start-1, baseMax-1,
1077                          diffCursor - 1, url, webUrl, profileName+pathTail(diffMatches, 0), base.getElement().get(0).getPath(), base.getElement().get(0).getPath(), trimDifferential, contextName, resultPathBase, false, redirector, srcSD);
1078                      
1079                    } else {
1080                      StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
1081                      //                if (t.getCode().equals("Extension") && t.hasProfile() && !t.getProfile().contains(":")) {
1082                      // lloydfix                  dt = 
1083                      //                }
1084                      if (dt == null)
1085                        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");
1086                      contextName = dt.getUrl();
1087                      int start = diffCursor;
1088                      while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+"."))
1089                        diffCursor++;
1090                      processPaths(indent+"  ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start-1, dt.getSnapshot().getElement().size()-1,
1091                          diffCursor - 1, url, webUrl, profileName+pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false, redirector, srcSD);
1092                    }
1093                  } else if (outcome.getType().get(0).getCode().equals("Extension")) {
1094                    // 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)
1095                    StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
1096                    for (ElementDefinition extEd : dt.getSnapshot().getElement()) {
1097                      // We only want the children that aren't the root
1098                      if (extEd.getPath().contains(".")) {
1099                        ElementDefinition extUrlEd = updateURLs(url, webUrl, extEd.copy());
1100                        extUrlEd.setPath(fixedPathDest(outcome.getPath(), extUrlEd.getPath(), redirector, null));
1101                        //                      updateFromBase(extUrlEd, currentBase);
1102                        markDerived(extUrlEd);
1103                        result.getElement().add(extUrlEd);
1104                      }
1105                    }                  
1106                  }
1107                }
1108              }
1109              // ---
1110              diffpos++;
1111            }
1112          }
1113          baseCursor++;
1114        }
1115      }
1116    }
1117    
1118    int i = 0;
1119    for (ElementDefinition e : result.getElement()) {
1120      i++;
1121      if (e.hasMinElement() && e.getMinElement().getValue()==null)
1122        throw new Error("null min");
1123    }
1124    return res;
1125  }
1126
1127
1128  private void removeStatusExtensions(ElementDefinition outcome) {
1129    outcome.removeExtension(ToolingExtensions.EXT_FMM_LEVEL);
1130    outcome.removeExtension(ToolingExtensions.EXT_STANDARDS_STATUS);
1131    outcome.removeExtension(ToolingExtensions.EXT_NORMATIVE_VERSION);
1132    outcome.removeExtension(ToolingExtensions.EXT_WORKGROUP);    
1133  }
1134
1135
1136  private String descED(List<ElementDefinition> list, int index) {
1137    return index >=0 && index < list.size() ? list.get(index).present() : "X";
1138  }
1139
1140
1141  private boolean baseHasChildren(StructureDefinitionSnapshotComponent base, ElementDefinition ed) {
1142    int index = base.getElement().indexOf(ed);
1143    if (index == -1 || index >= base.getElement().size()-1)
1144      return false;
1145    String p = base.getElement().get(index+1).getPath();
1146    return isChildOf(p, ed.getPath());
1147  }
1148
1149
1150  private boolean isChildOf(String sub, String focus) {
1151    if (focus.endsWith("[x]")) {
1152      focus = focus.substring(0, focus.length()-3);
1153      return sub.startsWith(focus);
1154    } else 
1155      return sub.startsWith(focus+".");
1156  }
1157
1158
1159  private int indexOfFirstNonChild(StructureDefinitionSnapshotComponent base, ElementDefinition currentBase, int i, int baseLimit) {
1160    return baseLimit+1;
1161  }
1162
1163
1164  private String rootName(String cpath) {
1165    String t = tail(cpath);
1166    return t.replace("[x]", "");
1167  }
1168
1169
1170  private String determineTypeSlicePath(String path, String cpath) {
1171    String headP = path.substring(0, path.lastIndexOf("."));
1172//    String tailP = path.substring(path.lastIndexOf(".")+1);
1173    String tailC = cpath.substring(cpath.lastIndexOf(".")+1);
1174    return headP+"."+tailC;
1175  }
1176
1177
1178  private boolean isImplicitSlicing(ElementDefinition ed, String path) {
1179    if (ed == null || ed.getPath() == null || path == null)
1180      return false;
1181    if (path.equals(ed.getPath()))
1182      return false;
1183    boolean ok = path.endsWith("[x]") && ed.getPath().startsWith(path.substring(0, path.length()-3));
1184    return ok;
1185  }
1186
1187
1188  private boolean diffsConstrainTypes(List<ElementDefinition> diffMatches, String cPath, List<TypeSlice> typeList) {
1189//    if (diffMatches.size() < 2)
1190//      return false;
1191    String p = diffMatches.get(0).getPath();
1192    if (!p.endsWith("[x]") && !cPath.endsWith("[x]"))
1193      return false;
1194    typeList.clear();
1195    String rn = tail(cPath);
1196    rn = rn.substring(0, rn.length()-3);
1197    for (int i = 0; i < diffMatches.size(); i++) {
1198      ElementDefinition ed = diffMatches.get(i);
1199      String n = tail(ed.getPath());
1200      if (!n.startsWith(rn))
1201        return false;
1202      String s = n.substring(rn.length());
1203      if (!s.contains(".")) {
1204        if (ed.hasSliceName() && ed.getType().size() == 1) {
1205          typeList.add(new TypeSlice(ed, ed.getTypeFirstRep().getWorkingCode()));
1206        } else if (!ed.hasSliceName() && !s.equals("[x]")) {
1207          if (isDataType(s))
1208            typeList.add(new TypeSlice(ed, s));
1209          else if (isConstrainedDataType(s))
1210            typeList.add(new TypeSlice(ed, baseType(s)));
1211          else if (isPrimitive(Utilities.uncapitalize(s)))
1212            typeList.add(new TypeSlice(ed, Utilities.uncapitalize(s)));
1213        } else if (!ed.hasSliceName() && s.equals("[x]"))
1214          typeList.add(new TypeSlice(ed, null));
1215      }
1216    }
1217    return true;
1218  }
1219
1220
1221  private List<ElementRedirection> redirectorStack(List<ElementRedirection> redirector, ElementDefinition outcome, String path) {
1222    List<ElementRedirection> result = new ArrayList<ElementRedirection>();
1223    result.addAll(redirector);
1224    result.add(new ElementRedirection(outcome, path));
1225    return result;
1226  }
1227
1228
1229  private List<TypeRefComponent> getByTypeName(List<TypeRefComponent> type, String t) {
1230    List<TypeRefComponent> res = new ArrayList<TypeRefComponent>();
1231    for (TypeRefComponent tr : type) {
1232      if (t.equals(tr.getWorkingCode()))
1233          res.add(tr);
1234    }
1235    return res;
1236  }
1237
1238
1239  private void replaceFromContentReference(ElementDefinition outcome, ElementDefinition tgt) {
1240    outcome.setContentReference(null);
1241    outcome.getType().clear(); // though it should be clear anyway
1242    outcome.getType().addAll(tgt.getType());    
1243  }
1244
1245
1246  private boolean baseWalksInto(List<ElementDefinition> elements, int cursor) {
1247    if (cursor >= elements.size())
1248      return false;
1249    String path = elements.get(cursor).getPath();
1250    String prevPath = elements.get(cursor - 1).getPath();
1251    return path.startsWith(prevPath + ".");
1252  }
1253
1254
1255  private ElementDefinition overWriteWithCurrent(ElementDefinition profile, ElementDefinition usage) throws FHIRFormatError {
1256    ElementDefinition res = profile.copy();
1257    if (usage.hasSliceName())
1258      res.setSliceName(usage.getSliceName());
1259    if (usage.hasLabel())
1260      res.setLabel(usage.getLabel());
1261    for (Coding c : usage.getCode())
1262      res.addCode(c);
1263    
1264    if (usage.hasDefinition())
1265      res.setDefinition(usage.getDefinition());
1266    if (usage.hasShort())
1267      res.setShort(usage.getShort());
1268    if (usage.hasComment())
1269      res.setComment(usage.getComment());
1270    if (usage.hasRequirements())
1271      res.setRequirements(usage.getRequirements());
1272    for (StringType c : usage.getAlias())
1273      res.addAlias(c.getValue());
1274    if (usage.hasMin())
1275      res.setMin(usage.getMin());
1276    if (usage.hasMax())
1277      res.setMax(usage.getMax());
1278     
1279    if (usage.hasFixed())
1280      res.setFixed(usage.getFixed());
1281    if (usage.hasPattern())
1282      res.setPattern(usage.getPattern());
1283    if (usage.hasExample())
1284      res.setExample(usage.getExample());
1285    if (usage.hasMinValue())
1286      res.setMinValue(usage.getMinValue());
1287    if (usage.hasMaxValue())
1288      res.setMaxValue(usage.getMaxValue());     
1289    if (usage.hasMaxLength())
1290      res.setMaxLength(usage.getMaxLength());
1291    if (usage.hasMustSupport())
1292      res.setMustSupport(usage.getMustSupport());
1293    if (usage.hasBinding())
1294      res.setBinding(usage.getBinding().copy());
1295    for (ElementDefinitionConstraintComponent c : usage.getConstraint())
1296      res.addConstraint(c);
1297    for (Extension e : usage.getExtension()) {
1298      if (!res.hasExtension(e.getUrl()))
1299        res.addExtension(e.copy());
1300    }
1301    
1302    return res;
1303  }
1304
1305
1306  private boolean checkExtensionDoco(ElementDefinition base) {
1307    // see task 3970. For an extension, there's no point copying across all the underlying definitional stuff
1308    boolean isExtension = base.getPath().equals("Extension") || base.getPath().endsWith(".extension") || base.getPath().endsWith(".modifierExtension");
1309    if (isExtension) {
1310      base.setDefinition("An Extension");
1311      base.setShort("Extension");
1312      base.setCommentElement(null);
1313      base.setRequirementsElement(null);
1314      base.getAlias().clear();
1315      base.getMapping().clear();
1316    }
1317    return isExtension;
1318  }
1319
1320
1321  private String pathTail(List<ElementDefinition> diffMatches, int i) {
1322    
1323    ElementDefinition d = diffMatches.get(i);
1324    String s = d.getPath().contains(".") ? d.getPath().substring(d.getPath().lastIndexOf(".")+1) : d.getPath();
1325    return "."+s + (d.hasType() && d.getType().get(0).hasProfile() ? "["+d.getType().get(0).getProfile()+"]" : "");
1326  }
1327
1328
1329  private void markDerived(ElementDefinition outcome) {
1330    for (ElementDefinitionConstraintComponent inv : outcome.getConstraint())
1331      inv.setUserData(IS_DERIVED, true);
1332  }
1333
1334
1335  private String summarizeSlicing(ElementDefinitionSlicingComponent slice) {
1336    StringBuilder b = new StringBuilder();
1337    boolean first = true;
1338    for (ElementDefinitionSlicingDiscriminatorComponent d : slice.getDiscriminator()) {
1339      if (first)
1340        first = false;
1341      else
1342        b.append(", ");
1343      b.append(d);
1344    }
1345    b.append("(");
1346    if (slice.hasOrdered())
1347      b.append(slice.getOrderedElement().asStringValue());
1348    b.append("/");
1349    if (slice.hasRules())
1350      b.append(slice.getRules().toCode());
1351    b.append(")");
1352    if (slice.hasDescription()) {
1353      b.append(" \"");
1354      b.append(slice.getDescription());
1355      b.append("\"");
1356    }
1357    return b.toString();
1358  }
1359
1360
1361  private void updateFromBase(ElementDefinition derived, ElementDefinition base) {
1362    if (base.hasBase()) {
1363      if (!derived.hasBase())
1364        derived.setBase(new ElementDefinitionBaseComponent());
1365      derived.getBase().setPath(base.getBase().getPath());
1366      derived.getBase().setMin(base.getBase().getMin());
1367      derived.getBase().setMax(base.getBase().getMax());
1368    } else {
1369      if (!derived.hasBase())
1370        derived.setBase(new ElementDefinitionBaseComponent());
1371      derived.getBase().setPath(base.getPath());
1372      derived.getBase().setMin(base.getMin());
1373      derived.getBase().setMax(base.getMax());
1374    }
1375  }
1376
1377
1378  private boolean pathStartsWith(String p1, String p2) {
1379    return p1.startsWith(p2);
1380  }
1381
1382  private boolean pathMatches(String p1, String p2) {
1383    return p1.equals(p2) || (p2.endsWith("[x]") && p1.startsWith(p2.substring(0, p2.length()-3)) && !p1.substring(p2.length()-3).contains("."));
1384  }
1385
1386
1387  private String fixedPathSource(String contextPath, String pathSimple, List<ElementRedirection> redirector) {
1388    if (contextPath == null)
1389      return pathSimple;
1390//    String ptail = pathSimple.substring(contextPath.length() + 1);
1391    if (redirector.size() > 0) {
1392      String ptail = pathSimple.substring(contextPath.length()+1);
1393      return redirector.get(redirector.size()-1).getPath()+"."+ptail;
1394//      return contextPath+"."+tail(redirector.getPath())+"."+ptail.substring(ptail.indexOf(".")+1);
1395    } else {
1396      String ptail = pathSimple.substring(pathSimple.indexOf(".")+1);
1397      return contextPath+"."+ptail;
1398    }
1399  }
1400  
1401  private String fixedPathDest(String contextPath, String pathSimple, List<ElementRedirection> redirector, String redirectSource) {
1402    String s;
1403    if (contextPath == null)
1404      s = pathSimple;
1405    else {
1406      if (redirector.size() > 0) {
1407        String ptail = pathSimple.substring(redirectSource.length() + 1);
1408  //      ptail = ptail.substring(ptail.indexOf(".")+1);
1409        s = contextPath+"."+/*tail(redirector.getPath())+"."+*/ptail;
1410      } else {
1411        String ptail = pathSimple.substring(pathSimple.indexOf(".")+1);
1412        s = contextPath+"."+ptail;
1413      }
1414    }
1415    return s;
1416  }  
1417
1418  private StructureDefinition getProfileForDataType(TypeRefComponent type)  {
1419    StructureDefinition sd = null;
1420    if (type.hasProfile()) {
1421      sd = context.fetchResource(StructureDefinition.class, type.getProfile().get(0).getValue());
1422      if (sd == null)
1423        System.out.println("Failed to find referenced profile: " + type.getProfile());
1424    }
1425    if (sd == null)
1426      sd = context.fetchTypeDefinition(type.getWorkingCode());
1427    if (sd == null)
1428      System.out.println("XX: failed to find profle for type: " + type.getWorkingCode()); // debug GJM
1429    return sd;
1430  }
1431
1432  private StructureDefinition getProfileForDataType(String type)  {
1433    StructureDefinition sd = context.fetchTypeDefinition(type);
1434    if (sd == null)
1435      System.out.println("XX: failed to find profle for type: " + type); // debug GJM
1436    return sd;
1437  }
1438
1439
1440  public static String typeCode(List<TypeRefComponent> types) {
1441    StringBuilder b = new StringBuilder();
1442    boolean first = true;
1443    for (TypeRefComponent type : types) {
1444      if (first) first = false; else b.append(", ");
1445      b.append(type.getWorkingCode());
1446      if (type.hasTargetProfile())
1447        b.append("{"+type.getTargetProfile()+"}");
1448      else if (type.hasProfile())
1449        b.append("{"+type.getProfile()+"}");
1450    }
1451    return b.toString();
1452  }
1453
1454
1455  private boolean isDataType(List<TypeRefComponent> types) {
1456    if (types.isEmpty())
1457      return false;
1458    for (TypeRefComponent type : types) {
1459      String t = type.getWorkingCode();
1460      if (!isDataType(t) && !isPrimitive(t))
1461        return false;
1462    }
1463    return true;
1464  }
1465
1466
1467  /**
1468   * Finds internal references in an Element's Binding and StructureDefinition references (in TypeRef) and bases them on the given url
1469   * @param url - the base url to use to turn internal references into absolute references
1470   * @param element - the Element to update
1471   * @return - the updated Element
1472   */
1473  private ElementDefinition updateURLs(String url, String webUrl, ElementDefinition element) {
1474    if (element != null) {
1475      ElementDefinition defn = element;
1476      if (defn.hasBinding() && defn.getBinding().hasValueSet() && defn.getBinding().getValueSet().startsWith("#"))
1477        defn.getBinding().setValueSet(url+defn.getBinding().getValueSet());
1478      for (TypeRefComponent t : defn.getType()) {
1479        for (UriType u : t.getProfile()) {
1480          if (u.getValue().startsWith("#"))
1481            u.setValue(url+t.getProfile());
1482        }
1483        for (UriType u : t.getTargetProfile()) {
1484          if (u.getValue().startsWith("#"))
1485            u.setValue(url+t.getTargetProfile());
1486        }
1487      }
1488      if (webUrl != null) {
1489        // also, must touch up the markdown
1490        if (element.hasDefinition())
1491          element.setDefinition(processRelativeUrls(element.getDefinition(), webUrl));
1492        if (element.hasComment())
1493          element.setComment(processRelativeUrls(element.getComment(), webUrl));
1494        if (element.hasRequirements())
1495          element.setRequirements(processRelativeUrls(element.getRequirements(), webUrl));
1496        if (element.hasMeaningWhenMissing())
1497          element.setMeaningWhenMissing(processRelativeUrls(element.getMeaningWhenMissing(), webUrl));
1498      }
1499    }
1500    return element;
1501  }
1502
1503  private String processRelativeUrls(String markdown, String webUrl) {
1504    StringBuilder b = new StringBuilder();
1505    int i = 0;
1506    while (i < markdown.length()) {
1507      if (i < markdown.length()-3 && markdown.substring(i, i+2).equals("](")) {
1508         int j = i + 2;
1509        while (j < markdown.length() && markdown.charAt(j) != ')')
1510          j++;
1511        if (j < markdown.length()) {
1512          String url = markdown.substring(i+2, j);
1513          if (!Utilities.isAbsoluteUrl(url)) {
1514            b.append("](");
1515            b.append(webUrl);
1516            i = i + 1;
1517          } else
1518            b.append(markdown.charAt(i));
1519        } else 
1520          b.append(markdown.charAt(i));
1521      } else {
1522        b.append(markdown.charAt(i));
1523      }
1524      i++;
1525    }
1526    return b.toString();
1527  }
1528
1529
1530  private List<ElementDefinition> getSiblings(List<ElementDefinition> list, ElementDefinition current) {
1531    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
1532    String path = current.getPath();
1533    int cursor = list.indexOf(current)+1;
1534    while (cursor < list.size() && list.get(cursor).getPath().length() >= path.length()) {
1535      if (pathMatches(list.get(cursor).getPath(), path))
1536        result.add(list.get(cursor));
1537      cursor++;
1538    }
1539    return result;
1540  }
1541
1542  private void updateFromSlicing(ElementDefinitionSlicingComponent dst, ElementDefinitionSlicingComponent src) {
1543    if (src.hasOrderedElement())
1544      dst.setOrderedElement(src.getOrderedElement().copy());
1545    if (src.hasDiscriminator()) {
1546      //    dst.getDiscriminator().addAll(src.getDiscriminator());  Can't use addAll because it uses object equality, not string equality
1547      for (ElementDefinitionSlicingDiscriminatorComponent s : src.getDiscriminator()) {
1548        boolean found = false;
1549        for (ElementDefinitionSlicingDiscriminatorComponent d : dst.getDiscriminator()) {
1550          if (matches(d, s)) {
1551            found = true;
1552            break;
1553          }
1554        }
1555        if (!found)
1556          dst.getDiscriminator().add(s);
1557      }
1558    }
1559    if (src.hasRulesElement())
1560      dst.setRulesElement(src.getRulesElement().copy());
1561  }
1562
1563  private boolean orderMatches(BooleanType diff, BooleanType base) {
1564    return (diff == null) || (base == null) || (diff.getValue() == base.getValue());
1565  }
1566
1567  private boolean discriminatorMatches(List<ElementDefinitionSlicingDiscriminatorComponent> diff, List<ElementDefinitionSlicingDiscriminatorComponent> base) {
1568    if (diff.isEmpty() || base.isEmpty())
1569        return true;
1570    if (diff.size() != base.size())
1571        return false;
1572    for (int i = 0; i < diff.size(); i++)
1573        if (!matches(diff.get(i), base.get(i)))
1574                return false;
1575    return true;
1576  }
1577
1578  private boolean matches(ElementDefinitionSlicingDiscriminatorComponent c1, ElementDefinitionSlicingDiscriminatorComponent c2) {
1579    return c1.getType().equals(c2.getType()) && c1.getPath().equals(c2.getPath());
1580  }
1581
1582
1583  private boolean ruleMatches(SlicingRules diff, SlicingRules base) {
1584    return (diff == null) || (base == null) || (diff == base) || (base == SlicingRules.OPEN) ||
1585        ((diff == SlicingRules.OPENATEND && base == SlicingRules.CLOSED));
1586  }
1587
1588  private boolean isSlicedToOneOnly(ElementDefinition e) {
1589    return (e.hasSlicing() && e.hasMaxElement() && e.getMax().equals("1"));
1590  }
1591
1592  private ElementDefinitionSlicingComponent makeExtensionSlicing() {
1593        ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent();
1594    slice.addDiscriminator().setPath("url").setType(DiscriminatorType.VALUE);
1595    slice.setOrdered(false);
1596    slice.setRules(SlicingRules.OPEN);
1597    return slice;
1598  }
1599
1600  private boolean isExtension(ElementDefinition currentBase) {
1601    return currentBase.getPath().endsWith(".extension") || currentBase.getPath().endsWith(".modifierExtension");
1602  }
1603
1604  private boolean hasInnerDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, List<ElementDefinition> base, boolean allowSlices) throws DefinitionException {
1605    end = Math.min(context.getElement().size(), end);
1606    start = Math.max(0,  start);
1607    
1608    for (int i = start; i <= end; i++) {
1609      String statedPath = context.getElement().get(i).getPath();
1610      if (statedPath.startsWith(path+".")) {
1611          return true;
1612      } else if (!statedPath.endsWith(path))
1613        break;
1614    }
1615    return false;
1616  }
1617
1618  private List<ElementDefinition> getDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, String profileName) throws DefinitionException {
1619    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
1620    for (int i = start; i <= end; i++) {
1621      String statedPath = context.getElement().get(i).getPath();
1622      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(".")))) {
1623        /* 
1624         * Commenting this out because it raises warnings when profiling inherited elements.  For example,
1625         * Error: unknown element 'Bundle.meta.profile' (or it is out of order) in profile ... (looking for 'Bundle.entry')
1626         * Not sure we have enough information here to do the check properly.  Might be better done when we're sorting the profile?
1627
1628        if (i != start && result.isEmpty() && !path.startsWith(context.getElement().get(start).getPath()))
1629          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));
1630
1631         */
1632        result.add(context.getElement().get(i));
1633      }
1634    }
1635    return result;
1636  }
1637
1638  private int findEndOfElement(StructureDefinitionDifferentialComponent context, int cursor) {
1639            int result = cursor;
1640            String path = context.getElement().get(cursor).getPath()+".";
1641            while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path))
1642              result++;
1643            return result;
1644          }
1645
1646  private int findEndOfElement(StructureDefinitionSnapshotComponent context, int cursor) {
1647            int result = cursor;
1648            String path = context.getElement().get(cursor).getPath()+".";
1649            while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path))
1650              result++;
1651            return result;
1652          }
1653
1654  private boolean unbounded(ElementDefinition definition) {
1655    StringType max = definition.getMaxElement();
1656    if (max == null)
1657      return false; // this is not valid
1658    if (max.getValue().equals("1"))
1659      return false;
1660    if (max.getValue().equals("0"))
1661      return false;
1662    return true;
1663  }
1664
1665  private void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, boolean trimDifferential, String purl, StructureDefinition srcSD) throws DefinitionException, FHIRException {
1666    source.setUserData(GENERATED_IN_SNAPSHOT, dest);
1667    // we start with a clone of the base profile ('dest') and we copy from the profile ('source')
1668    // over the top for anything the source has
1669    ElementDefinition base = dest;
1670    ElementDefinition derived = source;
1671    derived.setUserData(DERIVATION_POINTER, base);
1672    boolean isExtension = checkExtensionDoco(base);
1673
1674
1675    // Before applying changes, apply them to what's in the profile
1676    // TODO: follow Chris's rules - Done by Lloyd
1677    StructureDefinition profile = null;
1678    if (base.hasSliceName())
1679      profile = base.getType().size() == 1 && base.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, base.getTypeFirstRep().getProfile().get(0).getValue()) : null;
1680    if (profile==null)
1681      profile = source.getType().size() == 1 && source.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, source.getTypeFirstRep().getProfile().get(0).getValue()) : null;
1682    if (profile != null) {
1683      ElementDefinition e = profile.getSnapshot().getElement().get(0);
1684      base.setDefinition(e.getDefinition());
1685      base.setShort(e.getShort());
1686      if (e.hasCommentElement())
1687        base.setCommentElement(e.getCommentElement());
1688      if (e.hasRequirementsElement())
1689        base.setRequirementsElement(e.getRequirementsElement());
1690      base.getAlias().clear();
1691      base.getAlias().addAll(e.getAlias());
1692      base.getMapping().clear();
1693      base.getMapping().addAll(e.getMapping());
1694    } 
1695    if (derived != null) {
1696      if (derived.hasSliceName()) {
1697        base.setSliceName(derived.getSliceName());
1698      }
1699      
1700      if (derived.hasShortElement()) {
1701        if (!Base.compareDeep(derived.getShortElement(), base.getShortElement(), false))
1702          base.setShortElement(derived.getShortElement().copy());
1703        else if (trimDifferential)
1704          derived.setShortElement(null);
1705        else if (derived.hasShortElement())
1706          derived.getShortElement().setUserData(DERIVATION_EQUALS, true);
1707      }
1708
1709      if (derived.hasDefinitionElement()) {
1710        if (derived.getDefinition().startsWith("..."))
1711          base.setDefinition(base.getDefinition()+"\r\n"+derived.getDefinition().substring(3));
1712        else if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false))
1713          base.setDefinitionElement(derived.getDefinitionElement().copy());
1714        else if (trimDifferential)
1715          derived.setDefinitionElement(null);
1716        else if (derived.hasDefinitionElement())
1717          derived.getDefinitionElement().setUserData(DERIVATION_EQUALS, true);
1718      }
1719
1720      if (derived.hasCommentElement()) {
1721        if (derived.getComment().startsWith("..."))
1722          base.setComment(base.getComment()+"\r\n"+derived.getComment().substring(3));
1723        else if (derived.hasCommentElement()!= base.hasCommentElement() || !Base.compareDeep(derived.getCommentElement(), base.getCommentElement(), false))
1724          base.setCommentElement(derived.getCommentElement().copy());
1725        else if (trimDifferential)
1726          base.setCommentElement(derived.getCommentElement().copy());
1727        else if (derived.hasCommentElement())
1728          derived.getCommentElement().setUserData(DERIVATION_EQUALS, true);
1729      }
1730
1731      if (derived.hasLabelElement()) {
1732        if (derived.getLabel().startsWith("..."))
1733          base.setLabel(base.getLabel()+"\r\n"+derived.getLabel().substring(3));
1734        else if (!base.hasLabelElement() || !Base.compareDeep(derived.getLabelElement(), base.getLabelElement(), false))
1735          base.setLabelElement(derived.getLabelElement().copy());
1736        else if (trimDifferential)
1737          base.setLabelElement(derived.getLabelElement().copy());
1738        else if (derived.hasLabelElement())
1739          derived.getLabelElement().setUserData(DERIVATION_EQUALS, true);
1740      }
1741
1742      if (derived.hasRequirementsElement()) {
1743        if (derived.getRequirements().startsWith("..."))
1744          base.setRequirements(base.getRequirements()+"\r\n"+derived.getRequirements().substring(3));
1745        else if (!base.hasRequirementsElement() || !Base.compareDeep(derived.getRequirementsElement(), base.getRequirementsElement(), false))
1746          base.setRequirementsElement(derived.getRequirementsElement().copy());
1747        else if (trimDifferential)
1748          base.setRequirementsElement(derived.getRequirementsElement().copy());
1749        else if (derived.hasRequirementsElement())
1750          derived.getRequirementsElement().setUserData(DERIVATION_EQUALS, true);
1751      }
1752      // sdf-9
1753      if (derived.hasRequirements() && !base.getPath().contains("."))
1754        derived.setRequirements(null);
1755      if (base.hasRequirements() && !base.getPath().contains("."))
1756        base.setRequirements(null);
1757
1758      if (derived.hasAlias()) {
1759        if (!Base.compareDeep(derived.getAlias(), base.getAlias(), false))
1760          for (StringType s : derived.getAlias()) {
1761            if (!base.hasAlias(s.getValue()))
1762              base.getAlias().add(s.copy());
1763          }
1764        else if (trimDifferential)
1765          derived.getAlias().clear();
1766        else
1767          for (StringType t : derived.getAlias())
1768            t.setUserData(DERIVATION_EQUALS, true);
1769      }
1770
1771      if (derived.hasMinElement()) {
1772        if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) {
1773          if (derived.getMin() < base.getMin() && !derived.hasSliceName()) // in a slice, minimum cardinality rules do not apply 
1774            messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived min ("+Integer.toString(derived.getMin())+") cannot be less than base min ("+Integer.toString(base.getMin())+")", ValidationMessage.IssueSeverity.ERROR));
1775          base.setMinElement(derived.getMinElement().copy());
1776        } else if (trimDifferential)
1777          derived.setMinElement(null);
1778        else
1779          derived.getMinElement().setUserData(DERIVATION_EQUALS, true);
1780      }
1781
1782      if (derived.hasMaxElement()) {
1783        if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) {
1784          if (isLargerMax(derived.getMax(), base.getMax()))
1785            messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Element "+base.getPath()+": derived max ("+derived.getMax()+") cannot be greater than base max ("+base.getMax()+")", ValidationMessage.IssueSeverity.ERROR));
1786          base.setMaxElement(derived.getMaxElement().copy());
1787        } else if (trimDifferential)
1788          derived.setMaxElement(null);
1789        else
1790          derived.getMaxElement().setUserData(DERIVATION_EQUALS, true);
1791      }
1792
1793      if (derived.hasFixed()) {
1794        if (!Base.compareDeep(derived.getFixed(), base.getFixed(), true)) {
1795          base.setFixed(derived.getFixed().copy());
1796        } else if (trimDifferential)
1797          derived.setFixed(null);
1798        else
1799          derived.getFixed().setUserData(DERIVATION_EQUALS, true);
1800      }
1801
1802      if (derived.hasPattern()) {
1803        if (!Base.compareDeep(derived.getPattern(), base.getPattern(), false)) {
1804          base.setPattern(derived.getPattern().copy());
1805        } else
1806          if (trimDifferential)
1807            derived.setPattern(null);
1808          else
1809            derived.getPattern().setUserData(DERIVATION_EQUALS, true);
1810      }
1811
1812      for (ElementDefinitionExampleComponent ex : derived.getExample()) {
1813        boolean found = false;
1814        for (ElementDefinitionExampleComponent exS : base.getExample())
1815          if (Base.compareDeep(ex, exS, false))
1816            found = true;
1817        if (!found)
1818          base.addExample(ex.copy());
1819        else if (trimDifferential)
1820          derived.getExample().remove(ex);
1821        else
1822          ex.setUserData(DERIVATION_EQUALS, true);
1823      }
1824
1825      if (derived.hasMaxLengthElement()) {
1826        if (!Base.compareDeep(derived.getMaxLengthElement(), base.getMaxLengthElement(), false))
1827          base.setMaxLengthElement(derived.getMaxLengthElement().copy());
1828        else if (trimDifferential)
1829          derived.setMaxLengthElement(null);
1830        else
1831          derived.getMaxLengthElement().setUserData(DERIVATION_EQUALS, true);
1832      }
1833
1834      // todo: what to do about conditions?
1835      // condition : id 0..*
1836
1837      if (derived.hasMustSupportElement()) {
1838        if (!(base.hasMustSupportElement() && Base.compareDeep(derived.getMustSupportElement(), base.getMustSupportElement(), false)))
1839          base.setMustSupportElement(derived.getMustSupportElement().copy());
1840        else if (trimDifferential)
1841          derived.setMustSupportElement(null);
1842        else
1843          derived.getMustSupportElement().setUserData(DERIVATION_EQUALS, true);
1844      }
1845
1846
1847      // profiles cannot change : isModifier, defaultValue, meaningWhenMissing
1848      // but extensions can change isModifier
1849      if (isExtension) {
1850        if (derived.hasIsModifierElement() && !(base.hasIsModifierElement() && Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false)))
1851          base.setIsModifierElement(derived.getIsModifierElement().copy());
1852        else if (trimDifferential)
1853          derived.setIsModifierElement(null);
1854        else if (derived.hasIsModifierElement())
1855          derived.getIsModifierElement().setUserData(DERIVATION_EQUALS, true);
1856        if (derived.hasIsModifierReasonElement() && !(base.hasIsModifierReasonElement() && Base.compareDeep(derived.getIsModifierReasonElement(), base.getIsModifierReasonElement(), false)))
1857          base.setIsModifierReasonElement(derived.getIsModifierReasonElement().copy());
1858        else if (trimDifferential)
1859          derived.setIsModifierReasonElement(null);
1860        else if (derived.hasIsModifierReasonElement())
1861          derived.getIsModifierReasonElement().setUserData(DERIVATION_EQUALS, true);
1862      }
1863
1864      if (derived.hasBinding()) {
1865        if (!base.hasBinding() || !Base.compareDeep(derived.getBinding(), base.getBinding(), false)) {
1866          if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && derived.getBinding().getStrength() != BindingStrength.REQUIRED)
1867            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));
1868//            throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode());
1869          else if (base.hasBinding() && derived.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && base.getBinding().hasValueSet() && derived.getBinding().hasValueSet()) {
1870            ValueSet baseVs = context.fetchResource(ValueSet.class, base.getBinding().getValueSet());
1871            ValueSet contextVs = context.fetchResource(ValueSet.class, derived.getBinding().getValueSet());
1872            if (baseVs == null) {
1873              messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING));
1874            } else if (contextVs == null) {
1875              messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING));
1876            } else {
1877              ValueSetExpansionOutcome expBase = context.expandVS(baseVs, true, false);
1878              ValueSetExpansionOutcome expDerived = context.expandVS(contextVs, true, false);
1879              if (expBase.getValueset() == null)
1880                messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING));
1881              else if (expDerived.getValueset() == null)
1882                messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING));
1883              else if (!isSubset(expBase.getValueset(), expDerived.getValueset()))
1884                messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" is not a subset of binding "+base.getBinding().getValueSet(), ValidationMessage.IssueSeverity.ERROR));
1885
1886            }
1887          }
1888          base.setBinding(derived.getBinding().copy());
1889        } else if (trimDifferential)
1890          derived.setBinding(null);
1891        else
1892          derived.getBinding().setUserData(DERIVATION_EQUALS, true);
1893      } // else if (base.hasBinding() && doesn't have bindable type )
1894        //  base
1895
1896      if (derived.hasIsSummaryElement()) {
1897        if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false)) {
1898          if (base.hasIsSummary())
1899            throw new Error("Error in profile "+pn+" at "+derived.getPath()+": Base isSummary = "+base.getIsSummaryElement().asStringValue()+", derived isSummary = "+derived.getIsSummaryElement().asStringValue());
1900          base.setIsSummaryElement(derived.getIsSummaryElement().copy());
1901        } else if (trimDifferential)
1902          derived.setIsSummaryElement(null);
1903        else
1904          derived.getIsSummaryElement().setUserData(DERIVATION_EQUALS, true);
1905      }
1906
1907      if (derived.hasType()) {
1908        if (!Base.compareDeep(derived.getType(), base.getType(), false)) {
1909          if (base.hasType()) {
1910            for (TypeRefComponent ts : derived.getType()) {
1911//              if (!ts.hasCode()) { // ommitted in the differential; copy it over....
1912//                if (base.getType().size() > 1) 
1913//                  throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": constrained type code must be present if there are multiple types ("+base.typeSummary()+")");
1914//                if (base.getType().get(0).getCode() != null)
1915//                  ts.setCode(base.getType().get(0).getCode());
1916//              }
1917              boolean ok = false;
1918              CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1919              String t = ts.getWorkingCode();
1920              for (TypeRefComponent td : base.getType()) {;
1921                String tt = td.getWorkingCode();
1922                b.append(tt);
1923                if (td.hasCode() && (tt.equals(t) || "Extension".equals(tt) || (t.equals("uri") && tt.equals("string"))  || // work around for old badly generated SDs
1924                    "Element".equals(tt) || "*".equals(tt) ||
1925                    (("Resource".equals(tt) || ("DomainResource".equals(tt)) && pkp.isResource(t)))))
1926                  ok = true;
1927              }
1928              if (!ok)
1929                throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal constrained type "+t+" from "+b.toString()+" in "+srcSD.getUrl());
1930            }
1931          }
1932          base.getType().clear();
1933          for (TypeRefComponent t : derived.getType()) {
1934            TypeRefComponent tt = t.copy();
1935//            tt.setUserData(DERIVATION_EQUALS, true);
1936            base.getType().add(tt);
1937          }
1938        }
1939        else if (trimDifferential)
1940          derived.getType().clear();
1941        else
1942          for (TypeRefComponent t : derived.getType())
1943            t.setUserData(DERIVATION_EQUALS, true);
1944      }
1945
1946      if (derived.hasMapping()) {
1947        // todo: mappings are not cumulative - one replaces another
1948        if (!Base.compareDeep(derived.getMapping(), base.getMapping(), false)) {
1949          for (ElementDefinitionMappingComponent s : derived.getMapping()) {
1950            boolean found = false;
1951            for (ElementDefinitionMappingComponent d : base.getMapping()) {
1952              found = found || (d.getIdentity().equals(s.getIdentity()) && d.getMap().equals(s.getMap()));
1953            }
1954            if (!found)
1955              base.getMapping().add(s);
1956          }
1957        }
1958        else if (trimDifferential)
1959          derived.getMapping().clear();
1960        else
1961          for (ElementDefinitionMappingComponent t : derived.getMapping())
1962            t.setUserData(DERIVATION_EQUALS, true);
1963      }
1964
1965      // todo: constraints are cumulative. there is no replacing
1966      for (ElementDefinitionConstraintComponent s : base.getConstraint()) { 
1967        s.setUserData(IS_DERIVED, true);
1968        if (!s.hasSource())
1969          s.setSource(base.getId());
1970      }
1971      if (derived.hasConstraint()) {
1972        for (ElementDefinitionConstraintComponent s : derived.getConstraint()) {
1973          ElementDefinitionConstraintComponent inv = s.copy();
1974          base.getConstraint().add(inv);
1975        }
1976      }
1977      
1978      // now, check that we still have a bindable type; if not, delete the binding - see task 8477
1979      if (dest.hasBinding() && !hasBindableType(dest))
1980        dest.setBinding(null);
1981        
1982      // finally, we copy any extensions from source to dest
1983      for (Extension ex : derived.getExtension()) {
1984        StructureDefinition sd  = context.fetchResource(StructureDefinition.class, ex.getUrl());
1985        if (sd == null || sd.getSnapshot() == null || sd.getSnapshot().getElementFirstRep().getMax().equals("1"))
1986          ToolingExtensions.removeExtension(dest, ex.getUrl());
1987        dest.addExtension(ex.copy());
1988      }
1989    }
1990  }
1991
1992  private boolean hasBindableType(ElementDefinition ed) {
1993    for (TypeRefComponent tr : ed.getType()) {
1994      if (Utilities.existsInList(tr.getWorkingCode(), "Coding", "CodeableConcept", "Quantity", "uri", "string", "code"))
1995        return true;
1996    }
1997    return false;
1998  }
1999
2000
2001  private boolean isLargerMax(String derived, String base) {
2002    if ("*".equals(base))
2003      return false;
2004    if ("*".equals(derived))
2005      return true;
2006    return Integer.parseInt(derived) > Integer.parseInt(base);
2007  }
2008
2009
2010  private boolean isSubset(ValueSet expBase, ValueSet expDerived) {
2011    return codesInExpansion(expDerived.getExpansion().getContains(), expBase.getExpansion());
2012  }
2013
2014
2015  private boolean codesInExpansion(List<ValueSetExpansionContainsComponent> contains, ValueSetExpansionComponent expansion) {
2016    for (ValueSetExpansionContainsComponent cc : contains) {
2017      if (!inExpansion(cc, expansion.getContains()))
2018        return false;
2019      if (!codesInExpansion(cc.getContains(), expansion))
2020        return false;
2021    }
2022    return true;
2023  }
2024
2025
2026  private boolean inExpansion(ValueSetExpansionContainsComponent cc, List<ValueSetExpansionContainsComponent> contains) {
2027    for (ValueSetExpansionContainsComponent cc1 : contains) {
2028      if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode()))
2029        return true;
2030      if (inExpansion(cc,  cc1.getContains()))
2031        return true;
2032    }
2033    return false;
2034  }
2035
2036  public void closeDifferential(StructureDefinition base, StructureDefinition derived) throws FHIRException {
2037    for (ElementDefinition edb : base.getSnapshot().getElement()) {
2038      if (isImmediateChild(edb) && !edb.getPath().endsWith(".id")) {
2039        ElementDefinition edm = getMatchInDerived(edb, derived.getDifferential().getElement());
2040        if (edm == null) {
2041          ElementDefinition edd = derived.getDifferential().addElement();
2042          edd.setPath(edb.getPath());
2043          edd.setMax("0");
2044        } else if (edb.hasSlicing()) {
2045          closeChildren(base, edb, derived, edm);
2046        }
2047      }
2048    }
2049    sortDifferential(base, derived, derived.getName(), new ArrayList<String>());
2050  }
2051
2052  private void closeChildren(StructureDefinition base, ElementDefinition edb, StructureDefinition derived, ElementDefinition edm) {
2053    String path = edb.getPath()+".";
2054    int baseStart = base.getSnapshot().getElement().indexOf(edb);
2055    int baseEnd = findEnd(base.getSnapshot().getElement(), edb, baseStart+1);
2056    int diffStart = derived.getDifferential().getElement().indexOf(edm);
2057    int diffEnd = findEnd(derived.getDifferential().getElement(), edm, diffStart+1);
2058    
2059    for (int cBase = baseStart; cBase < baseEnd; cBase++) {
2060      ElementDefinition edBase = base.getSnapshot().getElement().get(cBase);
2061      if (isImmediateChild(edBase, edb)) {
2062        ElementDefinition edMatch = getMatchInDerived(edBase, derived.getDifferential().getElement(), diffStart, diffEnd);
2063        if (edMatch == null) {
2064          ElementDefinition edd = derived.getDifferential().addElement();
2065          edd.setPath(edBase.getPath());
2066          edd.setMax("0");
2067        } else {
2068          closeChildren(base, edBase, derived, edMatch);
2069        }        
2070      }
2071    }
2072  }
2073
2074
2075
2076
2077  private int findEnd(List<ElementDefinition> list, ElementDefinition ed, int cursor) {
2078    String path = ed.getPath()+".";
2079    while (cursor < list.size() && list.get(cursor).getPath().startsWith(path))
2080      cursor++;
2081    return cursor;
2082  }
2083
2084
2085  private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list) {
2086    for (ElementDefinition t : list)
2087      if (t.getPath().equals(ed.getPath()))
2088        return t;
2089    return null;
2090  }
2091
2092  private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list, int start, int end) {
2093    for (int i = start; i < end; i++) {
2094      ElementDefinition t = list.get(i);
2095      if (t.getPath().equals(ed.getPath()))
2096        return t;
2097    }
2098    return null;
2099  }
2100
2101
2102  private boolean isImmediateChild(ElementDefinition ed) {
2103    String p = ed.getPath();
2104    if (!p.contains("."))
2105      return false;
2106    p = p.substring(p.indexOf(".")+1);
2107    return !p.contains(".");
2108  }
2109
2110  private boolean isImmediateChild(ElementDefinition candidate, ElementDefinition base) {
2111    String p = candidate.getPath();
2112    if (!p.contains("."))
2113      return false;
2114    if (!p.startsWith(base.getPath()+"."))
2115      return false;
2116    p = p.substring(base.getPath().length()+1);
2117    return !p.contains(".");
2118  }
2119
2120  public XhtmlNode generateExtensionTable(String defFile, StructureDefinition ed, String imageFolder, boolean inlineGraphics, boolean full, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException {
2121    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true);
2122    gen.setTranslator(getTranslator());
2123    TableModel model = gen.initNormalTable(corePath, false, true, ed.getId(), false);
2124
2125    boolean deep = false;
2126    String m = "";
2127    boolean vdeep = false;
2128    if (ed.getSnapshot().getElementFirstRep().getIsModifier())
2129      m = "modifier_";
2130    for (ElementDefinition eld : ed.getSnapshot().getElement()) {
2131      deep = deep || eld.getPath().contains("Extension.extension.");
2132      vdeep = vdeep || eld.getPath().contains("Extension.extension.extension.");
2133    }
2134    Row r = gen.new Row();
2135    model.getRows().add(r);
2136    String en;
2137    if (!full)
2138      en = ed.getName();
2139    else if (ed.getSnapshot().getElement().get(0).getIsModifier())
2140      en = "modifierExtension";
2141    else 
2142      en = "extension";
2143    
2144    r.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), en, null, null));
2145    r.getCells().add(gen.new Cell());
2146    r.getCells().add(gen.new Cell(null, null, describeCardinality(ed.getSnapshot().getElement().get(0), null, new UnusedTracker()), null, null));
2147
2148    ElementDefinition ved = null;
2149    if (full || vdeep) {
2150      r.getCells().add(gen.new Cell("", "", "Extension", null, null));
2151
2152      r.setIcon(deep ? "icon_"+m+"extension_complex.png" : "icon_extension_simple.png", deep ? HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX : HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
2153      List<ElementDefinition> children = getChildren(ed.getSnapshot().getElement(), ed.getSnapshot().getElement().get(0));
2154      for (ElementDefinition child : children)
2155        if (!child.getPath().endsWith(".id"))
2156          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, null);
2157    } else if (deep) {
2158      List<ElementDefinition> children = new ArrayList<ElementDefinition>();
2159      for (ElementDefinition ted : ed.getSnapshot().getElement()) {
2160        if (ted.getPath().equals("Extension.extension"))
2161          children.add(ted);
2162      }
2163
2164      r.getCells().add(gen.new Cell("", "", "Extension", null, null));
2165      r.setIcon("icon_"+m+"extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
2166      
2167      for (ElementDefinition c : children) {
2168        ved = getValueFor(ed, c);
2169        ElementDefinition ued = getUrlFor(ed, c);
2170        if (ved != null && ued != null) {
2171          Row r1 = gen.new Row();
2172          r.getSubRows().add(r1);
2173          r1.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), ((UriType) ued.getFixed()).getValue(), null, null));
2174          r1.getCells().add(gen.new Cell());
2175          r1.getCells().add(gen.new Cell(null, null, describeCardinality(c, null, new UnusedTracker()), null, null));
2176          genTypes(gen, r1, ved, defFile, ed, corePath, imagePath);
2177          Cell cell = gen.new Cell();
2178          cell.addMarkdown(c.getDefinition());
2179          r1.getCells().add(cell);
2180          r1.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);      
2181        }
2182      }
2183    } else  {
2184      for (ElementDefinition ted : ed.getSnapshot().getElement()) {
2185        if (ted.getPath().startsWith("Extension.value"))
2186          ved = ted;
2187      }
2188
2189      genTypes(gen, r, ved, defFile, ed, corePath, imagePath);
2190
2191      r.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);      
2192    }
2193    Cell c = gen.new Cell("", "", "URL = "+ed.getUrl(), null, null);
2194    Piece cc = gen.new Piece(null, ed.getName()+": ", null);
2195    c.addPiece(gen.new Piece("br")).addPiece(cc);
2196    c.addMarkdown(ed.getDescription());
2197    
2198    if (!full && !(deep || vdeep) && ved != null && ved.hasBinding()) {  
2199        c.addPiece(gen.new Piece("br"));
2200      BindingResolution br = pkp.resolveBinding(ed, ved.getBinding(), ved.getPath());
2201      c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold")));
2202      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)));
2203      if (ved.getBinding().hasStrength()) {
2204        c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, " (", null)));
2205        c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(corePath+"terminologies.html#"+ved.getBinding().getStrength().toCode(), egt(ved.getBinding().getStrengthElement()), ved.getBinding().getStrength().getDefinition())));              
2206        c.getPieces().add(gen.new Piece(null, ")", null));
2207      }
2208    }
2209    c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, describeExtensionContext(ed), null));
2210    r.getCells().add(c);
2211    
2212    try {
2213      return gen.generate(model, corePath, 0, outputTracker);
2214        } catch (org.hl7.fhir.exceptions.FHIRException e) {
2215                throw new FHIRException(e.getMessage(), e);
2216        }
2217  }
2218
2219  private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) {
2220    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
2221    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
2222      if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url"))
2223        return ed.getSnapshot().getElement().get(i);
2224      i++;
2225    }
2226    return null;
2227  }
2228
2229  private ElementDefinition getValueFor(StructureDefinition ed, ElementDefinition c) {
2230    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
2231    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
2232      if (ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".value"))
2233        return ed.getSnapshot().getElement().get(i);
2234      i++;
2235    }
2236    return null;
2237  }
2238
2239
2240  private static final int AGG_NONE = 0;
2241  private static final int AGG_IND = 1;
2242  private static final int AGG_GR = 2;
2243  private static final boolean TABLE_FORMAT_FOR_FIXED_VALUES = false;
2244  
2245  private Cell genTypes(HierarchicalTableGenerator gen, Row r, ElementDefinition e, String profileBaseFileName, StructureDefinition profile, String corePath, String imagePath) {
2246    Cell c = gen.new Cell();
2247    r.getCells().add(c);
2248    List<TypeRefComponent> types = e.getType();
2249    if (!e.hasType()) {
2250      if (e.hasContentReference()) {
2251        return c;
2252      } else {
2253      ElementDefinition d = (ElementDefinition) e.getUserData(DERIVATION_POINTER);
2254      if (d != null && d.hasType()) {
2255        types = new ArrayList<ElementDefinition.TypeRefComponent>();
2256        for (TypeRefComponent tr : d.getType()) {
2257          TypeRefComponent tt = tr.copy();
2258          tt.setUserData(DERIVATION_EQUALS, true);
2259          types.add(tt);
2260        }
2261      } else
2262        return c;
2263    }
2264    }
2265
2266    boolean first = true;
2267
2268    TypeRefComponent tl = null;
2269    for (TypeRefComponent t : types) {
2270      if (first)
2271        first = false;
2272      else
2273        c.addPiece(checkForNoChange(tl, gen.new Piece(null,", ", null)));
2274      tl = t;
2275      if (t.hasTarget()) {
2276        c.getPieces().add(gen.new Piece(corePath+"references.html", t.getWorkingCode(), null));
2277        c.getPieces().add(gen.new Piece(null, "(", null));
2278        boolean tfirst = true;
2279        for (UriType u : t.getTargetProfile()) {
2280          if (tfirst)
2281            tfirst = false;
2282          else
2283            c.addPiece(gen.new Piece(null, " | ", null));
2284          genTargetLink(gen, profileBaseFileName, corePath, c, t, u.getValue());
2285        }
2286        c.getPieces().add(gen.new Piece(null, ")", null));
2287        if (t.getAggregation().size() > 0) {
2288          c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", " {", null));
2289          boolean firstA = true;
2290          for (Enumeration<AggregationMode> a : t.getAggregation()) {
2291            if (firstA = true)
2292              firstA = false;
2293            else
2294              c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", ", ", null));
2295            c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", codeForAggregation(a.getValue()), hintForAggregation(a.getValue())));
2296          }
2297          c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", "}", null));
2298        }
2299      } else if (t.hasProfile() && (!t.getWorkingCode().equals("Extension") || isProfiledType(t.getProfile()))) { // a profiled type
2300        String ref;
2301        ref = pkp.getLinkForProfile(profile, t.getProfile().get(0).getValue());
2302        if (ref != null) {
2303          String[] parts = ref.split("\\|");
2304          if (parts[0].startsWith("http:") || parts[0].startsWith("https:")) {
2305//            c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], "<" + parts[1] + ">", t.getCode()))); Lloyd
2306            c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], parts[1], t.getWorkingCode())));
2307          } else {
2308//            c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().startsWith(corePath)? corePath: "")+parts[0], "<" + parts[1] + ">", t.getCode())));
2309            c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().get(0).getValue().startsWith(corePath+"StructureDefinition")? corePath: "")+parts[0], parts[1], t.getWorkingCode())));
2310          }
2311        } else
2312          c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().get(0).getValue().startsWith(corePath)? corePath: "")+ref, t.getWorkingCode(), null)));
2313      } else {
2314        String tc = t.getWorkingCode();
2315        if (pkp != null && pkp.hasLinkFor(tc)) {
2316          c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, tc), tc, null)));
2317      } else
2318          c.addPiece(checkForNoChange(t, gen.new Piece(null, tc, null)));
2319      }
2320    }
2321    return c;
2322  }
2323
2324
2325  public void genTargetLink(HierarchicalTableGenerator gen, String profileBaseFileName, String corePath, Cell c, TypeRefComponent t, String u) {
2326    if (u.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
2327      StructureDefinition sd = context.fetchResource(StructureDefinition.class, u);
2328      if (sd != null) {
2329        String disp = sd.hasTitle() ? sd.getTitle() : sd.getName();
2330        c.addPiece(checkForNoChange(t, gen.new Piece(checkPrepend(corePath, sd.getUserString("path")), disp, null)));
2331      } else {
2332        String rn = u.substring(40);
2333        c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, rn), rn, null)));
2334      }
2335    } else if (Utilities.isAbsoluteUrl(u)) {
2336      StructureDefinition sd = context.fetchResource(StructureDefinition.class, u);
2337      if (sd != null) {
2338        String disp = sd.hasTitle() ? sd.getTitle() : sd.getName();
2339        String ref = pkp.getLinkForProfile(null, sd.getUrl());
2340        if (ref.contains("|"))
2341          ref = ref.substring(0,  ref.indexOf("|"));
2342        c.addPiece(checkForNoChange(t, gen.new Piece(ref, disp, null)));
2343      } else
2344        c.addPiece(checkForNoChange(t, gen.new Piece(null, u, null)));        
2345    } else if (t.hasTargetProfile() && u.startsWith("#"))
2346      c.addPiece(checkForNoChange(t, gen.new Piece(corePath+profileBaseFileName+"."+u.substring(1).toLowerCase()+".html", u, null)));
2347  }
2348
2349  private boolean isProfiledType(List<CanonicalType> theProfile) {
2350    for (CanonicalType next : theProfile){
2351      if (StringUtils.defaultString(next.getValueAsString()).contains(":")) {
2352        return true;
2353      }
2354    }
2355    return false;
2356  }
2357
2358
2359  private String codeForAggregation(AggregationMode a) {
2360    switch (a) {
2361    case BUNDLED : return "b";
2362    case CONTAINED : return "c";
2363    case REFERENCED: return "r";
2364    default: return "?";
2365    }
2366  }
2367
2368  private String hintForAggregation(AggregationMode a) {
2369    if (a != null)
2370      return a.getDefinition();
2371    else 
2372      return null;
2373  }
2374
2375
2376  private String checkPrepend(String corePath, String path) {
2377    if (pkp.prependLinks() && !(path.startsWith("http:") || path.startsWith("https:")))
2378      return corePath+path;
2379    else 
2380      return path;
2381  }
2382
2383
2384  private ElementDefinition getElementByName(List<ElementDefinition> elements, String contentReference) {
2385    for (ElementDefinition ed : elements)
2386      if (ed.hasSliceName() && ("#"+ed.getSliceName()).equals(contentReference))
2387        return ed;
2388    return null;
2389  }
2390
2391  private ElementDefinition getElementById(List<ElementDefinition> elements, String contentReference) {
2392    for (ElementDefinition ed : elements)
2393      if (ed.hasId() && ("#"+ed.getId()).equals(contentReference))
2394        return ed;
2395    return null;
2396  }
2397
2398
2399  public static String describeExtensionContext(StructureDefinition ext) {
2400    StringBuilder b = new StringBuilder();
2401    b.append("Use on ");
2402    for (int i = 0; i < ext.getContext().size(); i++) {
2403      StructureDefinitionContextComponent ec = ext.getContext().get(i);
2404      if (i > 0) 
2405        b.append(i < ext.getContext().size() - 1 ? ", " : " or ");
2406      b.append(ec.getType().getDisplay());
2407      b.append(" ");
2408      b.append(ec.getExpression());
2409    }
2410    if (ext.hasContextInvariant()) {
2411      b.append(", with <a href=\"structuredefinition-definitions.html#StructureDefinition.contextInvariant\">Context Invariant</a> = ");
2412      boolean first = true;
2413      for (StringType s : ext.getContextInvariant()) {
2414        if (first)
2415          first = false;
2416        else
2417          b.append(", ");
2418        b.append("<code>"+s.getValue()+"</code>");
2419      }
2420    }
2421    return b.toString(); 
2422  }
2423
2424  private String describeCardinality(ElementDefinition definition, ElementDefinition fallback, UnusedTracker tracker) {
2425    IntegerType min = definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
2426    StringType max = definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
2427    if (min.isEmpty() && fallback != null)
2428      min = fallback.getMinElement();
2429    if (max.isEmpty() && fallback != null)
2430      max = fallback.getMaxElement();
2431
2432    tracker.used = !max.isEmpty() && !max.getValue().equals("0");
2433
2434    if (min.isEmpty() && max.isEmpty())
2435      return null;
2436    else
2437      return (!min.hasValue() ? "" : Integer.toString(min.getValue())) + ".." + (!max.hasValue() ? "" : max.getValue());
2438  }
2439
2440  private void genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef, UnusedTracker tracker, ElementDefinition fallback) {
2441    IntegerType min = !hasDef ? new IntegerType() : definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
2442    StringType max = !hasDef ? new StringType() : definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
2443    if (min.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) {
2444      ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER);
2445      if (base.hasMinElement()) {
2446        min = base.getMinElement().copy();
2447        min.setUserData(DERIVATION_EQUALS, true);
2448      }
2449    }
2450    if (max.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) {
2451      ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER);
2452      if (base.hasMaxElement()) {
2453        max = base.getMaxElement().copy();
2454        max.setUserData(DERIVATION_EQUALS, true);
2455      }
2456    }
2457    if (min.isEmpty() && fallback != null)
2458      min = fallback.getMinElement();
2459    if (max.isEmpty() && fallback != null)
2460      max = fallback.getMaxElement();
2461
2462    if (!max.isEmpty())
2463      tracker.used = !max.getValue().equals("0");
2464
2465    Cell cell = gen.new Cell(null, null, null, null, null);
2466    row.getCells().add(cell);
2467    if (!min.isEmpty() || !max.isEmpty()) {
2468      cell.addPiece(checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), null)));
2469      cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", null)));
2470      cell.addPiece(checkForNoChange(min, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), null)));
2471    }
2472  }
2473
2474
2475  private Piece checkForNoChange(Element source, Piece piece) {
2476    if (source.hasUserData(DERIVATION_EQUALS)) {
2477      piece.addStyle("opacity: 0.4");
2478    }
2479    return piece;
2480  }
2481
2482  private Piece checkForNoChange(Element src1, Element src2, Piece piece) {
2483    if (src1.hasUserData(DERIVATION_EQUALS) && src2.hasUserData(DERIVATION_EQUALS)) {
2484      piece.addStyle("opacity: 0.5");
2485    }
2486    return piece;
2487  }
2488
2489  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 {
2490    assert(diff != snapshot);// check it's ok to get rid of one of these
2491    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true);
2492    gen.setTranslator(getTranslator());
2493    TableModel model = gen.initNormalTable(corePath, false, true, profile.getId()+(diff ? "d" : "s"), false);
2494    List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement();
2495    List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
2496    profiles.add(profile);
2497    if (list.isEmpty()) {
2498      ElementDefinition root = new ElementDefinition().setPath(profile.getType());
2499      root.setId(profile.getType());
2500      list.add(root);
2501    }
2502    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, null);
2503    try {
2504      return gen.generate(model, imagePath, 0, outputTracker);
2505        } catch (org.hl7.fhir.exceptions.FHIRException e) {
2506                throw new FHIRException("Error generating table for profile " + profile.getUrl() + ": " + e.getMessage(), e);
2507        }
2508  }
2509
2510
2511  public XhtmlNode generateGrid(String defFile, StructureDefinition profile, String imageFolder, boolean inlineGraphics, String profileBaseFileName, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException {
2512    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true);
2513    gen.setTranslator(getTranslator());
2514    TableModel model = gen.initGridTable(corePath, profile.getId());
2515    List<ElementDefinition> list = profile.getSnapshot().getElement();
2516    List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
2517    profiles.add(profile);
2518    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));
2519    try {
2520      return gen.generate(model, imagePath, 1, outputTracker);
2521    } catch (org.hl7.fhir.exceptions.FHIRException e) {
2522      throw new FHIRException(e.getMessage(), e);
2523    }
2524  }
2525
2526
2527  private boolean usesMustSupport(List<ElementDefinition> list) {
2528    for (ElementDefinition ed : list)
2529      if (ed.hasMustSupport() && ed.getMustSupport())
2530        return true;
2531    return false;
2532  }
2533
2534
2535  private Row 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, Row slicingRow) throws IOException, FHIRException {
2536    Row originalRow = slicingRow;
2537    StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1);
2538    String s = tail(element.getPath());
2539    if (element.hasSliceName())
2540      s = s +":"+element.getSliceName();
2541    Row typesRow = null;
2542    
2543    List<ElementDefinition> children = getChildren(all, element);
2544    boolean isExtension = (s.equals("extension") || s.equals("modifierExtension"));
2545//    if (!snapshot && isExtension && extensions != null && extensions != isExtension)
2546//      return;
2547
2548    if (!onlyInformationIsMapping(all, element)) {
2549      Row row = gen.new Row();
2550      row.setAnchor(element.getPath());
2551      row.setColor(getRowColor(element, isConstraintMode));
2552      if (element.hasSlicing())
2553        row.setLineColor(1);
2554      else if (element.hasSliceName())
2555        row.setLineColor(2);
2556      else
2557        row.setLineColor(0);
2558      boolean hasDef = element != null;
2559      boolean ext = false;
2560      if (tail(element.getPath()).equals("extension")) {
2561        if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue()))
2562          row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
2563        else
2564          row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
2565        ext = true;
2566      } else if (tail(element.getPath()).equals("modifierExtension")) {
2567        if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue()))
2568          row.setIcon("icon_modifier_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
2569        else
2570          row.setIcon("icon_modifier_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
2571      } else if (!hasDef || element.getType().size() == 0)
2572        row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
2573      else if (hasDef && element.getType().size() > 1) {
2574        if (allAreReference(element.getType()))
2575          row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
2576        else {
2577          row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE);
2578          typesRow = row;
2579        }
2580      } else if (hasDef && element.getType().get(0).getWorkingCode() != null && element.getType().get(0).getWorkingCode().startsWith("@"))
2581        row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE);
2582      else if (hasDef && isPrimitive(element.getType().get(0).getWorkingCode()))
2583        row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
2584      else if (hasDef && element.getType().get(0).hasTarget())
2585        row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
2586      else if (hasDef && isDataType(element.getType().get(0).getWorkingCode()))
2587        row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
2588      else
2589        row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
2590      String ref = defPath == null ? null : defPath + element.getId();
2591      UnusedTracker used = new UnusedTracker();
2592      used.used = true;
2593      if (logicalModel && element.hasRepresentation(PropertyRepresentation.XMLATTR))
2594        s = "@"+s;
2595      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);
2596      row.getCells().add(left);
2597      Cell gc = gen.new Cell();
2598      row.getCells().add(gc);
2599      if (element != null && element.getIsModifier())
2600        checkForNoChange(element.getIsModifierElement(), gc.addStyledText(translate("sd.table", "This element is a modifier element"), "?!", null, null, null, false));
2601      if (element != null && element.getMustSupport())
2602        checkForNoChange(element.getMustSupportElement(), gc.addStyledText(translate("sd.table", "This element must be supported"), "S", "white", "red", null, false));
2603      if (element != null && element.getIsSummary())
2604        checkForNoChange(element.getIsSummaryElement(), gc.addStyledText(translate("sd.table", "This element is included in summaries"), "\u03A3", null, null, null, false));
2605      if (element != null && (!element.getConstraint().isEmpty() || !element.getCondition().isEmpty()))
2606        gc.addStyledText(translate("sd.table", "This element has or is affected by some invariants"), "I", null, null, null, false);
2607
2608      ExtensionContext extDefn = null;
2609      if (ext) {
2610        if (element != null && element.getType().size() == 1 && element.getType().get(0).hasProfile()) {
2611          String eurl = element.getType().get(0).getProfile().get(0).getValue();
2612          extDefn = locateExtension(StructureDefinition.class, eurl);
2613          if (extDefn == null) {
2614            genCardinality(gen, element, row, hasDef, used, null);
2615            row.getCells().add(gen.new Cell(null, null, "?? "+element.getType().get(0).getProfile(), null, null));
2616            generateDescription(gen, row, element, null, used.used, profile.getUrl(), eurl, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot);
2617          } else {
2618            String name = urltail(eurl);
2619            left.getPieces().get(0).setText(name);
2620            // left.getPieces().get(0).setReference((String) extDefn.getExtensionStructure().getTag("filename"));
2621            left.getPieces().get(0).setHint(translate("sd.table", "Extension URL")+" = "+extDefn.getUrl());
2622            genCardinality(gen, element, row, hasDef, used, extDefn.getElement());
2623            ElementDefinition valueDefn = extDefn.getExtensionValueDefinition();
2624            if (valueDefn != null && !"0".equals(valueDefn.getMax()))
2625               genTypes(gen, row, valueDefn, profileBaseFileName, profile, corePath, imagePath);
2626             else // if it's complex, we just call it nothing
2627                // genTypes(gen, row, extDefn.getSnapshot().getElement().get(0), profileBaseFileName, profile);
2628              row.getCells().add(gen.new Cell(null, null, "("+translate("sd.table", "Complex")+")", null, null));
2629            generateDescription(gen, row, element, extDefn.getElement(), used.used, null, extDefn.getUrl(), profile, corePath, imagePath, root, logicalModel, allInvariants, valueDefn, snapshot);
2630          }
2631        } else {
2632          genCardinality(gen, element, row, hasDef, used, null);
2633          if ("0".equals(element.getMax()))
2634            row.getCells().add(gen.new Cell());            
2635          else
2636            genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath);
2637          generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot);
2638        }
2639      } else {
2640        genCardinality(gen, element, row, hasDef, used, null);
2641        if (element.hasSlicing())
2642          row.getCells().add(gen.new Cell(null, corePath+"profiling.html#slicing", "(Slice Definition)", null, null));
2643        else if (hasDef && !"0".equals(element.getMax()) && typesRow == null)
2644          genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath);
2645        else
2646          row.getCells().add(gen.new Cell());
2647        generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot);
2648      }
2649      if (element.hasSlicing()) {
2650        if (standardExtensionSlicing(element)) {
2651          used.used = true; // doesn't matter whether we have a type, we're used if we're setting up slicing ... element.hasType() && element.getType().get(0).hasProfile();
2652          showMissing = false; //?
2653        } else {
2654          row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE);
2655          slicingRow = row;
2656          for (Cell cell : row.getCells())
2657            for (Piece p : cell.getPieces()) {
2658              p.addStyle("font-style: italic");
2659            }
2660        }
2661      } else if (element.hasSliceName()) {
2662        row.setIcon("icon_slice_item.png", HierarchicalTableGenerator.TEXT_ICON_SLICE_ITEM);
2663      }
2664      if (used.used || showMissing)
2665        rows.add(row);
2666      if (!used.used && !element.hasSlicing()) {
2667        for (Cell cell : row.getCells())
2668          for (Piece p : cell.getPieces()) {
2669            p.setStyle("text-decoration:line-through");
2670            p.setReference(null);
2671          }
2672      } else{
2673        if (slicingRow != originalRow && !children.isEmpty()) {
2674          // we've entered a slice; we're going to create a holder row for the slice children
2675          Row hrow = gen.new Row();
2676          hrow.setAnchor(element.getPath());
2677          hrow.setColor(getRowColor(element, isConstraintMode));
2678          hrow.setLineColor(1);
2679          hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
2680          hrow.getCells().add(gen.new Cell(null, null, "(All Slices)", "", null));
2681          hrow.getCells().add(gen.new Cell());
2682          hrow.getCells().add(gen.new Cell());
2683          hrow.getCells().add(gen.new Cell());
2684          hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all slices", "", null));
2685          row.getSubRows().add(hrow);
2686          row = hrow;
2687        }
2688        if (typesRow != null && !children.isEmpty()) {
2689          // we've entered a typing slice; we're going to create a holder row for the all types children
2690          Row hrow = gen.new Row();
2691          hrow.setAnchor(element.getPath());
2692          hrow.setColor(getRowColor(element, isConstraintMode));
2693          hrow.setLineColor(1);
2694          hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
2695          hrow.getCells().add(gen.new Cell(null, null, "(All Types)", "", null));
2696          hrow.getCells().add(gen.new Cell());
2697          hrow.getCells().add(gen.new Cell());
2698          hrow.getCells().add(gen.new Cell());
2699          hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all Types", "", null));
2700          row.getSubRows().add(hrow);
2701          row = hrow;
2702        }
2703          
2704        Row currRow = row; 
2705        for (ElementDefinition child : children) {
2706          if (!child.hasSliceName())
2707            currRow = row; 
2708          if (logicalModel || !child.getPath().endsWith(".id") || (child.getPath().endsWith(".id") && (profile != null) && (profile.getDerivation() == TypeDerivationRule.CONSTRAINT)))  
2709            currRow = genElement(defPath, gen, currRow.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, snapshot, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants, currRow);
2710        }
2711//        if (!snapshot && (extensions == null || !extensions))
2712//          for (ElementDefinition child : children)
2713//            if (child.getPath().endsWith(".extension") || child.getPath().endsWith(".modifierExtension"))
2714//              genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, true, false, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants);
2715      }
2716      if (typesRow != null) {
2717        makeChoiceRows(typesRow.getSubRows(), element, gen, corePath, profileBaseFileName);
2718      }
2719    }
2720    return slicingRow;
2721  }
2722
2723  private void makeChoiceRows(List<Row> subRows, ElementDefinition element, HierarchicalTableGenerator gen, String corePath, String profileBaseFileName) {
2724    // create a child for each choice
2725    for (TypeRefComponent tr : element.getType()) {
2726      Row choicerow = gen.new Row();
2727      String t = tr.getWorkingCode();
2728      if (isReference(t)) {
2729        choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), null, null));
2730        choicerow.getCells().add(gen.new Cell());
2731        choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
2732        choicerow.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
2733        Cell c = gen.new Cell();
2734        choicerow.getCells().add(c);
2735        if (ADD_REFERENCE_TO_TABLE) {
2736          if (tr.getWorkingCode().equals("canonical"))
2737            c.getPieces().add(gen.new Piece(corePath+"datatypes.html#canonical", "canonical", null));
2738          else
2739            c.getPieces().add(gen.new Piece(corePath+"references.html#Reference", "Reference", null));
2740          c.getPieces().add(gen.new Piece(null, "(", null));
2741    }
2742        boolean first = true;
2743        for (CanonicalType rt : tr.getTargetProfile()) {
2744          if (!first)
2745            c.getPieces().add(gen.new Piece(null, " | ", null));
2746          genTargetLink(gen, profileBaseFileName, corePath, c, tr, rt.getValue());
2747          first = false;
2748  }
2749        if (ADD_REFERENCE_TO_TABLE) 
2750          c.getPieces().add(gen.new Piece(null, ")", null));
2751        
2752      } else {
2753        StructureDefinition sd = context.fetchTypeDefinition(t);
2754        if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) {
2755          choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]",  Utilities.capitalize(t)), sd.getDescription(), null));
2756          choicerow.getCells().add(gen.new Cell());
2757          choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
2758          choicerow.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
2759          choicerow.getCells().add(gen.new Cell(null, corePath+"datatypes.html#"+t, t, null, null));
2760          //      } else if (definitions.getConstraints().contthnsKey(t)) {
2761          //        ProfiledType pt = definitions.getConstraints().get(t);
2762          //        choicerow.getCells().add(gen.new Cell(null, null, e.getName().replace("[x]", Utilities.capitalize(pt.getBaseType())), definitions.getTypes().containsKey(t) ? definitions.getTypes().get(t).getDefinition() : null, null));
2763          //        choicerow.getCells().add(gen.new Cell());
2764          //        choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
2765          //        choicerow.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
2766          //        choicerow.getCells().add(gen.new Cell(null, definitions.getSrcFile(t)+".html#"+t.replace("*", "open"), t, null, null));
2767        } else {
2768          choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]",  Utilities.capitalize(t)), sd.getDescription(), null));
2769          choicerow.getCells().add(gen.new Cell());
2770          choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
2771          choicerow.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
2772          choicerow.getCells().add(gen.new Cell(null, pkp.getLinkFor(corePath, t), t, null, null));
2773        }
2774      }    
2775      choicerow.getCells().add(gen.new Cell());
2776      subRows.add(choicerow);
2777    }
2778  }
2779
2780  private boolean isReference(String t) {
2781    return t.equals("Reference") || t.equals("canonical"); 
2782  }  
2783
2784
2785
2786  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, FHIRException {
2787    StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1);
2788    String s = tail(element.getPath());
2789    List<ElementDefinition> children = getChildren(all, element);
2790    boolean isExtension = (s.equals("extension") || s.equals("modifierExtension"));
2791
2792    if (!onlyInformationIsMapping(all, element)) {
2793      Row row = gen.new Row();
2794      row.setAnchor(element.getPath());
2795      row.setColor(getRowColor(element, isConstraintMode));
2796      if (element.hasSlicing())
2797        row.setLineColor(1);
2798      else if (element.hasSliceName())
2799        row.setLineColor(2);
2800      else
2801        row.setLineColor(0);
2802      boolean hasDef = element != null;
2803      String ref = defPath == null ? null : defPath + element.getId();
2804      UnusedTracker used = new UnusedTracker();
2805      used.used = true;
2806      Cell left = gen.new Cell();
2807      if (element.getType().size() == 1 && element.getType().get(0).isPrimitive())
2808        left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())).addStyle("font-weight:bold"));
2809      else
2810        left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())));
2811      if (element.hasSliceName()) {
2812        left.getPieces().add(gen.new Piece("br"));
2813        String indent = StringUtils.repeat('\u00A0', 1+2*(element.getPath().split("\\.").length));
2814        left.getPieces().add(gen.new Piece(null, indent + "("+element.getSliceName() + ")", null));
2815      }
2816      row.getCells().add(left);
2817
2818      ExtensionContext extDefn = null;
2819      genCardinality(gen, element, row, hasDef, used, null);
2820      if (hasDef && !"0".equals(element.getMax()))
2821        genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath);
2822      else
2823        row.getCells().add(gen.new Cell());
2824      generateGridDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, null);
2825/*      if (element.hasSlicing()) {
2826        if (standardExtensionSlicing(element)) {
2827          used.used = element.hasType() && element.getType().get(0).hasProfile();
2828          showMissing = false;
2829        } else {
2830          row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE);
2831          row.getCells().get(2).getPieces().clear();
2832          for (Cell cell : row.getCells())
2833            for (Piece p : cell.getPieces()) {
2834              p.addStyle("font-style: italic");
2835            }
2836        }
2837      }*/
2838      rows.add(row);
2839      for (ElementDefinition child : children)
2840        if (child.getMustSupport())
2841          genGridElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, corePath, imagePath, false, isConstraintMode);
2842    }
2843  }
2844
2845
2846  private ExtensionContext locateExtension(Class<StructureDefinition> class1, String value)  {
2847    if (value.contains("#")) {
2848      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
2849      if (ext == null)
2850        return null;
2851      String tail = value.substring(value.indexOf("#")+1);
2852      ElementDefinition ed = null;
2853      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
2854        if (tail.equals(ted.getSliceName())) {
2855          ed = ted;
2856          return new ExtensionContext(ext, ed);
2857        }
2858      }
2859      return null;
2860    } else {
2861      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value);
2862      if (ext == null)
2863        return null;
2864      else 
2865        return new ExtensionContext(ext, ext.getSnapshot().getElement().get(0));
2866    }
2867  }
2868
2869
2870  private boolean extensionIsComplex(String value) {
2871    if (value.contains("#")) {
2872      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
2873    if (ext == null)
2874      return false;
2875      String tail = value.substring(value.indexOf("#")+1);
2876      ElementDefinition ed = null;
2877      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
2878        if (tail.equals(ted.getSliceName())) {
2879          ed = ted;
2880          break;
2881        }
2882      }
2883      if (ed == null)
2884        return false;
2885      int i = ext.getSnapshot().getElement().indexOf(ed);
2886      int j = i+1;
2887      while (j < ext.getSnapshot().getElement().size() && !ext.getSnapshot().getElement().get(j).getPath().equals(ed.getPath()))
2888        j++;
2889      return j - i > 5;
2890    } else {
2891      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value);
2892      return ext != null && ext.getSnapshot().getElement().size() > 5;
2893    }
2894  }
2895
2896
2897  private String getRowColor(ElementDefinition element, boolean isConstraintMode) {
2898    switch (element.getUserInt(UD_ERROR_STATUS)) {
2899    case STATUS_HINT: return ROW_COLOR_HINT;
2900    case STATUS_WARNING: return ROW_COLOR_WARNING;
2901    case STATUS_ERROR: return ROW_COLOR_ERROR;
2902    case STATUS_FATAL: return ROW_COLOR_FATAL;
2903    }
2904    if (isConstraintMode && !element.getMustSupport() && !element.getIsModifier() && element.getPath().contains("."))
2905      return null; // ROW_COLOR_NOT_MUST_SUPPORT;
2906    else
2907      return null;
2908  }
2909
2910
2911  private String urltail(String path) {
2912    if (path.contains("#"))
2913      return path.substring(path.lastIndexOf('#')+1);
2914    if (path.contains("/"))
2915      return path.substring(path.lastIndexOf('/')+1);
2916    else
2917      return path;
2918
2919  }
2920
2921  private boolean standardExtensionSlicing(ElementDefinition element) {
2922    String t = tail(element.getPath());
2923    return (t.equals("extension") || t.equals("modifierExtension"))
2924          && 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);
2925  }
2926
2927  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, boolean snapshot) throws IOException, FHIRException {
2928    return generateDescription(gen, row, definition, fallback, used, baseURL, url, profile, corePath, imagePath, root, logicalModel, allInvariants, null, snapshot);
2929  }
2930  
2931  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, boolean snapshot) throws IOException, FHIRException {
2932    Cell c = gen.new Cell();
2933    row.getCells().add(c);
2934
2935    if (used) {
2936      if (logicalModel && ToolingExtensions.hasExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) {
2937        if (root) {
2938          c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold"));
2939          c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null));        
2940        } else if (!root && ToolingExtensions.hasExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace") && 
2941            !ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace").equals(ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))) {
2942          c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold"));
2943          c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null));        
2944        }
2945      }
2946      
2947      if (definition.hasContentReference()) {
2948        ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference());
2949        if (ed == null)
2950          c.getPieces().add(gen.new Piece(null, translate("sd.table", "Unknown reference to %s", definition.getContentReference()), null));
2951        else
2952          c.getPieces().add(gen.new Piece("#"+ed.getPath(), translate("sd.table", "See %s", ed.getPath()), null));
2953      }
2954      if (definition.getPath().endsWith("url") && definition.hasFixed()) {
2955        c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen")));
2956      } else {
2957        if (definition != null && definition.hasShort()) {
2958          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2959          c.addPiece(checkForNoChange(definition.getShortElement(), gen.new Piece(null, gt(definition.getShortElement()), null)));
2960        } else if (fallback != null && fallback.hasShort()) {
2961          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2962          c.addPiece(checkForNoChange(fallback.getShortElement(), gen.new Piece(null, gt(fallback.getShortElement()), null)));
2963        }
2964        if (url != null) {
2965          if (!c.getPieces().isEmpty()) 
2966            c.addPiece(gen.new Piece("br"));
2967          String fullUrl = url.startsWith("#") ? baseURL+url : url;
2968          StructureDefinition ed = context.fetchResource(StructureDefinition.class, url);
2969          String ref = null;
2970          String ref2 = null;
2971          String fixedUrl = null;
2972          if (ed != null) {
2973            String p = ed.getUserString("path");
2974            if (p != null) {
2975              ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p);
2976            }             
2977            fixedUrl = getFixedUrl(ed);
2978            if (fixedUrl != null) {// if its null, we guess that it's not a profiled extension? 
2979              if (fixedUrl.equals(url))
2980                fixedUrl = null;
2981              else {
2982                StructureDefinition ed2 = context.fetchResource(StructureDefinition.class, fixedUrl);
2983                if (ed2 != null) {
2984                  String p2 = ed2.getUserString("path");
2985                  if (p2 != null) {
2986                    ref2 = p2.startsWith("http:") || igmode ? p2 : Utilities.pathURL(corePath, p2);
2987                  }                              
2988                }
2989              }
2990            }
2991          }
2992          if (fixedUrl == null) {
2993            c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold"));
2994            c.getPieces().add(gen.new Piece(ref, fullUrl, null));
2995          } else { 
2996            // reference to a profile take on the extension show the base URL
2997            c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold"));
2998            c.getPieces().add(gen.new Piece(ref2, fixedUrl, null));
2999            c.getPieces().add(gen.new Piece(null, translate("sd.table", " profiled by ")+" ", null).addStyle("font-weight:bold"));
3000            c.getPieces().add(gen.new Piece(ref, fullUrl, null));
3001          
3002          }
3003        }
3004
3005        if (definition.hasSlicing()) {
3006          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3007          c.getPieces().add(gen.new Piece(null, translate("sd.table", "Slice")+": ", null).addStyle("font-weight:bold"));
3008          c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null));
3009        }
3010        if (definition != null) {
3011          ElementDefinitionBindingComponent binding = null;
3012          if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty())
3013            binding = valueDefn.getBinding();
3014          else if (definition.hasBinding())
3015            binding = definition.getBinding();
3016          if (binding!=null && !binding.isEmpty()) {
3017            if (!c.getPieces().isEmpty()) 
3018              c.addPiece(gen.new Piece("br"));
3019            BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath());
3020            c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold")));
3021            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)));
3022            if (binding.hasStrength()) {
3023              c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null)));
3024              c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), egt(binding.getStrengthElement()), binding.getStrength().getDefinition())));              
3025              
3026              c.getPieces().add(gen.new Piece(null, ")", null));
3027            }
3028            if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) {
3029              br = pkp.resolveBinding(profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), definition.getPath());
3030              c.addPiece(gen.new Piece("br"));
3031              c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"extension-elementdefinition-maxvalueset.html", translate("sd.table", "Max Binding")+": ", "Max Value Set Extension").addStyle("font-weight:bold")));             
3032              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)));
3033            }
3034            if (binding.hasExtension(ToolingExtensions.EXT_MIN_VALUESET)) {
3035              br = pkp.resolveBinding(profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MIN_VALUESET), definition.getPath());
3036              c.addPiece(gen.new Piece("br"));
3037              c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"extension-elementdefinition-minvalueset.html", translate("sd.table", "Min Binding")+": ", "Min Value Set Extension").addStyle("font-weight:bold")));             
3038              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)));
3039            }
3040          }
3041          for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
3042            if (!inv.hasSource() || allInvariants) {
3043              if (!c.getPieces().isEmpty()) 
3044                c.addPiece(gen.new Piece("br"));
3045              c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold")));
3046              c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, gt(inv.getHumanElement()), null)));
3047            }
3048          }
3049          if ((definition.hasBase() && definition.getBase().getMax().equals("*")) || (definition.hasMax() && definition.getMax().equals("*"))) {
3050            if (c.getPieces().size() > 0)
3051              c.addPiece(gen.new Piece("br"));
3052            if (definition.hasOrderMeaning()) {
3053              c.getPieces().add(gen.new Piece(null, "This repeating element order: "+definition.getOrderMeaning(), null));
3054            } else {
3055              // don't show this, this it's important: c.getPieces().add(gen.new Piece(null, "This repeating element has no defined order", null));
3056            }           
3057          }
3058
3059          if (definition.hasFixed()) {
3060            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3061            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, translate("sd.table", "Fixed Value")+": ", null).addStyle("font-weight:bold")));
3062            if (!useTableForFixedValues || definition.getFixed().isPrimitive()) {
3063              String s = buildJson(definition.getFixed());
3064              String link = null;
3065              if (Utilities.isAbsoluteUrl(s))
3066                link = pkp.getLinkForUrl(corePath, s);
3067              c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen")));
3068            } else {
3069              c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "As shown", null).addStyle("color: darkgreen")));
3070              genFixedValue(gen, row, definition.getFixed(), snapshot, false, corePath);              
3071            }
3072            if (isCoded(definition.getFixed()) && !hasDescription(definition.getFixed())) {
3073              Piece p = describeCoded(gen, definition.getFixed());
3074              if (p != null)
3075                c.getPieces().add(p);
3076            }
3077          } else if (definition.hasPattern()) {
3078            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3079            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, translate("sd.table", "Required Pattern")+": ", null).addStyle("font-weight:bold")));
3080            if (!useTableForFixedValues || definition.getPattern().isPrimitive())
3081            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen")));
3082            else {
3083              c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "At least the following", null).addStyle("color: darkgreen")));
3084              genFixedValue(gen, row, definition.getPattern(), snapshot, true, corePath);
3085            }
3086          } else if (definition.hasExample()) {
3087            for (ElementDefinitionExampleComponent ex : definition.getExample()) {
3088              if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3089              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, translate("sd.table", "Example")+("".equals("General")? "" : " "+ex.getLabel()+"'")+": ", null).addStyle("font-weight:bold")));
3090              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen")));
3091            }
3092          }
3093          if (definition.hasMaxLength() && definition.getMaxLength()!=0) {
3094            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3095            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold")));
3096            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen")));
3097          }
3098          if (profile != null) {
3099            for (StructureDefinitionMappingComponent md : profile.getMapping()) {
3100              if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) {
3101                ElementDefinitionMappingComponent map = null;
3102                for (ElementDefinitionMappingComponent m : definition.getMapping()) 
3103                  if (m.getIdentity().equals(md.getIdentity()))
3104                    map = m;
3105                if (map != null) {
3106                  for (int i = 0; i<definition.getMapping().size(); i++){
3107                    c.addPiece(gen.new Piece("br"));
3108                    c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null));
3109                  }
3110                }
3111              }
3112            }
3113          }
3114        }
3115      }
3116    }
3117    return c;
3118  }
3119
3120  private void genFixedValue(HierarchicalTableGenerator gen, Row erow, Type value, boolean snapshot, boolean pattern, String corePath) {
3121    String ref = pkp.getLinkFor(corePath, value.fhirType());
3122    ref = ref.substring(0, ref.indexOf(".html"))+"-definitions.html#";
3123    StructureDefinition sd = context.fetchTypeDefinition(value.fhirType());
3124    
3125    for (org.hl7.fhir.r4.model.Property t : value.children()) {
3126      if (t.getValues().size() > 0 || snapshot) {
3127        ElementDefinition ed = findElementDefinition(sd, t.getName());
3128        if (t.getValues().size() == 0 || (t.getValues().size() == 1 && t.getValues().get(0).isEmpty())) {
3129          Row row = gen.new Row();
3130          erow.getSubRows().add(row);
3131          Cell c = gen.new Cell();
3132          row.getCells().add(c);
3133          c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref+ed.getPath() : corePath+"element-definitions.html#"+ed.getBase().getPath()), t.getName(), null));
3134          c = gen.new Cell();
3135          row.getCells().add(c);
3136          c.addPiece(gen.new Piece(null, null, null));
3137          c = gen.new Cell();
3138          row.getCells().add(c);
3139          if (!pattern) {
3140            c.addPiece(gen.new Piece(null, "0..0", null));
3141            row.setIcon("icon_fixed.gif", "Fixed Value" /*HierarchicalTableGenerator.TEXT_ICON_FIXED*/);
3142          } else if (isPrimitive(t.getTypeCode())) {
3143            row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
3144            c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null));
3145          } else if (isReference(t.getTypeCode())) { 
3146            row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
3147            c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null));
3148          } else { 
3149            row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
3150            c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null));
3151          }
3152          c = gen.new Cell();
3153          row.getCells().add(c);
3154          c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, t.getTypeCode()), t.getTypeCode(), null));
3155          c = gen.new Cell();
3156          c.addPiece(gen.new Piece(null, ed.getShort(), null));
3157          row.getCells().add(c);
3158        } else {
3159          for (Base b : t.getValues()) {
3160            Row row = gen.new Row();
3161            erow.getSubRows().add(row);
3162            row.setIcon("icon_fixed.gif", "Fixed Value" /*HierarchicalTableGenerator.TEXT_ICON_FIXED*/);
3163
3164            Cell c = gen.new Cell();
3165            row.getCells().add(c);
3166            c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref+ed.getPath() : corePath+"element-definitions.html#"+ed.getBase().getPath()), t.getName(), null));
3167
3168            c = gen.new Cell();
3169            row.getCells().add(c);
3170            c.addPiece(gen.new Piece(null, null, null));
3171
3172            c = gen.new Cell();
3173            row.getCells().add(c);
3174            if (pattern)
3175              c.addPiece(gen.new Piece(null, "1.."+(t.getMaxCardinality() == 2147483647 ? "*" : Integer.toString(t.getMaxCardinality())), null));
3176            else
3177              c.addPiece(gen.new Piece(null, "1..1", null));
3178
3179            c = gen.new Cell();
3180            row.getCells().add(c);
3181            c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, b.fhirType()), b.fhirType(), null));
3182
3183            if (b.isPrimitive()) {
3184              c = gen.new Cell();
3185              row.getCells().add(c);
3186              c.addPiece(gen.new Piece(null, ed.getShort(), null));
3187              c.addPiece(gen.new Piece("br"));
3188              c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold"));
3189              String s = b.primitiveValue();
3190              // ok. let's see if we can find a relevant link for this
3191              String link = null;
3192              if (Utilities.isAbsoluteUrl(s))
3193                link = pkp.getLinkForUrl(corePath, s);
3194              c.getPieces().add(gen.new Piece(link, s, null).addStyle("color: darkgreen"));
3195            } else {
3196              c = gen.new Cell();
3197              row.getCells().add(c);
3198              c.addPiece(gen.new Piece(null, ed.getShort(), null));
3199              c.addPiece(gen.new Piece("br"));
3200              c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold"));
3201              c.getPieces().add(gen.new Piece(null, "(complex)", null).addStyle("color: darkgreen"));
3202              genFixedValue(gen, row, (Type) b, snapshot, pattern, corePath);
3203            }
3204          }
3205        }
3206      }
3207    }
3208  }
3209
3210
3211  private ElementDefinition findElementDefinition(StructureDefinition sd, String name) {
3212    String path = sd.getType()+"."+name;
3213    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
3214      if (ed.getPath().equals(path))
3215        return ed;
3216    }
3217    throw new FHIRException("Unable to find element "+path);
3218  }
3219
3220
3221  private String getFixedUrl(StructureDefinition sd) {
3222    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
3223      if (ed.getPath().equals("Extension.url")) {
3224        if (ed.hasFixed() && ed.getFixed() instanceof UriType)
3225          return ed.getFixed().primitiveValue();
3226      }
3227    }
3228    return null;
3229  }
3230
3231
3232  private Piece describeCoded(HierarchicalTableGenerator gen, Type fixed) {
3233    if (fixed instanceof Coding) {
3234      Coding c = (Coding) fixed;
3235      ValidationResult vr = context.validateCode(terminologyServiceOptions , c.getSystem(), c.getCode(), c.getDisplay());
3236      if (vr.getDisplay() != null)
3237        return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen");
3238    } else if (fixed instanceof CodeableConcept) {
3239      CodeableConcept cc = (CodeableConcept) fixed;
3240      for (Coding c : cc.getCoding()) {
3241        ValidationResult vr = context.validateCode(terminologyServiceOptions, c.getSystem(), c.getCode(), c.getDisplay());
3242        if (vr.getDisplay() != null)
3243          return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen");
3244      }
3245    }
3246    return null;
3247  }
3248
3249
3250  private boolean hasDescription(Type fixed) {
3251    if (fixed instanceof Coding) {
3252      return ((Coding) fixed).hasDisplay();
3253    } else if (fixed instanceof CodeableConcept) {
3254      CodeableConcept cc = (CodeableConcept) fixed;
3255      if (cc.hasText())
3256        return true;
3257      for (Coding c : cc.getCoding())
3258        if (c.hasDisplay())
3259         return true;
3260    } // (fixed instanceof CodeType) || (fixed instanceof Quantity);
3261    return false;
3262  }
3263
3264
3265  private boolean isCoded(Type fixed) {
3266    return (fixed instanceof Coding) || (fixed instanceof CodeableConcept) || (fixed instanceof CodeType) || (fixed instanceof Quantity);
3267  }
3268
3269
3270  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, FHIRException {
3271    Cell c = gen.new Cell();
3272    row.getCells().add(c);
3273
3274    if (used) {
3275      if (definition.hasContentReference()) {
3276        ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference());
3277        if (ed == null)
3278          c.getPieces().add(gen.new Piece(null, "Unknown reference to "+definition.getContentReference(), null));
3279        else
3280          c.getPieces().add(gen.new Piece("#"+ed.getPath(), "See "+ed.getPath(), null));
3281      }
3282      if (definition.getPath().endsWith("url") && definition.hasFixed()) {
3283        c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen")));
3284      } else {
3285        if (url != null) {
3286          if (!c.getPieces().isEmpty()) 
3287            c.addPiece(gen.new Piece("br"));
3288          String fullUrl = url.startsWith("#") ? baseURL+url : url;
3289          StructureDefinition ed = context.fetchResource(StructureDefinition.class, url);
3290          String ref = null;
3291          if (ed != null) {
3292            String p = ed.getUserString("path");
3293            if (p != null) {
3294              ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p);
3295            }
3296          }
3297          c.getPieces().add(gen.new Piece(null, "URL: ", null).addStyle("font-weight:bold"));
3298          c.getPieces().add(gen.new Piece(ref, fullUrl, null));
3299        }
3300
3301        if (definition.hasSlicing()) {
3302          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3303          c.getPieces().add(gen.new Piece(null, "Slice: ", null).addStyle("font-weight:bold"));
3304          c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null));
3305        }
3306        if (definition != null) {
3307          ElementDefinitionBindingComponent binding = null;
3308          if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty())
3309            binding = valueDefn.getBinding();
3310          else if (definition.hasBinding())
3311            binding = definition.getBinding();
3312          if (binding!=null && !binding.isEmpty()) {
3313            if (!c.getPieces().isEmpty()) 
3314              c.addPiece(gen.new Piece("br"));
3315            BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath());
3316            c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, "Binding: ", null).addStyle("font-weight:bold")));
3317            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)));
3318            if (binding.hasStrength()) {
3319              c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null)));
3320              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));
3321            }
3322          }
3323          for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
3324            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3325            c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold")));
3326            c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null)));
3327          }
3328          if (definition.hasFixed()) {
3329            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3330            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight:bold")));
3331            String s = buildJson(definition.getFixed());
3332            String link = null;
3333            if (Utilities.isAbsoluteUrl(s))
3334              link = pkp.getLinkForUrl(corePath, s);
3335            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen")));
3336          } else if (definition.hasPattern()) {
3337            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3338            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "Required Pattern: ", null).addStyle("font-weight:bold")));
3339            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen")));
3340          } else if (definition.hasExample()) {
3341            for (ElementDefinitionExampleComponent ex : definition.getExample()) {
3342              if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3343              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, "Example'"+("".equals("General")? "" : " "+ex.getLabel()+"'")+": ", null).addStyle("font-weight:bold")));
3344              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen")));
3345            }
3346          }
3347          if (definition.hasMaxLength() && definition.getMaxLength()!=0) {
3348            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3349            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold")));
3350            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen")));
3351          }
3352          if (profile != null) {
3353            for (StructureDefinitionMappingComponent md : profile.getMapping()) {
3354              if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) {
3355                ElementDefinitionMappingComponent map = null;
3356                for (ElementDefinitionMappingComponent m : definition.getMapping()) 
3357                  if (m.getIdentity().equals(md.getIdentity()))
3358                    map = m;
3359                if (map != null) {
3360                  for (int i = 0; i<definition.getMapping().size(); i++){
3361                    c.addPiece(gen.new Piece("br"));
3362                    c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null));
3363                  }
3364                }
3365              }
3366            }
3367          }
3368          if (definition.hasDefinition()) {
3369            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3370            c.getPieces().add(gen.new Piece(null, "Definition: ", null).addStyle("font-weight:bold"));
3371            c.addPiece(gen.new Piece("br"));
3372            c.addMarkdown(definition.getDefinition());
3373//            c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null)));
3374          }
3375          if (definition.getComment()!=null) {
3376            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3377            c.getPieces().add(gen.new Piece(null, "Comments: ", null).addStyle("font-weight:bold"));
3378            c.addPiece(gen.new Piece("br"));
3379            c.addMarkdown(definition.getComment());
3380//            c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null)));
3381          }
3382        }
3383      }
3384    }
3385    return c;
3386  }
3387
3388
3389
3390  private String buildJson(Type value) throws IOException {
3391    if (value instanceof PrimitiveType)
3392      return ((PrimitiveType) value).asStringValue();
3393
3394    IParser json = context.newJsonParser();
3395    return json.composeString(value, null);
3396  }
3397
3398
3399  public String describeSlice(ElementDefinitionSlicingComponent slicing) {
3400    return translate("sd.table", "%s, %s by %s", slicing.getOrdered() ? translate("sd.table", "Ordered") : translate("sd.table", "Unordered"), describe(slicing.getRules()), commas(slicing.getDiscriminator()));
3401  }
3402
3403  private String commas(List<ElementDefinitionSlicingDiscriminatorComponent> list) {
3404    CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder();
3405    for (ElementDefinitionSlicingDiscriminatorComponent id : list)
3406      c.append(id.getType().toCode()+":"+id.getPath());
3407    return c.toString();
3408  }
3409
3410
3411  private String describe(SlicingRules rules) {
3412    if (rules == null)
3413      return translate("sd.table", "Unspecified");
3414    switch (rules) {
3415    case CLOSED : return translate("sd.table", "Closed");
3416    case OPEN : return translate("sd.table", "Open");
3417    case OPENATEND : return translate("sd.table", "Open At End");
3418    default:
3419      return "??";
3420    }
3421  }
3422
3423  private boolean onlyInformationIsMapping(List<ElementDefinition> list, ElementDefinition e) {
3424    return (!e.hasSliceName() && !e.hasSlicing() && (onlyInformationIsMapping(e))) &&
3425        getChildren(list, e).isEmpty();
3426  }
3427
3428  private boolean onlyInformationIsMapping(ElementDefinition d) {
3429    return !d.hasShort() && !d.hasDefinition() &&
3430        !d.hasRequirements() && !d.getAlias().isEmpty() && !d.hasMinElement() &&
3431        !d.hasMax() && !d.getType().isEmpty() && !d.hasContentReference() &&
3432        !d.hasExample() && !d.hasFixed() && !d.hasMaxLengthElement() &&
3433        !d.getCondition().isEmpty() && !d.getConstraint().isEmpty() && !d.hasMustSupportElement() &&
3434        !d.hasBinding();
3435  }
3436
3437  private boolean allAreReference(List<TypeRefComponent> types) {
3438    for (TypeRefComponent t : types) {
3439      if (!t.hasTarget())
3440        return false;
3441    }
3442    return true;
3443  }
3444
3445  private List<ElementDefinition> getChildren(List<ElementDefinition> all, ElementDefinition element) {
3446    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
3447    int i = all.indexOf(element)+1;
3448    while (i < all.size() && all.get(i).getPath().length() > element.getPath().length()) {
3449      if ((all.get(i).getPath().substring(0, element.getPath().length()+1).equals(element.getPath()+".")) && !all.get(i).getPath().substring(element.getPath().length()+1).contains("."))
3450        result.add(all.get(i));
3451      i++;
3452    }
3453    return result;
3454  }
3455
3456  private String tail(String path) {
3457    if (path.contains("."))
3458      return path.substring(path.lastIndexOf('.')+1);
3459    else
3460      return path;
3461  }
3462
3463  private boolean isDataType(String value) {
3464    StructureDefinition sd = context.fetchTypeDefinition(value);
3465    if (sd == null) // might be running before all SDs are available
3466      return Utilities.existsInList(value, "Address", "Age", "Annotation", "Attachment", "CodeableConcept", "Coding", "ContactPoint", "Count", "Distance", "Duration", "HumanName", "Identifier", "Money", "Period", "Quantity", "Range", "Ratio", "Reference", "SampledData", "Signature", "Timing", 
3467            "ContactDetail", "Contributor", "DataRequirement", "Expression", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition", "UsageContext");
3468    else 
3469      return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION;
3470  }
3471
3472  private boolean isConstrainedDataType(String value) {
3473    StructureDefinition sd = context.fetchTypeDefinition(value);
3474    if (sd == null) // might be running before all SDs are available
3475      return Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity");
3476    else 
3477      return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.CONSTRAINT;
3478  }
3479
3480  private String baseType(String value) {
3481    StructureDefinition sd = context.fetchTypeDefinition(value);
3482    if (sd != null) // might be running before all SDs are available
3483      return sd.getType();
3484    if (Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity"))
3485      return "Quantity";
3486     throw new Error("Internal error -  type not known "+value);
3487  }
3488
3489
3490  public boolean isPrimitive(String value) {
3491    StructureDefinition sd = context.fetchTypeDefinition(value);
3492    if (sd == null) // might be running before all SDs are available
3493      return Utilities.existsInList(value, "base64Binary", "boolean", "canonical", "code", "date", "dateTime", "decimal", "id", "instant", "integer", "markdown", "oid", "positiveInt", "string", "time", "unsignedInt", "uri", "url", "uuid");
3494    else 
3495      return sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
3496  }
3497
3498//  private static String listStructures(StructureDefinition p) {
3499//    StringBuilder b = new StringBuilder();
3500//    boolean first = true;
3501//    for (ProfileStructureComponent s : p.getStructure()) {
3502//      if (first)
3503//        first = false;
3504//      else
3505//        b.append(", ");
3506//      if (pkp != null && pkp.hasLinkFor(s.getType()))
3507//        b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>");
3508//      else
3509//        b.append(s.getType());
3510//    }
3511//    return b.toString();
3512//  }
3513
3514
3515  public StructureDefinition getProfile(StructureDefinition source, String url) {
3516        StructureDefinition profile = null;
3517        String code = null;
3518        if (url.startsWith("#")) {
3519                profile = source;
3520                code = url.substring(1);
3521        } else if (context != null) {
3522                String[] parts = url.split("\\#");
3523                profile = context.fetchResource(StructureDefinition.class, parts[0]);
3524      code = parts.length == 1 ? null : parts[1];
3525        }         
3526        if (profile == null)
3527                return null;
3528        if (code == null)
3529                return profile;
3530        for (Resource r : profile.getContained()) {
3531                if (r instanceof StructureDefinition && r.getId().equals(code))
3532                        return (StructureDefinition) r;
3533        }
3534        return null;
3535  }
3536
3537
3538
3539  public static class ElementDefinitionHolder {
3540    private String name;
3541    private ElementDefinition self;
3542    private int baseIndex = 0;
3543    private List<ElementDefinitionHolder> children;
3544    private boolean placeHolder = false;
3545
3546    public ElementDefinitionHolder(ElementDefinition self, boolean isPlaceholder) {
3547      super();
3548      this.self = self;
3549      this.name = self.getPath();
3550      this.placeHolder = isPlaceholder;
3551      children = new ArrayList<ElementDefinitionHolder>();      
3552    }
3553
3554    public ElementDefinitionHolder(ElementDefinition self) {
3555      this(self, false);
3556    }
3557
3558    public ElementDefinition getSelf() {
3559      return self;
3560    }
3561
3562    public List<ElementDefinitionHolder> getChildren() {
3563      return children;
3564    }
3565
3566    public int getBaseIndex() {
3567      return baseIndex;
3568    }
3569
3570    public void setBaseIndex(int baseIndex) {
3571      this.baseIndex = baseIndex;
3572    }
3573
3574    public boolean isPlaceHolder() {
3575      return this.placeHolder;
3576    }
3577
3578    @Override
3579    public String toString() {
3580      if (self.hasSliceName())
3581        return self.getPath()+"("+self.getSliceName()+")";
3582      else
3583        return self.getPath();
3584    }
3585  }
3586
3587  public static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> {
3588
3589    private boolean inExtension;
3590    private List<ElementDefinition> snapshot;
3591    private int prefixLength;
3592    private String base;
3593    private String name;
3594    private Set<String> errors = new HashSet<String>();
3595
3596    public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, int prefixLength, String name) {
3597      this.inExtension = inExtension;
3598      this.snapshot = snapshot;
3599      this.prefixLength = prefixLength;
3600      this.base = base;
3601      this.name = name;
3602    }
3603
3604    @Override
3605    public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) {
3606      if (o1.getBaseIndex() == 0)
3607        o1.setBaseIndex(find(o1.getSelf().getPath()));
3608      if (o2.getBaseIndex() == 0)
3609        o2.setBaseIndex(find(o2.getSelf().getPath()));
3610      return o1.getBaseIndex() - o2.getBaseIndex();
3611    }
3612
3613    private int find(String path) {
3614      String op = path;
3615      int lc = 0;
3616      String actual = base+path.substring(prefixLength);
3617      for (int i = 0; i < snapshot.size(); i++) {
3618        String p = snapshot.get(i).getPath();
3619        if (p.equals(actual)) {
3620          return i;
3621        }
3622        if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length()-3)) && !(actual.endsWith("[x]")) && !actual.substring(p.length()-3).contains(".")) {
3623          return i;
3624        }
3625        if (path.startsWith(p+".") && snapshot.get(i).hasContentReference()) {
3626          String ref = snapshot.get(i).getContentReference();
3627          if (ref.substring(1, 2).toUpperCase().equals(ref.substring(1,2))) {
3628            actual = base+(ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength);
3629            path = actual;
3630          } else {
3631            // Older versions of FHIR (e.g. 2016May) had reference of the style #parameter instead of #Parameters.parameter, so we have to handle that
3632            actual = base+(path.substring(0,  path.indexOf(".")+1) + ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength);
3633            path = actual;
3634          }
3635            
3636          i = 0;
3637          lc++;
3638          if (lc > MAX_RECURSION_LIMIT)
3639            throw new Error("Internal recursion detection: find() loop path recursion > "+MAX_RECURSION_LIMIT+" - check paths are valid (for path "+path+"/"+op+")");
3640        }
3641      }
3642      if (prefixLength == 0)
3643        errors.add("Differential contains path "+path+" which is not found in the base");
3644      else
3645        errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the base");
3646      return 0;
3647    }
3648
3649    public void checkForErrors(List<String> errorList) {
3650      if (errors.size() > 0) {
3651//        CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
3652//        for (String s : errors)
3653//          b.append("StructureDefinition "+name+": "+s);
3654//        throw new DefinitionException(b.toString());
3655        for (String s : errors)
3656          if (s.startsWith("!"))
3657            errorList.add("!StructureDefinition "+name+": "+s.substring(1));
3658          else
3659            errorList.add("StructureDefinition "+name+": "+s);
3660      }
3661    }
3662  }
3663
3664
3665  public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors) throws FHIRException  {
3666    final List<ElementDefinition> diffList = diff.getDifferential().getElement();
3667    int lastCount = diffList.size();
3668    // first, we move the differential elements into a tree
3669    if (diffList.isEmpty())
3670      return;
3671    
3672    ElementDefinitionHolder edh = null;
3673    int i = 0;
3674    if (diffList.get(0).getPath().contains(".")) {
3675      String newPath = diffList.get(0).getPath().split("\\.")[0];
3676      ElementDefinition e = new ElementDefinition(new StringType(newPath));
3677      edh = new ElementDefinitionHolder(e, true);
3678    } else {
3679      edh = new ElementDefinitionHolder(diffList.get(0));
3680      i = 1;
3681    }
3682
3683    boolean hasSlicing = false;
3684    List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly
3685    for(ElementDefinition elt : diffList) {
3686      if (elt.hasSlicing() || paths.contains(elt.getPath())) {
3687        hasSlicing = true;
3688        break;
3689      }
3690      paths.add(elt.getPath());
3691    }
3692    if(!hasSlicing) {
3693      // if Differential does not have slicing then safe to pre-sort the list
3694      // so elements and subcomponents are together
3695      Collections.sort(diffList, new ElementNameCompare());
3696    }
3697
3698    processElementsIntoTree(edh, i, diff.getDifferential().getElement());
3699
3700    // now, we sort the siblings throughout the tree
3701    ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name);
3702    sortElements(edh, cmp, errors);
3703
3704    // now, we serialise them back to a list
3705    diffList.clear();
3706    writeElements(edh, diffList);
3707    
3708    if (lastCount != diffList.size())
3709      errors.add("Sort failed: counts differ; at least one of the paths in the differential is illegal");
3710  }
3711
3712  private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) {
3713    String path = edh.getSelf().getPath();
3714    final String prefix = path + ".";
3715    while (i < list.size() && list.get(i).getPath().startsWith(prefix)) {
3716      if (list.get(i).getPath().substring(prefix.length()+1).contains(".")) {
3717        String newPath = prefix + list.get(i).getPath().substring(prefix.length()).split("\\.")[0];
3718        ElementDefinition e = new ElementDefinition(new StringType(newPath));
3719        ElementDefinitionHolder child = new ElementDefinitionHolder(e, true);
3720        edh.getChildren().add(child);
3721        i = processElementsIntoTree(child, i, list);
3722        
3723      } else {
3724        ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i));
3725        edh.getChildren().add(child);
3726        i = processElementsIntoTree(child, i+1, list);
3727      }
3728    }
3729    return i;
3730  }
3731
3732  private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) throws FHIRException {
3733    if (edh.getChildren().size() == 1)
3734      // 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
3735      edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath());
3736    else
3737      Collections.sort(edh.getChildren(), cmp);
3738    cmp.checkForErrors(errors);
3739
3740    for (ElementDefinitionHolder child : edh.getChildren()) {
3741      if (child.getChildren().size() > 0) {
3742        ElementDefinitionComparer ccmp = getComparer(cmp, child);
3743        if (ccmp != null)
3744        sortElements(child, ccmp, errors);
3745      }
3746    }
3747  }
3748
3749
3750  public ElementDefinitionComparer getComparer(ElementDefinitionComparer cmp, ElementDefinitionHolder child) throws FHIRException, Error {
3751    // what we have to check for here is running off the base profile into a data type profile
3752    ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex());
3753    ElementDefinitionComparer ccmp;
3754    if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getWorkingCode()) || ed.getType().get(0).getWorkingCode().equals(ed.getPath())) {
3755      ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name);
3756    } else if (ed.getType().get(0).getWorkingCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) {
3757      StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue());
3758      if (profile==null)
3759        ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case
3760      else
3761      ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
3762    } else if (ed.getType().size() == 1 && !ed.getType().get(0).getWorkingCode().equals("*")) {
3763      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
3764      if (profile==null)
3765        throw new FHIRException("Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath());
3766      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
3767    } else if (child.getSelf().getType().size() == 1) {
3768      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(child.getSelf().getType().get(0).getWorkingCode()));
3769      if (profile==null)
3770        throw new FHIRException("Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath());
3771      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), child.getSelf().getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
3772    } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) {
3773      String edLastNode = ed.getPath().replaceAll("(.*\\.)*(.*)", "$2");
3774      String childLastNode = child.getSelf().getPath().replaceAll("(.*\\.)*(.*)", "$2");
3775      String p = childLastNode.substring(edLastNode.length()-3);
3776      if (isPrimitive(Utilities.uncapitalize(p)))
3777        p = Utilities.uncapitalize(p);
3778      StructureDefinition sd = context.fetchResource(StructureDefinition.class, sdNs(p));
3779      if (sd == null)
3780        throw new Error("Unable to find profile '"+p+"' at "+ed.getId());
3781      ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name);
3782    } else if (child.getSelf().hasType() && child.getSelf().getType().get(0).getWorkingCode().equals("Reference")) {
3783      for (TypeRefComponent t: child.getSelf().getType()) {
3784        if (!t.getWorkingCode().equals("Reference")) {
3785          throw new Error("Can't have children on an element with a polymorphic type - you must slice and constrain the types first (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")");
3786        }
3787      }
3788      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
3789      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
3790    } else if (!child.getSelf().hasType() && ed.getType().get(0).getWorkingCode().equals("Reference")) {
3791      for (TypeRefComponent t: ed.getType()) {
3792        if (!t.getWorkingCode().equals("Reference")) {
3793          throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")");
3794        }
3795      }
3796      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
3797      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
3798    } else {
3799      // this is allowed if we only profile the extensions
3800      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs("Element"));
3801      if (profile==null)
3802        throw new FHIRException("Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath());
3803      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), "Element", child.getSelf().getPath().length(), cmp.name);
3804//      throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")");
3805    }
3806    return ccmp;
3807  }
3808
3809  private static String sdNs(String type) {
3810    return sdNs(type, null);
3811  }
3812  
3813  public static String sdNs(String type, String overrideVersionNs) {
3814    if (Utilities.isAbsoluteUrl(type))
3815      return type;
3816    else if (overrideVersionNs != null)
3817      return Utilities.pathURL(overrideVersionNs, type);
3818    else
3819      return "http://hl7.org/fhir/StructureDefinition/"+type;
3820  }
3821
3822
3823  private boolean isAbstract(String code) {
3824    return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") || code.equals("DomainResource");
3825  }
3826
3827
3828  private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) {
3829    if (!edh.isPlaceHolder())
3830      list.add(edh.getSelf());
3831    for (ElementDefinitionHolder child : edh.getChildren()) {
3832      writeElements(child, list);
3833    }
3834  }
3835
3836  /**
3837   * First compare element by path then by name if same
3838   */
3839  private static class ElementNameCompare implements Comparator<ElementDefinition> {
3840
3841    @Override
3842    public int compare(ElementDefinition o1, ElementDefinition o2) {
3843      String path1 = normalizePath(o1);
3844      String path2 = normalizePath(o2);
3845      int cmp = path1.compareTo(path2);
3846      if (cmp == 0) {
3847        String name1 = o1.hasSliceName() ? o1.getSliceName() : "";
3848        String name2 = o2.hasSliceName() ? o2.getSliceName() : "";
3849        cmp = name1.compareTo(name2);
3850      }
3851      return cmp;
3852    }
3853
3854    private static String normalizePath(ElementDefinition e) {
3855      if (!e.hasPath()) return "";
3856      String path = e.getPath();
3857      // if sorting element names make sure onset[x] appears before onsetAge, onsetDate, etc.
3858      // so strip off the [x] suffix when comparing the path names.
3859      if (path.endsWith("[x]")) {
3860        path = path.substring(0, path.length()-3);
3861      }
3862      return path;
3863    }
3864
3865  }
3866
3867
3868  // generate schematrons for the rules in a structure definition
3869  public void generateSchematrons(OutputStream dest, StructureDefinition structure) throws IOException, DefinitionException {
3870    if (structure.getDerivation() != TypeDerivationRule.CONSTRAINT)
3871      throw new DefinitionException("not the right kind of structure to generate schematrons for");
3872    if (!structure.hasSnapshot())
3873      throw new DefinitionException("needs a snapshot");
3874
3875        StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBaseDefinition());
3876
3877        if (base != null) {
3878          SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName());
3879
3880          ElementDefinition ed = structure.getSnapshot().getElement().get(0);
3881          generateForChildren(sch, "f:"+ed.getPath(), ed, structure, base);
3882          sch.dump();
3883        }
3884  }
3885
3886  // generate a CSV representation of the structure definition
3887  public void generateCsvs(OutputStream dest, StructureDefinition structure, boolean asXml) throws IOException, DefinitionException, Exception {
3888    if (!structure.hasSnapshot())
3889      throw new DefinitionException("needs a snapshot");
3890
3891    CSVWriter csv = new CSVWriter(dest, structure, asXml);
3892
3893    for (ElementDefinition child : structure.getSnapshot().getElement()) {
3894      csv.processElement(child);
3895    }
3896    csv.dump();
3897  }
3898  
3899  // generate an Excel representation of the structure definition
3900  public void generateXlsx(OutputStream dest, StructureDefinition structure, boolean asXml, boolean hideMustSupportFalse) throws IOException, DefinitionException, Exception {
3901    if (!structure.hasSnapshot())
3902      throw new DefinitionException("needs a snapshot");
3903
3904    XLSXWriter xlsx = new XLSXWriter(dest, structure, asXml, hideMustSupportFalse);
3905
3906    for (ElementDefinition child : structure.getSnapshot().getElement()) {
3907      xlsx.processElement(child);
3908    }
3909    xlsx.dump();
3910    xlsx.close();
3911  }
3912  
3913  private class Slicer extends ElementDefinitionSlicingComponent {
3914    String criteria = "";
3915    String name = "";   
3916    boolean check;
3917    public Slicer(boolean cantCheck) {
3918      super();
3919      this.check = cantCheck;
3920    }
3921  }
3922  
3923  private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, StructureDefinition structure) {
3924    // given a child in a structure, it's sliced. figure out the slicing xpath
3925    if (child.getPath().endsWith(".extension")) {
3926      ElementDefinition ued = getUrlFor(structure, child);
3927      if ((ued == null || !ued.hasFixed()) && !(child.hasType() && (child.getType().get(0).hasProfile())))
3928        return new Slicer(false);
3929      else {
3930      Slicer s = new Slicer(true);
3931      String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).getValue() : ((UriType) ued.getFixed()).asStringValue();
3932      s.name = " with URL = '"+url+"'";
3933      s.criteria = "[@url = '"+url+"']";
3934      return s;
3935      }
3936    } else
3937      return new Slicer(false);
3938  }
3939
3940  private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, StructureDefinition structure, StructureDefinition base) throws IOException {
3941    //    generateForChild(txt, structure, child);
3942    List<ElementDefinition> children = getChildList(structure, ed);
3943    String sliceName = null;
3944    ElementDefinitionSlicingComponent slicing = null;
3945    for (ElementDefinition child : children) {
3946      String name = tail(child.getPath());
3947      if (child.hasSlicing()) {
3948        sliceName = name;
3949        slicing = child.getSlicing();        
3950      } else if (!name.equals(sliceName))
3951        slicing = null;
3952      
3953      ElementDefinition based = getByPath(base, child.getPath());
3954      boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin()));
3955      boolean doMax = child.hasMax() && !child.getMax().equals("*") && (based == null || (!child.getMax().equals(based.getMax())));
3956      Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure);
3957      if (slicer.check) {
3958        if (doMin || doMax) {
3959          Section s = sch.section(xpath);
3960          Rule r = s.rule(xpath);
3961          if (doMin) 
3962            r.assrt("count(f:"+name+slicer.criteria+") >= "+Integer.toString(child.getMin()), name+slicer.name+": minimum cardinality of '"+name+"' is "+Integer.toString(child.getMin()));
3963          if (doMax) 
3964            r.assrt("count(f:"+name+slicer.criteria+") <= "+child.getMax(), name+slicer.name+": maximum cardinality of '"+name+"' is "+child.getMax());
3965          }
3966        }
3967      }
3968    for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
3969      if (inv.hasXpath()) {
3970        Section s = sch.section(ed.getPath());
3971        Rule r = s.rule(xpath);
3972        r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId()+": " : "")+inv.getHuman()+(inv.hasUserData(IS_DERIVED) ? " (inherited)" : ""));
3973      }
3974    }
3975    for (ElementDefinition child : children) {
3976      String name = tail(child.getPath());
3977      generateForChildren(sch, xpath+"/f:"+name, child, structure, base);
3978    }
3979  }
3980
3981
3982
3983
3984  private ElementDefinition getByPath(StructureDefinition base, String path) {
3985                for (ElementDefinition ed : base.getSnapshot().getElement()) {
3986                        if (ed.getPath().equals(path))
3987                                return ed;
3988                        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)))
3989                                return ed;
3990    }
3991          return null;
3992  }
3993
3994
3995  public void setIds(StructureDefinition sd, boolean checkFirst) throws DefinitionException  {
3996    if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) {
3997      if (!sd.hasDifferential())
3998        sd.setDifferential(new StructureDefinitionDifferentialComponent());
3999      generateIds(sd.getDifferential().getElement(), sd.getUrl());
4000    }
4001    if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) {
4002      if (!sd.hasSnapshot())
4003        sd.setSnapshot(new StructureDefinitionSnapshotComponent());
4004      generateIds(sd.getSnapshot().getElement(), sd.getUrl());
4005    }
4006  }
4007
4008
4009  private boolean hasMissingIds(List<ElementDefinition> list) {
4010    for (ElementDefinition ed : list) {
4011      if (!ed.hasId())
4012        return true;
4013    }    
4014    return false;
4015  }
4016
4017  public class SliceList {
4018
4019    private Map<String, String> slices = new HashMap<>();
4020    
4021    public void seeElement(ElementDefinition ed) {
4022      Iterator<Map.Entry<String,String>> iter = slices.entrySet().iterator();
4023      while (iter.hasNext()) {
4024        Map.Entry<String,String> entry = iter.next();
4025        if (entry.getKey().length() > ed.getPath().length() || entry.getKey().equals(ed.getPath()))
4026          iter.remove();
4027      }
4028      
4029      if (ed.hasSliceName()) 
4030        slices.put(ed.getPath(), ed.getSliceName());
4031    }
4032
4033    public String[] analyse(List<String> paths) {
4034      String s = paths.get(0);
4035      String[] res = new String[paths.size()];
4036      res[0] = null;
4037      for (int i = 1; i < paths.size(); i++) {
4038        s = s + "."+paths.get(i);
4039        if (slices.containsKey(s)) 
4040          res[i] = slices.get(s);
4041        else
4042          res[i] = null;
4043      }
4044      return res;
4045    }
4046
4047  }
4048
4049  private void generateIds(List<ElementDefinition> list, String name) throws DefinitionException  {
4050    if (list.isEmpty())
4051      return;
4052    
4053    Map<String, String> idMap = new HashMap<String, String>();
4054    Map<String, String> idList = new HashMap<String, String>();
4055    
4056    SliceList sliceInfo = new SliceList();
4057    // first pass, update the element ids
4058    for (ElementDefinition ed : list) {
4059      List<String> paths = new ArrayList<String>();
4060      if (!ed.hasPath())
4061        throw new DefinitionException("No path on element Definition "+Integer.toString(list.indexOf(ed))+" in "+name);
4062      sliceInfo.seeElement(ed);
4063      String[] pl = ed.getPath().split("\\.");
4064      for (int i = paths.size(); i < pl.length; i++) // -1 because the last path is in focus
4065        paths.add(pl[i]);
4066      String slices[] = sliceInfo.analyse(paths);
4067      
4068      StringBuilder b = new StringBuilder();
4069      b.append(paths.get(0));
4070      for (int i = 1; i < paths.size(); i++) {
4071        b.append(".");
4072        String s = paths.get(i);
4073        String p = slices[i];
4074        b.append(s);
4075        if (p != null) {
4076          b.append(":");
4077          b.append(p);
4078        }
4079      }
4080      String bs = b.toString();
4081      idMap.put(ed.hasId() ? ed.getId() : ed.getPath(), bs);
4082      ed.setId(bs);
4083      if (idList.containsKey(bs)) {
4084        if (exception || messages == null)
4085          throw new DefinitionException("Same id '"+bs+"'on multiple elements "+idList.get(bs)+"/"+ed.getPath()+" in "+name);
4086        else
4087          messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, name+"."+bs, "Duplicate Element id "+bs, ValidationMessage.IssueSeverity.ERROR));
4088      }
4089      idList.put(bs, ed.getPath());
4090      if (ed.hasContentReference()) {
4091        String s = ed.getContentReference().substring(1);
4092        if (idMap.containsKey(s))
4093          ed.setContentReference("#"+idMap.get(s));
4094        
4095      }
4096    }  
4097    // second path - fix up any broken path based id references
4098    
4099  }
4100
4101
4102//  private String describeExtension(ElementDefinition ed) {
4103//    if (!ed.hasType() || !ed.getTypeFirstRep().hasProfile())
4104//      return "";
4105//    return "$"+urlTail(ed.getTypeFirstRep().getProfile());
4106//  }
4107//
4108
4109  private String urlTail(String profile) {
4110    return profile.contains("/") ? profile.substring(profile.lastIndexOf("/")+1) : profile;
4111  }
4112
4113
4114  private String checkName(String name) {
4115//    if (name.contains("."))
4116////      throw new Exception("Illegal name "+name+": no '.'");
4117//    if (name.contains(" "))
4118//      throw new Exception("Illegal name "+name+": no spaces");
4119    StringBuilder b = new StringBuilder();
4120    for (char c : name.toCharArray()) {
4121      if (!Utilities.existsInList(c, '.', ' ', ':', '"', '\'', '(', ')', '&', '[', ']'))
4122        b.append(c);
4123    }
4124    return b.toString().toLowerCase();
4125  }
4126
4127
4128  private int charCount(String path, char t) {
4129    int res = 0;
4130    for (char ch : path.toCharArray()) {
4131      if (ch == t)
4132        res++;
4133    }
4134    return res;
4135  }
4136
4137//
4138//private void generateForChild(TextStreamWriter txt,
4139//    StructureDefinition structure, ElementDefinition child) {
4140//  // TODO Auto-generated method stub
4141//
4142//}
4143
4144  private interface ExampleValueAccessor {
4145    Type getExampleValue(ElementDefinition ed);
4146    String getId();
4147  }
4148
4149  private class BaseExampleValueAccessor implements ExampleValueAccessor {
4150    @Override
4151    public Type getExampleValue(ElementDefinition ed) {
4152      if (ed.hasFixed())
4153        return ed.getFixed();
4154      if (ed.hasExample())
4155        return ed.getExample().get(0).getValue();
4156      else
4157        return null;
4158    }
4159
4160    @Override
4161    public String getId() {
4162      return "-genexample";
4163    }
4164  }
4165  
4166  private class ExtendedExampleValueAccessor implements ExampleValueAccessor {
4167    private String index;
4168
4169    public ExtendedExampleValueAccessor(String index) {
4170      this.index = index;
4171    }
4172    @Override
4173    public Type getExampleValue(ElementDefinition ed) {
4174      if (ed.hasFixed())
4175        return ed.getFixed();
4176      for (Extension ex : ed.getExtension()) {
4177       String ndx = ToolingExtensions.readStringExtension(ex, "index");
4178       Type value = ToolingExtensions.getExtension(ex, "exValue").getValue();
4179       if (index.equals(ndx) && value != null)
4180         return value;
4181      }
4182      return null;
4183    }
4184    @Override
4185    public String getId() {
4186      return "-genexample-"+index;
4187    }
4188  }
4189  
4190  public List<org.hl7.fhir.r4.elementmodel.Element> generateExamples(StructureDefinition sd, boolean evenWhenNoExamples) throws FHIRException {
4191    List<org.hl7.fhir.r4.elementmodel.Element> examples = new ArrayList<org.hl7.fhir.r4.elementmodel.Element>();
4192    if (sd.hasSnapshot()) {
4193      if (evenWhenNoExamples || hasAnyExampleValues(sd)) 
4194        examples.add(generateExample(sd, new BaseExampleValueAccessor()));
4195      for (int i = 1; i <= 50; i++) {
4196        if (hasAnyExampleValues(sd, Integer.toString(i))) 
4197          examples.add(generateExample(sd, new ExtendedExampleValueAccessor(Integer.toString(i))));
4198      }
4199    }
4200    return examples;
4201  }
4202
4203  private org.hl7.fhir.r4.elementmodel.Element generateExample(StructureDefinition profile, ExampleValueAccessor accessor) throws FHIRException {
4204    ElementDefinition ed = profile.getSnapshot().getElementFirstRep();
4205    org.hl7.fhir.r4.elementmodel.Element r = new org.hl7.fhir.r4.elementmodel.Element(ed.getPath(), new Property(context, ed, profile));
4206    List<ElementDefinition> children = getChildMap(profile, ed);
4207    for (ElementDefinition child : children) {
4208      if (child.getPath().endsWith(".id")) {
4209        org.hl7.fhir.r4.elementmodel.Element id = new org.hl7.fhir.r4.elementmodel.Element("id", new Property(context, child, profile));
4210        id.setValue(profile.getId()+accessor.getId());
4211        r.getChildren().add(id);
4212      } else { 
4213        org.hl7.fhir.r4.elementmodel.Element e = createExampleElement(profile, child, accessor);
4214        if (e != null)
4215          r.getChildren().add(e);
4216      }
4217    }
4218    return r;
4219  }
4220
4221  private org.hl7.fhir.r4.elementmodel.Element createExampleElement(StructureDefinition profile, ElementDefinition ed, ExampleValueAccessor accessor) throws FHIRException {
4222    Type v = accessor.getExampleValue(ed);
4223    if (v != null) {
4224      return new ObjectConverter(context).convert(new Property(context, ed, profile), v);
4225    } else {
4226      org.hl7.fhir.r4.elementmodel.Element res = new org.hl7.fhir.r4.elementmodel.Element(tail(ed.getPath()), new Property(context, ed, profile));
4227      boolean hasValue = false;
4228      List<ElementDefinition> children = getChildMap(profile, ed);
4229      for (ElementDefinition child : children) {
4230        if (!child.hasContentReference()) {
4231        org.hl7.fhir.r4.elementmodel.Element e = createExampleElement(profile, child, accessor);
4232        if (e != null) {
4233          hasValue = true;
4234          res.getChildren().add(e);
4235        }
4236      }
4237      }
4238      if (hasValue)
4239        return res;
4240      else
4241        return null;
4242    }
4243  }
4244
4245  private boolean hasAnyExampleValues(StructureDefinition sd, String index) {
4246    for (ElementDefinition ed : sd.getSnapshot().getElement())
4247      for (Extension ex : ed.getExtension()) {
4248        String ndx = ToolingExtensions.readStringExtension(ex, "index");
4249        Extension exv = ToolingExtensions.getExtension(ex, "exValue");
4250        if (exv != null) {
4251          Type value = exv.getValue();
4252        if (index.equals(ndx) && value != null)
4253          return true;
4254        }
4255       }
4256    return false;
4257  }
4258
4259
4260  private boolean hasAnyExampleValues(StructureDefinition sd) {
4261    for (ElementDefinition ed : sd.getSnapshot().getElement())
4262      if (ed.hasExample())
4263        return true;
4264    return false;
4265  }
4266
4267
4268  public void populateLogicalSnapshot(StructureDefinition sd) throws FHIRException {
4269    sd.getSnapshot().getElement().add(sd.getDifferential().getElementFirstRep().copy());
4270    
4271    if (sd.hasBaseDefinition()) {
4272    StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
4273    if (base == null)
4274      throw new FHIRException("Unable to find base definition for logical model: "+sd.getBaseDefinition()+" from "+sd.getUrl());
4275    copyElements(sd, base.getSnapshot().getElement());
4276    }
4277    copyElements(sd, sd.getDifferential().getElement());
4278  }
4279
4280
4281  private void copyElements(StructureDefinition sd, List<ElementDefinition> list) {
4282    for (ElementDefinition ed : list) {
4283      if (ed.getPath().contains(".")) {
4284        ElementDefinition n = ed.copy();
4285        n.setPath(sd.getSnapshot().getElementFirstRep().getPath()+"."+ed.getPath().substring(ed.getPath().indexOf(".")+1));
4286        sd.getSnapshot().addElement(n);
4287      }
4288    }
4289  }
4290
4291    
4292  public void cleanUpDifferential(StructureDefinition sd) {
4293    if (sd.getDifferential().getElement().size() > 1)
4294      cleanUpDifferential(sd, 1);
4295  }
4296  
4297  private void cleanUpDifferential(StructureDefinition sd, int start) {
4298    int level = Utilities.charCount(sd.getDifferential().getElement().get(start).getPath(), '.');
4299    int c = start;
4300    int len = sd.getDifferential().getElement().size();
4301    HashSet<String> paths = new HashSet<String>();
4302    while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') == level) {
4303      ElementDefinition ed = sd.getDifferential().getElement().get(c);
4304      if (!paths.contains(ed.getPath())) {
4305        paths.add(ed.getPath());
4306        int ic = c+1; 
4307        while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 
4308          ic++;
4309        ElementDefinition slicer = null;
4310        List<ElementDefinition> slices = new ArrayList<ElementDefinition>();
4311        slices.add(ed);
4312        while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') == level) {
4313          ElementDefinition edi = sd.getDifferential().getElement().get(ic);
4314          if (ed.getPath().equals(edi.getPath())) {
4315            if (slicer == null) {
4316              slicer = new ElementDefinition();
4317              slicer.setPath(edi.getPath());
4318              slicer.getSlicing().setRules(SlicingRules.OPEN);
4319              sd.getDifferential().getElement().add(c, slicer);
4320              c++;
4321              ic++;
4322            }
4323            slices.add(edi);
4324          }
4325          ic++;
4326          while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 
4327            ic++;
4328        }
4329        // now we're at the end, we're going to figure out the slicing discriminator
4330        if (slicer != null)
4331          determineSlicing(slicer, slices);
4332      }
4333      c++;
4334      if (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) {
4335        cleanUpDifferential(sd, c);
4336        c++;
4337        while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) 
4338          c++;
4339      }
4340  }
4341  }
4342
4343
4344  private void determineSlicing(ElementDefinition slicer, List<ElementDefinition> slices) {
4345    // first, name them
4346    int i = 0;
4347    for (ElementDefinition ed : slices) {
4348      if (ed.hasUserData("slice-name")) {
4349        ed.setSliceName(ed.getUserString("slice-name"));
4350      } else {
4351        i++;
4352        ed.setSliceName("slice-"+Integer.toString(i));
4353      }
4354    }
4355    // now, the hard bit, how are they differentiated? 
4356    // right now, we hard code this...
4357    if (slicer.getPath().endsWith(".extension") || slicer.getPath().endsWith(".modifierExtension"))
4358      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url");
4359    else if (slicer.getPath().equals("DiagnosticReport.result"))
4360      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("reference.code");
4361    else if (slicer.getPath().equals("Observation.related"))
4362      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("target.reference.code");
4363    else if (slicer.getPath().equals("Bundle.entry"))
4364      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("resource.@profile");
4365    else  
4366      throw new Error("No slicing for "+slicer.getPath()); 
4367  }
4368
4369  public class SpanEntry {
4370    private List<SpanEntry> children = new ArrayList<SpanEntry>();
4371    private boolean profile;
4372    private String id;
4373    private String name;
4374    private String resType;
4375    private String cardinality;
4376    private String description;
4377    private String profileLink;
4378    private String resLink;
4379    private String type;
4380    
4381    public String getName() {
4382      return name;
4383    }
4384    public void setName(String name) {
4385      this.name = name;
4386    }
4387    public String getResType() {
4388      return resType;
4389    }
4390    public void setResType(String resType) {
4391      this.resType = resType;
4392    }
4393    public String getCardinality() {
4394      return cardinality;
4395    }
4396    public void setCardinality(String cardinality) {
4397      this.cardinality = cardinality;
4398    }
4399    public String getDescription() {
4400      return description;
4401    }
4402    public void setDescription(String description) {
4403      this.description = description;
4404    }
4405    public String getProfileLink() {
4406      return profileLink;
4407    }
4408    public void setProfileLink(String profileLink) {
4409      this.profileLink = profileLink;
4410    }
4411    public String getResLink() {
4412      return resLink;
4413    }
4414    public void setResLink(String resLink) {
4415      this.resLink = resLink;
4416    }
4417    public String getId() {
4418      return id;
4419    }
4420    public void setId(String id) {
4421      this.id = id;
4422    }
4423    public boolean isProfile() {
4424      return profile;
4425    }
4426    public void setProfile(boolean profile) {
4427      this.profile = profile;
4428    }
4429    public List<SpanEntry> getChildren() {
4430      return children;
4431    }
4432    public String getType() {
4433      return type;
4434    }
4435    public void setType(String type) {
4436      this.type = type;
4437    }
4438    
4439  }
4440
4441  public XhtmlNode generateSpanningTable(StructureDefinition profile, String imageFolder, boolean onlyConstraints, String constraintPrefix, Set<String> outputTracker) throws IOException, FHIRException {
4442    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, false, true);
4443    gen.setTranslator(getTranslator());
4444    TableModel model = initSpanningTable(gen, "", false, profile.getId());
4445    Set<String> processed = new HashSet<String>();
4446    SpanEntry span = buildSpanningTable("(focus)", "", profile, processed, onlyConstraints, constraintPrefix);
4447    
4448    genSpanEntry(gen, model.getRows(), span);
4449    return gen.generate(model, "", 0, outputTracker);
4450  }
4451
4452  private SpanEntry buildSpanningTable(String name, String cardinality, StructureDefinition profile, Set<String> processed, boolean onlyConstraints, String constraintPrefix) throws IOException {
4453    SpanEntry res = buildSpanEntryFromProfile(name, cardinality, profile);
4454    boolean wantProcess = !processed.contains(profile.getUrl());
4455    processed.add(profile.getUrl());
4456    if (wantProcess && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
4457      for (ElementDefinition ed : profile.getSnapshot().getElement()) {
4458        if (!"0".equals(ed.getMax()) && ed.getType().size() > 0) {
4459          String card = getCardinality(ed, profile.getSnapshot().getElement());
4460          if (!card.endsWith(".0")) {
4461            List<String> refProfiles = listReferenceProfiles(ed);
4462            if (refProfiles.size() > 0) {
4463              String uri = refProfiles.get(0);
4464              if (uri != null) {
4465                StructureDefinition sd = context.fetchResource(StructureDefinition.class, uri);
4466                if (sd != null && (!onlyConstraints || (sd.getDerivation() == TypeDerivationRule.CONSTRAINT && (constraintPrefix == null || sd.getUrl().startsWith(constraintPrefix))))) {
4467                  res.getChildren().add(buildSpanningTable(nameForElement(ed), card, sd, processed, onlyConstraints, constraintPrefix));
4468                }
4469              }
4470            }
4471          }
4472        } 
4473      }
4474    }
4475    return res;
4476  }
4477
4478
4479  private String getCardinality(ElementDefinition ed, List<ElementDefinition> list) {
4480    int min = ed.getMin();
4481    int max = !ed.hasMax() || ed.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(ed.getMax());
4482    while (ed != null && ed.getPath().contains(".")) {
4483      ed = findParent(ed, list);
4484      if (ed.getMax().equals("0"))
4485        max = 0;
4486      else if (!ed.getMax().equals("1") && !ed.hasSlicing())
4487        max = Integer.MAX_VALUE;
4488      if (ed.getMin() == 0)
4489        min = 0;
4490    }
4491    return Integer.toString(min)+".."+(max == Integer.MAX_VALUE ? "*" : Integer.toString(max));
4492  }
4493
4494
4495  private ElementDefinition findParent(ElementDefinition ed, List<ElementDefinition> list) {
4496    int i = list.indexOf(ed)-1;
4497    while (i >= 0 && !ed.getPath().startsWith(list.get(i).getPath()+"."))
4498      i--;
4499    if (i == -1)
4500      return null;
4501    else
4502      return list.get(i);
4503  }
4504
4505
4506  private List<String> listReferenceProfiles(ElementDefinition ed) {
4507    List<String> res = new ArrayList<String>();
4508    for (TypeRefComponent tr : ed.getType()) {
4509      // code is null if we're dealing with "value" and profile is null if we just have Reference()
4510      if (tr.hasTarget() && tr.hasTargetProfile())
4511        for (UriType u : tr.getTargetProfile())
4512          res.add(u.getValue());
4513    }
4514    return res;
4515  }
4516
4517
4518  private String nameForElement(ElementDefinition ed) {
4519    return ed.getPath().substring(ed.getPath().indexOf(".")+1);
4520  }
4521
4522
4523  private SpanEntry buildSpanEntryFromProfile(String name, String cardinality, StructureDefinition profile) throws IOException {
4524    SpanEntry res = new SpanEntry();
4525    res.setName(name);
4526    res.setCardinality(cardinality);
4527    res.setProfileLink(profile.getUserString("path"));
4528    res.setResType(profile.getType());
4529    StructureDefinition base = context.fetchResource(StructureDefinition.class, res.getResType());
4530    if (base != null)
4531      res.setResLink(base.getUserString("path"));
4532    res.setId(profile.getId());
4533    res.setProfile(profile.getDerivation() == TypeDerivationRule.CONSTRAINT);
4534    StringBuilder b = new StringBuilder();
4535    b.append(res.getResType());
4536    boolean first = true;
4537    boolean open = false;
4538    if (profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
4539      res.setDescription(profile.getName());
4540      for (ElementDefinition ed : profile.getSnapshot().getElement()) {
4541        if (isKeyProperty(ed.getBase().getPath()) && ed.hasFixed()) {
4542          if (first) {
4543            open = true;
4544            first = false;
4545            b.append("[");
4546          } else {
4547            b.append(", ");
4548          }
4549          b.append(tail(ed.getBase().getPath()));
4550          b.append("=");
4551          b.append(summarize(ed.getFixed()));
4552        }
4553      }
4554      if (open)
4555        b.append("]");
4556    } else
4557      res.setDescription("Base FHIR "+profile.getName());
4558    res.setType(b.toString());
4559    return res ;
4560  }
4561
4562
4563  private String summarize(Type value) throws IOException {
4564    if (value instanceof Coding)
4565      return summarizeCoding((Coding) value);
4566    else if (value instanceof CodeableConcept)
4567      return summarizeCodeableConcept((CodeableConcept) value);
4568    else
4569      return buildJson(value);
4570  }
4571
4572
4573  private String summarizeCoding(Coding value) {
4574    String uri = value.getSystem();
4575    String system = NarrativeGenerator.describeSystem(uri);
4576    if (Utilities.isURL(system)) {
4577      if (system.equals("http://cap.org/protocols"))
4578        system = "CAP Code";
4579    }
4580    return system+" "+value.getCode();
4581  }
4582
4583
4584  private String summarizeCodeableConcept(CodeableConcept value) {
4585    if (value.hasCoding())
4586      return summarizeCoding(value.getCodingFirstRep());
4587    else
4588      return value.getText();
4589  }
4590
4591
4592  private boolean isKeyProperty(String path) {
4593    return Utilities.existsInList(path, "Observation.code");
4594  }
4595
4596
4597  public TableModel initSpanningTable(HierarchicalTableGenerator gen, String prefix, boolean isLogical, String id) {
4598    TableModel model = gen.new TableModel(id, false);
4599    
4600    model.setDocoImg(prefix+"help16.png");
4601    model.setDocoRef(prefix+"formats.html#table"); // todo: change to graph definition
4602    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Property", "A profiled resource", null, 0));
4603    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));
4604    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Content", "What goes here", null, 0));
4605    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Description", "Description of the profile", null, 0));
4606    return model;
4607  }
4608
4609  private void genSpanEntry(HierarchicalTableGenerator gen, List<Row> rows, SpanEntry span) throws IOException {
4610    Row row = gen.new Row();
4611    rows.add(row);
4612    row.setAnchor(span.getId());
4613    //row.setColor(..?);
4614    if (span.isProfile()) 
4615      row.setIcon("icon_profile.png", HierarchicalTableGenerator.TEXT_ICON_PROFILE);
4616    else
4617      row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
4618    
4619    row.getCells().add(gen.new Cell(null, null, span.getName(), null, null));
4620    row.getCells().add(gen.new Cell(null, null, span.getCardinality(), null, null));
4621    row.getCells().add(gen.new Cell(null, span.getProfileLink(), span.getType(), null, null));
4622    row.getCells().add(gen.new Cell(null, null, span.getDescription(), null, null));
4623
4624    for (SpanEntry child : span.getChildren())
4625      genSpanEntry(gen, row.getSubRows(), child);
4626  }
4627
4628
4629  public static ElementDefinitionSlicingDiscriminatorComponent interpretR2Discriminator(String discriminator, boolean isExists) {
4630    if (discriminator.endsWith("@pattern"))
4631      return makeDiscriminator(DiscriminatorType.PATTERN, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 
4632    if (discriminator.endsWith("@profile"))
4633      return makeDiscriminator(DiscriminatorType.PROFILE, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 
4634    if (discriminator.endsWith("@type")) 
4635      return makeDiscriminator(DiscriminatorType.TYPE, discriminator.length() == 5 ? "" : discriminator.substring(0,discriminator.length()-6));
4636    if (discriminator.endsWith("@exists"))
4637      return makeDiscriminator(DiscriminatorType.EXISTS, discriminator.length() == 7 ? "" : discriminator.substring(0,discriminator.length()-8)); 
4638    if (isExists)
4639      return makeDiscriminator(DiscriminatorType.EXISTS, discriminator); 
4640    return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(discriminator);
4641  }
4642
4643
4644  private static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType dType, String str) {
4645    return new ElementDefinitionSlicingDiscriminatorComponent().setType(dType).setPath(Utilities.noString(str)? "$this" : str);
4646  }
4647
4648
4649  public static String buildR2Discriminator(ElementDefinitionSlicingDiscriminatorComponent t) throws FHIRException {
4650    switch (t.getType()) {
4651    case PROFILE: return t.getPath()+"/@profile";
4652    case TYPE: return t.getPath()+"/@type";
4653    case VALUE: return t.getPath();
4654    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
4655    default: throw new FHIRException("Unable to represent "+t.getType().toCode()+":"+t.getPath()+" in R2");    
4656    }
4657  }
4658
4659
4660  public static StructureDefinition makeExtensionForVersionedURL(IWorkerContext context, String url) {
4661    String epath = url.substring(54);
4662    if (!epath.contains("."))
4663      return null;
4664    String type = epath.substring(0, epath.indexOf("."));
4665    StructureDefinition sd = context.fetchTypeDefinition(type);
4666    if (sd == null)
4667      return null;
4668    ElementDefinition ed = null;
4669    for (ElementDefinition t : sd.getSnapshot().getElement()) {
4670      if (t.getPath().equals(epath)) {
4671        ed = t;
4672        break;
4673      }
4674    }
4675    if (ed == null)
4676      return null;
4677    if ("Element".equals(ed.typeSummary()) || "BackboneElement".equals(ed.typeSummary())) {
4678      return null;
4679    } else {
4680      StructureDefinition template = context.fetchResource(StructureDefinition.class, "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities");
4681      StructureDefinition ext = template.copy();
4682      ext.setUrl(url);
4683      ext.setId("extension-"+epath);
4684      ext.setName("Extension-"+epath);
4685      ext.setTitle("Extension for r4 "+epath);
4686      ext.setStatus(sd.getStatus());
4687      ext.setDate(sd.getDate());
4688      ext.getContact().clear();
4689      ext.getContact().addAll(sd.getContact());
4690      ext.setFhirVersion(sd.getFhirVersion());
4691      ext.setDescription(ed.getDefinition());
4692      ext.getContext().clear();
4693      ext.addContext().setType(ExtensionContextType.ELEMENT).setExpression(epath.substring(0, epath.lastIndexOf(".")));
4694      ext.getDifferential().getElement().clear();
4695      ext.getSnapshot().getElement().get(3).setFixed(new UriType(url));
4696      ext.getSnapshot().getElement().set(4, ed.copy());
4697      ext.getSnapshot().getElement().get(4).setPath("Extension.value"+Utilities.capitalize(ed.typeSummary()));
4698      return ext;      
4699    }
4700
4701  }
4702
4703
4704  public boolean isThrowException() {
4705    return exception;
4706  }
4707
4708
4709  public void setThrowException(boolean exception) {
4710    this.exception = exception;
4711  }
4712
4713
4714  public TerminologyServiceOptions getTerminologyServiceOptions() {
4715    return terminologyServiceOptions;
4716  }
4717
4718
4719  public void setTerminologyServiceOptions(TerminologyServiceOptions terminologyServiceOptions) {
4720    this.terminologyServiceOptions = terminologyServiceOptions;
4721  }
4722
4723
4724  public boolean isNewSlicingProcessing() {
4725    return newSlicingProcessing;
4726  }
4727
4728
4729  public void setNewSlicingProcessing(boolean newSlicingProcessing) {
4730    this.newSlicingProcessing = newSlicingProcessing;
4731  }
4732
4733
4734  public boolean isDebug() {
4735    return debug;
4736  }
4737
4738
4739  public void setDebug(boolean debug) {
4740    this.debug = debug;
4741  }
4742
4743
4744
4745  
4746}