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