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