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