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