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