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      // todo: what to do about conditions?
1845      // condition : id 0..*
1846
1847      if (derived.hasMustSupportElement()) {
1848        if (!(base.hasMustSupportElement() && Base.compareDeep(derived.getMustSupportElement(), base.getMustSupportElement(), false)))
1849          base.setMustSupportElement(derived.getMustSupportElement().copy());
1850        else if (trimDifferential)
1851          derived.setMustSupportElement(null);
1852        else
1853          derived.getMustSupportElement().setUserData(DERIVATION_EQUALS, true);
1854      }
1855
1856
1857      // profiles cannot change : isModifier, defaultValue, meaningWhenMissing
1858      // but extensions can change isModifier
1859      if (isExtension) {
1860        if (derived.hasIsModifierElement() && !(base.hasIsModifierElement() && Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false)))
1861          base.setIsModifierElement(derived.getIsModifierElement().copy());
1862        else if (trimDifferential)
1863          derived.setIsModifierElement(null);
1864        else if (derived.hasIsModifierElement())
1865          derived.getIsModifierElement().setUserData(DERIVATION_EQUALS, true);
1866        if (derived.hasIsModifierReasonElement() && !(base.hasIsModifierReasonElement() && Base.compareDeep(derived.getIsModifierReasonElement(), base.getIsModifierReasonElement(), false)))
1867          base.setIsModifierReasonElement(derived.getIsModifierReasonElement().copy());
1868        else if (trimDifferential)
1869          derived.setIsModifierReasonElement(null);
1870        else if (derived.hasIsModifierReasonElement())
1871          derived.getIsModifierReasonElement().setUserData(DERIVATION_EQUALS, true);
1872      }
1873
1874      if (derived.hasBinding()) {
1875        if (!base.hasBinding() || !Base.compareDeep(derived.getBinding(), base.getBinding(), false)) {
1876          if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && derived.getBinding().getStrength() != BindingStrength.REQUIRED)
1877            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));
1878//            throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode());
1879          else if (base.hasBinding() && derived.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && base.getBinding().hasValueSet() && derived.getBinding().hasValueSet()) {
1880            ValueSet baseVs = context.fetchResource(ValueSet.class, base.getBinding().getValueSet());
1881            ValueSet contextVs = context.fetchResource(ValueSet.class, derived.getBinding().getValueSet());
1882            if (baseVs == null) {
1883              messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING));
1884            } else if (contextVs == null) {
1885              messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be located", ValidationMessage.IssueSeverity.WARNING));
1886            } else {
1887              ValueSetExpansionOutcome expBase = context.expandVS(baseVs, true, false);
1888              ValueSetExpansionOutcome expDerived = context.expandVS(contextVs, true, false);
1889              if (expBase.getValueset() == null)
1890                messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING));
1891              else if (expDerived.getValueset() == null)
1892                messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSet()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING));
1893              else if (!isSubset(expBase.getValueset(), expDerived.getValueset()))
1894                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));
1895
1896            }
1897          }
1898          base.setBinding(derived.getBinding().copy());
1899        } else if (trimDifferential)
1900          derived.setBinding(null);
1901        else
1902          derived.getBinding().setUserData(DERIVATION_EQUALS, true);
1903      } // else if (base.hasBinding() && doesn't have bindable type )
1904        //  base
1905
1906      if (derived.hasIsSummaryElement()) {
1907        if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false)) {
1908          if (base.hasIsSummary())
1909            throw new Error("Error in profile "+pn+" at "+derived.getPath()+": Base isSummary = "+base.getIsSummaryElement().asStringValue()+", derived isSummary = "+derived.getIsSummaryElement().asStringValue());
1910          base.setIsSummaryElement(derived.getIsSummaryElement().copy());
1911        } else if (trimDifferential)
1912          derived.setIsSummaryElement(null);
1913        else
1914          derived.getIsSummaryElement().setUserData(DERIVATION_EQUALS, true);
1915      }
1916
1917      if (derived.hasType()) {
1918        if (!Base.compareDeep(derived.getType(), base.getType(), false)) {
1919          if (base.hasType()) {
1920            for (TypeRefComponent ts : derived.getType()) {
1921//              if (!ts.hasCode()) { // ommitted in the differential; copy it over....
1922//                if (base.getType().size() > 1) 
1923//                  throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": constrained type code must be present if there are multiple types ("+base.typeSummary()+")");
1924//                if (base.getType().get(0).getCode() != null)
1925//                  ts.setCode(base.getType().get(0).getCode());
1926//              }
1927              boolean ok = false;
1928              CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1929              String t = ts.getWorkingCode();
1930              for (TypeRefComponent td : base.getType()) {;
1931                String tt = td.getWorkingCode();
1932                b.append(tt);
1933                if (td.hasCode() && (tt.equals(t) || "Extension".equals(tt) || (t.equals("uri") && tt.equals("string"))  || // work around for old badly generated SDs
1934                    "Element".equals(tt) || "*".equals(tt) ||
1935                    (("Resource".equals(tt) || ("DomainResource".equals(tt)) && pkp.isResource(t)))))
1936                  ok = true;
1937              }
1938              if (!ok)
1939                throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal constrained type "+t+" from "+b.toString()+" in "+srcSD.getUrl());
1940            }
1941          }
1942          base.getType().clear();
1943          for (TypeRefComponent t : derived.getType()) {
1944            TypeRefComponent tt = t.copy();
1945//            tt.setUserData(DERIVATION_EQUALS, true);
1946            base.getType().add(tt);
1947          }
1948        }
1949        else if (trimDifferential)
1950          derived.getType().clear();
1951        else
1952          for (TypeRefComponent t : derived.getType())
1953            t.setUserData(DERIVATION_EQUALS, true);
1954      }
1955
1956      if (derived.hasMapping()) {
1957        // todo: mappings are not cumulative - one replaces another
1958        if (!Base.compareDeep(derived.getMapping(), base.getMapping(), false)) {
1959          for (ElementDefinitionMappingComponent s : derived.getMapping()) {
1960            boolean found = false;
1961            for (ElementDefinitionMappingComponent d : base.getMapping()) {
1962              found = found || (d.getIdentity().equals(s.getIdentity()) && d.getMap().equals(s.getMap()));
1963            }
1964            if (!found)
1965              base.getMapping().add(s);
1966          }
1967        }
1968        else if (trimDifferential)
1969          derived.getMapping().clear();
1970        else
1971          for (ElementDefinitionMappingComponent t : derived.getMapping())
1972            t.setUserData(DERIVATION_EQUALS, true);
1973      }
1974
1975      // todo: constraints are cumulative. there is no replacing
1976      for (ElementDefinitionConstraintComponent s : base.getConstraint()) { 
1977        s.setUserData(IS_DERIVED, true);
1978        if (!s.hasSource())
1979          s.setSource(base.getId());
1980      }
1981      if (derived.hasConstraint()) {
1982        for (ElementDefinitionConstraintComponent s : derived.getConstraint()) {
1983          ElementDefinitionConstraintComponent inv = s.copy();
1984          base.getConstraint().add(inv);
1985        }
1986      }
1987      
1988      // now, check that we still have a bindable type; if not, delete the binding - see task 8477
1989      if (dest.hasBinding() && !hasBindableType(dest))
1990        dest.setBinding(null);
1991        
1992      // finally, we copy any extensions from source to dest
1993      for (Extension ex : derived.getExtension()) {
1994        StructureDefinition sd  = context.fetchResource(StructureDefinition.class, ex.getUrl());
1995        if (sd == null || sd.getSnapshot() == null || sd.getSnapshot().getElementFirstRep().getMax().equals("1"))
1996          ToolingExtensions.removeExtension(dest, ex.getUrl());
1997        dest.addExtension(ex.copy());
1998      }
1999    }
2000  }
2001
2002  private boolean hasBindableType(ElementDefinition ed) {
2003    for (TypeRefComponent tr : ed.getType()) {
2004      if (Utilities.existsInList(tr.getWorkingCode(), "Coding", "CodeableConcept", "Quantity", "uri", "string", "code"))
2005        return true;
2006    }
2007    return false;
2008  }
2009
2010
2011  private boolean isLargerMax(String derived, String base) {
2012    if ("*".equals(base))
2013      return false;
2014    if ("*".equals(derived))
2015      return true;
2016    return Integer.parseInt(derived) > Integer.parseInt(base);
2017  }
2018
2019
2020  private boolean isSubset(ValueSet expBase, ValueSet expDerived) {
2021    return codesInExpansion(expDerived.getExpansion().getContains(), expBase.getExpansion());
2022  }
2023
2024
2025  private boolean codesInExpansion(List<ValueSetExpansionContainsComponent> contains, ValueSetExpansionComponent expansion) {
2026    for (ValueSetExpansionContainsComponent cc : contains) {
2027      if (!inExpansion(cc, expansion.getContains()))
2028        return false;
2029      if (!codesInExpansion(cc.getContains(), expansion))
2030        return false;
2031    }
2032    return true;
2033  }
2034
2035
2036  private boolean inExpansion(ValueSetExpansionContainsComponent cc, List<ValueSetExpansionContainsComponent> contains) {
2037    for (ValueSetExpansionContainsComponent cc1 : contains) {
2038      if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode()))
2039        return true;
2040      if (inExpansion(cc,  cc1.getContains()))
2041        return true;
2042    }
2043    return false;
2044  }
2045
2046  public void closeDifferential(StructureDefinition base, StructureDefinition derived) throws FHIRException {
2047    for (ElementDefinition edb : base.getSnapshot().getElement()) {
2048      if (isImmediateChild(edb) && !edb.getPath().endsWith(".id")) {
2049        ElementDefinition edm = getMatchInDerived(edb, derived.getDifferential().getElement());
2050        if (edm == null) {
2051          ElementDefinition edd = derived.getDifferential().addElement();
2052          edd.setPath(edb.getPath());
2053          edd.setMax("0");
2054        } else if (edb.hasSlicing()) {
2055          closeChildren(base, edb, derived, edm);
2056        }
2057      }
2058    }
2059    sortDifferential(base, derived, derived.getName(), new ArrayList<String>());
2060  }
2061
2062  private void closeChildren(StructureDefinition base, ElementDefinition edb, StructureDefinition derived, ElementDefinition edm) {
2063    String path = edb.getPath()+".";
2064    int baseStart = base.getSnapshot().getElement().indexOf(edb);
2065    int baseEnd = findEnd(base.getSnapshot().getElement(), edb, baseStart+1);
2066    int diffStart = derived.getDifferential().getElement().indexOf(edm);
2067    int diffEnd = findEnd(derived.getDifferential().getElement(), edm, diffStart+1);
2068    
2069    for (int cBase = baseStart; cBase < baseEnd; cBase++) {
2070      ElementDefinition edBase = base.getSnapshot().getElement().get(cBase);
2071      if (isImmediateChild(edBase, edb)) {
2072        ElementDefinition edMatch = getMatchInDerived(edBase, derived.getDifferential().getElement(), diffStart, diffEnd);
2073        if (edMatch == null) {
2074          ElementDefinition edd = derived.getDifferential().addElement();
2075          edd.setPath(edBase.getPath());
2076          edd.setMax("0");
2077        } else {
2078          closeChildren(base, edBase, derived, edMatch);
2079        }        
2080      }
2081    }
2082  }
2083
2084
2085
2086
2087  private int findEnd(List<ElementDefinition> list, ElementDefinition ed, int cursor) {
2088    String path = ed.getPath()+".";
2089    while (cursor < list.size() && list.get(cursor).getPath().startsWith(path))
2090      cursor++;
2091    return cursor;
2092  }
2093
2094
2095  private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list) {
2096    for (ElementDefinition t : list)
2097      if (t.getPath().equals(ed.getPath()))
2098        return t;
2099    return null;
2100  }
2101
2102  private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list, int start, int end) {
2103    for (int i = start; i < end; i++) {
2104      ElementDefinition t = list.get(i);
2105      if (t.getPath().equals(ed.getPath()))
2106        return t;
2107    }
2108    return null;
2109  }
2110
2111
2112  private boolean isImmediateChild(ElementDefinition ed) {
2113    String p = ed.getPath();
2114    if (!p.contains("."))
2115      return false;
2116    p = p.substring(p.indexOf(".")+1);
2117    return !p.contains(".");
2118  }
2119
2120  private boolean isImmediateChild(ElementDefinition candidate, ElementDefinition base) {
2121    String p = candidate.getPath();
2122    if (!p.contains("."))
2123      return false;
2124    if (!p.startsWith(base.getPath()+"."))
2125      return false;
2126    p = p.substring(base.getPath().length()+1);
2127    return !p.contains(".");
2128  }
2129
2130  public XhtmlNode generateExtensionTable(String defFile, StructureDefinition ed, String imageFolder, boolean inlineGraphics, boolean full, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException {
2131    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true);
2132    gen.setTranslator(getTranslator());
2133    TableModel model = gen.initNormalTable(corePath, false, true, ed.getId(), false);
2134
2135    boolean deep = false;
2136    String m = "";
2137    boolean vdeep = false;
2138    if (ed.getSnapshot().getElementFirstRep().getIsModifier())
2139      m = "modifier_";
2140    for (ElementDefinition eld : ed.getSnapshot().getElement()) {
2141      deep = deep || eld.getPath().contains("Extension.extension.");
2142      vdeep = vdeep || eld.getPath().contains("Extension.extension.extension.");
2143    }
2144    Row r = gen.new Row();
2145    model.getRows().add(r);
2146    String en;
2147    if (!full)
2148      en = ed.getName();
2149    else if (ed.getSnapshot().getElement().get(0).getIsModifier())
2150      en = "modifierExtension";
2151    else 
2152      en = "extension";
2153    
2154    r.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), en, null, null));
2155    r.getCells().add(gen.new Cell());
2156    r.getCells().add(gen.new Cell(null, null, describeCardinality(ed.getSnapshot().getElement().get(0), null, new UnusedTracker()), null, null));
2157
2158    ElementDefinition ved = null;
2159    if (full || vdeep) {
2160      r.getCells().add(gen.new Cell("", "", "Extension", null, null));
2161
2162      r.setIcon(deep ? "icon_"+m+"extension_complex.png" : "icon_extension_simple.png", deep ? HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX : HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
2163      List<ElementDefinition> children = getChildren(ed.getSnapshot().getElement(), ed.getSnapshot().getElement().get(0));
2164      for (ElementDefinition child : children)
2165        if (!child.getPath().endsWith(".id"))
2166          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);
2167    } else if (deep) {
2168      List<ElementDefinition> children = new ArrayList<ElementDefinition>();
2169      for (ElementDefinition ted : ed.getSnapshot().getElement()) {
2170        if (ted.getPath().equals("Extension.extension"))
2171          children.add(ted);
2172      }
2173
2174      r.getCells().add(gen.new Cell("", "", "Extension", null, null));
2175      r.setIcon("icon_"+m+"extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
2176      
2177      for (ElementDefinition c : children) {
2178        ved = getValueFor(ed, c);
2179        ElementDefinition ued = getUrlFor(ed, c);
2180        if (ved != null && ued != null) {
2181          Row r1 = gen.new Row();
2182          r.getSubRows().add(r1);
2183          r1.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), ((UriType) ued.getFixed()).getValue(), null, null));
2184          r1.getCells().add(gen.new Cell());
2185          r1.getCells().add(gen.new Cell(null, null, describeCardinality(c, null, new UnusedTracker()), null, null));
2186          genTypes(gen, r1, ved, defFile, ed, corePath, imagePath);
2187          Cell cell = gen.new Cell();
2188          cell.addMarkdown(c.getDefinition());
2189          r1.getCells().add(cell);
2190          r1.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);      
2191        }
2192      }
2193    } else  {
2194      for (ElementDefinition ted : ed.getSnapshot().getElement()) {
2195        if (ted.getPath().startsWith("Extension.value"))
2196          ved = ted;
2197      }
2198
2199      genTypes(gen, r, ved, defFile, ed, corePath, imagePath);
2200
2201      r.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);      
2202    }
2203    Cell c = gen.new Cell("", "", "URL = "+ed.getUrl(), null, null);
2204    Piece cc = gen.new Piece(null, ed.getName()+": ", null);
2205    c.addPiece(gen.new Piece("br")).addPiece(cc);
2206    c.addMarkdown(ed.getDescription());
2207    
2208    if (!full && !(deep || vdeep) && ved != null && ved.hasBinding()) {  
2209        c.addPiece(gen.new Piece("br"));
2210      BindingResolution br = pkp.resolveBinding(ed, ved.getBinding(), ved.getPath());
2211      c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold")));
2212      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)));
2213      if (ved.getBinding().hasStrength()) {
2214        c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, " (", null)));
2215        c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(corePath+"terminologies.html#"+ved.getBinding().getStrength().toCode(), egt(ved.getBinding().getStrengthElement()), ved.getBinding().getStrength().getDefinition())));              
2216        c.getPieces().add(gen.new Piece(null, ")", null));
2217      }
2218    }
2219    c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, describeExtensionContext(ed), null));
2220    r.getCells().add(c);
2221    
2222    try {
2223      return gen.generate(model, corePath, 0, outputTracker);
2224        } catch (org.hl7.fhir.exceptions.FHIRException e) {
2225                throw new FHIRException(e.getMessage(), e);
2226        }
2227  }
2228
2229  private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) {
2230    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
2231    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
2232      if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url"))
2233        return ed.getSnapshot().getElement().get(i);
2234      i++;
2235    }
2236    return null;
2237  }
2238
2239  private ElementDefinition getValueFor(StructureDefinition ed, ElementDefinition c) {
2240    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
2241    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
2242      if (ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".value"))
2243        return ed.getSnapshot().getElement().get(i);
2244      i++;
2245    }
2246    return null;
2247  }
2248
2249
2250  private static final int AGG_NONE = 0;
2251  private static final int AGG_IND = 1;
2252  private static final int AGG_GR = 2;
2253  private static final boolean TABLE_FORMAT_FOR_FIXED_VALUES = false;
2254  
2255  private Cell genTypes(HierarchicalTableGenerator gen, Row r, ElementDefinition e, String profileBaseFileName, StructureDefinition profile, String corePath, String imagePath) {
2256    Cell c = gen.new Cell();
2257    r.getCells().add(c);
2258    List<TypeRefComponent> types = e.getType();
2259    if (!e.hasType()) {
2260      if (e.hasContentReference()) {
2261        return c;
2262      } else {
2263      ElementDefinition d = (ElementDefinition) e.getUserData(DERIVATION_POINTER);
2264      if (d != null && d.hasType()) {
2265        types = new ArrayList<ElementDefinition.TypeRefComponent>();
2266        for (TypeRefComponent tr : d.getType()) {
2267          TypeRefComponent tt = tr.copy();
2268          tt.setUserData(DERIVATION_EQUALS, true);
2269          types.add(tt);
2270        }
2271      } else
2272        return c;
2273    }
2274    }
2275
2276    boolean first = true;
2277
2278    TypeRefComponent tl = null;
2279    for (TypeRefComponent t : types) {
2280      if (first)
2281        first = false;
2282      else
2283        c.addPiece(checkForNoChange(tl, gen.new Piece(null,", ", null)));
2284      tl = t;
2285      if (t.hasTarget()) {
2286        c.getPieces().add(gen.new Piece(corePath+"references.html", t.getWorkingCode(), null));
2287        c.getPieces().add(gen.new Piece(null, "(", null));
2288        boolean tfirst = true;
2289        for (UriType u : t.getTargetProfile()) {
2290          if (tfirst)
2291            tfirst = false;
2292          else
2293            c.addPiece(gen.new Piece(null, " | ", null));
2294          genTargetLink(gen, profileBaseFileName, corePath, c, t, u.getValue());
2295        }
2296        c.getPieces().add(gen.new Piece(null, ")", null));
2297        if (t.getAggregation().size() > 0) {
2298          c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", " {", null));
2299          boolean firstA = true;
2300          for (Enumeration<AggregationMode> a : t.getAggregation()) {
2301            if (firstA = true)
2302              firstA = false;
2303            else
2304              c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", ", ", null));
2305            c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", codeForAggregation(a.getValue()), hintForAggregation(a.getValue())));
2306          }
2307          c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", "}", null));
2308        }
2309      } else if (t.hasProfile() && (!t.getWorkingCode().equals("Extension") || isProfiledType(t.getProfile()))) { // a profiled type
2310        String ref;
2311        ref = pkp.getLinkForProfile(profile, t.getProfile().get(0).getValue());
2312        if (ref != null) {
2313          String[] parts = ref.split("\\|");
2314          if (parts[0].startsWith("http:") || parts[0].startsWith("https:")) {
2315//            c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], "<" + parts[1] + ">", t.getCode()))); Lloyd
2316            c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], parts[1], t.getWorkingCode())));
2317          } else {
2318//            c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().startsWith(corePath)? corePath: "")+parts[0], "<" + parts[1] + ">", t.getCode())));
2319            c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().get(0).getValue().startsWith(corePath+"StructureDefinition")? corePath: "")+parts[0], parts[1], t.getWorkingCode())));
2320          }
2321        } else
2322          c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().get(0).getValue().startsWith(corePath)? corePath: "")+ref, t.getWorkingCode(), null)));
2323      } else {
2324        String tc = t.getWorkingCode();
2325        if (pkp != null && pkp.hasLinkFor(tc)) {
2326          c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, tc), tc, null)));
2327      } else
2328          c.addPiece(checkForNoChange(t, gen.new Piece(null, tc, null)));
2329      }
2330    }
2331    return c;
2332  }
2333
2334
2335  public void genTargetLink(HierarchicalTableGenerator gen, String profileBaseFileName, String corePath, Cell c, TypeRefComponent t, String u) {
2336    if (u.startsWith("http://hl7.org/fhir/StructureDefinition/")) {
2337      StructureDefinition sd = context.fetchResource(StructureDefinition.class, u);
2338      if (sd != null) {
2339        String disp = sd.hasTitle() ? sd.getTitle() : sd.getName();
2340        c.addPiece(checkForNoChange(t, gen.new Piece(checkPrepend(corePath, sd.getUserString("path")), disp, null)));
2341      } else {
2342        String rn = u.substring(40);
2343        c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, rn), rn, null)));
2344      }
2345    } else if (Utilities.isAbsoluteUrl(u)) {
2346      StructureDefinition sd = context.fetchResource(StructureDefinition.class, u);
2347      if (sd != null) {
2348        String disp = sd.hasTitle() ? sd.getTitle() : sd.getName();
2349        String ref = pkp.getLinkForProfile(null, sd.getUrl());
2350        if (ref.contains("|"))
2351          ref = ref.substring(0,  ref.indexOf("|"));
2352        c.addPiece(checkForNoChange(t, gen.new Piece(ref, disp, null)));
2353      } else
2354        c.addPiece(checkForNoChange(t, gen.new Piece(null, u, null)));        
2355    } else if (t.hasTargetProfile() && u.startsWith("#"))
2356      c.addPiece(checkForNoChange(t, gen.new Piece(corePath+profileBaseFileName+"."+u.substring(1).toLowerCase()+".html", u, null)));
2357  }
2358
2359  private boolean isProfiledType(List<CanonicalType> theProfile) {
2360    for (CanonicalType next : theProfile){
2361      if (StringUtils.defaultString(next.getValueAsString()).contains(":")) {
2362        return true;
2363      }
2364    }
2365    return false;
2366  }
2367
2368
2369  private String codeForAggregation(AggregationMode a) {
2370    switch (a) {
2371    case BUNDLED : return "b";
2372    case CONTAINED : return "c";
2373    case REFERENCED: return "r";
2374    default: return "?";
2375    }
2376  }
2377
2378  private String hintForAggregation(AggregationMode a) {
2379    if (a != null)
2380      return a.getDefinition();
2381    else 
2382      return null;
2383  }
2384
2385
2386  private String checkPrepend(String corePath, String path) {
2387    if (pkp.prependLinks() && !(path.startsWith("http:") || path.startsWith("https:")))
2388      return corePath+path;
2389    else 
2390      return path;
2391  }
2392
2393
2394  private ElementDefinition getElementByName(List<ElementDefinition> elements, String contentReference) {
2395    for (ElementDefinition ed : elements)
2396      if (ed.hasSliceName() && ("#"+ed.getSliceName()).equals(contentReference))
2397        return ed;
2398    return null;
2399  }
2400
2401  private ElementDefinition getElementById(List<ElementDefinition> elements, String contentReference) {
2402    for (ElementDefinition ed : elements)
2403      if (ed.hasId() && ("#"+ed.getId()).equals(contentReference))
2404        return ed;
2405    return null;
2406  }
2407
2408
2409  public static String describeExtensionContext(StructureDefinition ext) {
2410    StringBuilder b = new StringBuilder();
2411    b.append("Use on ");
2412    for (int i = 0; i < ext.getContext().size(); i++) {
2413      StructureDefinitionContextComponent ec = ext.getContext().get(i);
2414      if (i > 0) 
2415        b.append(i < ext.getContext().size() - 1 ? ", " : " or ");
2416      b.append(ec.getType().getDisplay());
2417      b.append(" ");
2418      b.append(ec.getExpression());
2419    }
2420    if (ext.hasContextInvariant()) {
2421      b.append(", with <a href=\"structuredefinition-definitions.html#StructureDefinition.contextInvariant\">Context Invariant</a> = ");
2422      boolean first = true;
2423      for (StringType s : ext.getContextInvariant()) {
2424        if (first)
2425          first = false;
2426        else
2427          b.append(", ");
2428        b.append("<code>"+s.getValue()+"</code>");
2429      }
2430    }
2431    return b.toString(); 
2432  }
2433
2434  private String describeCardinality(ElementDefinition definition, ElementDefinition fallback, UnusedTracker tracker) {
2435    IntegerType min = definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
2436    StringType max = definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
2437    if (min.isEmpty() && fallback != null)
2438      min = fallback.getMinElement();
2439    if (max.isEmpty() && fallback != null)
2440      max = fallback.getMaxElement();
2441
2442    tracker.used = !max.isEmpty() && !max.getValue().equals("0");
2443
2444    if (min.isEmpty() && max.isEmpty())
2445      return null;
2446    else
2447      return (!min.hasValue() ? "" : Integer.toString(min.getValue())) + ".." + (!max.hasValue() ? "" : max.getValue());
2448  }
2449
2450  private void genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef, UnusedTracker tracker, ElementDefinition fallback) {
2451    IntegerType min = !hasDef ? new IntegerType() : definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
2452    StringType max = !hasDef ? new StringType() : definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
2453    if (min.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) {
2454      ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER);
2455      if (base.hasMinElement()) {
2456        min = base.getMinElement().copy();
2457        min.setUserData(DERIVATION_EQUALS, true);
2458      }
2459    }
2460    if (max.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) {
2461      ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER);
2462      if (base.hasMaxElement()) {
2463        max = base.getMaxElement().copy();
2464        max.setUserData(DERIVATION_EQUALS, true);
2465      }
2466    }
2467    if (min.isEmpty() && fallback != null)
2468      min = fallback.getMinElement();
2469    if (max.isEmpty() && fallback != null)
2470      max = fallback.getMaxElement();
2471
2472    if (!max.isEmpty())
2473      tracker.used = !max.getValue().equals("0");
2474
2475    Cell cell = gen.new Cell(null, null, null, null, null);
2476    row.getCells().add(cell);
2477    if (!min.isEmpty() || !max.isEmpty()) {
2478      cell.addPiece(checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), null)));
2479      cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", null)));
2480      cell.addPiece(checkForNoChange(min, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), null)));
2481    }
2482  }
2483
2484
2485  private Piece checkForNoChange(Element source, Piece piece) {
2486    if (source.hasUserData(DERIVATION_EQUALS)) {
2487      piece.addStyle("opacity: 0.4");
2488    }
2489    return piece;
2490  }
2491
2492  private Piece checkForNoChange(Element src1, Element src2, Piece piece) {
2493    if (src1.hasUserData(DERIVATION_EQUALS) && src2.hasUserData(DERIVATION_EQUALS)) {
2494      piece.addStyle("opacity: 0.5");
2495    }
2496    return piece;
2497  }
2498
2499  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 {
2500    assert(diff != snapshot);// check it's ok to get rid of one of these
2501    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true);
2502    gen.setTranslator(getTranslator());
2503    TableModel model = gen.initNormalTable(corePath, false, true, profile.getId()+(diff ? "d" : "s"), false);
2504    List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement();
2505    List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
2506    profiles.add(profile);
2507    if (list.isEmpty()) {
2508      ElementDefinition root = new ElementDefinition().setPath(profile.getType());
2509      root.setId(profile.getType());
2510      list.add(root);
2511    }
2512    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);
2513    try {
2514      return gen.generate(model, imagePath, 0, outputTracker);
2515        } catch (org.hl7.fhir.exceptions.FHIRException e) {
2516                throw new FHIRException("Error generating table for profile " + profile.getUrl() + ": " + e.getMessage(), e);
2517        }
2518  }
2519
2520
2521  public XhtmlNode generateGrid(String defFile, StructureDefinition profile, String imageFolder, boolean inlineGraphics, String profileBaseFileName, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException {
2522    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics, true);
2523    gen.setTranslator(getTranslator());
2524    TableModel model = gen.initGridTable(corePath, profile.getId());
2525    List<ElementDefinition> list = profile.getSnapshot().getElement();
2526    List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
2527    profiles.add(profile);
2528    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));
2529    try {
2530      return gen.generate(model, imagePath, 1, outputTracker);
2531    } catch (org.hl7.fhir.exceptions.FHIRException e) {
2532      throw new FHIRException(e.getMessage(), e);
2533    }
2534  }
2535
2536
2537  private boolean usesMustSupport(List<ElementDefinition> list) {
2538    for (ElementDefinition ed : list)
2539      if (ed.hasMustSupport() && ed.getMustSupport())
2540        return true;
2541    return false;
2542  }
2543
2544
2545  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 {
2546    Row originalRow = slicingRow;
2547    StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1);
2548    String s = tail(element.getPath());
2549    if (element.hasSliceName())
2550      s = s +":"+element.getSliceName();
2551    Row typesRow = null;
2552    
2553    List<ElementDefinition> children = getChildren(all, element);
2554    boolean isExtension = (s.equals("extension") || s.equals("modifierExtension"));
2555//    if (!snapshot && isExtension && extensions != null && extensions != isExtension)
2556//      return;
2557
2558    if (!onlyInformationIsMapping(all, element)) {
2559      Row row = gen.new Row();
2560      row.setAnchor(element.getPath());
2561      row.setColor(getRowColor(element, isConstraintMode));
2562      if (element.hasSlicing())
2563        row.setLineColor(1);
2564      else if (element.hasSliceName())
2565        row.setLineColor(2);
2566      else
2567        row.setLineColor(0);
2568      boolean hasDef = element != null;
2569      boolean ext = false;
2570      if (tail(element.getPath()).equals("extension")) {
2571        if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue()))
2572          row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
2573        else
2574          row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
2575        ext = true;
2576      } else if (tail(element.getPath()).equals("modifierExtension")) {
2577        if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue()))
2578          row.setIcon("icon_modifier_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
2579        else
2580          row.setIcon("icon_modifier_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
2581      } else if (!hasDef || element.getType().size() == 0)
2582        row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
2583      else if (hasDef && element.getType().size() > 1) {
2584        if (allAreReference(element.getType()))
2585          row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
2586        else {
2587          row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE);
2588          typesRow = row;
2589        }
2590      } else if (hasDef && element.getType().get(0).getWorkingCode() != null && element.getType().get(0).getWorkingCode().startsWith("@"))
2591        row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE);
2592      else if (hasDef && isPrimitive(element.getType().get(0).getWorkingCode()))
2593        row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
2594      else if (hasDef && element.getType().get(0).hasTarget())
2595        row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
2596      else if (hasDef && isDataType(element.getType().get(0).getWorkingCode()))
2597        row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
2598      else
2599        row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
2600      String ref = defPath == null ? null : defPath + element.getId();
2601      UnusedTracker used = new UnusedTracker();
2602      used.used = true;
2603      if (logicalModel && element.hasRepresentation(PropertyRepresentation.XMLATTR))
2604        s = "@"+s;
2605      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);
2606      row.getCells().add(left);
2607      Cell gc = gen.new Cell();
2608      row.getCells().add(gc);
2609      if (element != null && element.getIsModifier())
2610        checkForNoChange(element.getIsModifierElement(), gc.addStyledText(translate("sd.table", "This element is a modifier element"), "?!", null, null, null, false));
2611      if (element != null && element.getMustSupport())
2612        checkForNoChange(element.getMustSupportElement(), gc.addStyledText(translate("sd.table", "This element must be supported"), "S", "white", "red", null, false));
2613      if (element != null && element.getIsSummary())
2614        checkForNoChange(element.getIsSummaryElement(), gc.addStyledText(translate("sd.table", "This element is included in summaries"), "\u03A3", null, null, null, false));
2615      if (element != null && (!element.getConstraint().isEmpty() || !element.getCondition().isEmpty()))
2616        gc.addStyledText(translate("sd.table", "This element has or is affected by some invariants"), "I", null, null, null, false);
2617
2618      ExtensionContext extDefn = null;
2619      if (ext) {
2620        if (element != null && element.getType().size() == 1 && element.getType().get(0).hasProfile()) {
2621          String eurl = element.getType().get(0).getProfile().get(0).getValue();
2622          extDefn = locateExtension(StructureDefinition.class, eurl);
2623          if (extDefn == null) {
2624            genCardinality(gen, element, row, hasDef, used, null);
2625            row.getCells().add(gen.new Cell(null, null, "?? "+element.getType().get(0).getProfile(), null, null));
2626            generateDescription(gen, row, element, null, used.used, profile.getUrl(), eurl, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot);
2627          } else {
2628            String name = urltail(eurl);
2629            left.getPieces().get(0).setText(name);
2630            // left.getPieces().get(0).setReference((String) extDefn.getExtensionStructure().getTag("filename"));
2631            left.getPieces().get(0).setHint(translate("sd.table", "Extension URL")+" = "+extDefn.getUrl());
2632            genCardinality(gen, element, row, hasDef, used, extDefn.getElement());
2633            ElementDefinition valueDefn = extDefn.getExtensionValueDefinition();
2634            if (valueDefn != null && !"0".equals(valueDefn.getMax()))
2635               genTypes(gen, row, valueDefn, profileBaseFileName, profile, corePath, imagePath);
2636             else // if it's complex, we just call it nothing
2637                // genTypes(gen, row, extDefn.getSnapshot().getElement().get(0), profileBaseFileName, profile);
2638              row.getCells().add(gen.new Cell(null, null, "("+translate("sd.table", "Complex")+")", null, null));
2639            generateDescription(gen, row, element, extDefn.getElement(), used.used, null, extDefn.getUrl(), profile, corePath, imagePath, root, logicalModel, allInvariants, valueDefn, snapshot);
2640          }
2641        } else {
2642          genCardinality(gen, element, row, hasDef, used, null);
2643          if ("0".equals(element.getMax()))
2644            row.getCells().add(gen.new Cell());            
2645          else
2646            genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath);
2647          generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot);
2648        }
2649      } else {
2650        genCardinality(gen, element, row, hasDef, used, null);
2651        if (element.hasSlicing())
2652          row.getCells().add(gen.new Cell(null, corePath+"profiling.html#slicing", "(Slice Definition)", null, null));
2653        else if (hasDef && !"0".equals(element.getMax()) && typesRow == null)
2654          genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath);
2655        else
2656          row.getCells().add(gen.new Cell());
2657        generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants, snapshot);
2658      }
2659      if (element.hasSlicing()) {
2660        if (standardExtensionSlicing(element)) {
2661          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();
2662          showMissing = false; //?
2663        } else {
2664          row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE);
2665          slicingRow = row;
2666          for (Cell cell : row.getCells())
2667            for (Piece p : cell.getPieces()) {
2668              p.addStyle("font-style: italic");
2669            }
2670        }
2671      } else if (element.hasSliceName()) {
2672        row.setIcon("icon_slice_item.png", HierarchicalTableGenerator.TEXT_ICON_SLICE_ITEM);
2673      }
2674      if (used.used || showMissing)
2675        rows.add(row);
2676      if (!used.used && !element.hasSlicing()) {
2677        for (Cell cell : row.getCells())
2678          for (Piece p : cell.getPieces()) {
2679            p.setStyle("text-decoration:line-through");
2680            p.setReference(null);
2681          }
2682      } else{
2683        if (slicingRow != originalRow && !children.isEmpty()) {
2684          // we've entered a slice; we're going to create a holder row for the slice children
2685          Row hrow = gen.new Row();
2686          hrow.setAnchor(element.getPath());
2687          hrow.setColor(getRowColor(element, isConstraintMode));
2688          hrow.setLineColor(1);
2689          hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
2690          hrow.getCells().add(gen.new Cell(null, null, "(All Slices)", "", null));
2691          hrow.getCells().add(gen.new Cell());
2692          hrow.getCells().add(gen.new Cell());
2693          hrow.getCells().add(gen.new Cell());
2694          hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all slices", "", null));
2695          row.getSubRows().add(hrow);
2696          row = hrow;
2697        }
2698        if (typesRow != null && !children.isEmpty()) {
2699          // we've entered a typing slice; we're going to create a holder row for the all types children
2700          Row hrow = gen.new Row();
2701          hrow.setAnchor(element.getPath());
2702          hrow.setColor(getRowColor(element, isConstraintMode));
2703          hrow.setLineColor(1);
2704          hrow.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
2705          hrow.getCells().add(gen.new Cell(null, null, "(All Types)", "", null));
2706          hrow.getCells().add(gen.new Cell());
2707          hrow.getCells().add(gen.new Cell());
2708          hrow.getCells().add(gen.new Cell());
2709          hrow.getCells().add(gen.new Cell(null, null, "Content/Rules for all Types", "", null));
2710          row.getSubRows().add(hrow);
2711          row = hrow;
2712        }
2713          
2714        Row currRow = row; 
2715        for (ElementDefinition child : children) {
2716          if (!child.hasSliceName())
2717            currRow = row; 
2718          if (logicalModel || !child.getPath().endsWith(".id") || (child.getPath().endsWith(".id") && (profile != null) && (profile.getDerivation() == TypeDerivationRule.CONSTRAINT)))  
2719            currRow = genElement(defPath, gen, currRow.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, snapshot, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants, currRow);
2720        }
2721//        if (!snapshot && (extensions == null || !extensions))
2722//          for (ElementDefinition child : children)
2723//            if (child.getPath().endsWith(".extension") || child.getPath().endsWith(".modifierExtension"))
2724//              genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, true, false, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants);
2725      }
2726      if (typesRow != null) {
2727        makeChoiceRows(typesRow.getSubRows(), element, gen, corePath, profileBaseFileName);
2728      }
2729    }
2730    return slicingRow;
2731  }
2732
2733  private void makeChoiceRows(List<Row> subRows, ElementDefinition element, HierarchicalTableGenerator gen, String corePath, String profileBaseFileName) {
2734    // create a child for each choice
2735    for (TypeRefComponent tr : element.getType()) {
2736      Row choicerow = gen.new Row();
2737      String t = tr.getWorkingCode();
2738      if (isReference(t)) {
2739        choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]", Utilities.capitalize(t)), null, null));
2740        choicerow.getCells().add(gen.new Cell());
2741        choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
2742        choicerow.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
2743        Cell c = gen.new Cell();
2744        choicerow.getCells().add(c);
2745        if (ADD_REFERENCE_TO_TABLE) {
2746          if (tr.getWorkingCode().equals("canonical"))
2747            c.getPieces().add(gen.new Piece(corePath+"datatypes.html#canonical", "canonical", null));
2748          else
2749            c.getPieces().add(gen.new Piece(corePath+"references.html#Reference", "Reference", null));
2750          c.getPieces().add(gen.new Piece(null, "(", null));
2751    }
2752        boolean first = true;
2753        for (CanonicalType rt : tr.getTargetProfile()) {
2754          if (!first)
2755            c.getPieces().add(gen.new Piece(null, " | ", null));
2756          genTargetLink(gen, profileBaseFileName, corePath, c, tr, rt.getValue());
2757          first = false;
2758  }
2759        if (ADD_REFERENCE_TO_TABLE) 
2760          c.getPieces().add(gen.new Piece(null, ")", null));
2761        
2762      } else {
2763        StructureDefinition sd = context.fetchTypeDefinition(t);
2764        if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE) {
2765          choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]",  Utilities.capitalize(t)), sd.getDescription(), null));
2766          choicerow.getCells().add(gen.new Cell());
2767          choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
2768          choicerow.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
2769          choicerow.getCells().add(gen.new Cell(null, corePath+"datatypes.html#"+t, t, null, null));
2770          //      } else if (definitions.getConstraints().contthnsKey(t)) {
2771          //        ProfiledType pt = definitions.getConstraints().get(t);
2772          //        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));
2773          //        choicerow.getCells().add(gen.new Cell());
2774          //        choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
2775          //        choicerow.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
2776          //        choicerow.getCells().add(gen.new Cell(null, definitions.getSrcFile(t)+".html#"+t.replace("*", "open"), t, null, null));
2777        } else {
2778          choicerow.getCells().add(gen.new Cell(null, null, tail(element.getPath()).replace("[x]",  Utilities.capitalize(t)), sd.getDescription(), null));
2779          choicerow.getCells().add(gen.new Cell());
2780          choicerow.getCells().add(gen.new Cell(null, null, "", null, null));
2781          choicerow.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
2782          choicerow.getCells().add(gen.new Cell(null, pkp.getLinkFor(corePath, t), t, null, null));
2783        }
2784      }    
2785      choicerow.getCells().add(gen.new Cell());
2786      subRows.add(choicerow);
2787    }
2788  }
2789
2790  private boolean isReference(String t) {
2791    return t.equals("Reference") || t.equals("canonical"); 
2792  }  
2793
2794
2795
2796  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 {
2797    StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1);
2798    String s = tail(element.getPath());
2799    List<ElementDefinition> children = getChildren(all, element);
2800    boolean isExtension = (s.equals("extension") || s.equals("modifierExtension"));
2801
2802    if (!onlyInformationIsMapping(all, element)) {
2803      Row row = gen.new Row();
2804      row.setAnchor(element.getPath());
2805      row.setColor(getRowColor(element, isConstraintMode));
2806      if (element.hasSlicing())
2807        row.setLineColor(1);
2808      else if (element.hasSliceName())
2809        row.setLineColor(2);
2810      else
2811        row.setLineColor(0);
2812      boolean hasDef = element != null;
2813      String ref = defPath == null ? null : defPath + element.getId();
2814      UnusedTracker used = new UnusedTracker();
2815      used.used = true;
2816      Cell left = gen.new Cell();
2817      if (element.getType().size() == 1 && element.getType().get(0).isPrimitive())
2818        left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())).addStyle("font-weight:bold"));
2819      else
2820        left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())));
2821      if (element.hasSliceName()) {
2822        left.getPieces().add(gen.new Piece("br"));
2823        String indent = StringUtils.repeat('\u00A0', 1+2*(element.getPath().split("\\.").length));
2824        left.getPieces().add(gen.new Piece(null, indent + "("+element.getSliceName() + ")", null));
2825      }
2826      row.getCells().add(left);
2827
2828      ExtensionContext extDefn = null;
2829      genCardinality(gen, element, row, hasDef, used, null);
2830      if (hasDef && !"0".equals(element.getMax()))
2831        genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath);
2832      else
2833        row.getCells().add(gen.new Cell());
2834      generateGridDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, null);
2835/*      if (element.hasSlicing()) {
2836        if (standardExtensionSlicing(element)) {
2837          used.used = element.hasType() && element.getType().get(0).hasProfile();
2838          showMissing = false;
2839        } else {
2840          row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE);
2841          row.getCells().get(2).getPieces().clear();
2842          for (Cell cell : row.getCells())
2843            for (Piece p : cell.getPieces()) {
2844              p.addStyle("font-style: italic");
2845            }
2846        }
2847      }*/
2848      rows.add(row);
2849      for (ElementDefinition child : children)
2850        if (child.getMustSupport())
2851          genGridElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, corePath, imagePath, false, isConstraintMode);
2852    }
2853  }
2854
2855
2856  private ExtensionContext locateExtension(Class<StructureDefinition> class1, String value)  {
2857    if (value.contains("#")) {
2858      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
2859      if (ext == null)
2860        return null;
2861      String tail = value.substring(value.indexOf("#")+1);
2862      ElementDefinition ed = null;
2863      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
2864        if (tail.equals(ted.getSliceName())) {
2865          ed = ted;
2866          return new ExtensionContext(ext, ed);
2867        }
2868      }
2869      return null;
2870    } else {
2871      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value);
2872      if (ext == null)
2873        return null;
2874      else 
2875        return new ExtensionContext(ext, ext.getSnapshot().getElement().get(0));
2876    }
2877  }
2878
2879
2880  private boolean extensionIsComplex(String value) {
2881    if (value.contains("#")) {
2882      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
2883    if (ext == null)
2884      return false;
2885      String tail = value.substring(value.indexOf("#")+1);
2886      ElementDefinition ed = null;
2887      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
2888        if (tail.equals(ted.getSliceName())) {
2889          ed = ted;
2890          break;
2891        }
2892      }
2893      if (ed == null)
2894        return false;
2895      int i = ext.getSnapshot().getElement().indexOf(ed);
2896      int j = i+1;
2897      while (j < ext.getSnapshot().getElement().size() && !ext.getSnapshot().getElement().get(j).getPath().equals(ed.getPath()))
2898        j++;
2899      return j - i > 5;
2900    } else {
2901      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value);
2902      return ext != null && ext.getSnapshot().getElement().size() > 5;
2903    }
2904  }
2905
2906
2907  private String getRowColor(ElementDefinition element, boolean isConstraintMode) {
2908    switch (element.getUserInt(UD_ERROR_STATUS)) {
2909    case STATUS_HINT: return ROW_COLOR_HINT;
2910    case STATUS_WARNING: return ROW_COLOR_WARNING;
2911    case STATUS_ERROR: return ROW_COLOR_ERROR;
2912    case STATUS_FATAL: return ROW_COLOR_FATAL;
2913    }
2914    if (isConstraintMode && !element.getMustSupport() && !element.getIsModifier() && element.getPath().contains("."))
2915      return null; // ROW_COLOR_NOT_MUST_SUPPORT;
2916    else
2917      return null;
2918  }
2919
2920
2921  private String urltail(String path) {
2922    if (path.contains("#"))
2923      return path.substring(path.lastIndexOf('#')+1);
2924    if (path.contains("/"))
2925      return path.substring(path.lastIndexOf('/')+1);
2926    else
2927      return path;
2928
2929  }
2930
2931  private boolean standardExtensionSlicing(ElementDefinition element) {
2932    String t = tail(element.getPath());
2933    return (t.equals("extension") || t.equals("modifierExtension"))
2934          && 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);
2935  }
2936
2937  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 {
2938    return generateDescription(gen, row, definition, fallback, used, baseURL, url, profile, corePath, imagePath, root, logicalModel, allInvariants, null, snapshot);
2939  }
2940  
2941  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 {
2942    Cell c = gen.new Cell();
2943    row.getCells().add(c);
2944
2945    if (used) {
2946      if (logicalModel && ToolingExtensions.hasExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) {
2947        if (root) {
2948          c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold"));
2949          c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null));        
2950        } else if (!root && ToolingExtensions.hasExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace") && 
2951            !ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace").equals(ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))) {
2952          c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold"));
2953          c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null));        
2954        }
2955      }
2956      
2957      if (definition.hasContentReference()) {
2958        ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference());
2959        if (ed == null)
2960          c.getPieces().add(gen.new Piece(null, translate("sd.table", "Unknown reference to %s", definition.getContentReference()), null));
2961        else
2962          c.getPieces().add(gen.new Piece("#"+ed.getPath(), translate("sd.table", "See %s", ed.getPath()), null));
2963      }
2964      if (definition.getPath().endsWith("url") && definition.hasFixed()) {
2965        c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen")));
2966      } else {
2967        if (definition != null && definition.hasShort()) {
2968          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2969          c.addPiece(checkForNoChange(definition.getShortElement(), gen.new Piece(null, gt(definition.getShortElement()), null)));
2970        } else if (fallback != null && fallback.hasShort()) {
2971          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2972          c.addPiece(checkForNoChange(fallback.getShortElement(), gen.new Piece(null, gt(fallback.getShortElement()), null)));
2973        }
2974        if (url != null) {
2975          if (!c.getPieces().isEmpty()) 
2976            c.addPiece(gen.new Piece("br"));
2977          String fullUrl = url.startsWith("#") ? baseURL+url : url;
2978          StructureDefinition ed = context.fetchResource(StructureDefinition.class, url);
2979          String ref = null;
2980          String ref2 = null;
2981          String fixedUrl = null;
2982          if (ed != null) {
2983            String p = ed.getUserString("path");
2984            if (p != null) {
2985              ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p);
2986            }             
2987            fixedUrl = getFixedUrl(ed);
2988            if (fixedUrl != null) {// if its null, we guess that it's not a profiled extension? 
2989              if (fixedUrl.equals(url))
2990                fixedUrl = null;
2991              else {
2992                StructureDefinition ed2 = context.fetchResource(StructureDefinition.class, fixedUrl);
2993                if (ed2 != null) {
2994                  String p2 = ed2.getUserString("path");
2995                  if (p2 != null) {
2996                    ref2 = p2.startsWith("http:") || igmode ? p2 : Utilities.pathURL(corePath, p2);
2997                  }                              
2998                }
2999              }
3000            }
3001          }
3002          if (fixedUrl == null) {
3003            c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold"));
3004            c.getPieces().add(gen.new Piece(ref, fullUrl, null));
3005          } else { 
3006            // reference to a profile take on the extension show the base URL
3007            c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold"));
3008            c.getPieces().add(gen.new Piece(ref2, fixedUrl, null));
3009            c.getPieces().add(gen.new Piece(null, translate("sd.table", " profiled by ")+" ", null).addStyle("font-weight:bold"));
3010            c.getPieces().add(gen.new Piece(ref, fullUrl, null));
3011          
3012          }
3013        }
3014
3015        if (definition.hasSlicing()) {
3016          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3017          c.getPieces().add(gen.new Piece(null, translate("sd.table", "Slice")+": ", null).addStyle("font-weight:bold"));
3018          c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null));
3019        }
3020        if (definition != null) {
3021          ElementDefinitionBindingComponent binding = null;
3022          if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty())
3023            binding = valueDefn.getBinding();
3024          else if (definition.hasBinding())
3025            binding = definition.getBinding();
3026          if (binding!=null && !binding.isEmpty()) {
3027            if (!c.getPieces().isEmpty()) 
3028              c.addPiece(gen.new Piece("br"));
3029            BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath());
3030            c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold")));
3031            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)));
3032            if (binding.hasStrength()) {
3033              c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null)));
3034              c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), egt(binding.getStrengthElement()), binding.getStrength().getDefinition())));              
3035              
3036              c.getPieces().add(gen.new Piece(null, ")", null));
3037            }
3038            if (binding.hasExtension(ToolingExtensions.EXT_MAX_VALUESET)) {
3039              br = pkp.resolveBinding(profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MAX_VALUESET), definition.getPath());
3040              c.addPiece(gen.new Piece("br"));
3041              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")));             
3042              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)));
3043            }
3044            if (binding.hasExtension(ToolingExtensions.EXT_MIN_VALUESET)) {
3045              br = pkp.resolveBinding(profile, ToolingExtensions.readStringExtension(binding, ToolingExtensions.EXT_MIN_VALUESET), definition.getPath());
3046              c.addPiece(gen.new Piece("br"));
3047              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")));             
3048              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)));
3049            }
3050          }
3051          for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
3052            if (!inv.hasSource() || allInvariants) {
3053              if (!c.getPieces().isEmpty()) 
3054                c.addPiece(gen.new Piece("br"));
3055              c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold")));
3056              c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, gt(inv.getHumanElement()), null)));
3057            }
3058          }
3059          if ((definition.hasBase() && definition.getBase().getMax().equals("*")) || (definition.hasMax() && definition.getMax().equals("*"))) {
3060            if (c.getPieces().size() > 0)
3061              c.addPiece(gen.new Piece("br"));
3062            if (definition.hasOrderMeaning()) {
3063              c.getPieces().add(gen.new Piece(null, "This repeating element order: "+definition.getOrderMeaning(), null));
3064            } else {
3065              // don't show this, this it's important: c.getPieces().add(gen.new Piece(null, "This repeating element has no defined order", null));
3066            }           
3067          }
3068
3069          if (definition.hasFixed()) {
3070            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3071            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, translate("sd.table", "Fixed Value")+": ", null).addStyle("font-weight:bold")));
3072            if (!useTableForFixedValues || definition.getFixed().isPrimitive()) {
3073              String s = buildJson(definition.getFixed());
3074              String link = null;
3075              if (Utilities.isAbsoluteUrl(s))
3076                link = pkp.getLinkForUrl(corePath, s);
3077              c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen")));
3078            } else {
3079              c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "As shown", null).addStyle("color: darkgreen")));
3080              genFixedValue(gen, row, definition.getFixed(), snapshot, false, corePath);              
3081            }
3082            if (isCoded(definition.getFixed()) && !hasDescription(definition.getFixed())) {
3083              Piece p = describeCoded(gen, definition.getFixed());
3084              if (p != null)
3085                c.getPieces().add(p);
3086            }
3087          } else if (definition.hasPattern()) {
3088            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3089            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, translate("sd.table", "Required Pattern")+": ", null).addStyle("font-weight:bold")));
3090            if (!useTableForFixedValues || definition.getPattern().isPrimitive())
3091            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen")));
3092            else {
3093              c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "At least the following", null).addStyle("color: darkgreen")));
3094              genFixedValue(gen, row, definition.getPattern(), snapshot, true, corePath);
3095            }
3096          } else if (definition.hasExample()) {
3097            for (ElementDefinitionExampleComponent ex : definition.getExample()) {
3098              if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3099              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, translate("sd.table", "Example")+("".equals("General")? "" : " "+ex.getLabel()+"'")+": ", null).addStyle("font-weight:bold")));
3100              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen")));
3101            }
3102          }
3103          if (definition.hasMaxLength() && definition.getMaxLength()!=0) {
3104            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3105            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold")));
3106            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen")));
3107          }
3108          if (profile != null) {
3109            for (StructureDefinitionMappingComponent md : profile.getMapping()) {
3110              if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) {
3111                ElementDefinitionMappingComponent map = null;
3112                for (ElementDefinitionMappingComponent m : definition.getMapping()) 
3113                  if (m.getIdentity().equals(md.getIdentity()))
3114                    map = m;
3115                if (map != null) {
3116                  for (int i = 0; i<definition.getMapping().size(); i++){
3117                    c.addPiece(gen.new Piece("br"));
3118                    c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null));
3119                  }
3120                }
3121              }
3122            }
3123          }
3124        }
3125      }
3126    }
3127    return c;
3128  }
3129
3130  private void genFixedValue(HierarchicalTableGenerator gen, Row erow, Type value, boolean snapshot, boolean pattern, String corePath) {
3131    String ref = pkp.getLinkFor(corePath, value.fhirType());
3132    ref = ref.substring(0, ref.indexOf(".html"))+"-definitions.html#";
3133    StructureDefinition sd = context.fetchTypeDefinition(value.fhirType());
3134    
3135    for (org.hl7.fhir.r4.model.Property t : value.children()) {
3136      if (t.getValues().size() > 0 || snapshot) {
3137        ElementDefinition ed = findElementDefinition(sd, t.getName());
3138        if (t.getValues().size() == 0 || (t.getValues().size() == 1 && t.getValues().get(0).isEmpty())) {
3139          Row row = gen.new Row();
3140          erow.getSubRows().add(row);
3141          Cell c = gen.new Cell();
3142          row.getCells().add(c);
3143          c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref+ed.getPath() : corePath+"element-definitions.html#"+ed.getBase().getPath()), t.getName(), null));
3144          c = gen.new Cell();
3145          row.getCells().add(c);
3146          c.addPiece(gen.new Piece(null, null, null));
3147          c = gen.new Cell();
3148          row.getCells().add(c);
3149          if (!pattern) {
3150            c.addPiece(gen.new Piece(null, "0..0", null));
3151            row.setIcon("icon_fixed.gif", "Fixed Value" /*HierarchicalTableGenerator.TEXT_ICON_FIXED*/);
3152          } else if (isPrimitive(t.getTypeCode())) {
3153            row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
3154            c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null));
3155          } else if (isReference(t.getTypeCode())) { 
3156            row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
3157            c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null));
3158          } else { 
3159            row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
3160            c.addPiece(gen.new Piece(null, "0.."+(t.getMaxCardinality() == 2147483647 ? "*": Integer.toString(t.getMaxCardinality())), null));
3161          }
3162          c = gen.new Cell();
3163          row.getCells().add(c);
3164          c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, t.getTypeCode()), t.getTypeCode(), null));
3165          c = gen.new Cell();
3166          c.addPiece(gen.new Piece(null, ed.getShort(), null));
3167          row.getCells().add(c);
3168        } else {
3169          for (Base b : t.getValues()) {
3170            Row row = gen.new Row();
3171            erow.getSubRows().add(row);
3172            row.setIcon("icon_fixed.gif", "Fixed Value" /*HierarchicalTableGenerator.TEXT_ICON_FIXED*/);
3173
3174            Cell c = gen.new Cell();
3175            row.getCells().add(c);
3176            c.addPiece(gen.new Piece((ed.getBase().getPath().equals(ed.getPath()) ? ref+ed.getPath() : corePath+"element-definitions.html#"+ed.getBase().getPath()), t.getName(), null));
3177
3178            c = gen.new Cell();
3179            row.getCells().add(c);
3180            c.addPiece(gen.new Piece(null, null, null));
3181
3182            c = gen.new Cell();
3183            row.getCells().add(c);
3184            if (pattern)
3185              c.addPiece(gen.new Piece(null, "1.."+(t.getMaxCardinality() == 2147483647 ? "*" : Integer.toString(t.getMaxCardinality())), null));
3186            else
3187              c.addPiece(gen.new Piece(null, "1..1", null));
3188
3189            c = gen.new Cell();
3190            row.getCells().add(c);
3191            c.addPiece(gen.new Piece(pkp.getLinkFor(corePath, b.fhirType()), b.fhirType(), null));
3192
3193            if (b.isPrimitive()) {
3194              c = gen.new Cell();
3195              row.getCells().add(c);
3196              c.addPiece(gen.new Piece(null, ed.getShort(), null));
3197              c.addPiece(gen.new Piece("br"));
3198              c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold"));
3199              String s = b.primitiveValue();
3200              // ok. let's see if we can find a relevant link for this
3201              String link = null;
3202              if (Utilities.isAbsoluteUrl(s))
3203                link = pkp.getLinkForUrl(corePath, s);
3204              c.getPieces().add(gen.new Piece(link, s, null).addStyle("color: darkgreen"));
3205            } else {
3206              c = gen.new Cell();
3207              row.getCells().add(c);
3208              c.addPiece(gen.new Piece(null, ed.getShort(), null));
3209              c.addPiece(gen.new Piece("br"));
3210              c.getPieces().add(gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight: bold"));
3211              c.getPieces().add(gen.new Piece(null, "(complex)", null).addStyle("color: darkgreen"));
3212              genFixedValue(gen, row, (Type) b, snapshot, pattern, corePath);
3213            }
3214          }
3215        }
3216      }
3217    }
3218  }
3219
3220
3221  private ElementDefinition findElementDefinition(StructureDefinition sd, String name) {
3222    String path = sd.getType()+"."+name;
3223    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
3224      if (ed.getPath().equals(path))
3225        return ed;
3226    }
3227    throw new FHIRException("Unable to find element "+path);
3228  }
3229
3230
3231  private String getFixedUrl(StructureDefinition sd) {
3232    for (ElementDefinition ed : sd.getSnapshot().getElement()) {
3233      if (ed.getPath().equals("Extension.url")) {
3234        if (ed.hasFixed() && ed.getFixed() instanceof UriType)
3235          return ed.getFixed().primitiveValue();
3236      }
3237    }
3238    return null;
3239  }
3240
3241
3242  private Piece describeCoded(HierarchicalTableGenerator gen, Type fixed) {
3243    if (fixed instanceof Coding) {
3244      Coding c = (Coding) fixed;
3245      ValidationResult vr = context.validateCode(terminologyServiceOptions , c.getSystem(), c.getCode(), c.getDisplay());
3246      if (vr.getDisplay() != null)
3247        return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen");
3248    } else if (fixed instanceof CodeableConcept) {
3249      CodeableConcept cc = (CodeableConcept) fixed;
3250      for (Coding c : cc.getCoding()) {
3251        ValidationResult vr = context.validateCode(terminologyServiceOptions, c.getSystem(), c.getCode(), c.getDisplay());
3252        if (vr.getDisplay() != null)
3253          return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen");
3254      }
3255    }
3256    return null;
3257  }
3258
3259
3260  private boolean hasDescription(Type fixed) {
3261    if (fixed instanceof Coding) {
3262      return ((Coding) fixed).hasDisplay();
3263    } else if (fixed instanceof CodeableConcept) {
3264      CodeableConcept cc = (CodeableConcept) fixed;
3265      if (cc.hasText())
3266        return true;
3267      for (Coding c : cc.getCoding())
3268        if (c.hasDisplay())
3269         return true;
3270    } // (fixed instanceof CodeType) || (fixed instanceof Quantity);
3271    return false;
3272  }
3273
3274
3275  private boolean isCoded(Type fixed) {
3276    return (fixed instanceof Coding) || (fixed instanceof CodeableConcept) || (fixed instanceof CodeType) || (fixed instanceof Quantity);
3277  }
3278
3279
3280  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 {
3281    Cell c = gen.new Cell();
3282    row.getCells().add(c);
3283
3284    if (used) {
3285      if (definition.hasContentReference()) {
3286        ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference());
3287        if (ed == null)
3288          c.getPieces().add(gen.new Piece(null, "Unknown reference to "+definition.getContentReference(), null));
3289        else
3290          c.getPieces().add(gen.new Piece("#"+ed.getPath(), "See "+ed.getPath(), null));
3291      }
3292      if (definition.getPath().endsWith("url") && definition.hasFixed()) {
3293        c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen")));
3294      } else {
3295        if (url != null) {
3296          if (!c.getPieces().isEmpty()) 
3297            c.addPiece(gen.new Piece("br"));
3298          String fullUrl = url.startsWith("#") ? baseURL+url : url;
3299          StructureDefinition ed = context.fetchResource(StructureDefinition.class, url);
3300          String ref = null;
3301          if (ed != null) {
3302            String p = ed.getUserString("path");
3303            if (p != null) {
3304              ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p);
3305            }
3306          }
3307          c.getPieces().add(gen.new Piece(null, "URL: ", null).addStyle("font-weight:bold"));
3308          c.getPieces().add(gen.new Piece(ref, fullUrl, null));
3309        }
3310
3311        if (definition.hasSlicing()) {
3312          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3313          c.getPieces().add(gen.new Piece(null, "Slice: ", null).addStyle("font-weight:bold"));
3314          c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null));
3315        }
3316        if (definition != null) {
3317          ElementDefinitionBindingComponent binding = null;
3318          if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty())
3319            binding = valueDefn.getBinding();
3320          else if (definition.hasBinding())
3321            binding = definition.getBinding();
3322          if (binding!=null && !binding.isEmpty()) {
3323            if (!c.getPieces().isEmpty()) 
3324              c.addPiece(gen.new Piece("br"));
3325            BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath());
3326            c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, "Binding: ", null).addStyle("font-weight:bold")));
3327            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)));
3328            if (binding.hasStrength()) {
3329              c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null)));
3330              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));
3331            }
3332          }
3333          for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
3334            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3335            c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold")));
3336            c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null)));
3337          }
3338          if (definition.hasFixed()) {
3339            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3340            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight:bold")));
3341            String s = buildJson(definition.getFixed());
3342            String link = null;
3343            if (Utilities.isAbsoluteUrl(s))
3344              link = pkp.getLinkForUrl(corePath, s);
3345            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(link, s, null).addStyle("color: darkgreen")));
3346          } else if (definition.hasPattern()) {
3347            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3348            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "Required Pattern: ", null).addStyle("font-weight:bold")));
3349            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen")));
3350          } else if (definition.hasExample()) {
3351            for (ElementDefinitionExampleComponent ex : definition.getExample()) {
3352              if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3353              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, "Example'"+("".equals("General")? "" : " "+ex.getLabel()+"'")+": ", null).addStyle("font-weight:bold")));
3354              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen")));
3355            }
3356          }
3357          if (definition.hasMaxLength() && definition.getMaxLength()!=0) {
3358            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3359            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold")));
3360            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen")));
3361          }
3362          if (profile != null) {
3363            for (StructureDefinitionMappingComponent md : profile.getMapping()) {
3364              if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) {
3365                ElementDefinitionMappingComponent map = null;
3366                for (ElementDefinitionMappingComponent m : definition.getMapping()) 
3367                  if (m.getIdentity().equals(md.getIdentity()))
3368                    map = m;
3369                if (map != null) {
3370                  for (int i = 0; i<definition.getMapping().size(); i++){
3371                    c.addPiece(gen.new Piece("br"));
3372                    c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null));
3373                  }
3374                }
3375              }
3376            }
3377          }
3378          if (definition.hasDefinition()) {
3379            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3380            c.getPieces().add(gen.new Piece(null, "Definition: ", null).addStyle("font-weight:bold"));
3381            c.addPiece(gen.new Piece("br"));
3382            c.addMarkdown(definition.getDefinition());
3383//            c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null)));
3384          }
3385          if (definition.getComment()!=null) {
3386            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
3387            c.getPieces().add(gen.new Piece(null, "Comments: ", null).addStyle("font-weight:bold"));
3388            c.addPiece(gen.new Piece("br"));
3389            c.addMarkdown(definition.getComment());
3390//            c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null)));
3391          }
3392        }
3393      }
3394    }
3395    return c;
3396  }
3397
3398
3399
3400  private String buildJson(Type value) throws IOException {
3401    if (value instanceof PrimitiveType)
3402      return ((PrimitiveType) value).asStringValue();
3403
3404    IParser json = context.newJsonParser();
3405    return json.composeString(value, null);
3406  }
3407
3408
3409  public String describeSlice(ElementDefinitionSlicingComponent slicing) {
3410    return translate("sd.table", "%s, %s by %s", slicing.getOrdered() ? translate("sd.table", "Ordered") : translate("sd.table", "Unordered"), describe(slicing.getRules()), commas(slicing.getDiscriminator()));
3411  }
3412
3413  private String commas(List<ElementDefinitionSlicingDiscriminatorComponent> list) {
3414    CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder();
3415    for (ElementDefinitionSlicingDiscriminatorComponent id : list)
3416      c.append(id.getType().toCode()+":"+id.getPath());
3417    return c.toString();
3418  }
3419
3420
3421  private String describe(SlicingRules rules) {
3422    if (rules == null)
3423      return translate("sd.table", "Unspecified");
3424    switch (rules) {
3425    case CLOSED : return translate("sd.table", "Closed");
3426    case OPEN : return translate("sd.table", "Open");
3427    case OPENATEND : return translate("sd.table", "Open At End");
3428    default:
3429      return "??";
3430    }
3431  }
3432
3433  private boolean onlyInformationIsMapping(List<ElementDefinition> list, ElementDefinition e) {
3434    return (!e.hasSliceName() && !e.hasSlicing() && (onlyInformationIsMapping(e))) &&
3435        getChildren(list, e).isEmpty();
3436  }
3437
3438  private boolean onlyInformationIsMapping(ElementDefinition d) {
3439    return !d.hasShort() && !d.hasDefinition() &&
3440        !d.hasRequirements() && !d.getAlias().isEmpty() && !d.hasMinElement() &&
3441        !d.hasMax() && !d.getType().isEmpty() && !d.hasContentReference() &&
3442        !d.hasExample() && !d.hasFixed() && !d.hasMaxLengthElement() &&
3443        !d.getCondition().isEmpty() && !d.getConstraint().isEmpty() && !d.hasMustSupportElement() &&
3444        !d.hasBinding();
3445  }
3446
3447  private boolean allAreReference(List<TypeRefComponent> types) {
3448    for (TypeRefComponent t : types) {
3449      if (!t.hasTarget())
3450        return false;
3451    }
3452    return true;
3453  }
3454
3455  private List<ElementDefinition> getChildren(List<ElementDefinition> all, ElementDefinition element) {
3456    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
3457    int i = all.indexOf(element)+1;
3458    while (i < all.size() && all.get(i).getPath().length() > element.getPath().length()) {
3459      if ((all.get(i).getPath().substring(0, element.getPath().length()+1).equals(element.getPath()+".")) && !all.get(i).getPath().substring(element.getPath().length()+1).contains("."))
3460        result.add(all.get(i));
3461      i++;
3462    }
3463    return result;
3464  }
3465
3466  private String tail(String path) {
3467    if (path.contains("."))
3468      return path.substring(path.lastIndexOf('.')+1);
3469    else
3470      return path;
3471  }
3472
3473  private boolean isDataType(String value) {
3474    StructureDefinition sd = context.fetchTypeDefinition(value);
3475    if (sd == null) // might be running before all SDs are available
3476      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", 
3477            "ContactDetail", "Contributor", "DataRequirement", "Expression", "ParameterDefinition", "RelatedArtifact", "TriggerDefinition", "UsageContext");
3478    else 
3479      return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION;
3480  }
3481
3482  private boolean isConstrainedDataType(String value) {
3483    StructureDefinition sd = context.fetchTypeDefinition(value);
3484    if (sd == null) // might be running before all SDs are available
3485      return Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity");
3486    else 
3487      return sd.getKind() == StructureDefinitionKind.COMPLEXTYPE && sd.getDerivation() == TypeDerivationRule.CONSTRAINT;
3488  }
3489
3490  private String baseType(String value) {
3491    StructureDefinition sd = context.fetchTypeDefinition(value);
3492    if (sd != null) // might be running before all SDs are available
3493      return sd.getType();
3494    if (Utilities.existsInList(value, "SimpleQuantity", "MoneyQuantity"))
3495      return "Quantity";
3496     throw new Error("Internal error -  type not known "+value);
3497  }
3498
3499
3500  public boolean isPrimitive(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, "base64Binary", "boolean", "canonical", "code", "date", "dateTime", "decimal", "id", "instant", "integer", "markdown", "oid", "positiveInt", "string", "time", "unsignedInt", "uri", "url", "uuid");
3504    else 
3505      return sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
3506  }
3507
3508//  private static String listStructures(StructureDefinition p) {
3509//    StringBuilder b = new StringBuilder();
3510//    boolean first = true;
3511//    for (ProfileStructureComponent s : p.getStructure()) {
3512//      if (first)
3513//        first = false;
3514//      else
3515//        b.append(", ");
3516//      if (pkp != null && pkp.hasLinkFor(s.getType()))
3517//        b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>");
3518//      else
3519//        b.append(s.getType());
3520//    }
3521//    return b.toString();
3522//  }
3523
3524
3525  public StructureDefinition getProfile(StructureDefinition source, String url) {
3526        StructureDefinition profile = null;
3527        String code = null;
3528        if (url.startsWith("#")) {
3529                profile = source;
3530                code = url.substring(1);
3531        } else if (context != null) {
3532                String[] parts = url.split("\\#");
3533                profile = context.fetchResource(StructureDefinition.class, parts[0]);
3534      code = parts.length == 1 ? null : parts[1];
3535        }         
3536        if (profile == null)
3537                return null;
3538        if (code == null)
3539                return profile;
3540        for (Resource r : profile.getContained()) {
3541                if (r instanceof StructureDefinition && r.getId().equals(code))
3542                        return (StructureDefinition) r;
3543        }
3544        return null;
3545  }
3546
3547
3548
3549  public static class ElementDefinitionHolder {
3550    private String name;
3551    private ElementDefinition self;
3552    private int baseIndex = 0;
3553    private List<ElementDefinitionHolder> children;
3554    private boolean placeHolder = false;
3555
3556    public ElementDefinitionHolder(ElementDefinition self, boolean isPlaceholder) {
3557      super();
3558      this.self = self;
3559      this.name = self.getPath();
3560      this.placeHolder = isPlaceholder;
3561      children = new ArrayList<ElementDefinitionHolder>();      
3562    }
3563
3564    public ElementDefinitionHolder(ElementDefinition self) {
3565      this(self, false);
3566    }
3567
3568    public ElementDefinition getSelf() {
3569      return self;
3570    }
3571
3572    public List<ElementDefinitionHolder> getChildren() {
3573      return children;
3574    }
3575
3576    public int getBaseIndex() {
3577      return baseIndex;
3578    }
3579
3580    public void setBaseIndex(int baseIndex) {
3581      this.baseIndex = baseIndex;
3582    }
3583
3584    public boolean isPlaceHolder() {
3585      return this.placeHolder;
3586    }
3587
3588    @Override
3589    public String toString() {
3590      if (self.hasSliceName())
3591        return self.getPath()+"("+self.getSliceName()+")";
3592      else
3593        return self.getPath();
3594    }
3595  }
3596
3597  public static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> {
3598
3599    private boolean inExtension;
3600    private List<ElementDefinition> snapshot;
3601    private int prefixLength;
3602    private String base;
3603    private String name;
3604    private Set<String> errors = new HashSet<String>();
3605
3606    public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, int prefixLength, String name) {
3607      this.inExtension = inExtension;
3608      this.snapshot = snapshot;
3609      this.prefixLength = prefixLength;
3610      this.base = base;
3611      this.name = name;
3612    }
3613
3614    @Override
3615    public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) {
3616      if (o1.getBaseIndex() == 0)
3617        o1.setBaseIndex(find(o1.getSelf().getPath()));
3618      if (o2.getBaseIndex() == 0)
3619        o2.setBaseIndex(find(o2.getSelf().getPath()));
3620      return o1.getBaseIndex() - o2.getBaseIndex();
3621    }
3622
3623    private int find(String path) {
3624      String op = path;
3625      int lc = 0;
3626      String actual = base+path.substring(prefixLength);
3627      for (int i = 0; i < snapshot.size(); i++) {
3628        String p = snapshot.get(i).getPath();
3629        if (p.equals(actual)) {
3630          return i;
3631        }
3632        if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length()-3)) && !(actual.endsWith("[x]")) && !actual.substring(p.length()-3).contains(".")) {
3633          return i;
3634        }
3635        if (path.startsWith(p+".") && snapshot.get(i).hasContentReference()) {
3636          String ref = snapshot.get(i).getContentReference();
3637          if (ref.substring(1, 2).toUpperCase().equals(ref.substring(1,2))) {
3638            actual = base+(ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength);
3639            path = actual;
3640          } else {
3641            // Older versions of FHIR (e.g. 2016May) had reference of the style #parameter instead of #Parameters.parameter, so we have to handle that
3642            actual = base+(path.substring(0,  path.indexOf(".")+1) + ref.substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength);
3643            path = actual;
3644          }
3645            
3646          i = 0;
3647          lc++;
3648          if (lc > MAX_RECURSION_LIMIT)
3649            throw new Error("Internal recursion detection: find() loop path recursion > "+MAX_RECURSION_LIMIT+" - check paths are valid (for path "+path+"/"+op+")");
3650        }
3651      }
3652      if (prefixLength == 0)
3653        errors.add("Differential contains path "+path+" which is not found in the base");
3654      else
3655        errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the base");
3656      return 0;
3657    }
3658
3659    public void checkForErrors(List<String> errorList) {
3660      if (errors.size() > 0) {
3661//        CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
3662//        for (String s : errors)
3663//          b.append("StructureDefinition "+name+": "+s);
3664//        throw new DefinitionException(b.toString());
3665        for (String s : errors)
3666          if (s.startsWith("!"))
3667            errorList.add("!StructureDefinition "+name+": "+s.substring(1));
3668          else
3669            errorList.add("StructureDefinition "+name+": "+s);
3670      }
3671    }
3672  }
3673
3674
3675  public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors) throws FHIRException  {
3676    final List<ElementDefinition> diffList = diff.getDifferential().getElement();
3677    int lastCount = diffList.size();
3678    // first, we move the differential elements into a tree
3679    if (diffList.isEmpty())
3680      return;
3681    
3682    ElementDefinitionHolder edh = null;
3683    int i = 0;
3684    if (diffList.get(0).getPath().contains(".")) {
3685      String newPath = diffList.get(0).getPath().split("\\.")[0];
3686      ElementDefinition e = new ElementDefinition(new StringType(newPath));
3687      edh = new ElementDefinitionHolder(e, true);
3688    } else {
3689      edh = new ElementDefinitionHolder(diffList.get(0));
3690      i = 1;
3691    }
3692
3693    boolean hasSlicing = false;
3694    List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly
3695    for(ElementDefinition elt : diffList) {
3696      if (elt.hasSlicing() || paths.contains(elt.getPath())) {
3697        hasSlicing = true;
3698        break;
3699      }
3700      paths.add(elt.getPath());
3701    }
3702    if(!hasSlicing) {
3703      // if Differential does not have slicing then safe to pre-sort the list
3704      // so elements and subcomponents are together
3705      Collections.sort(diffList, new ElementNameCompare());
3706    }
3707
3708    processElementsIntoTree(edh, i, diff.getDifferential().getElement());
3709
3710    // now, we sort the siblings throughout the tree
3711    ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name);
3712    sortElements(edh, cmp, errors);
3713
3714    // now, we serialise them back to a list
3715    diffList.clear();
3716    writeElements(edh, diffList);
3717    
3718    if (lastCount != diffList.size())
3719      errors.add("Sort failed: counts differ; at least one of the paths in the differential is illegal");
3720  }
3721
3722  private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) {
3723    String path = edh.getSelf().getPath();
3724    final String prefix = path + ".";
3725    while (i < list.size() && list.get(i).getPath().startsWith(prefix)) {
3726      if (list.get(i).getPath().substring(prefix.length()+1).contains(".")) {
3727        String newPath = prefix + list.get(i).getPath().substring(prefix.length()).split("\\.")[0];
3728        ElementDefinition e = new ElementDefinition(new StringType(newPath));
3729        ElementDefinitionHolder child = new ElementDefinitionHolder(e, true);
3730        edh.getChildren().add(child);
3731        i = processElementsIntoTree(child, i, list);
3732        
3733      } else {
3734        ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i));
3735        edh.getChildren().add(child);
3736        i = processElementsIntoTree(child, i+1, list);
3737      }
3738    }
3739    return i;
3740  }
3741
3742  private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) throws FHIRException {
3743    if (edh.getChildren().size() == 1)
3744      // 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
3745      edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath());
3746    else
3747      Collections.sort(edh.getChildren(), cmp);
3748    cmp.checkForErrors(errors);
3749
3750    for (ElementDefinitionHolder child : edh.getChildren()) {
3751      if (child.getChildren().size() > 0) {
3752        ElementDefinitionComparer ccmp = getComparer(cmp, child);
3753        if (ccmp != null)
3754        sortElements(child, ccmp, errors);
3755      }
3756    }
3757  }
3758
3759
3760  public ElementDefinitionComparer getComparer(ElementDefinitionComparer cmp, ElementDefinitionHolder child) throws FHIRException, Error {
3761    // what we have to check for here is running off the base profile into a data type profile
3762    ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex());
3763    ElementDefinitionComparer ccmp;
3764    if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getWorkingCode()) || ed.getType().get(0).getWorkingCode().equals(ed.getPath())) {
3765      ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name);
3766    } else if (ed.getType().get(0).getWorkingCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) {
3767      StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue());
3768      if (profile==null)
3769        ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case
3770      else
3771      ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
3772    } else if (ed.getType().size() == 1 && !ed.getType().get(0).getWorkingCode().equals("*")) {
3773      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
3774      if (profile==null)
3775        throw new FHIRException("Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath());
3776      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
3777    } else if (child.getSelf().getType().size() == 1) {
3778      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(child.getSelf().getType().get(0).getWorkingCode()));
3779      if (profile==null)
3780        throw new FHIRException("Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath());
3781      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), child.getSelf().getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
3782    } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) {
3783      String edLastNode = ed.getPath().replaceAll("(.*\\.)*(.*)", "$2");
3784      String childLastNode = child.getSelf().getPath().replaceAll("(.*\\.)*(.*)", "$2");
3785      String p = childLastNode.substring(edLastNode.length()-3);
3786      if (isPrimitive(Utilities.uncapitalize(p)))
3787        p = Utilities.uncapitalize(p);
3788      StructureDefinition sd = context.fetchResource(StructureDefinition.class, sdNs(p));
3789      if (sd == null)
3790        throw new Error("Unable to find profile '"+p+"' at "+ed.getId());
3791      ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name);
3792    } else if (child.getSelf().hasType() && child.getSelf().getType().get(0).getWorkingCode().equals("Reference")) {
3793      for (TypeRefComponent t: child.getSelf().getType()) {
3794        if (!t.getWorkingCode().equals("Reference")) {
3795          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())+")");
3796        }
3797      }
3798      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
3799      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
3800    } else if (!child.getSelf().hasType() && ed.getType().get(0).getWorkingCode().equals("Reference")) {
3801      for (TypeRefComponent t: ed.getType()) {
3802        if (!t.getWorkingCode().equals("Reference")) {
3803          throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")");
3804        }
3805      }
3806      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs(ed.getType().get(0).getWorkingCode()));
3807      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getWorkingCode(), child.getSelf().getPath().length(), cmp.name);
3808    } else {
3809      // this is allowed if we only profile the extensions
3810      StructureDefinition profile = context.fetchResource(StructureDefinition.class, sdNs("Element"));
3811      if (profile==null)
3812        throw new FHIRException("Unable to resolve profile " + sdNs(ed.getType().get(0).getWorkingCode()) + " in element " + ed.getPath());
3813      ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), "Element", child.getSelf().getPath().length(), cmp.name);
3814//      throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")");
3815    }
3816    return ccmp;
3817  }
3818
3819  private static String sdNs(String type) {
3820    return sdNs(type, null);
3821  }
3822  
3823  public static String sdNs(String type, String overrideVersionNs) {
3824    if (Utilities.isAbsoluteUrl(type))
3825      return type;
3826    else if (overrideVersionNs != null)
3827      return Utilities.pathURL(overrideVersionNs, type);
3828    else
3829      return "http://hl7.org/fhir/StructureDefinition/"+type;
3830  }
3831
3832
3833  private boolean isAbstract(String code) {
3834    return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") || code.equals("DomainResource");
3835  }
3836
3837
3838  private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) {
3839    if (!edh.isPlaceHolder())
3840      list.add(edh.getSelf());
3841    for (ElementDefinitionHolder child : edh.getChildren()) {
3842      writeElements(child, list);
3843    }
3844  }
3845
3846  /**
3847   * First compare element by path then by name if same
3848   */
3849  private static class ElementNameCompare implements Comparator<ElementDefinition> {
3850
3851    @Override
3852    public int compare(ElementDefinition o1, ElementDefinition o2) {
3853      String path1 = normalizePath(o1);
3854      String path2 = normalizePath(o2);
3855      int cmp = path1.compareTo(path2);
3856      if (cmp == 0) {
3857        String name1 = o1.hasSliceName() ? o1.getSliceName() : "";
3858        String name2 = o2.hasSliceName() ? o2.getSliceName() : "";
3859        cmp = name1.compareTo(name2);
3860      }
3861      return cmp;
3862    }
3863
3864    private static String normalizePath(ElementDefinition e) {
3865      if (!e.hasPath()) return "";
3866      String path = e.getPath();
3867      // if sorting element names make sure onset[x] appears before onsetAge, onsetDate, etc.
3868      // so strip off the [x] suffix when comparing the path names.
3869      if (path.endsWith("[x]")) {
3870        path = path.substring(0, path.length()-3);
3871      }
3872      return path;
3873    }
3874
3875  }
3876
3877
3878  // generate schematrons for the rules in a structure definition
3879  public void generateSchematrons(OutputStream dest, StructureDefinition structure) throws IOException, DefinitionException {
3880    if (structure.getDerivation() != TypeDerivationRule.CONSTRAINT)
3881      throw new DefinitionException("not the right kind of structure to generate schematrons for");
3882    if (!structure.hasSnapshot())
3883      throw new DefinitionException("needs a snapshot");
3884
3885        StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBaseDefinition());
3886
3887        if (base != null) {
3888          SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName());
3889
3890          ElementDefinition ed = structure.getSnapshot().getElement().get(0);
3891          generateForChildren(sch, "f:"+ed.getPath(), ed, structure, base);
3892          sch.dump();
3893        }
3894  }
3895
3896  // generate a CSV representation of the structure definition
3897  public void generateCsvs(OutputStream dest, StructureDefinition structure, boolean asXml) throws IOException, DefinitionException, Exception {
3898    if (!structure.hasSnapshot())
3899      throw new DefinitionException("needs a snapshot");
3900
3901    CSVWriter csv = new CSVWriter(dest, structure, asXml);
3902
3903    for (ElementDefinition child : structure.getSnapshot().getElement()) {
3904      csv.processElement(child);
3905    }
3906    csv.dump();
3907  }
3908  
3909  // generate an Excel representation of the structure definition
3910  public void generateXlsx(OutputStream dest, StructureDefinition structure, boolean asXml, boolean hideMustSupportFalse) throws IOException, DefinitionException, Exception {
3911    if (!structure.hasSnapshot())
3912      throw new DefinitionException("needs a snapshot");
3913
3914    XLSXWriter xlsx = new XLSXWriter(dest, structure, asXml, hideMustSupportFalse);
3915
3916    for (ElementDefinition child : structure.getSnapshot().getElement()) {
3917      xlsx.processElement(child);
3918    }
3919    xlsx.dump();
3920    xlsx.close();
3921  }
3922  
3923  private class Slicer extends ElementDefinitionSlicingComponent {
3924    String criteria = "";
3925    String name = "";   
3926    boolean check;
3927    public Slicer(boolean cantCheck) {
3928      super();
3929      this.check = cantCheck;
3930    }
3931  }
3932  
3933  private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, StructureDefinition structure) {
3934    // given a child in a structure, it's sliced. figure out the slicing xpath
3935    if (child.getPath().endsWith(".extension")) {
3936      ElementDefinition ued = getUrlFor(structure, child);
3937      if ((ued == null || !ued.hasFixed()) && !(child.hasType() && (child.getType().get(0).hasProfile())))
3938        return new Slicer(false);
3939      else {
3940      Slicer s = new Slicer(true);
3941      String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).getValue() : ((UriType) ued.getFixed()).asStringValue();
3942      s.name = " with URL = '"+url+"'";
3943      s.criteria = "[@url = '"+url+"']";
3944      return s;
3945      }
3946    } else
3947      return new Slicer(false);
3948  }
3949
3950  private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, StructureDefinition structure, StructureDefinition base) throws IOException {
3951    //    generateForChild(txt, structure, child);
3952    List<ElementDefinition> children = getChildList(structure, ed);
3953    String sliceName = null;
3954    ElementDefinitionSlicingComponent slicing = null;
3955    for (ElementDefinition child : children) {
3956      String name = tail(child.getPath());
3957      if (child.hasSlicing()) {
3958        sliceName = name;
3959        slicing = child.getSlicing();        
3960      } else if (!name.equals(sliceName))
3961        slicing = null;
3962      
3963      ElementDefinition based = getByPath(base, child.getPath());
3964      boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin()));
3965      boolean doMax = child.hasMax() && !child.getMax().equals("*") && (based == null || (!child.getMax().equals(based.getMax())));
3966      Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure);
3967      if (slicer.check) {
3968        if (doMin || doMax) {
3969          Section s = sch.section(xpath);
3970          Rule r = s.rule(xpath);
3971          if (doMin) 
3972            r.assrt("count(f:"+name+slicer.criteria+") >= "+Integer.toString(child.getMin()), name+slicer.name+": minimum cardinality of '"+name+"' is "+Integer.toString(child.getMin()));
3973          if (doMax) 
3974            r.assrt("count(f:"+name+slicer.criteria+") <= "+child.getMax(), name+slicer.name+": maximum cardinality of '"+name+"' is "+child.getMax());
3975          }
3976        }
3977      }
3978    for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
3979      if (inv.hasXpath()) {
3980        Section s = sch.section(ed.getPath());
3981        Rule r = s.rule(xpath);
3982        r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId()+": " : "")+inv.getHuman()+(inv.hasUserData(IS_DERIVED) ? " (inherited)" : ""));
3983      }
3984    }
3985    for (ElementDefinition child : children) {
3986      String name = tail(child.getPath());
3987      generateForChildren(sch, xpath+"/f:"+name, child, structure, base);
3988    }
3989  }
3990
3991
3992
3993
3994  private ElementDefinition getByPath(StructureDefinition base, String path) {
3995                for (ElementDefinition ed : base.getSnapshot().getElement()) {
3996                        if (ed.getPath().equals(path))
3997                                return ed;
3998                        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)))
3999                                return ed;
4000    }
4001          return null;
4002  }
4003
4004
4005  public void setIds(StructureDefinition sd, boolean checkFirst) throws DefinitionException  {
4006    if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) {
4007      if (!sd.hasDifferential())
4008        sd.setDifferential(new StructureDefinitionDifferentialComponent());
4009      generateIds(sd.getDifferential().getElement(), sd.getUrl());
4010    }
4011    if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) {
4012      if (!sd.hasSnapshot())
4013        sd.setSnapshot(new StructureDefinitionSnapshotComponent());
4014      generateIds(sd.getSnapshot().getElement(), sd.getUrl());
4015    }
4016  }
4017
4018
4019  private boolean hasMissingIds(List<ElementDefinition> list) {
4020    for (ElementDefinition ed : list) {
4021      if (!ed.hasId())
4022        return true;
4023    }    
4024    return false;
4025  }
4026
4027  public class SliceList {
4028
4029    private Map<String, String> slices = new HashMap<>();
4030    
4031    public void seeElement(ElementDefinition ed) {
4032      Iterator<Map.Entry<String,String>> iter = slices.entrySet().iterator();
4033      while (iter.hasNext()) {
4034        Map.Entry<String,String> entry = iter.next();
4035        if (entry.getKey().length() > ed.getPath().length() || entry.getKey().equals(ed.getPath()))
4036          iter.remove();
4037      }
4038      
4039      if (ed.hasSliceName()) 
4040        slices.put(ed.getPath(), ed.getSliceName());
4041    }
4042
4043    public String[] analyse(List<String> paths) {
4044      String s = paths.get(0);
4045      String[] res = new String[paths.size()];
4046      res[0] = null;
4047      for (int i = 1; i < paths.size(); i++) {
4048        s = s + "."+paths.get(i);
4049        if (slices.containsKey(s)) 
4050          res[i] = slices.get(s);
4051        else
4052          res[i] = null;
4053      }
4054      return res;
4055    }
4056
4057  }
4058
4059  private void generateIds(List<ElementDefinition> list, String name) throws DefinitionException  {
4060    if (list.isEmpty())
4061      return;
4062    
4063    Map<String, String> idMap = new HashMap<String, String>();
4064    Map<String, String> idList = new HashMap<String, String>();
4065    
4066    SliceList sliceInfo = new SliceList();
4067    // first pass, update the element ids
4068    for (ElementDefinition ed : list) {
4069      List<String> paths = new ArrayList<String>();
4070      if (!ed.hasPath())
4071        throw new DefinitionException("No path on element Definition "+Integer.toString(list.indexOf(ed))+" in "+name);
4072      sliceInfo.seeElement(ed);
4073      String[] pl = ed.getPath().split("\\.");
4074      for (int i = paths.size(); i < pl.length; i++) // -1 because the last path is in focus
4075        paths.add(pl[i]);
4076      String slices[] = sliceInfo.analyse(paths);
4077      
4078      StringBuilder b = new StringBuilder();
4079      b.append(paths.get(0));
4080      for (int i = 1; i < paths.size(); i++) {
4081        b.append(".");
4082        String s = paths.get(i);
4083        String p = slices[i];
4084        b.append(s);
4085        if (p != null) {
4086          b.append(":");
4087          b.append(p);
4088        }
4089      }
4090      String bs = b.toString();
4091      idMap.put(ed.hasId() ? ed.getId() : ed.getPath(), bs);
4092      ed.setId(bs);
4093      if (idList.containsKey(bs)) {
4094        if (exception || messages == null)
4095          throw new DefinitionException("Same id '"+bs+"'on multiple elements "+idList.get(bs)+"/"+ed.getPath()+" in "+name);
4096        else
4097          messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, name+"."+bs, "Duplicate Element id "+bs, ValidationMessage.IssueSeverity.ERROR));
4098      }
4099      idList.put(bs, ed.getPath());
4100      if (ed.hasContentReference()) {
4101        String s = ed.getContentReference().substring(1);
4102        if (idMap.containsKey(s))
4103          ed.setContentReference("#"+idMap.get(s));
4104        
4105      }
4106    }  
4107    // second path - fix up any broken path based id references
4108    
4109  }
4110
4111
4112//  private String describeExtension(ElementDefinition ed) {
4113//    if (!ed.hasType() || !ed.getTypeFirstRep().hasProfile())
4114//      return "";
4115//    return "$"+urlTail(ed.getTypeFirstRep().getProfile());
4116//  }
4117//
4118
4119  private String urlTail(String profile) {
4120    return profile.contains("/") ? profile.substring(profile.lastIndexOf("/")+1) : profile;
4121  }
4122
4123
4124  private String checkName(String name) {
4125//    if (name.contains("."))
4126////      throw new Exception("Illegal name "+name+": no '.'");
4127//    if (name.contains(" "))
4128//      throw new Exception("Illegal name "+name+": no spaces");
4129    StringBuilder b = new StringBuilder();
4130    for (char c : name.toCharArray()) {
4131      if (!Utilities.existsInList(c, '.', ' ', ':', '"', '\'', '(', ')', '&', '[', ']'))
4132        b.append(c);
4133    }
4134    return b.toString().toLowerCase();
4135  }
4136
4137
4138  private int charCount(String path, char t) {
4139    int res = 0;
4140    for (char ch : path.toCharArray()) {
4141      if (ch == t)
4142        res++;
4143    }
4144    return res;
4145  }
4146
4147//
4148//private void generateForChild(TextStreamWriter txt,
4149//    StructureDefinition structure, ElementDefinition child) {
4150//  // TODO Auto-generated method stub
4151//
4152//}
4153
4154  private interface ExampleValueAccessor {
4155    Type getExampleValue(ElementDefinition ed);
4156    String getId();
4157  }
4158
4159  private class BaseExampleValueAccessor implements ExampleValueAccessor {
4160    @Override
4161    public Type getExampleValue(ElementDefinition ed) {
4162      if (ed.hasFixed())
4163        return ed.getFixed();
4164      if (ed.hasExample())
4165        return ed.getExample().get(0).getValue();
4166      else
4167        return null;
4168    }
4169
4170    @Override
4171    public String getId() {
4172      return "-genexample";
4173    }
4174  }
4175  
4176  private class ExtendedExampleValueAccessor implements ExampleValueAccessor {
4177    private String index;
4178
4179    public ExtendedExampleValueAccessor(String index) {
4180      this.index = index;
4181    }
4182    @Override
4183    public Type getExampleValue(ElementDefinition ed) {
4184      if (ed.hasFixed())
4185        return ed.getFixed();
4186      for (Extension ex : ed.getExtension()) {
4187       String ndx = ToolingExtensions.readStringExtension(ex, "index");
4188       Type value = ToolingExtensions.getExtension(ex, "exValue").getValue();
4189       if (index.equals(ndx) && value != null)
4190         return value;
4191      }
4192      return null;
4193    }
4194    @Override
4195    public String getId() {
4196      return "-genexample-"+index;
4197    }
4198  }
4199  
4200  public List<org.hl7.fhir.r4.elementmodel.Element> generateExamples(StructureDefinition sd, boolean evenWhenNoExamples) throws FHIRException {
4201    List<org.hl7.fhir.r4.elementmodel.Element> examples = new ArrayList<org.hl7.fhir.r4.elementmodel.Element>();
4202    if (sd.hasSnapshot()) {
4203      if (evenWhenNoExamples || hasAnyExampleValues(sd)) 
4204        examples.add(generateExample(sd, new BaseExampleValueAccessor()));
4205      for (int i = 1; i <= 50; i++) {
4206        if (hasAnyExampleValues(sd, Integer.toString(i))) 
4207          examples.add(generateExample(sd, new ExtendedExampleValueAccessor(Integer.toString(i))));
4208      }
4209    }
4210    return examples;
4211  }
4212
4213  private org.hl7.fhir.r4.elementmodel.Element generateExample(StructureDefinition profile, ExampleValueAccessor accessor) throws FHIRException {
4214    ElementDefinition ed = profile.getSnapshot().getElementFirstRep();
4215    org.hl7.fhir.r4.elementmodel.Element r = new org.hl7.fhir.r4.elementmodel.Element(ed.getPath(), new Property(context, ed, profile));
4216    List<ElementDefinition> children = getChildMap(profile, ed);
4217    for (ElementDefinition child : children) {
4218      if (child.getPath().endsWith(".id")) {
4219        org.hl7.fhir.r4.elementmodel.Element id = new org.hl7.fhir.r4.elementmodel.Element("id", new Property(context, child, profile));
4220        id.setValue(profile.getId()+accessor.getId());
4221        r.getChildren().add(id);
4222      } else { 
4223        org.hl7.fhir.r4.elementmodel.Element e = createExampleElement(profile, child, accessor);
4224        if (e != null)
4225          r.getChildren().add(e);
4226      }
4227    }
4228    return r;
4229  }
4230
4231  private org.hl7.fhir.r4.elementmodel.Element createExampleElement(StructureDefinition profile, ElementDefinition ed, ExampleValueAccessor accessor) throws FHIRException {
4232    Type v = accessor.getExampleValue(ed);
4233    if (v != null) {
4234      return new ObjectConverter(context).convert(new Property(context, ed, profile), v);
4235    } else {
4236      org.hl7.fhir.r4.elementmodel.Element res = new org.hl7.fhir.r4.elementmodel.Element(tail(ed.getPath()), new Property(context, ed, profile));
4237      boolean hasValue = false;
4238      List<ElementDefinition> children = getChildMap(profile, ed);
4239      for (ElementDefinition child : children) {
4240        if (!child.hasContentReference()) {
4241        org.hl7.fhir.r4.elementmodel.Element e = createExampleElement(profile, child, accessor);
4242        if (e != null) {
4243          hasValue = true;
4244          res.getChildren().add(e);
4245        }
4246      }
4247      }
4248      if (hasValue)
4249        return res;
4250      else
4251        return null;
4252    }
4253  }
4254
4255  private boolean hasAnyExampleValues(StructureDefinition sd, String index) {
4256    for (ElementDefinition ed : sd.getSnapshot().getElement())
4257      for (Extension ex : ed.getExtension()) {
4258        String ndx = ToolingExtensions.readStringExtension(ex, "index");
4259        Extension exv = ToolingExtensions.getExtension(ex, "exValue");
4260        if (exv != null) {
4261          Type value = exv.getValue();
4262        if (index.equals(ndx) && value != null)
4263          return true;
4264        }
4265       }
4266    return false;
4267  }
4268
4269
4270  private boolean hasAnyExampleValues(StructureDefinition sd) {
4271    for (ElementDefinition ed : sd.getSnapshot().getElement())
4272      if (ed.hasExample())
4273        return true;
4274    return false;
4275  }
4276
4277
4278  public void populateLogicalSnapshot(StructureDefinition sd) throws FHIRException {
4279    sd.getSnapshot().getElement().add(sd.getDifferential().getElementFirstRep().copy());
4280    
4281    if (sd.hasBaseDefinition()) {
4282    StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
4283    if (base == null)
4284      throw new FHIRException("Unable to find base definition for logical model: "+sd.getBaseDefinition()+" from "+sd.getUrl());
4285    copyElements(sd, base.getSnapshot().getElement());
4286    }
4287    copyElements(sd, sd.getDifferential().getElement());
4288  }
4289
4290
4291  private void copyElements(StructureDefinition sd, List<ElementDefinition> list) {
4292    for (ElementDefinition ed : list) {
4293      if (ed.getPath().contains(".")) {
4294        ElementDefinition n = ed.copy();
4295        n.setPath(sd.getSnapshot().getElementFirstRep().getPath()+"."+ed.getPath().substring(ed.getPath().indexOf(".")+1));
4296        sd.getSnapshot().addElement(n);
4297      }
4298    }
4299  }
4300
4301    
4302  public void cleanUpDifferential(StructureDefinition sd) {
4303    if (sd.getDifferential().getElement().size() > 1)
4304      cleanUpDifferential(sd, 1);
4305  }
4306  
4307  private void cleanUpDifferential(StructureDefinition sd, int start) {
4308    int level = Utilities.charCount(sd.getDifferential().getElement().get(start).getPath(), '.');
4309    int c = start;
4310    int len = sd.getDifferential().getElement().size();
4311    HashSet<String> paths = new HashSet<String>();
4312    while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') == level) {
4313      ElementDefinition ed = sd.getDifferential().getElement().get(c);
4314      if (!paths.contains(ed.getPath())) {
4315        paths.add(ed.getPath());
4316        int ic = c+1; 
4317        while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 
4318          ic++;
4319        ElementDefinition slicer = null;
4320        List<ElementDefinition> slices = new ArrayList<ElementDefinition>();
4321        slices.add(ed);
4322        while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') == level) {
4323          ElementDefinition edi = sd.getDifferential().getElement().get(ic);
4324          if (ed.getPath().equals(edi.getPath())) {
4325            if (slicer == null) {
4326              slicer = new ElementDefinition();
4327              slicer.setPath(edi.getPath());
4328              slicer.getSlicing().setRules(SlicingRules.OPEN);
4329              sd.getDifferential().getElement().add(c, slicer);
4330              c++;
4331              ic++;
4332            }
4333            slices.add(edi);
4334          }
4335          ic++;
4336          while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 
4337            ic++;
4338        }
4339        // now we're at the end, we're going to figure out the slicing discriminator
4340        if (slicer != null)
4341          determineSlicing(slicer, slices);
4342      }
4343      c++;
4344      if (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) {
4345        cleanUpDifferential(sd, c);
4346        c++;
4347        while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) 
4348          c++;
4349      }
4350  }
4351  }
4352
4353
4354  private void determineSlicing(ElementDefinition slicer, List<ElementDefinition> slices) {
4355    // first, name them
4356    int i = 0;
4357    for (ElementDefinition ed : slices) {
4358      if (ed.hasUserData("slice-name")) {
4359        ed.setSliceName(ed.getUserString("slice-name"));
4360      } else {
4361        i++;
4362        ed.setSliceName("slice-"+Integer.toString(i));
4363      }
4364    }
4365    // now, the hard bit, how are they differentiated? 
4366    // right now, we hard code this...
4367    if (slicer.getPath().endsWith(".extension") || slicer.getPath().endsWith(".modifierExtension"))
4368      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url");
4369    else if (slicer.getPath().equals("DiagnosticReport.result"))
4370      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("reference.code");
4371    else if (slicer.getPath().equals("Observation.related"))
4372      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("target.reference.code");
4373    else if (slicer.getPath().equals("Bundle.entry"))
4374      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("resource.@profile");
4375    else  
4376      throw new Error("No slicing for "+slicer.getPath()); 
4377  }
4378
4379  public class SpanEntry {
4380    private List<SpanEntry> children = new ArrayList<SpanEntry>();
4381    private boolean profile;
4382    private String id;
4383    private String name;
4384    private String resType;
4385    private String cardinality;
4386    private String description;
4387    private String profileLink;
4388    private String resLink;
4389    private String type;
4390    
4391    public String getName() {
4392      return name;
4393    }
4394    public void setName(String name) {
4395      this.name = name;
4396    }
4397    public String getResType() {
4398      return resType;
4399    }
4400    public void setResType(String resType) {
4401      this.resType = resType;
4402    }
4403    public String getCardinality() {
4404      return cardinality;
4405    }
4406    public void setCardinality(String cardinality) {
4407      this.cardinality = cardinality;
4408    }
4409    public String getDescription() {
4410      return description;
4411    }
4412    public void setDescription(String description) {
4413      this.description = description;
4414    }
4415    public String getProfileLink() {
4416      return profileLink;
4417    }
4418    public void setProfileLink(String profileLink) {
4419      this.profileLink = profileLink;
4420    }
4421    public String getResLink() {
4422      return resLink;
4423    }
4424    public void setResLink(String resLink) {
4425      this.resLink = resLink;
4426    }
4427    public String getId() {
4428      return id;
4429    }
4430    public void setId(String id) {
4431      this.id = id;
4432    }
4433    public boolean isProfile() {
4434      return profile;
4435    }
4436    public void setProfile(boolean profile) {
4437      this.profile = profile;
4438    }
4439    public List<SpanEntry> getChildren() {
4440      return children;
4441    }
4442    public String getType() {
4443      return type;
4444    }
4445    public void setType(String type) {
4446      this.type = type;
4447    }
4448    
4449  }
4450
4451  public XhtmlNode generateSpanningTable(StructureDefinition profile, String imageFolder, boolean onlyConstraints, String constraintPrefix, Set<String> outputTracker) throws IOException, FHIRException {
4452    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, false, true);
4453    gen.setTranslator(getTranslator());
4454    TableModel model = initSpanningTable(gen, "", false, profile.getId());
4455    Set<String> processed = new HashSet<String>();
4456    SpanEntry span = buildSpanningTable("(focus)", "", profile, processed, onlyConstraints, constraintPrefix);
4457    
4458    genSpanEntry(gen, model.getRows(), span);
4459    return gen.generate(model, "", 0, outputTracker);
4460  }
4461
4462  private SpanEntry buildSpanningTable(String name, String cardinality, StructureDefinition profile, Set<String> processed, boolean onlyConstraints, String constraintPrefix) throws IOException {
4463    SpanEntry res = buildSpanEntryFromProfile(name, cardinality, profile);
4464    boolean wantProcess = !processed.contains(profile.getUrl());
4465    processed.add(profile.getUrl());
4466    if (wantProcess && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
4467      for (ElementDefinition ed : profile.getSnapshot().getElement()) {
4468        if (!"0".equals(ed.getMax()) && ed.getType().size() > 0) {
4469          String card = getCardinality(ed, profile.getSnapshot().getElement());
4470          if (!card.endsWith(".0")) {
4471            List<String> refProfiles = listReferenceProfiles(ed);
4472            if (refProfiles.size() > 0) {
4473              String uri = refProfiles.get(0);
4474              if (uri != null) {
4475                StructureDefinition sd = context.fetchResource(StructureDefinition.class, uri);
4476                if (sd != null && (!onlyConstraints || (sd.getDerivation() == TypeDerivationRule.CONSTRAINT && (constraintPrefix == null || sd.getUrl().startsWith(constraintPrefix))))) {
4477                  res.getChildren().add(buildSpanningTable(nameForElement(ed), card, sd, processed, onlyConstraints, constraintPrefix));
4478                }
4479              }
4480            }
4481          }
4482        } 
4483      }
4484    }
4485    return res;
4486  }
4487
4488
4489  private String getCardinality(ElementDefinition ed, List<ElementDefinition> list) {
4490    int min = ed.getMin();
4491    int max = !ed.hasMax() || ed.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(ed.getMax());
4492    while (ed != null && ed.getPath().contains(".")) {
4493      ed = findParent(ed, list);
4494      if (ed.getMax().equals("0"))
4495        max = 0;
4496      else if (!ed.getMax().equals("1") && !ed.hasSlicing())
4497        max = Integer.MAX_VALUE;
4498      if (ed.getMin() == 0)
4499        min = 0;
4500    }
4501    return Integer.toString(min)+".."+(max == Integer.MAX_VALUE ? "*" : Integer.toString(max));
4502  }
4503
4504
4505  private ElementDefinition findParent(ElementDefinition ed, List<ElementDefinition> list) {
4506    int i = list.indexOf(ed)-1;
4507    while (i >= 0 && !ed.getPath().startsWith(list.get(i).getPath()+"."))
4508      i--;
4509    if (i == -1)
4510      return null;
4511    else
4512      return list.get(i);
4513  }
4514
4515
4516  private List<String> listReferenceProfiles(ElementDefinition ed) {
4517    List<String> res = new ArrayList<String>();
4518    for (TypeRefComponent tr : ed.getType()) {
4519      // code is null if we're dealing with "value" and profile is null if we just have Reference()
4520      if (tr.hasTarget() && tr.hasTargetProfile())
4521        for (UriType u : tr.getTargetProfile())
4522          res.add(u.getValue());
4523    }
4524    return res;
4525  }
4526
4527
4528  private String nameForElement(ElementDefinition ed) {
4529    return ed.getPath().substring(ed.getPath().indexOf(".")+1);
4530  }
4531
4532
4533  private SpanEntry buildSpanEntryFromProfile(String name, String cardinality, StructureDefinition profile) throws IOException {
4534    SpanEntry res = new SpanEntry();
4535    res.setName(name);
4536    res.setCardinality(cardinality);
4537    res.setProfileLink(profile.getUserString("path"));
4538    res.setResType(profile.getType());
4539    StructureDefinition base = context.fetchResource(StructureDefinition.class, res.getResType());
4540    if (base != null)
4541      res.setResLink(base.getUserString("path"));
4542    res.setId(profile.getId());
4543    res.setProfile(profile.getDerivation() == TypeDerivationRule.CONSTRAINT);
4544    StringBuilder b = new StringBuilder();
4545    b.append(res.getResType());
4546    boolean first = true;
4547    boolean open = false;
4548    if (profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
4549      res.setDescription(profile.getName());
4550      for (ElementDefinition ed : profile.getSnapshot().getElement()) {
4551        if (isKeyProperty(ed.getBase().getPath()) && ed.hasFixed()) {
4552          if (first) {
4553            open = true;
4554            first = false;
4555            b.append("[");
4556          } else {
4557            b.append(", ");
4558          }
4559          b.append(tail(ed.getBase().getPath()));
4560          b.append("=");
4561          b.append(summarize(ed.getFixed()));
4562        }
4563      }
4564      if (open)
4565        b.append("]");
4566    } else
4567      res.setDescription("Base FHIR "+profile.getName());
4568    res.setType(b.toString());
4569    return res ;
4570  }
4571
4572
4573  private String summarize(Type value) throws IOException {
4574    if (value instanceof Coding)
4575      return summarizeCoding((Coding) value);
4576    else if (value instanceof CodeableConcept)
4577      return summarizeCodeableConcept((CodeableConcept) value);
4578    else
4579      return buildJson(value);
4580  }
4581
4582
4583  private String summarizeCoding(Coding value) {
4584    String uri = value.getSystem();
4585    String system = NarrativeGenerator.describeSystem(uri);
4586    if (Utilities.isURL(system)) {
4587      if (system.equals("http://cap.org/protocols"))
4588        system = "CAP Code";
4589    }
4590    return system+" "+value.getCode();
4591  }
4592
4593
4594  private String summarizeCodeableConcept(CodeableConcept value) {
4595    if (value.hasCoding())
4596      return summarizeCoding(value.getCodingFirstRep());
4597    else
4598      return value.getText();
4599  }
4600
4601
4602  private boolean isKeyProperty(String path) {
4603    return Utilities.existsInList(path, "Observation.code");
4604  }
4605
4606
4607  public TableModel initSpanningTable(HierarchicalTableGenerator gen, String prefix, boolean isLogical, String id) {
4608    TableModel model = gen.new TableModel(id, false);
4609    
4610    model.setDocoImg(prefix+"help16.png");
4611    model.setDocoRef(prefix+"formats.html#table"); // todo: change to graph definition
4612    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Property", "A profiled resource", null, 0));
4613    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));
4614    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Content", "What goes here", null, 0));
4615    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Description", "Description of the profile", null, 0));
4616    return model;
4617  }
4618
4619  private void genSpanEntry(HierarchicalTableGenerator gen, List<Row> rows, SpanEntry span) throws IOException {
4620    Row row = gen.new Row();
4621    rows.add(row);
4622    row.setAnchor(span.getId());
4623    //row.setColor(..?);
4624    if (span.isProfile()) 
4625      row.setIcon("icon_profile.png", HierarchicalTableGenerator.TEXT_ICON_PROFILE);
4626    else
4627      row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
4628    
4629    row.getCells().add(gen.new Cell(null, null, span.getName(), null, null));
4630    row.getCells().add(gen.new Cell(null, null, span.getCardinality(), null, null));
4631    row.getCells().add(gen.new Cell(null, span.getProfileLink(), span.getType(), null, null));
4632    row.getCells().add(gen.new Cell(null, null, span.getDescription(), null, null));
4633
4634    for (SpanEntry child : span.getChildren())
4635      genSpanEntry(gen, row.getSubRows(), child);
4636  }
4637
4638
4639  public static ElementDefinitionSlicingDiscriminatorComponent interpretR2Discriminator(String discriminator, boolean isExists) {
4640    if (discriminator.endsWith("@pattern"))
4641      return makeDiscriminator(DiscriminatorType.PATTERN, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 
4642    if (discriminator.endsWith("@profile"))
4643      return makeDiscriminator(DiscriminatorType.PROFILE, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 
4644    if (discriminator.endsWith("@type")) 
4645      return makeDiscriminator(DiscriminatorType.TYPE, discriminator.length() == 5 ? "" : discriminator.substring(0,discriminator.length()-6));
4646    if (discriminator.endsWith("@exists"))
4647      return makeDiscriminator(DiscriminatorType.EXISTS, discriminator.length() == 7 ? "" : discriminator.substring(0,discriminator.length()-8)); 
4648    if (isExists)
4649      return makeDiscriminator(DiscriminatorType.EXISTS, discriminator); 
4650    return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(discriminator);
4651  }
4652
4653
4654  private static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType dType, String str) {
4655    return new ElementDefinitionSlicingDiscriminatorComponent().setType(dType).setPath(Utilities.noString(str)? "$this" : str);
4656  }
4657
4658
4659  public static String buildR2Discriminator(ElementDefinitionSlicingDiscriminatorComponent t) throws FHIRException {
4660    switch (t.getType()) {
4661    case PROFILE: return t.getPath()+"/@profile";
4662    case TYPE: return t.getPath()+"/@type";
4663    case VALUE: return t.getPath();
4664    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
4665    default: throw new FHIRException("Unable to represent "+t.getType().toCode()+":"+t.getPath()+" in R2");    
4666    }
4667  }
4668
4669
4670  public static StructureDefinition makeExtensionForVersionedURL(IWorkerContext context, String url) {
4671    String epath = url.substring(54);
4672    if (!epath.contains("."))
4673      return null;
4674    String type = epath.substring(0, epath.indexOf("."));
4675    StructureDefinition sd = context.fetchTypeDefinition(type);
4676    if (sd == null)
4677      return null;
4678    ElementDefinition ed = null;
4679    for (ElementDefinition t : sd.getSnapshot().getElement()) {
4680      if (t.getPath().equals(epath)) {
4681        ed = t;
4682        break;
4683      }
4684    }
4685    if (ed == null)
4686      return null;
4687    if ("Element".equals(ed.typeSummary()) || "BackboneElement".equals(ed.typeSummary())) {
4688      return null;
4689    } else {
4690      StructureDefinition template = context.fetchResource(StructureDefinition.class, "http://fhir-registry.smarthealthit.org/StructureDefinition/capabilities");
4691      StructureDefinition ext = template.copy();
4692      ext.setUrl(url);
4693      ext.setId("extension-"+epath);
4694      ext.setName("Extension-"+epath);
4695      ext.setTitle("Extension for r4 "+epath);
4696      ext.setStatus(sd.getStatus());
4697      ext.setDate(sd.getDate());
4698      ext.getContact().clear();
4699      ext.getContact().addAll(sd.getContact());
4700      ext.setFhirVersion(sd.getFhirVersion());
4701      ext.setDescription(ed.getDefinition());
4702      ext.getContext().clear();
4703      ext.addContext().setType(ExtensionContextType.ELEMENT).setExpression(epath.substring(0, epath.lastIndexOf(".")));
4704      ext.getDifferential().getElement().clear();
4705      ext.getSnapshot().getElement().get(3).setFixed(new UriType(url));
4706      ext.getSnapshot().getElement().set(4, ed.copy());
4707      ext.getSnapshot().getElement().get(4).setPath("Extension.value"+Utilities.capitalize(ed.typeSummary()));
4708      return ext;      
4709    }
4710
4711  }
4712
4713
4714  public boolean isThrowException() {
4715    return exception;
4716  }
4717
4718
4719  public void setThrowException(boolean exception) {
4720    this.exception = exception;
4721  }
4722
4723
4724  public TerminologyServiceOptions getTerminologyServiceOptions() {
4725    return terminologyServiceOptions;
4726  }
4727
4728
4729  public void setTerminologyServiceOptions(TerminologyServiceOptions terminologyServiceOptions) {
4730    this.terminologyServiceOptions = terminologyServiceOptions;
4731  }
4732
4733
4734  public boolean isNewSlicingProcessing() {
4735    return newSlicingProcessing;
4736  }
4737
4738
4739  public void setNewSlicingProcessing(boolean newSlicingProcessing) {
4740    this.newSlicingProcessing = newSlicingProcessing;
4741  }
4742
4743
4744  public boolean isDebug() {
4745    return debug;
4746  }
4747
4748
4749  public void setDebug(boolean debug) {
4750    this.debug = debug;
4751  }
4752
4753
4754
4755  
4756}