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