001package org.hl7.fhir.dstu3.conformance;
002
003/*-
004 * #%L
005 * org.hl7.fhir.dstu3
006 * %%
007 * Copyright (C) 2014 - 2019 Health Level 7
008 * %%
009 * Licensed under the Apache License, Version 2.0 (the "License");
010 * you may not use this file except in compliance with the License.
011 * You may obtain a copy of the License at
012 * 
013 *      http://www.apache.org/licenses/LICENSE-2.0
014 * 
015 * Unless required by applicable law or agreed to in writing, software
016 * distributed under the License is distributed on an "AS IS" BASIS,
017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
018 * See the License for the specific language governing permissions and
019 * limitations under the License.
020 * #L%
021 */
022
023import java.io.IOException;
024import java.io.OutputStream;
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.Comparator;
028import java.util.HashMap;
029import java.util.HashSet;
030import java.util.List;
031import java.util.Map;
032import java.util.Set;
033
034import org.apache.commons.lang3.StringUtils;
035import org.hl7.fhir.dstu3.conformance.ProfileUtilities.ProfileKnowledgeProvider.BindingResolution;
036import org.hl7.fhir.dstu3.context.IWorkerContext;
037import org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult;
038import org.hl7.fhir.dstu3.elementmodel.ObjectConverter;
039import org.hl7.fhir.dstu3.elementmodel.Property;
040import org.hl7.fhir.dstu3.formats.IParser;
041import org.hl7.fhir.dstu3.model.Base;
042import org.hl7.fhir.dstu3.model.BooleanType;
043import org.hl7.fhir.dstu3.model.CodeType;
044import org.hl7.fhir.dstu3.model.CodeableConcept;
045import org.hl7.fhir.dstu3.model.Coding;
046import org.hl7.fhir.dstu3.model.Element;
047import org.hl7.fhir.dstu3.model.ElementDefinition;
048import org.hl7.fhir.dstu3.model.ElementDefinition.AggregationMode;
049import org.hl7.fhir.dstu3.model.ElementDefinition.DiscriminatorType;
050import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionBaseComponent;
051import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionBindingComponent;
052import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionConstraintComponent;
053import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionExampleComponent;
054import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionMappingComponent;
055import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionSlicingComponent;
056import org.hl7.fhir.dstu3.model.ElementDefinition.ElementDefinitionSlicingDiscriminatorComponent;
057import org.hl7.fhir.dstu3.model.ElementDefinition.SlicingRules;
058import org.hl7.fhir.dstu3.model.ElementDefinition.TypeRefComponent;
059import org.hl7.fhir.dstu3.model.Enumeration;
060import org.hl7.fhir.dstu3.model.Enumerations.BindingStrength;
061import org.hl7.fhir.dstu3.model.Extension;
062import org.hl7.fhir.dstu3.model.IntegerType;
063import org.hl7.fhir.dstu3.model.PrimitiveType;
064import org.hl7.fhir.dstu3.model.Quantity;
065import org.hl7.fhir.dstu3.model.Reference;
066import org.hl7.fhir.dstu3.model.Resource;
067import org.hl7.fhir.dstu3.model.StringType;
068import org.hl7.fhir.dstu3.model.StructureDefinition;
069import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionDifferentialComponent;
070import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionKind;
071import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionMappingComponent;
072import org.hl7.fhir.dstu3.model.StructureDefinition.StructureDefinitionSnapshotComponent;
073import org.hl7.fhir.dstu3.model.StructureDefinition.TypeDerivationRule;
074import org.hl7.fhir.dstu3.model.Type;
075import org.hl7.fhir.dstu3.model.UriType;
076import org.hl7.fhir.dstu3.model.ValueSet;
077import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent;
078import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionContainsComponent;
079import org.hl7.fhir.dstu3.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
080import org.hl7.fhir.dstu3.utils.NarrativeGenerator;
081import org.hl7.fhir.dstu3.utils.ToolingExtensions;
082import org.hl7.fhir.dstu3.utils.TranslatingUtilities;
083import org.hl7.fhir.dstu3.utils.formats.CSVWriter;
084import org.hl7.fhir.exceptions.DefinitionException;
085import org.hl7.fhir.exceptions.FHIRException;
086import org.hl7.fhir.exceptions.FHIRFormatError;
087import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
088import org.hl7.fhir.utilities.Utilities;
089import org.hl7.fhir.utilities.validation.ValidationMessage;
090import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
091import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
092import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
093import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
094import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
095import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
096import org.hl7.fhir.utilities.xhtml.XhtmlNode;
097import org.hl7.fhir.utilities.xml.SchematronWriter;
098import org.hl7.fhir.utilities.xml.SchematronWriter.Rule;
099import org.hl7.fhir.utilities.xml.SchematronWriter.SchematronType;
100import org.hl7.fhir.utilities.xml.SchematronWriter.Section;
101
102/**
103 * This class provides a set of utility operations for working with Profiles.
104 * Key functionality:
105 *  * getChildMap --?
106 *  * getChildList
107 *  * generateSnapshot: Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
108 *  * closeDifferential: fill out a differential by excluding anything not mentioned
109 *  * generateExtensionsTable: generate the HTML for a hierarchical table presentation of the extensions
110 *  * generateTable: generate  the HTML for a hierarchical table presentation of a structure
111 *  * generateSpanningTable: generate the HTML for a table presentation of a network of structures, starting at a nominated point
112 *  * summarise: describe the contents of a profile
113 *  
114 * note to maintainers: Do not make modifications to the snapshot generation without first changing the snapshot generation test cases to demonstrate the grounds for your change
115 *  
116 * @author Grahame
117 *
118 */
119public class ProfileUtilities extends TranslatingUtilities {
120
121  private static int nextSliceId = 0;
122  
123  public class ExtensionContext {
124
125    private ElementDefinition element;
126    private StructureDefinition defn;
127
128    public ExtensionContext(StructureDefinition ext, ElementDefinition ed) {
129      this.defn = ext;
130      this.element = ed;
131    }
132
133    public ElementDefinition getElement() {
134      return element;
135    }
136
137    public StructureDefinition getDefn() {
138      return defn;
139    }
140
141    public String getUrl() {
142      if (element == defn.getSnapshot().getElement().get(0))
143        return defn.getUrl();
144      else
145        return element.getSliceName();
146    }
147
148    public ElementDefinition getExtensionValueDefinition() {
149      int i = defn.getSnapshot().getElement().indexOf(element)+1;
150      while (i < defn.getSnapshot().getElement().size()) {
151        ElementDefinition ed = defn.getSnapshot().getElement().get(i);
152        if (ed.getPath().equals(element.getPath()))
153          return null;
154        if (ed.getPath().startsWith(element.getPath()+".value"))
155          return ed;
156        i++;
157      }
158      return null;
159    }
160    
161  }
162
163  private static final String ROW_COLOR_ERROR = "#ffcccc";
164  private static final String ROW_COLOR_FATAL = "#ff9999";
165  private static final String ROW_COLOR_WARNING = "#ffebcc";
166  private static final String ROW_COLOR_HINT = "#ebf5ff";
167  private static final String ROW_COLOR_NOT_MUST_SUPPORT = "#d6eaf8";
168  public static final int STATUS_OK = 0;
169  public static final int STATUS_HINT = 1;
170  public static final int STATUS_WARNING = 2;
171  public static final int STATUS_ERROR = 3;
172  public static final int STATUS_FATAL = 4;
173
174
175  private static final String DERIVATION_EQUALS = "derivation.equals";
176  public static final String DERIVATION_POINTER = "derived.pointer";
177  public static final String IS_DERIVED = "derived.fact";
178  public static final String UD_ERROR_STATUS = "error-status";
179  private static final String GENERATED_IN_SNAPSHOT = "profileutilities.snapshot.processed";
180
181  // note that ProfileUtilities are used re-entrantly internally, so nothing with process state can be here
182  private final IWorkerContext context;
183  private List<ValidationMessage> messages;
184  private List<String> snapshotStack = new ArrayList<String>();
185  private ProfileKnowledgeProvider pkp;
186  private boolean igmode;
187
188  public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp) {
189    super();
190    this.context = context;
191    this.messages = messages;
192    this.pkp = pkp;
193  }
194
195  private class UnusedTracker {
196    private boolean used;
197  }
198
199  public boolean isIgmode() {
200    return igmode;
201  }
202
203
204  public void setIgmode(boolean igmode) {
205    this.igmode = igmode;
206  }
207
208  public interface ProfileKnowledgeProvider {
209    public class BindingResolution {
210      public String display;
211      public String url;
212    }
213    boolean isDatatype(String typeSimple);
214    boolean isResource(String typeSimple);
215    boolean hasLinkFor(String typeSimple);
216    String getLinkFor(String corePath, String typeSimple);
217    BindingResolution resolveBinding(StructureDefinition def, ElementDefinitionBindingComponent binding, String path);
218    String getLinkForProfile(StructureDefinition profile, String url);
219    boolean prependLinks();
220  }
221
222
223
224  public static List<ElementDefinition> getChildMap(StructureDefinition profile, ElementDefinition element) throws DefinitionException {
225    if (element.getContentReference()!=null) {
226      for (ElementDefinition e : profile.getSnapshot().getElement()) {
227        if (element.getContentReference().equals("#"+e.getId()))
228          return getChildMap(profile, e);
229      }
230      throw new DefinitionException("Unable to resolve name reference "+element.getContentReference()+" at path "+element.getPath());
231
232    } else {
233      List<ElementDefinition> res = new ArrayList<ElementDefinition>();
234      List<ElementDefinition> elements = profile.getSnapshot().getElement();
235      String path = element.getPath();
236      for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) {
237        ElementDefinition e = elements.get(index);
238        if (e.getPath().startsWith(path + ".")) {
239          // We only want direct children, not all descendants
240          if (!e.getPath().substring(path.length()+1).contains("."))
241            res.add(e);
242        } else
243          break;
244      }
245      return res;
246    }
247  }
248
249
250  public static List<ElementDefinition> getSliceList(StructureDefinition profile, ElementDefinition element) throws DefinitionException {
251    if (!element.hasSlicing())
252      throw new Error("getSliceList should only be called when the element has slicing");
253
254    List<ElementDefinition> res = new ArrayList<ElementDefinition>();
255    List<ElementDefinition> elements = profile.getSnapshot().getElement();
256    String path = element.getPath();
257    for (int index = elements.indexOf(element) + 1; index < elements.size(); index++) {
258      ElementDefinition e = elements.get(index);
259      if (e.getPath().startsWith(path + ".") || e.getPath().equals(path)) {
260        // We want elements with the same path (until we hit an element that doesn't start with the same path)
261        if (e.getPath().equals(element.getPath()))
262          res.add(e);
263      } else
264        break;
265    }
266    return res;
267  }
268
269
270  /**
271   * Given a Structure, navigate to the element given by the path and return the direct children of that element
272   *
273   * @param structure The structure to navigate into
274   * @param path The path of the element within the structure to get the children for
275   * @return A List containing the element children (all of them are Elements)
276   */
277  public static List<ElementDefinition> getChildList(StructureDefinition profile, String path, String id) {
278    List<ElementDefinition> res = new ArrayList<ElementDefinition>();
279
280    boolean capturing = id==null;
281    if (id==null && !path.contains("."))
282      capturing = true;
283    
284    for (ElementDefinition e : profile.getSnapshot().getElement()) {
285      if (!capturing && id!=null && e.getId().equals(id)) {
286        capturing = true;
287      }
288      
289      // If our element is a slice, stop capturing children as soon as we see the next slice
290      if (capturing && e.hasId() && id!= null && !e.getId().equals(id) && e.getPath().equals(path))
291        break;
292      
293      if (capturing) {
294        String p = e.getPath();
295  
296        if (!Utilities.noString(e.getContentReference()) && path.startsWith(p)) {
297          if (path.length() > p.length())
298            return getChildList(profile, e.getContentReference()+"."+path.substring(p.length()+1), null);
299          else
300            return getChildList(profile, e.getContentReference(), null);
301          
302        } else if (p.startsWith(path+".") && !p.equals(path)) {
303          String tail = p.substring(path.length()+1);
304          if (!tail.contains(".")) {
305            res.add(e);
306          }
307        }
308      }
309    }
310
311    return res;
312  }
313
314
315  public static List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element) {
316    return getChildList(structure, element.getPath(), element.getId());
317        }
318
319  public void updateMaps(StructureDefinition base, StructureDefinition derived) throws DefinitionException {
320    if (base == null)
321        throw new DefinitionException("no base profile provided");
322    if (derived == null)
323      throw new DefinitionException("no derived structure provided");
324    
325    for (StructureDefinitionMappingComponent baseMap : base.getMapping()) {
326      boolean found = false;
327      for (StructureDefinitionMappingComponent derivedMap : derived.getMapping()) {
328        if (derivedMap.getUri().equals(baseMap.getUri())) {
329          found = true;
330          break;
331        }
332      }
333      if (!found)
334        derived.getMapping().add(baseMap);
335    }
336  }
337  
338  /**
339   * Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
340   *
341   * @param base - the base structure on which the differential will be applied
342   * @param differential - the differential to apply to the base
343   * @param url - where the base has relative urls for profile references, these need to be converted to absolutes by prepending this URL
344   * @param trimDifferential - if this is true, then the snap short generator will remove any material in the element definitions that is not different to the base
345   * @return
346   * @throws FHIRException 
347   * @throws DefinitionException 
348   * @throws Exception
349   */
350  public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String profileName) throws DefinitionException, FHIRException {
351    if (base == null)
352      throw new DefinitionException("no base profile provided");
353    if (derived == null)
354      throw new DefinitionException("no derived structure provided");
355
356    if (snapshotStack.contains(derived.getUrl()))
357      throw new DefinitionException("Circular snapshot references detected; cannot generate snapshot (stack = "+snapshotStack.toString()+")");
358    snapshotStack.add(derived.getUrl());
359    
360
361    derived.setSnapshot(new StructureDefinitionSnapshotComponent());
362
363    // so we have two lists - the base list, and the differential list
364    // the differential list is only allowed to include things that are in the base list, but
365    // is allowed to include them multiple times - thereby slicing them
366
367    // our approach is to walk through the base list, and see whether the differential
368    // says anything about them.
369    int baseCursor = 0;
370    int diffCursor = 0; // we need a diff cursor because we can only look ahead, in the bound scoped by longer paths
371
372    if (derived.hasDifferential() && !derived.getDifferential().getElementFirstRep().getPath().contains(".") && !derived.getDifferential().getElementFirstRep().getType().isEmpty())
373      throw new Error("type on first differential element!");
374
375    for (ElementDefinition e : derived.getDifferential().getElement()) 
376      e.clearUserData(GENERATED_IN_SNAPSHOT);
377    
378    // we actually delegate the work to a subroutine so we can re-enter it with a different cursors
379    processPaths("", derived.getSnapshot(), base.getSnapshot(), derived.getDifferential(), baseCursor, diffCursor, base.getSnapshot().getElement().size()-1, 
380        derived.getDifferential().hasElement() ? derived.getDifferential().getElement().size()-1 : -1, url, derived.getId(), null, null, false, base.getUrl(), null, false);
381    if (!derived.getSnapshot().getElementFirstRep().getType().isEmpty())
382      throw new Error("type on first snapshot element for "+derived.getSnapshot().getElementFirstRep().getPath()+" in "+derived.getUrl()+" from "+base.getUrl());
383    updateMaps(base, derived);
384    setIds(derived, false);
385    
386    //Check that all differential elements have a corresponding snapshot element
387    for (ElementDefinition e : derived.getDifferential().getElement()) {
388      if (!e.hasUserData(GENERATED_IN_SNAPSHOT)) {
389        System.out.println("Error in snapshot generation: Snapshot for "+derived.getUrl()+" does not contain differential element with id: " + e.getId());
390        System.out.println("Differential: ");
391        for (ElementDefinition ed : derived.getDifferential().getElement())
392          System.out.println("  "+ed.getPath()+" : "+typeSummary(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  id = "+ed.getId());
393        System.out.println("Snapshot: ");
394        for (ElementDefinition ed : derived.getSnapshot().getElement())
395          System.out.println("  "+ed.getPath()+" : "+typeSummary(ed)+"["+ed.getMin()+".."+ed.getMax()+"]"+sliceSummary(ed)+"  id = "+ed.getId());
396        throw new DefinitionException("Snapshot for "+derived.getUrl()+" does not contain differential element with id: " + e.getId());
397//        System.out.println("**BAD Differential element: " + profileName + ":" + e.getId());
398      }
399    }
400  }
401
402  private String sliceSummary(ElementDefinition ed) {
403    if (!ed.hasSlicing() && !ed.hasSliceName())
404      return "";
405    if (ed.hasSliceName())
406      return " (slicename = "+ed.getSliceName()+")";
407    
408    StringBuilder b = new StringBuilder();
409    boolean first = true;
410    for (ElementDefinitionSlicingDiscriminatorComponent d : ed.getSlicing().getDiscriminator()) {
411      if (first) 
412        first = false;
413      else
414        b.append("|");
415      b.append(d.getPath());
416    }
417    return " (slicing by "+b.toString()+")";
418  }
419
420
421  private String typeSummary(ElementDefinition ed) {
422    StringBuilder b = new StringBuilder();
423    boolean first = true;
424    for (TypeRefComponent tr : ed.getType()) {
425      if (first) 
426        first = false;
427      else
428        b.append("|");
429      b.append(tr.getCode());
430    }
431    return b.toString();
432  }
433
434
435  private boolean findMatchingElement(String id, List<ElementDefinition> list) {
436    for (ElementDefinition ed : list) {
437      if (ed.getId().equals(id))
438        return true;
439      if (id.endsWith("[x]")) {
440        if (ed.getId().startsWith(id.substring(0, id.length()-3)) && !ed.getId().substring(id.length()-3).contains("."))
441          return true;
442      }
443    }
444    return false;
445  }
446
447
448  /**
449   * @param trimDifferential
450   * @throws DefinitionException, FHIRException 
451   * @throws Exception
452   */
453  private ElementDefinition processPaths(String indent, StructureDefinitionSnapshotComponent result, StructureDefinitionSnapshotComponent base, StructureDefinitionDifferentialComponent differential, int baseCursor, int diffCursor, int baseLimit,
454      int diffLimit, String url, String profileName, String contextPathSrc, String contextPathDst, boolean trimDifferential, String contextName, String resultPathBase, boolean slicingDone) throws DefinitionException, FHIRException {
455
456//    System.out.println(indent+"PP @ "+resultPathBase+": base = "+baseCursor+" to "+baseLimit+", diff = "+diffCursor+" to "+diffLimit+" (slicing = "+slicingDone+")");
457    ElementDefinition res = null; 
458    // just repeat processing entries until we run out of our allowed scope (1st entry, the allowed scope is all the entries)
459    while (baseCursor <= baseLimit) {
460      // get the current focus of the base, and decide what to do
461      ElementDefinition currentBase = base.getElement().get(baseCursor);
462      String cpath = fixedPath(contextPathSrc, currentBase.getPath());
463//      System.out.println(indent+" - "+cpath+": base = "+baseCursor+" to "+baseLimit+", diff = "+diffCursor+" to "+diffLimit+" (slicingDone = "+slicingDone+")");
464      List<ElementDefinition> diffMatches = getDiffMatches(differential, cpath, diffCursor, diffLimit, profileName, url); // get a list of matching elements in scope
465
466      // in the simple case, source is not sliced.
467      if (!currentBase.hasSlicing()) {
468        if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item
469          // so we just copy it in
470          ElementDefinition outcome = updateURLs(url, currentBase.copy());
471          outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
472          updateFromBase(outcome, currentBase);
473          markDerived(outcome);
474          if (resultPathBase == null)
475            resultPathBase = outcome.getPath();
476          else if (!outcome.getPath().startsWith(resultPathBase))
477            throw new DefinitionException("Adding wrong path");
478          result.getElement().add(outcome);
479          if (hasInnerDiffMatches(differential, cpath, diffCursor, diffLimit, base.getElement())) {
480            // well, the profile walks into this, so we need to as well
481            if (outcome.getType().size() > 1) {
482              for (TypeRefComponent t : outcome.getType()) {
483                if (!t.getCode().equals("Reference"))
484                  throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName);
485              }
486            }
487            StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
488            if (dt == null)
489              throw new DefinitionException(cpath+" has children for type "+typeCode(outcome.getType())+" in profile "+profileName+", but can't find type");
490            contextName = dt.getUrl();
491            int start = diffCursor;
492            while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), cpath+"."))
493              diffCursor++;
494            processPaths(indent+"  ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start, dt.getSnapshot().getElement().size()-1,
495                diffCursor-1, url, profileName, cpath, outcome.getPath(), trimDifferential, contextName, resultPathBase, false);
496          }
497          baseCursor++;
498        } else if (diffMatches.size() == 1 && (slicingDone || !(diffMatches.get(0).hasSlicing() || (isExtension(diffMatches.get(0)) && diffMatches.get(0).hasSliceName())))) {// one matching element in the differential
499          ElementDefinition template = null;
500          if (diffMatches.get(0).hasType() && diffMatches.get(0).getType().size() == 1 && diffMatches.get(0).getType().get(0).hasProfile() && !diffMatches.get(0).getType().get(0).getCode().equals("Reference")) {
501            String p = diffMatches.get(0).getType().get(0).getProfile();
502            StructureDefinition sd = context.fetchResource(StructureDefinition.class, p);
503            if (sd != null) {
504              if (!sd.hasSnapshot()) {
505                StructureDefinition sdb = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
506                if (sdb == null)
507                  throw new DefinitionException("no base for "+sd.getBaseDefinition());
508                generateSnapshot(sdb, sd, sd.getUrl(), sd.getName());
509              }
510              template = sd.getSnapshot().getElement().get(0).copy().setPath(currentBase.getPath());
511              template.setSliceName(null);
512              // temporary work around
513              if (!diffMatches.get(0).getType().get(0).getCode().equals("Extension")) {
514                template.setMin(currentBase.getMin());
515                template.setMax(currentBase.getMax());
516              }
517            }
518          } 
519          if (template == null)
520            template = currentBase.copy();
521          else
522            // some of what's in currentBase overrides template
523            template = overWriteWithCurrent(template, currentBase);
524          
525          ElementDefinition outcome = updateURLs(url, template);
526          outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
527          res = outcome;
528          updateFromBase(outcome, currentBase);
529          if (diffMatches.get(0).hasSliceName())
530            outcome.setSliceName(diffMatches.get(0).getSliceName());
531          outcome.setSlicing(null);
532          updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url);
533          if (outcome.getPath().endsWith("[x]") && outcome.getType().size() == 1 && !outcome.getType().get(0).getCode().equals("*")) // if the base profile allows multiple types, but the profile only allows one, rename it
534            outcome.setPath(outcome.getPath().substring(0, outcome.getPath().length()-3)+Utilities.capitalize(outcome.getType().get(0).getCode()));
535          if (resultPathBase == null)
536            resultPathBase = outcome.getPath();
537          else if (!outcome.getPath().startsWith(resultPathBase))
538            throw new DefinitionException("Adding wrong path");
539          result.getElement().add(outcome);
540          baseCursor++;
541          diffCursor = differential.getElement().indexOf(diffMatches.get(0))+1;
542          if (differential.getElement().size() > diffCursor && outcome.getPath().contains(".") && (isDataType(outcome.getType()) || outcome.hasContentReference())) {  // don't want to do this for the root, since that's base, and we're already processing it
543            if (pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".") && !baseWalksInto(base.getElement(), baseCursor)) {
544              if (outcome.getType().size() > 1) {
545                for (TypeRefComponent t : outcome.getType()) {
546                  if (!t.getCode().equals("Reference"))
547                    throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName);
548                }
549              }
550              int start = diffCursor;
551              while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+"."))
552                diffCursor++;
553              if (outcome.hasContentReference()) {
554                ElementDefinition tgt = getElementById(base.getElement(), outcome.getContentReference());
555                if (tgt == null)
556                  throw new DefinitionException("Unable to resolve reference to "+outcome.getContentReference());
557                replaceFromContentReference(outcome, tgt);
558                int nbc = base.getElement().indexOf(tgt)+1;
559                int nbl = nbc;
560                while (nbl < base.getElement().size() && base.getElement().get(nbl).getPath().startsWith(tgt.getPath()+"."))
561                  nbl++;
562                processPaths(indent+"  ", result, base, differential, nbc, start - 1, nbl-1, diffCursor - 1, url, profileName, tgt.getPath(), diffMatches.get(0).getPath(), trimDifferential, contextName, resultPathBase, false);
563              } else {
564                StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
565                if (dt == null)
566                  throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") for type "+typeCode(outcome.getType())+" in profile "+profileName+", but can't find type");
567                contextName = dt.getUrl();
568                processPaths(indent+"  ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start-1, dt.getSnapshot().getElement().size()-1,
569                    diffCursor - 1, url, profileName+pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false);
570              }
571            }
572          }
573        } else {
574          // ok, the differential slices the item. Let's check our pre-conditions to ensure that this is correct
575          if (!unbounded(currentBase) && !isSlicedToOneOnly(diffMatches.get(0)))
576            // you can only slice an element that doesn't repeat if the sum total of your slices is limited to 1
577            // (but you might do that in order to split up constraints by type)
578            throw new DefinitionException("Attempt to a slice an element that does not repeat: "+currentBase.getPath()+"/"+currentBase.getSliceName()+" from "+contextName+" in "+url);
579          if (!diffMatches.get(0).hasSlicing() && !isExtension(currentBase)) // well, the diff has set up a slice, but hasn't defined it. this is an error
580            throw new DefinitionException("differential does not have a slice: "+currentBase.getPath()+" in profile "+url);
581
582          // well, if it passed those preconditions then we slice the dest.
583          int start = 0;
584          int nbl = findEndOfElement(base, baseCursor);
585          if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && differential.getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0))+1) {
586            int ndc = differential.getElement().indexOf(diffMatches.get(0));
587            int ndl = findEndOfElement(differential, ndc);
588            processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, profileName+pathTail(diffMatches, 0), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true).setSlicing(diffMatches.get(0).getSlicing());
589            start++;
590          } else {
591            // we're just going to accept the differential slicing at face value
592            ElementDefinition outcome = updateURLs(url, currentBase.copy());
593            outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
594            updateFromBase(outcome, currentBase);
595
596            if (!diffMatches.get(0).hasSlicing())
597              outcome.setSlicing(makeExtensionSlicing());
598            else
599              outcome.setSlicing(diffMatches.get(0).getSlicing().copy());
600            if (!outcome.getPath().startsWith(resultPathBase))
601              throw new DefinitionException("Adding wrong path");
602            result.getElement().add(outcome);
603
604            // differential - if the first one in the list has a name, we'll process it. Else we'll treat it as the base definition of the slice.
605            if (!diffMatches.get(0).hasSliceName()) {
606              updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url);
607              if (!outcome.hasContentReference() && !outcome.hasType()) {
608                throw new DefinitionException("not done yet");
609              }
610              start++;
611              // result.getElement().remove(result.getElement().size()-1);
612            } else 
613              checkExtensionDoco(outcome);
614          }
615          // now, for each entry in the diff matches, we're going to process the base item
616          // our processing scope for base is all the children of the current path
617          int ndc = diffCursor;
618          int ndl = diffCursor;
619          for (int i = start; i < diffMatches.size(); i++) {
620            // our processing scope for the differential is the item in the list, and all the items before the next one in the list
621            ndc = differential.getElement().indexOf(diffMatches.get(i));
622            ndl = findEndOfElement(differential, ndc);
623/*            if (skipSlicingElement && i == 0) {
624              ndc = ndc + 1;
625              if (ndc > ndl)
626                continue;
627            }*/
628            // now we process the base scope repeatedly for each instance of the item in the differential list
629            processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, profileName+pathTail(diffMatches, i), contextPathSrc, contextPathDst, trimDifferential, contextName, resultPathBase, true);
630          }
631          // ok, done with that - next in the base list
632          baseCursor = nbl+1;
633          diffCursor = ndl+1;
634        }
635      } else {
636        // the item is already sliced in the base profile.
637        // here's the rules
638        //  1. irrespective of whether the slicing is ordered or not, the definition order must be maintained
639        //  2. slice element names have to match.
640        //  3. new slices must be introduced at the end
641        // corallory: you can't re-slice existing slices. is that ok?
642
643        // we're going to need this:
644        String path = currentBase.getPath();
645        ElementDefinition original = currentBase;
646
647        if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item
648          // copy across the currentbase, and all of its children and siblings
649          while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path)) {
650            ElementDefinition outcome = updateURLs(url, base.getElement().get(baseCursor).copy());
651            outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
652            if (!outcome.getPath().startsWith(resultPathBase))
653              throw new DefinitionException("Adding wrong path in profile " + profileName + ": "+outcome.getPath()+" vs " + resultPathBase);
654            result.getElement().add(outcome); // so we just copy it in
655            baseCursor++;
656          }
657        } else {
658          // first - check that the slicing is ok
659          boolean closed = currentBase.getSlicing().getRules() == SlicingRules.CLOSED;
660          int diffpos = 0;
661          boolean isExtension = cpath.endsWith(".extension") || cpath.endsWith(".modifierExtension");
662          if (diffMatches.get(0).hasSlicing()) { // it might be null if the differential doesn't want to say anything about slicing
663            if (!isExtension)
664              diffpos++; // if there's a slice on the first, we'll ignore any content it has
665            ElementDefinitionSlicingComponent dSlice = diffMatches.get(0).getSlicing();
666            ElementDefinitionSlicingComponent bSlice = currentBase.getSlicing();
667            if (dSlice.hasOrderedElement() && bSlice.hasOrderedElement() && !orderMatches(dSlice.getOrderedElement(), bSlice.getOrderedElement()))
668              throw new DefinitionException("Slicing rules on differential ("+summariseSlicing(dSlice)+") do not match those on base ("+summariseSlicing(bSlice)+") - order @ "+path+" ("+contextName+")");
669            if (!discriminatorMatches(dSlice.getDiscriminator(), bSlice.getDiscriminator()))
670             throw new DefinitionException("Slicing rules on differential ("+summariseSlicing(dSlice)+") do not match those on base ("+summariseSlicing(bSlice)+") - disciminator @ "+path+" ("+contextName+")");
671            if (!ruleMatches(dSlice.getRules(), bSlice.getRules()))
672             throw new DefinitionException("Slicing rules on differential ("+summariseSlicing(dSlice)+") do not match those on base ("+summariseSlicing(bSlice)+") - rule @ "+path+" ("+contextName+")");
673          }
674          if (diffMatches.size() > 1 && diffMatches.get(0).hasSlicing() && differential.getElement().indexOf(diffMatches.get(1)) > differential.getElement().indexOf(diffMatches.get(0))+1) {
675            throw new Error("Not done yet");
676          }
677          ElementDefinition outcome = updateURLs(url, currentBase.copy());
678          outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
679          updateFromBase(outcome, currentBase);
680          if (diffMatches.get(0).hasSlicing() /*&& !isExtension*/) {
681            updateFromSlicing(outcome.getSlicing(), diffMatches.get(0).getSlicing());
682            updateFromDefinition(outcome, diffMatches.get(0), profileName, closed, url); // if there's no slice, we don't want to update the unsliced description
683          } else if (!diffMatches.get(0).hasSliceName())
684            diffMatches.get(0).setUserData(GENERATED_IN_SNAPSHOT, true); // because of updateFromDefinition isn't called 
685          
686          result.getElement().add(outcome);
687
688          if (!diffMatches.get(0).hasSliceName()) { // it's not real content, just the slice
689            diffpos++; 
690          }
691
692          // now, we have two lists, base and diff. we're going to work through base, looking for matches in diff.
693          List<ElementDefinition> baseMatches = getSiblings(base.getElement(), currentBase);
694          for (ElementDefinition baseItem : baseMatches) {
695            baseCursor = base.getElement().indexOf(baseItem);
696            outcome = updateURLs(url, baseItem.copy());
697            updateFromBase(outcome, currentBase);
698            outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
699            outcome.setSlicing(null);
700            if (!outcome.getPath().startsWith(resultPathBase))
701              throw new DefinitionException("Adding wrong path");
702            if (diffpos < diffMatches.size() && diffMatches.get(diffpos).getSliceName().equals(outcome.getSliceName())) {
703              // if there's a diff, we update the outcome with diff
704              // no? updateFromDefinition(outcome, diffMatches.get(diffpos), profileName, closed, url);
705              //then process any children
706              int nbl = findEndOfElement(base, baseCursor);
707              int ndc = differential.getElement().indexOf(diffMatches.get(diffpos));
708              int ndl = findEndOfElement(differential, ndc);
709              // now we process the base scope repeatedly for each instance of the item in the differential list
710              processPaths(indent+"  ", result, base, differential, baseCursor, ndc, nbl, ndl, url, profileName+pathTail(diffMatches, diffpos), contextPathSrc, contextPathDst, closed, contextName, resultPathBase, true);
711              // ok, done with that - now set the cursors for if this is the end
712              baseCursor = nbl;
713              diffCursor = ndl+1;
714              diffpos++;
715            } else {
716              result.getElement().add(outcome);
717              baseCursor++;
718              // just copy any children on the base
719              while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path) && !base.getElement().get(baseCursor).getPath().equals(path)) {
720                outcome = updateURLs(url, base.getElement().get(baseCursor).copy());
721                outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
722                if (!outcome.getPath().startsWith(resultPathBase))
723                  throw new DefinitionException("Adding wrong path");
724                result.getElement().add(outcome);
725                baseCursor++;
726              }
727              //Lloyd - add this for test T15
728              baseCursor--;
729            }
730          }
731          // finally, we process any remaining entries in diff, which are new (and which are only allowed if the base wasn't closed
732          if (closed && diffpos < diffMatches.size())
733            throw new DefinitionException("The base snapshot marks a slicing as closed, but the differential tries to extend it in "+profileName+" at "+path+" ("+cpath+")");
734          if (diffpos == diffMatches.size()) {
735            diffCursor++;
736          } else {
737            while (diffpos < diffMatches.size()) {
738              ElementDefinition diffItem = diffMatches.get(diffpos);
739              for (ElementDefinition baseItem : baseMatches)
740                if (baseItem.getSliceName().equals(diffItem.getSliceName()))
741                  throw new DefinitionException("Named items are out of order in the slice");
742              outcome = updateURLs(url, currentBase.copy());
743              //            outcome = updateURLs(url, diffItem.copy());
744              outcome.setPath(fixedPath(contextPathDst, outcome.getPath()));
745              updateFromBase(outcome, currentBase);
746              outcome.setSlicing(null);
747              if (!outcome.getPath().startsWith(resultPathBase))
748                throw new DefinitionException("Adding wrong path");
749              result.getElement().add(outcome);
750              updateFromDefinition(outcome, diffItem, profileName, trimDifferential, url);
751              // --- LM Added this
752              diffCursor = differential.getElement().indexOf(diffItem)+1;
753              if (!outcome.getType().isEmpty() && (/*outcome.getType().get(0).getCode().equals("Extension") || */differential.getElement().size() > diffCursor) && outcome.getPath().contains(".") && isDataType(outcome.getType())) {  // don't want to do this for the root, since that's base, and we're already processing it
754                if (!baseWalksInto(base.getElement(), baseCursor)) {
755                  if (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".")) {
756                    if (outcome.getType().size() > 1)
757                      for (TypeRefComponent t : outcome.getType()) {
758                        if (!t.getCode().equals("Reference"))
759                          throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName);
760                      }
761                    TypeRefComponent t = outcome.getType().get(0);
762                    StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
763                    //                if (t.getCode().equals("Extension") && t.hasProfile() && !t.getProfile().contains(":")) {
764                    // lloydfix                  dt = 
765                    //                }
766                    if (dt == null)
767                      throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") for type "+typeCode(outcome.getType())+" in profile "+profileName+", but can't find type");
768                    contextName = dt.getUrl();
769                    int start = diffCursor;
770                    while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+"."))
771                      diffCursor++;
772                    processPaths(indent+"  ", result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start-1, dt.getSnapshot().getElement().size()-1,
773                        diffCursor - 1, url, profileName+pathTail(diffMatches, 0), diffMatches.get(0).getPath(), outcome.getPath(), trimDifferential, contextName, resultPathBase, false);
774                  } else if (outcome.getType().get(0).getCode().equals("Extension")) {
775                    // Force URL to appear if we're dealing with an extension.  (This is a kludge - may need to drill down in other cases where we're slicing and the type has a profile declaration that could be setting the fixed value)
776                    StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
777                    for (ElementDefinition extEd : dt.getSnapshot().getElement()) {
778                      // We only want the children that aren't the root
779                      if (extEd.getPath().contains(".")) {
780                        ElementDefinition extUrlEd = updateURLs(url, extEd.copy());
781                        extUrlEd.setPath(fixedPath(outcome.getPath(), extUrlEd.getPath()));
782                        //                      updateFromBase(extUrlEd, currentBase);
783                        markDerived(extUrlEd);
784                        result.getElement().add(extUrlEd);
785                      }
786                    }                  
787                  }
788                }
789              }
790              // ---
791              diffpos++;
792            }
793          }
794          baseCursor++;
795        }
796      }
797    }
798    
799    int i = 0;
800    for (ElementDefinition e : result.getElement()) {
801      i++;
802      if (e.hasMinElement() && e.getMinElement().getValue()==null)
803        throw new Error("null min");
804    }
805    return res;
806  }
807
808
809  private void replaceFromContentReference(ElementDefinition outcome, ElementDefinition tgt) {
810    outcome.setContentReference(null);
811    outcome.getType().clear(); // though it should be clear anyway
812    outcome.getType().addAll(tgt.getType());    
813  }
814
815
816  private boolean baseWalksInto(List<ElementDefinition> elements, int cursor) {
817    if (cursor >= elements.size())
818      return false;
819    String path = elements.get(cursor).getPath();
820    String prevPath = elements.get(cursor - 1).getPath();
821    return path.startsWith(prevPath + ".");
822  }
823
824
825  private ElementDefinition overWriteWithCurrent(ElementDefinition profile, ElementDefinition usage) throws FHIRFormatError {
826    ElementDefinition res = profile.copy();
827    if (usage.hasSliceName())
828      res.setSliceName(usage.getSliceName());
829    if (usage.hasLabel())
830      res.setLabel(usage.getLabel());
831    for (Coding c : usage.getCode())
832      res.addCode(c);
833    
834    if (usage.hasDefinition())
835      res.setDefinition(usage.getDefinition());
836    if (usage.hasShort())
837      res.setShort(usage.getShort());
838    if (usage.hasComment())
839      res.setComment(usage.getComment());
840    if (usage.hasRequirements())
841      res.setRequirements(usage.getRequirements());
842    for (StringType c : usage.getAlias())
843      res.addAlias(c.getValue());
844    if (usage.hasMin())
845      res.setMin(usage.getMin());
846    if (usage.hasMax())
847      res.setMax(usage.getMax());
848     
849    if (usage.hasFixed())
850      res.setFixed(usage.getFixed());
851    if (usage.hasPattern())
852      res.setPattern(usage.getPattern());
853    if (usage.hasExample())
854      res.setExample(usage.getExample());
855    if (usage.hasMinValue())
856      res.setMinValue(usage.getMinValue());
857    if (usage.hasMaxValue())
858      res.setMaxValue(usage.getMaxValue());     
859    if (usage.hasMaxLength())
860      res.setMaxLength(usage.getMaxLength());
861    if (usage.hasMustSupport())
862      res.setMustSupport(usage.getMustSupport());
863    if (usage.hasBinding())
864      res.setBinding(usage.getBinding().copy());
865    for (ElementDefinitionConstraintComponent c : usage.getConstraint())
866      res.addConstraint(c);
867    
868    return res;
869  }
870
871
872  private boolean checkExtensionDoco(ElementDefinition base) {
873    // see task 3970. For an extension, there's no point copying across all the underlying definitional stuff
874    boolean isExtension = base.getPath().equals("Extension") || base.getPath().endsWith(".extension") || base.getPath().endsWith(".modifierExtension");
875    if (isExtension) {
876      base.setDefinition("An Extension");
877      base.setShort("Extension");
878      base.setCommentElement(null);
879      base.setRequirementsElement(null);
880      base.getAlias().clear();
881      base.getMapping().clear();
882    }
883    return isExtension;
884  }
885
886
887  private String pathTail(List<ElementDefinition> diffMatches, int i) {
888    
889    ElementDefinition d = diffMatches.get(i);
890    String s = d.getPath().contains(".") ? d.getPath().substring(d.getPath().lastIndexOf(".")+1) : d.getPath();
891    return "."+s + (d.hasType() && d.getType().get(0).hasProfile() ? "["+d.getType().get(0).getProfile()+"]" : "");
892  }
893
894
895  private void markDerived(ElementDefinition outcome) {
896    for (ElementDefinitionConstraintComponent inv : outcome.getConstraint())
897      inv.setUserData(IS_DERIVED, true);
898  }
899
900
901  private String summariseSlicing(ElementDefinitionSlicingComponent slice) {
902    StringBuilder b = new StringBuilder();
903    boolean first = true;
904    for (ElementDefinitionSlicingDiscriminatorComponent d : slice.getDiscriminator()) {
905      if (first)
906        first = false;
907      else
908        b.append(", ");
909      b.append(d);
910    }
911    b.append("(");
912    if (slice.hasOrdered())
913      b.append(slice.getOrderedElement().asStringValue());
914    b.append("/");
915    if (slice.hasRules())
916      b.append(slice.getRules().toCode());
917    b.append(")");
918    if (slice.hasDescription()) {
919      b.append(" \"");
920      b.append(slice.getDescription());
921      b.append("\"");
922    }
923    return b.toString();
924  }
925
926
927  private void updateFromBase(ElementDefinition derived, ElementDefinition base) {
928    if (base.hasBase()) {
929      if (!derived.hasBase())
930        derived.setBase(new ElementDefinitionBaseComponent());
931      derived.getBase().setPath(base.getBase().getPath());
932      derived.getBase().setMin(base.getBase().getMin());
933      derived.getBase().setMax(base.getBase().getMax());
934    } else {
935      if (!derived.hasBase())
936        derived.setBase(new ElementDefinitionBaseComponent());
937      derived.getBase().setPath(base.getPath());
938      derived.getBase().setMin(base.getMin());
939      derived.getBase().setMax(base.getMax());
940    }
941  }
942
943
944  private boolean pathStartsWith(String p1, String p2) {
945    return p1.startsWith(p2);
946  }
947
948  private boolean pathMatches(String p1, String p2) {
949    return p1.equals(p2) || (p2.endsWith("[x]") && p1.startsWith(p2.substring(0, p2.length()-3)) && !p1.substring(p2.length()-3).contains("."));
950  }
951
952
953  private String fixedPath(String contextPath, String pathSimple) {
954    if (contextPath == null)
955      return pathSimple;
956    return contextPath+"."+pathSimple.substring(pathSimple.indexOf(".")+1);
957  }
958
959
960  private StructureDefinition getProfileForDataType(TypeRefComponent type)  {
961    StructureDefinition sd = null;
962    if (type.hasProfile() && !type.getCode().equals("Reference"))  
963      sd = context.fetchResource(StructureDefinition.class, type.getProfile()); 
964    if (sd == null)
965      sd = context.fetchTypeDefinition(type.getCode());
966    if (sd == null)
967      System.out.println("XX: failed to find profle for type: " + type.getCode()); // debug GJM
968    return sd;
969  }
970
971
972  public static String typeCode(List<TypeRefComponent> types) {
973    StringBuilder b = new StringBuilder();
974    boolean first = true;
975    for (TypeRefComponent type : types) {
976      if (first) first = false; else b.append(", ");
977      b.append(type.getCode());
978      if (type.hasTargetProfile())
979        b.append("{"+type.getTargetProfile()+"}");
980      else if (type.hasProfile())
981        b.append("{"+type.getProfile()+"}");
982    }
983    return b.toString();
984  }
985
986
987  private boolean isDataType(List<TypeRefComponent> types) {
988    if (types.isEmpty())
989      return false;
990    for (TypeRefComponent type : types) {
991      String t = type.getCode();
992      if (!isDataType(t) && !isPrimitive(t))
993        return false;
994    }
995    return true;
996  }
997
998
999  /**
1000   * Finds internal references in an Element's Binding and StructureDefinition references (in TypeRef) and bases them on the given url
1001   * @param url - the base url to use to turn internal references into absolute references
1002   * @param element - the Element to update
1003   * @return - the updated Element
1004   */
1005  private ElementDefinition updateURLs(String url, ElementDefinition element) {
1006    if (element != null) {
1007      ElementDefinition defn = element;
1008      if (defn.hasBinding() && defn.getBinding().getValueSet() instanceof Reference && ((Reference)defn.getBinding().getValueSet()).getReference().startsWith("#"))
1009        ((Reference)defn.getBinding().getValueSet()).setReference(url+((Reference)defn.getBinding().getValueSet()).getReference());
1010      for (TypeRefComponent t : defn.getType()) {
1011        if (t.hasProfile()) {
1012          if (t.getProfile().startsWith("#"))
1013            t.setProfile(url+t.getProfile());
1014        }
1015        if (t.hasTargetProfile()) {
1016          if (t.getTargetProfile().startsWith("#"))
1017            t.setTargetProfile(url+t.getTargetProfile());
1018        }
1019      }
1020    }
1021    return element;
1022  }
1023
1024  private List<ElementDefinition> getSiblings(List<ElementDefinition> list, ElementDefinition current) {
1025    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
1026    String path = current.getPath();
1027    int cursor = list.indexOf(current)+1;
1028    while (cursor < list.size() && list.get(cursor).getPath().length() >= path.length()) {
1029      if (pathMatches(list.get(cursor).getPath(), path))
1030        result.add(list.get(cursor));
1031      cursor++;
1032    }
1033    return result;
1034  }
1035
1036  private void updateFromSlicing(ElementDefinitionSlicingComponent dst, ElementDefinitionSlicingComponent src) {
1037    if (src.hasOrderedElement())
1038      dst.setOrderedElement(src.getOrderedElement().copy());
1039    if (src.hasDiscriminator()) {
1040      //    dst.getDiscriminator().addAll(src.getDiscriminator());  Can't use addAll because it uses object equality, not string equality
1041      for (ElementDefinitionSlicingDiscriminatorComponent s : src.getDiscriminator()) {
1042        boolean found = false;
1043        for (ElementDefinitionSlicingDiscriminatorComponent d : dst.getDiscriminator()) {
1044          if (matches(d, s)) {
1045            found = true;
1046            break;
1047          }
1048        }
1049        if (!found)
1050          dst.getDiscriminator().add(s);
1051      }
1052    }
1053    if (src.hasRulesElement())
1054      dst.setRulesElement(src.getRulesElement().copy());
1055  }
1056
1057  private boolean orderMatches(BooleanType diff, BooleanType base) {
1058    return (diff == null) || (base == null) || (diff.getValue() == base.getValue());
1059  }
1060
1061  private boolean discriminatorMatches(List<ElementDefinitionSlicingDiscriminatorComponent> diff, List<ElementDefinitionSlicingDiscriminatorComponent> base) {
1062    if (diff.isEmpty() || base.isEmpty())
1063        return true;
1064    if (diff.size() != base.size())
1065        return false;
1066    for (int i = 0; i < diff.size(); i++)
1067        if (!matches(diff.get(i), base.get(i)))
1068                return false;
1069    return true;
1070  }
1071
1072  private boolean matches(ElementDefinitionSlicingDiscriminatorComponent c1, ElementDefinitionSlicingDiscriminatorComponent c2) {
1073    return c1.getType().equals(c2.getType()) && c1.getPath().equals(c2.getPath());
1074  }
1075
1076
1077  private boolean ruleMatches(SlicingRules diff, SlicingRules base) {
1078    return (diff == null) || (base == null) || (diff == base) || (diff == SlicingRules.OPEN) ||
1079        ((diff == SlicingRules.OPENATEND && base == SlicingRules.CLOSED));
1080  }
1081
1082  private boolean isSlicedToOneOnly(ElementDefinition e) {
1083    return (e.hasSlicing() && e.hasMaxElement() && e.getMax().equals("1"));
1084  }
1085
1086  private ElementDefinitionSlicingComponent makeExtensionSlicing() {
1087        ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent();
1088        nextSliceId++;
1089        slice.setId(Integer.toString(nextSliceId));
1090    slice.addDiscriminator().setPath("url").setType(DiscriminatorType.VALUE);
1091    slice.setOrdered(false);
1092    slice.setRules(SlicingRules.OPEN);
1093    return slice;
1094  }
1095
1096  private boolean isExtension(ElementDefinition currentBase) {
1097    return currentBase.getPath().endsWith(".extension") || currentBase.getPath().endsWith(".modifierExtension");
1098  }
1099
1100  private boolean hasInnerDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, List<ElementDefinition> base) throws DefinitionException {
1101    for (int i = start; i <= end; i++) {
1102      String statedPath = context.getElement().get(i).getPath();
1103      if (statedPath.startsWith(path+".") && !statedPath.substring(path.length()+1).contains(".")) {
1104        boolean found = false;
1105        for (ElementDefinition ed : base) {
1106          String ep = ed.getPath();
1107          if (ep.equals(statedPath) || (ep.endsWith("[x]") && statedPath.length() > ep.length() - 2 && statedPath.substring(0, ep.length()-3).equals(ep.substring(0, ep.length()-3)) && !statedPath.substring(ep.length()).contains(".")))
1108            found = true;
1109        }
1110        if (!found)
1111          return true;
1112      }
1113    }
1114    return false;
1115  }
1116
1117  private List<ElementDefinition> getDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, String profileName, String url) throws DefinitionException {
1118    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
1119    for (int i = start; i <= end; i++) {
1120      String statedPath = context.getElement().get(i).getPath();
1121      if (statedPath.equals(path) || (path.endsWith("[x]") && statedPath.length() > path.length() - 2 && statedPath.substring(0, path.length()-3).equals(path.substring(0, path.length()-3)) && (statedPath.length() < path.length() || !statedPath.substring(path.length()).contains(".")))) {
1122        /* 
1123         * Commenting this out because it raises warnings when profiling inherited elements.  For example,
1124         * Error: unknown element 'Bundle.meta.profile' (or it is out of order) in profile ... (looking for 'Bundle.entry')
1125         * Not sure we have enough information here to do the check properly.  Might be better done when we're sorting the profile?
1126
1127        if (i != start && result.isEmpty() && !path.startsWith(context.getElement().get(start).getPath()))
1128          messages.add(new ValidationMessage(Source.ProfileValidator, IssueType.VALUE, "StructureDefinition.differential.element["+Integer.toString(start)+"]", "Error: unknown element '"+context.getElement().get(start).getPath()+"' (or it is out of order) in profile '"+url+"' (looking for '"+path+"')", IssueSeverity.WARNING));
1129
1130         */
1131        result.add(context.getElement().get(i));
1132      }
1133    }
1134    return result;
1135  }
1136
1137  private int findEndOfElement(StructureDefinitionDifferentialComponent context, int cursor) {
1138            int result = cursor;
1139            String path = context.getElement().get(cursor).getPath()+".";
1140            while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path))
1141              result++;
1142            return result;
1143          }
1144
1145  private int findEndOfElement(StructureDefinitionSnapshotComponent context, int cursor) {
1146            int result = cursor;
1147            String path = context.getElement().get(cursor).getPath()+".";
1148            while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path))
1149              result++;
1150            return result;
1151          }
1152
1153  private boolean unbounded(ElementDefinition definition) {
1154    StringType max = definition.getMaxElement();
1155    if (max == null)
1156      return false; // this is not valid
1157    if (max.getValue().equals("1"))
1158      return false;
1159    if (max.getValue().equals("0"))
1160      return false;
1161    return true;
1162  }
1163
1164  private void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, boolean trimDifferential, String purl) throws DefinitionException, FHIRException {
1165    source.setUserData(GENERATED_IN_SNAPSHOT, true);
1166    // we start with a clone of the base profile ('dest') and we copy from the profile ('source')
1167    // over the top for anything the source has
1168    ElementDefinition base = dest;
1169    ElementDefinition derived = source;
1170    derived.setUserData(DERIVATION_POINTER, base);
1171
1172    // Before applying changes, apply them to what's in the profile
1173    // TODO: follow Chris's rules
1174    StructureDefinition profile = source.getType().size() == 1 && source.getTypeFirstRep().hasProfile() ? context.fetchResource(StructureDefinition.class, source.getTypeFirstRep().getProfile()) : null;
1175    if (profile != null) {
1176      ElementDefinition e = profile.getSnapshot().getElement().get(0);
1177      base.setDefinition(e.getDefinition());
1178      base.setShort(e.getShort());
1179      if (e.hasCommentElement())
1180        base.setCommentElement(e.getCommentElement());
1181      if (e.hasRequirementsElement())
1182        base.setRequirementsElement(e.getRequirementsElement());
1183      base.getAlias().clear();
1184      base.getAlias().addAll(e.getAlias());
1185      base.getMapping().clear();
1186      base.getMapping().addAll(e.getMapping());
1187    }
1188    
1189    if (derived != null) {
1190      boolean isExtension = checkExtensionDoco(base);
1191
1192      if (derived.hasSliceName()) {
1193        base.setSliceName(derived.getSliceName());
1194      }
1195      
1196      if (derived.hasShortElement()) {
1197        if (!Base.compareDeep(derived.getShortElement(), base.getShortElement(), false))
1198          base.setShortElement(derived.getShortElement().copy());
1199        else if (trimDifferential)
1200          derived.setShortElement(null);
1201        else if (derived.hasShortElement())
1202          derived.getShortElement().setUserData(DERIVATION_EQUALS, true);
1203      }
1204
1205      if (derived.hasDefinitionElement()) {
1206        if (derived.getDefinition().startsWith("..."))
1207          base.setDefinition(base.getDefinition()+"\r\n"+derived.getDefinition().substring(3));
1208        else if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false))
1209          base.setDefinitionElement(derived.getDefinitionElement().copy());
1210        else if (trimDifferential)
1211          derived.setDefinitionElement(null);
1212        else if (derived.hasDefinitionElement())
1213          derived.getDefinitionElement().setUserData(DERIVATION_EQUALS, true);
1214      }
1215
1216      if (derived.hasCommentElement()) {
1217        if (derived.getComment().startsWith("..."))
1218          base.setComment(base.getComment()+"\r\n"+derived.getComment().substring(3));
1219        else if (derived.hasCommentElement()!= base.hasCommentElement() || !Base.compareDeep(derived.getCommentElement(), base.getCommentElement(), false))
1220          base.setCommentElement(derived.getCommentElement().copy());
1221        else if (trimDifferential)
1222          base.setCommentElement(derived.getCommentElement().copy());
1223        else if (derived.hasCommentElement())
1224          derived.getCommentElement().setUserData(DERIVATION_EQUALS, true);
1225      }
1226
1227      if (derived.hasLabelElement()) {
1228        if (derived.getLabel().startsWith("..."))
1229          base.setLabel(base.getLabel()+"\r\n"+derived.getLabel().substring(3));
1230        else if (!base.hasLabelElement() || !Base.compareDeep(derived.getLabelElement(), base.getLabelElement(), false))
1231          base.setLabelElement(derived.getLabelElement().copy());
1232        else if (trimDifferential)
1233          base.setLabelElement(derived.getLabelElement().copy());
1234        else if (derived.hasLabelElement())
1235          derived.getLabelElement().setUserData(DERIVATION_EQUALS, true);
1236      }
1237
1238      if (derived.hasRequirementsElement()) {
1239        if (derived.getRequirements().startsWith("..."))
1240          base.setRequirements(base.getRequirements()+"\r\n"+derived.getRequirements().substring(3));
1241        else if (!base.hasRequirementsElement() || !Base.compareDeep(derived.getRequirementsElement(), base.getRequirementsElement(), false))
1242          base.setRequirementsElement(derived.getRequirementsElement().copy());
1243        else if (trimDifferential)
1244          base.setRequirementsElement(derived.getRequirementsElement().copy());
1245        else if (derived.hasRequirementsElement())
1246          derived.getRequirementsElement().setUserData(DERIVATION_EQUALS, true);
1247      }
1248      // sdf-9
1249      if (derived.hasRequirements() && !base.getPath().contains("."))
1250        derived.setRequirements(null);
1251      if (base.hasRequirements() && !base.getPath().contains("."))
1252        base.setRequirements(null);
1253
1254      if (derived.hasAlias()) {
1255        if (!Base.compareDeep(derived.getAlias(), base.getAlias(), false))
1256          for (StringType s : derived.getAlias()) {
1257            if (!base.hasAlias(s.getValue()))
1258              base.getAlias().add(s.copy());
1259          }
1260        else if (trimDifferential)
1261          derived.getAlias().clear();
1262        else
1263          for (StringType t : derived.getAlias())
1264            t.setUserData(DERIVATION_EQUALS, true);
1265      }
1266
1267      if (derived.hasMinElement()) {
1268        if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) {
1269          if (derived.getMin() < base.getMin())
1270            messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Derived min  ("+Integer.toString(derived.getMin())+") cannot be less than base min ("+Integer.toString(base.getMin())+")", ValidationMessage.IssueSeverity.ERROR));
1271          base.setMinElement(derived.getMinElement().copy());
1272        } else if (trimDifferential)
1273          derived.setMinElement(null);
1274        else
1275          derived.getMinElement().setUserData(DERIVATION_EQUALS, true);
1276      }
1277
1278      if (derived.hasMaxElement()) {
1279        if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) {
1280          if (isLargerMax(derived.getMax(), base.getMax()))
1281            messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+source.getPath(), "Derived max ("+derived.getMax()+") cannot be greater than base max ("+base.getMax()+")", ValidationMessage.IssueSeverity.ERROR));
1282          base.setMaxElement(derived.getMaxElement().copy());
1283        } else if (trimDifferential)
1284          derived.setMaxElement(null);
1285        else
1286          derived.getMaxElement().setUserData(DERIVATION_EQUALS, true);
1287      }
1288
1289      if (derived.hasFixed()) {
1290        if (!Base.compareDeep(derived.getFixed(), base.getFixed(), true)) {
1291          base.setFixed(derived.getFixed().copy());
1292        } else if (trimDifferential)
1293          derived.setFixed(null);
1294        else
1295          derived.getFixed().setUserData(DERIVATION_EQUALS, true);
1296      }
1297
1298      if (derived.hasPattern()) {
1299        if (!Base.compareDeep(derived.getPattern(), base.getPattern(), false)) {
1300          base.setPattern(derived.getPattern().copy());
1301        } else
1302          if (trimDifferential)
1303            derived.setPattern(null);
1304          else
1305            derived.getPattern().setUserData(DERIVATION_EQUALS, true);
1306      }
1307
1308      for (ElementDefinitionExampleComponent ex : derived.getExample()) {
1309        boolean found = false;
1310        for (ElementDefinitionExampleComponent exS : base.getExample())
1311          if (Base.compareDeep(ex, exS, false))
1312            found = true;
1313        if (!found)
1314          base.addExample(ex.copy());
1315        else if (trimDifferential)
1316          derived.getExample().remove(ex);
1317        else
1318          ex.setUserData(DERIVATION_EQUALS, true);
1319      }
1320
1321      if (derived.hasMaxLengthElement()) {
1322        if (!Base.compareDeep(derived.getMaxLengthElement(), base.getMaxLengthElement(), false))
1323          base.setMaxLengthElement(derived.getMaxLengthElement().copy());
1324        else if (trimDifferential)
1325          derived.setMaxLengthElement(null);
1326        else
1327          derived.getMaxLengthElement().setUserData(DERIVATION_EQUALS, true);
1328      }
1329
1330      // todo: what to do about conditions?
1331      // condition : id 0..*
1332
1333      if (derived.hasMustSupportElement()) {
1334        if (!(base.hasMustSupportElement() && Base.compareDeep(derived.getMustSupportElement(), base.getMustSupportElement(), false)))
1335          base.setMustSupportElement(derived.getMustSupportElement().copy());
1336        else if (trimDifferential)
1337          derived.setMustSupportElement(null);
1338        else
1339          derived.getMustSupportElement().setUserData(DERIVATION_EQUALS, true);
1340      }
1341
1342
1343      // profiles cannot change : isModifier, defaultValue, meaningWhenMissing
1344      // but extensions can change isModifier
1345      if (isExtension) {
1346        if (derived.hasIsModifierElement() && !(base.hasIsModifierElement() && Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false)))
1347          base.setIsModifierElement(derived.getIsModifierElement().copy());
1348        else if (trimDifferential)
1349          derived.setIsModifierElement(null);
1350        else if (derived.hasIsModifierElement())
1351          derived.getIsModifierElement().setUserData(DERIVATION_EQUALS, true);
1352      }
1353
1354      if (derived.hasBinding()) {
1355        if (!base.hasBinding() || !Base.compareDeep(derived.getBinding(), base.getBinding(), false)) {
1356          if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && derived.getBinding().getStrength() != BindingStrength.REQUIRED)
1357            messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "illegal attempt to change the binding on "+derived.getPath()+" from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode(), ValidationMessage.IssueSeverity.ERROR));
1358//            throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode());
1359          else if (base.hasBinding() && derived.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && base.getBinding().hasValueSetReference() && derived.getBinding().hasValueSetReference()) {
1360            ValueSetExpansionOutcome expBase = context.expandVS(context.fetchResource(ValueSet.class, base.getBinding().getValueSetReference().getReference()), true, false);
1361            ValueSetExpansionOutcome expDerived = context.expandVS(context.fetchResource(ValueSet.class, derived.getBinding().getValueSetReference().getReference()), true, false);
1362            if (expBase.getValueset() == null)
1363              messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSetReference().getReference()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING));
1364            else if (expDerived.getValueset() == null)
1365              messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSetReference().getReference()+" could not be expanded", ValidationMessage.IssueSeverity.WARNING));
1366            else if (!isSubset(expBase.getValueset(), expDerived.getValueset()))
1367              messages.add(new ValidationMessage(Source.ProfileValidator, ValidationMessage.IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSetReference().getReference()+" is not a subset of binding "+base.getBinding().getValueSetReference().getReference(), ValidationMessage.IssueSeverity.ERROR));
1368          }
1369          base.setBinding(derived.getBinding().copy());
1370        } else if (trimDifferential)
1371          derived.setBinding(null);
1372        else
1373          derived.getBinding().setUserData(DERIVATION_EQUALS, true);
1374      } // else if (base.hasBinding() && doesn't have bindable type )
1375        //  base
1376
1377      if (derived.hasIsSummaryElement()) {
1378        if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false)) {
1379          if (base.hasIsSummary())
1380            throw new Error("Error in profile "+pn+" at "+derived.getPath()+": Base isSummary = "+base.getIsSummaryElement().asStringValue()+", derived isSummary = "+derived.getIsSummaryElement().asStringValue());
1381          base.setIsSummaryElement(derived.getIsSummaryElement().copy());
1382        } else if (trimDifferential)
1383          derived.setIsSummaryElement(null);
1384        else
1385          derived.getIsSummaryElement().setUserData(DERIVATION_EQUALS, true);
1386      }
1387
1388      if (derived.hasType()) {
1389        if (!Base.compareDeep(derived.getType(), base.getType(), false)) {
1390          if (base.hasType()) {
1391            for (TypeRefComponent ts : derived.getType()) {
1392              boolean ok = false;
1393              CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1394              for (TypeRefComponent td : base.getType()) {;
1395                b.append(td.getCode());
1396                if (td.hasCode() && (td.getCode().equals(ts.getCode()) || td.getCode().equals("Extension") ||
1397                    td.getCode().equals("Element") || td.getCode().equals("*") ||
1398                    ((td.getCode().equals("Resource") || (td.getCode().equals("DomainResource")) && pkp.isResource(ts.getCode())))))
1399                  ok = true;
1400              }
1401              if (!ok)
1402                throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal constrained type "+ts.getCode()+" from "+b.toString());
1403            }
1404          }
1405          base.getType().clear();
1406          for (TypeRefComponent t : derived.getType()) {
1407            TypeRefComponent tt = t.copy();
1408//            tt.setUserData(DERIVATION_EQUALS, true);
1409            base.getType().add(tt);
1410          }
1411        }
1412        else if (trimDifferential)
1413          derived.getType().clear();
1414        else
1415          for (TypeRefComponent t : derived.getType())
1416            t.setUserData(DERIVATION_EQUALS, true);
1417      }
1418
1419      if (derived.hasMapping()) {
1420        // todo: mappings are not cumulative - one replaces another
1421        if (!Base.compareDeep(derived.getMapping(), base.getMapping(), false)) {
1422          for (ElementDefinitionMappingComponent s : derived.getMapping()) {
1423            boolean found = false;
1424            for (ElementDefinitionMappingComponent d : base.getMapping()) {
1425              found = found || (d.getIdentity().equals(s.getIdentity()) && d.getMap().equals(s.getMap()));
1426            }
1427            if (!found)
1428              base.getMapping().add(s);
1429          }
1430        }
1431        else if (trimDifferential)
1432          derived.getMapping().clear();
1433        else
1434          for (ElementDefinitionMappingComponent t : derived.getMapping())
1435            t.setUserData(DERIVATION_EQUALS, true);
1436      }
1437
1438      // todo: constraints are cumulative. there is no replacing
1439      for (ElementDefinitionConstraintComponent s : base.getConstraint()) { 
1440        s.setUserData(IS_DERIVED, true);
1441        if (!s.hasSource())
1442          s.setSource(base.getId());
1443      }
1444      if (derived.hasConstraint()) {
1445        for (ElementDefinitionConstraintComponent s : derived.getConstraint()) {
1446          ElementDefinitionConstraintComponent inv = s.copy();
1447          base.getConstraint().add(inv);
1448        }
1449      }
1450      
1451      // now, check that we still have a bindable type; if not, delete the binding - see task 8477
1452      if (dest.hasBinding() && !hasBindableType(dest))
1453        dest.setBinding(null);
1454        
1455      // finally, we copy any extensions from source to dest
1456      for (Extension ex : base.getExtension()) {
1457        StructureDefinition sd  = context.fetchResource(StructureDefinition.class, ex.getUrl());
1458        if (sd == null || sd.getSnapshot() == null || sd.getSnapshot().getElementFirstRep().getMax().equals("1"))
1459          ToolingExtensions.removeExtension(dest, ex.getUrl());
1460        dest.addExtension(ex);
1461      }
1462    }
1463  }
1464
1465  private boolean hasBindableType(ElementDefinition ed) {
1466    for (TypeRefComponent tr : ed.getType()) {
1467      if (Utilities.existsInList(tr.getCode(), "Coding", "CodeableConcept", "Quantity", "url", "string", "code"))
1468        return true;
1469    }
1470    return false;
1471  }
1472
1473
1474  private boolean isLargerMax(String derived, String base) {
1475    if ("*".equals(base))
1476      return false;
1477    if ("*".equals(derived))
1478      return true;
1479    return Integer.parseInt(derived) > Integer.parseInt(base);
1480  }
1481
1482
1483  private boolean isSubset(ValueSet expBase, ValueSet expDerived) {
1484    return codesInExpansion(expDerived.getExpansion().getContains(), expBase.getExpansion());
1485  }
1486
1487
1488  private boolean codesInExpansion(List<ValueSetExpansionContainsComponent> contains, ValueSetExpansionComponent expansion) {
1489    for (ValueSetExpansionContainsComponent cc : contains) {
1490      if (!inExpansion(cc, expansion.getContains()))
1491        return false;
1492      if (!codesInExpansion(cc.getContains(), expansion))
1493        return false;
1494    }
1495    return true;
1496  }
1497
1498
1499  private boolean inExpansion(ValueSetExpansionContainsComponent cc, List<ValueSetExpansionContainsComponent> contains) {
1500    for (ValueSetExpansionContainsComponent cc1 : contains) {
1501      if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode()))
1502        return true;
1503      if (inExpansion(cc,  cc1.getContains()))
1504        return true;
1505    }
1506    return false;
1507  }
1508
1509  public void closeDifferential(StructureDefinition base, StructureDefinition derived) throws FHIRException {
1510    for (ElementDefinition edb : base.getSnapshot().getElement()) {
1511      if (isImmediateChild(edb) && !edb.getPath().endsWith(".id")) {
1512        ElementDefinition edm = getMatchInDerived(edb, derived.getDifferential().getElement());
1513        if (edm == null) {
1514          ElementDefinition edd = derived.getDifferential().addElement();
1515          edd.setPath(edb.getPath());
1516          edd.setMax("0");
1517        } else if (edb.hasSlicing()) {
1518          closeChildren(base, edb, derived, edm);
1519        }
1520      }
1521    }
1522    sortDifferential(base, derived, derived.getName(), new ArrayList<String>());
1523  }
1524
1525  private void closeChildren(StructureDefinition base, ElementDefinition edb, StructureDefinition derived, ElementDefinition edm) {
1526    String path = edb.getPath()+".";
1527    int baseStart = base.getSnapshot().getElement().indexOf(edb);
1528    int baseEnd = findEnd(base.getSnapshot().getElement(), edb, baseStart+1);
1529    int diffStart = derived.getDifferential().getElement().indexOf(edm);
1530    int diffEnd = findEnd(derived.getDifferential().getElement(), edm, diffStart+1);
1531    
1532    for (int cBase = baseStart; cBase < baseEnd; cBase++) {
1533      ElementDefinition edBase = base.getSnapshot().getElement().get(cBase);
1534      if (isImmediateChild(edBase, edb)) {
1535        ElementDefinition edMatch = getMatchInDerived(edBase, derived.getDifferential().getElement(), diffStart, diffEnd);
1536        if (edMatch == null) {
1537          ElementDefinition edd = derived.getDifferential().addElement();
1538          edd.setPath(edBase.getPath());
1539          edd.setMax("0");
1540        } else {
1541          closeChildren(base, edBase, derived, edMatch);
1542        }        
1543      }
1544    }
1545  }
1546
1547
1548
1549
1550  private int findEnd(List<ElementDefinition> list, ElementDefinition ed, int cursor) {
1551    String path = ed.getPath()+".";
1552    while (cursor < list.size() && list.get(cursor).getPath().startsWith(path))
1553      cursor++;
1554    return cursor;
1555  }
1556
1557
1558  private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list) {
1559    for (ElementDefinition t : list)
1560      if (t.getPath().equals(ed.getPath()))
1561        return t;
1562    return null;
1563  }
1564
1565  private ElementDefinition getMatchInDerived(ElementDefinition ed, List<ElementDefinition> list, int start, int end) {
1566    for (int i = start; i < end; i++) {
1567      ElementDefinition t = list.get(i);
1568      if (t.getPath().equals(ed.getPath()))
1569        return t;
1570    }
1571    return null;
1572  }
1573
1574
1575  private boolean isImmediateChild(ElementDefinition ed) {
1576    String p = ed.getPath();
1577    if (!p.contains("."))
1578      return false;
1579    p = p.substring(p.indexOf(".")+1);
1580    return !p.contains(".");
1581  }
1582
1583  private boolean isImmediateChild(ElementDefinition candidate, ElementDefinition base) {
1584    String p = candidate.getPath();
1585    if (!p.contains("."))
1586      return false;
1587    if (!p.startsWith(base.getPath()+"."))
1588      return false;
1589    p = p.substring(base.getPath().length()+1);
1590    return !p.contains(".");
1591  }
1592
1593  public XhtmlNode generateExtensionTable(String defFile, StructureDefinition ed, String imageFolder, boolean inlineGraphics, boolean full, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException {
1594    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics);
1595    gen.setTranslator(getTranslator());
1596    TableModel model = gen.initNormalTable(corePath, false, true, ed.getId(), false);
1597
1598    boolean deep = false;
1599    String m = "";
1600    boolean vdeep = false;
1601    if (ed.getSnapshot().getElementFirstRep().getIsModifier())
1602      m = "modifier_";
1603    for (ElementDefinition eld : ed.getSnapshot().getElement()) {
1604      deep = deep || eld.getPath().contains("Extension.extension.");
1605      vdeep = vdeep || eld.getPath().contains("Extension.extension.extension.");
1606    }
1607    Row r = gen.new Row();
1608    model.getRows().add(r);
1609    r.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), ed.getSnapshot().getElement().get(0).getIsModifier() ? "modifierExtension" : "extension", null, null));
1610    r.getCells().add(gen.new Cell());
1611    r.getCells().add(gen.new Cell(null, null, describeCardinality(ed.getSnapshot().getElement().get(0), null, new UnusedTracker()), null, null));
1612
1613    ElementDefinition ved = null;
1614    if (full || vdeep) {
1615      r.getCells().add(gen.new Cell("", "", "Extension", null, null));
1616
1617      r.setIcon(deep ? "icon_"+m+"extension_complex.png" : "icon_extension_simple.png", deep ? HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX : HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
1618      List<ElementDefinition> children = getChildren(ed.getSnapshot().getElement(), ed.getSnapshot().getElement().get(0));
1619      for (ElementDefinition child : children)
1620        if (!child.getPath().endsWith(".id"))
1621          genElement(defFile == null ? "" : defFile+"-definitions.html#extension.", gen, r.getSubRows(), child, ed.getSnapshot().getElement(), null, true, defFile, true, full, corePath, imagePath, true, false, false, false);
1622    } else if (deep) {
1623      List<ElementDefinition> children = new ArrayList<ElementDefinition>();
1624      for (ElementDefinition ted : ed.getSnapshot().getElement()) {
1625        if (ted.getPath().equals("Extension.extension"))
1626          children.add(ted);
1627      }
1628
1629      r.getCells().add(gen.new Cell("", "", "Extension", null, null));
1630      r.setIcon("icon_"+m+"extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
1631      
1632      for (ElementDefinition c : children) {
1633        ved = getValueFor(ed, c);
1634        ElementDefinition ued = getUrlFor(ed, c);
1635        if (ved != null && ued != null) {
1636          Row r1 = gen.new Row();
1637          r.getSubRows().add(r1);
1638          r1.getCells().add(gen.new Cell(null, defFile == null ? "" : defFile+"-definitions.html#extension."+ed.getName(), ((UriType) ued.getFixed()).getValue(), null, null));
1639          r1.getCells().add(gen.new Cell());
1640          r1.getCells().add(gen.new Cell(null, null, describeCardinality(c, null, new UnusedTracker()), null, null));
1641          genTypes(gen, r1, ved, defFile, ed, corePath, imagePath);
1642          r1.getCells().add(gen.new Cell(null, null, c.getDefinition(), null, null));
1643          r1.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);      
1644        }
1645      }
1646    } else  {
1647      for (ElementDefinition ted : ed.getSnapshot().getElement()) {
1648        if (ted.getPath().startsWith("Extension.value"))
1649          ved = ted;
1650      }
1651
1652      genTypes(gen, r, ved, defFile, ed, corePath, imagePath);
1653
1654      r.setIcon("icon_"+m+"extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);      
1655    }
1656    Cell c = gen.new Cell("", "", "URL = "+ed.getUrl(), null, null);
1657    c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, ed.getName()+": "+ed.getDescription(), null));
1658    if (!full && !(deep || vdeep) && ved != null && ved.hasBinding()) {  
1659        c.addPiece(gen.new Piece("br"));
1660      BindingResolution br = pkp.resolveBinding(ed, ved.getBinding(), ved.getPath());
1661      c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold")));
1662      c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath+br.url, br.display, null)));
1663      if (ved.getBinding().hasStrength()) {
1664        c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(null, " (", null)));
1665        c.getPieces().add(checkForNoChange(ved.getBinding(), gen.new Piece(corePath+"terminologies.html#"+ved.getBinding().getStrength().toCode(), egt(ved.getBinding().getStrengthElement()), ved.getBinding().getStrength().getDefinition())));              
1666        c.getPieces().add(gen.new Piece(null, ")", null));
1667      }
1668    }
1669    c.addPiece(gen.new Piece("br")).addPiece(gen.new Piece(null, describeExtensionContext(ed), null));
1670    r.getCells().add(c);
1671    
1672    try {
1673      return gen.generate(model, corePath, 0, outputTracker);
1674        } catch (org.hl7.fhir.exceptions.FHIRException e) {
1675                throw new FHIRException(e.getMessage(), e);
1676        }
1677  }
1678
1679  private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) {
1680    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
1681    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
1682      if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url"))
1683        return ed.getSnapshot().getElement().get(i);
1684      i++;
1685    }
1686    return null;
1687  }
1688
1689  private ElementDefinition getValueFor(StructureDefinition ed, ElementDefinition c) {
1690    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
1691    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
1692      if (ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".value"))
1693        return ed.getSnapshot().getElement().get(i);
1694      i++;
1695    }
1696    return null;
1697  }
1698
1699
1700  private static final int AGG_NONE = 0;
1701  private static final int AGG_IND = 1;
1702  private static final int AGG_GR = 2;
1703  private Cell genTypes(HierarchicalTableGenerator gen, Row r, ElementDefinition e, String profileBaseFileName, StructureDefinition profile, String corePath, String imagePath) {
1704    Cell c = gen.new Cell();
1705    r.getCells().add(c);
1706    List<TypeRefComponent> types = e.getType();
1707    if (!e.hasType()) {
1708      if (e.hasContentReference()) {
1709        return c;
1710      } else {
1711      ElementDefinition d = (ElementDefinition) e.getUserData(DERIVATION_POINTER);
1712      if (d != null && d.hasType()) {
1713        types = new ArrayList<ElementDefinition.TypeRefComponent>();
1714        for (TypeRefComponent tr : d.getType()) {
1715          TypeRefComponent tt = tr.copy();
1716          tt.setUserData(DERIVATION_EQUALS, true);
1717          types.add(tt);
1718        }
1719      } else
1720        return c;
1721    }
1722    }
1723
1724    boolean first = true;
1725    Element source = types.get(0); // either all types are the same, or we don't consider any of them the same
1726    int aggMode = AGG_NONE;
1727
1728    boolean allReference = !types.isEmpty();
1729    Set<AggregationMode> aggs = new HashSet<ElementDefinition.AggregationMode>();
1730    for (TypeRefComponent t : types) {
1731      if (t.getCode()!=null && t.getCode().equals("Reference") && t.hasProfile()) {
1732        for (Enumeration<AggregationMode> en : t.getAggregation())
1733          aggs.add(en.getValue());
1734      } else
1735        allReference = false;
1736      
1737    }
1738    if (allReference) {
1739      if (aggs.size() > 0) {
1740        boolean allSame = true;
1741        for (TypeRefComponent t : types) {
1742          for (AggregationMode agg : aggs) {
1743            boolean found = false;
1744            for (Enumeration<AggregationMode> en : t.getAggregation())
1745              if (en.getValue() == agg)
1746                found = true;
1747            if (!found)
1748              allSame = false;
1749          }
1750        }
1751        aggMode = allSame ? AGG_GR : AGG_IND;
1752        if (aggMode != AGG_GR)
1753          allReference = false;
1754      }
1755    } else 
1756      aggMode = aggs.size() == 0 ? AGG_NONE : AGG_IND;
1757
1758    if (allReference) {
1759      c.getPieces().add(gen.new Piece(corePath+"references.html", "Reference", null));
1760      c.getPieces().add(gen.new Piece(null, "(", null));
1761    }
1762    TypeRefComponent tl = null;
1763    for (TypeRefComponent t : types) {
1764      if (first)
1765        first = false;
1766      else if (allReference)
1767        c.addPiece(checkForNoChange(tl, gen.new Piece(null," | ", null)));
1768      else
1769        c.addPiece(checkForNoChange(tl, gen.new Piece(null,", ", null)));
1770      tl = t;
1771      if (t.getCode()!= null && t.getCode().equals("Reference")) {
1772        if (!allReference) {
1773          c.getPieces().add(gen.new Piece(corePath+"references.html", "Reference", null));
1774          c.getPieces().add(gen.new Piece(null, "(", null));
1775        }
1776        if (t.hasTargetProfile() && t.getTargetProfile().startsWith("http://hl7.org/fhir/StructureDefinition/")) {
1777          StructureDefinition sd = context.fetchResource(StructureDefinition.class, t.getTargetProfile());
1778          if (sd != null) {
1779            String disp = sd.hasTitle() ? sd.getTitle() : sd.getName();
1780            c.addPiece(checkForNoChange(t, gen.new Piece(checkPrepend(corePath, sd.getUserString("path")), disp, null)));
1781          } else {
1782            String rn = t.getTargetProfile().substring(40);
1783            c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, rn), rn, null)));
1784          }
1785        } else if (t.hasTargetProfile() && Utilities.isAbsoluteUrl(t.getTargetProfile())) {
1786          StructureDefinition sd = context.fetchResource(StructureDefinition.class, t.getTargetProfile());
1787          if (sd != null) {
1788            String disp = sd.hasTitle() ? sd.getTitle() : sd.getName();
1789            String ref = pkp.getLinkForProfile(null, sd.getUrl());
1790            if (ref.contains("|"))
1791              ref = ref.substring(0,  ref.indexOf("|"));
1792            c.addPiece(checkForNoChange(t, gen.new Piece(ref, disp, null)));
1793          } else
1794            c.addPiece(checkForNoChange(t, gen.new Piece(null, t.getTargetProfile(), null)));
1795        } else if (t.hasTargetProfile() && t.getTargetProfile().startsWith("#"))
1796          c.addPiece(checkForNoChange(t, gen.new Piece(corePath+profileBaseFileName+"."+t.getTargetProfile().substring(1).toLowerCase()+".html", t.getTargetProfile(), null)));
1797        else if (t.hasTargetProfile())
1798          c.addPiece(checkForNoChange(t, gen.new Piece(corePath+t.getTargetProfile(), t.getTargetProfile(), null)));
1799        if (!allReference) {
1800          c.getPieces().add(gen.new Piece(null, ")", null));
1801          if (t.getAggregation().size() > 0) {
1802            c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", " {", null));
1803            boolean firstA = true;
1804            for (Enumeration<AggregationMode> a : t.getAggregation()) {
1805              if (firstA = true)
1806                firstA = false;
1807              else
1808                c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", ", ", null));
1809              c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", codeForAggregation(a.getValue()), null));
1810            }
1811            c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", "}", null));
1812          }
1813        }
1814      } else if (t.hasProfile() && (!t.getCode().equals("Extension") || t.getProfile().contains(":"))) { // a profiled type
1815        String ref;
1816        ref = pkp.getLinkForProfile(profile, t.getProfile());
1817        if (ref != null) {
1818          String[] parts = ref.split("\\|");
1819          if (parts[0].startsWith("http:") || parts[0].startsWith("https:"))
1820            c.addPiece(checkForNoChange(t, gen.new Piece(parts[0], parts[1], t.getCode())));
1821          else
1822            c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().startsWith(corePath)? corePath: "")+parts[0], parts[1], t.getCode())));
1823        } else
1824          c.addPiece(checkForNoChange(t, gen.new Piece((t.getProfile().startsWith(corePath)? corePath: "")+ref, t.getCode(), null)));
1825      } else if (pkp.hasLinkFor(t.getCode())) {
1826        c.addPiece(checkForNoChange(t, gen.new Piece(pkp.getLinkFor(corePath, t.getCode()), t.getCode(), null)));
1827      } else
1828        c.addPiece(checkForNoChange(t, gen.new Piece(null, t.getCode(), null)));
1829    }
1830    if (allReference) {
1831      c.getPieces().add(gen.new Piece(null, ")", null));
1832      if (aggs.size() > 0) {
1833        c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", " {", null));
1834        boolean firstA = true;
1835        for (AggregationMode a : aggs) {
1836          if (firstA = true)
1837            firstA = false;
1838          else
1839            c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", ", ", null));
1840          c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", codeForAggregation(a), null));
1841        }
1842        c.getPieces().add(gen.new Piece(corePath+"valueset-resource-aggregation-mode.html", "}", null));
1843      }
1844    }
1845    return c;
1846  }
1847
1848  private String codeForAggregation(AggregationMode a) {
1849    switch (a) {
1850    case BUNDLED : return "b";
1851    case CONTAINED : return "c";
1852    case REFERENCED: return "r";
1853         default: return "?";
1854    }
1855  }
1856
1857
1858  private String checkPrepend(String corePath, String path) {
1859    if (pkp.prependLinks() && !(path.startsWith("http:") || path.startsWith("https:")))
1860      return corePath+path;
1861    else 
1862      return path;
1863  }
1864
1865
1866  private ElementDefinition getElementByName(List<ElementDefinition> elements, String contentReference) {
1867    for (ElementDefinition ed : elements)
1868      if (ed.hasSliceName() && ("#"+ed.getSliceName()).equals(contentReference))
1869        return ed;
1870    return null;
1871  }
1872
1873  private ElementDefinition getElementById(List<ElementDefinition> elements, String contentReference) {
1874    for (ElementDefinition ed : elements)
1875      if (ed.hasId() && ("#"+ed.getId()).equals(contentReference))
1876        return ed;
1877    return null;
1878  }
1879
1880
1881  public static String describeExtensionContext(StructureDefinition ext) {
1882    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1883    for (StringType t : ext.getContext())
1884      b.append(t.getValue());
1885    if (!ext.hasContextType())
1886      throw new Error("no context type on "+ext.getUrl());
1887    switch (ext.getContextType()) {
1888    case DATATYPE: return "Use on data type: "+b.toString();
1889    case EXTENSION: return "Use on extension: "+b.toString();
1890    case RESOURCE: return "Use on element: "+b.toString();
1891    default:
1892      return "??";
1893    }
1894  }
1895
1896  private String describeCardinality(ElementDefinition definition, ElementDefinition fallback, UnusedTracker tracker) {
1897    IntegerType min = definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
1898    StringType max = definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
1899    if (min.isEmpty() && fallback != null)
1900      min = fallback.getMinElement();
1901    if (max.isEmpty() && fallback != null)
1902      max = fallback.getMaxElement();
1903
1904    tracker.used = !max.isEmpty() && !max.getValue().equals("0");
1905
1906    if (min.isEmpty() && max.isEmpty())
1907      return null;
1908    else
1909      return (!min.hasValue() ? "" : Integer.toString(min.getValue())) + ".." + (!max.hasValue() ? "" : max.getValue());
1910  }
1911
1912  private void genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef, UnusedTracker tracker, ElementDefinition fallback) {
1913    IntegerType min = !hasDef ? new IntegerType() : definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
1914    StringType max = !hasDef ? new StringType() : definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
1915    if (min.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) {
1916      ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER);
1917      if (base.hasMinElement()) {
1918        min = base.getMinElement().copy();
1919        min.setUserData(DERIVATION_EQUALS, true);
1920      }
1921    }
1922    if (max.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) {
1923      ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER);
1924      if (base.hasMaxElement()) {
1925        max = base.getMaxElement().copy();
1926        max.setUserData(DERIVATION_EQUALS, true);
1927      }
1928    }
1929    if (min.isEmpty() && fallback != null)
1930      min = fallback.getMinElement();
1931    if (max.isEmpty() && fallback != null)
1932      max = fallback.getMaxElement();
1933
1934    if (!max.isEmpty())
1935      tracker.used = !max.getValue().equals("0");
1936
1937    Cell cell = gen.new Cell(null, null, null, null, null);
1938    row.getCells().add(cell);
1939    if (!min.isEmpty() || !max.isEmpty()) {
1940      cell.addPiece(checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), null)));
1941      cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", null)));
1942      cell.addPiece(checkForNoChange(min, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), null)));
1943    }
1944  }
1945
1946
1947  private Piece checkForNoChange(Element source, Piece piece) {
1948    if (source.hasUserData(DERIVATION_EQUALS)) {
1949      piece.addStyle("opacity: 0.4");
1950    }
1951    return piece;
1952  }
1953
1954  private Piece checkForNoChange(Element src1, Element src2, Piece piece) {
1955    if (src1.hasUserData(DERIVATION_EQUALS) && src2.hasUserData(DERIVATION_EQUALS)) {
1956      piece.addStyle("opacity: 0.5");
1957    }
1958    return piece;
1959  }
1960
1961  public XhtmlNode generateTable(String defFile, StructureDefinition profile, boolean diff, String imageFolder, boolean inlineGraphics, String profileBaseFileName, boolean snapshot, String corePath, String imagePath, boolean logicalModel, boolean allInvariants, Set<String> outputTracker) throws IOException, FHIRException {
1962    assert(diff != snapshot);// check it's ok to get rid of one of these
1963    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics);
1964    gen.setTranslator(getTranslator());
1965    TableModel model = gen.initNormalTable(corePath, false, true, profile.getId()+(diff ? "d" : "s"), false);
1966    List<ElementDefinition> list = diff ? profile.getDifferential().getElement() : profile.getSnapshot().getElement();
1967    List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
1968    profiles.add(profile);
1969    genElement(defFile == null ? null : defFile+"#", gen, model.getRows(), list.get(0), list, profiles, diff, profileBaseFileName, null, snapshot, corePath, imagePath, true, logicalModel, profile.getDerivation() == TypeDerivationRule.CONSTRAINT && usesMustSupport(list), allInvariants);
1970    try {
1971      return gen.generate(model, imagePath, 0, outputTracker);
1972        } catch (org.hl7.fhir.exceptions.FHIRException e) {
1973                throw new FHIRException(e.getMessage(), e);
1974        }
1975  }
1976
1977
1978  public XhtmlNode generateGrid(String defFile, StructureDefinition profile, String imageFolder, boolean inlineGraphics, String profileBaseFileName, String corePath, String imagePath, Set<String> outputTracker) throws IOException, FHIRException {
1979    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, inlineGraphics);
1980    gen.setTranslator(getTranslator());
1981    TableModel model = gen.initGridTable(corePath, profile.getId());
1982    List<ElementDefinition> list = profile.getSnapshot().getElement();
1983    List<StructureDefinition> profiles = new ArrayList<StructureDefinition>();
1984    profiles.add(profile);
1985    genGridElement(defFile == null ? null : defFile+"#", gen, model.getRows(), list.get(0), list, profiles, true, profileBaseFileName, null, corePath, imagePath, true, profile.getDerivation() == TypeDerivationRule.CONSTRAINT && usesMustSupport(list));
1986    try {
1987      return gen.generate(model, imagePath, 1, outputTracker);
1988    } catch (org.hl7.fhir.exceptions.FHIRException e) {
1989      throw new FHIRException(e.getMessage(), e);
1990    }
1991  }
1992
1993
1994  private boolean usesMustSupport(List<ElementDefinition> list) {
1995    for (ElementDefinition ed : list)
1996      if (ed.hasMustSupport() && ed.getMustSupport())
1997        return true;
1998    return false;
1999  }
2000
2001
2002  private void genElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element, List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName, Boolean extensions, boolean snapshot, String corePath, String imagePath, boolean root, boolean logicalModel, boolean isConstraintMode, boolean allInvariants) throws IOException {
2003    StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1);
2004    String s = tail(element.getPath());
2005    List<ElementDefinition> children = getChildren(all, element);
2006    boolean isExtension = (s.equals("extension") || s.equals("modifierExtension"));
2007    if (!snapshot && isExtension && extensions != null && extensions != isExtension)
2008      return;
2009
2010    if (!onlyInformationIsMapping(all, element)) {
2011      Row row = gen.new Row();
2012      row.setAnchor(element.getPath());
2013      row.setColor(getRowColor(element, isConstraintMode));
2014      boolean hasDef = element != null;
2015      boolean ext = false;
2016      if (s.equals("extension")) {
2017        if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile()))
2018          row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
2019        else
2020          row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
2021        ext = true;
2022      } else if (s.equals("modifierExtension")) {
2023        if (element.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile()))
2024          row.setIcon("icon_modifier_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
2025        else
2026          row.setIcon("icon_modifier_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
2027      } else if (!hasDef || element.getType().size() == 0)
2028        row.setIcon("icon_element.gif", HierarchicalTableGenerator.TEXT_ICON_ELEMENT);
2029      else if (hasDef && element.getType().size() > 1) {
2030        if (allTypesAre(element.getType(), "Reference"))
2031          row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
2032        else
2033          row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE);
2034      } else if (hasDef && element.getType().get(0).getCode() != null && element.getType().get(0).getCode().startsWith("@"))
2035        row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE);
2036      else if (hasDef && isPrimitive(element.getType().get(0).getCode()))
2037        row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
2038      else if (hasDef && isReference(element.getType().get(0).getCode()))
2039        row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
2040      else if (hasDef && isDataType(element.getType().get(0).getCode()))
2041        row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
2042      else
2043        row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
2044      String ref = defPath == null ? null : defPath + element.getId();
2045      UnusedTracker used = new UnusedTracker();
2046      used.used = true;
2047      Cell left = gen.new Cell(null, ref, s, (element.hasSliceName() ? translate("sd.table", "Slice")+" "+element.getSliceName() : "")+(hasDef && element.hasSliceName() ? ": " : "")+(!hasDef ? null : gt(element.getDefinitionElement())), null);
2048      row.getCells().add(left);
2049      Cell gc = gen.new Cell();
2050      row.getCells().add(gc);
2051      if (element != null && element.getIsModifier())
2052        checkForNoChange(element.getIsModifierElement(), gc.addStyledText(translate("sd.table", "This element is a modifier element"), "?!", null, null, null, false));
2053      if (element != null && element.getMustSupport())
2054        checkForNoChange(element.getMustSupportElement(), gc.addStyledText(translate("sd.table", "This element must be supported"), "S", "white", "red", null, false));
2055      if (element != null && element.getIsSummary())
2056        checkForNoChange(element.getIsSummaryElement(), gc.addStyledText(translate("sd.table", "This element is included in summaries"), "\u03A3", null, null, null, false));
2057      if (element != null && (!element.getConstraint().isEmpty() || !element.getCondition().isEmpty()))
2058        gc.addStyledText(translate("sd.table", "This element has or is affected by some invariants"), "I", null, null, null, false);
2059
2060      ExtensionContext extDefn = null;
2061      if (ext) {
2062        if (element != null && element.getType().size() == 1 && element.getType().get(0).hasProfile()) {
2063        extDefn = locateExtension(StructureDefinition.class, element.getType().get(0).getProfile());
2064          if (extDefn == null) {
2065            genCardinality(gen, element, row, hasDef, used, null);
2066            row.getCells().add(gen.new Cell(null, null, "?? "+element.getType().get(0).getProfile(), null, null));
2067            generateDescription(gen, row, element, null, used.used, profile.getUrl(), element.getType().get(0).getProfile(), profile, corePath, imagePath, root, logicalModel, allInvariants);
2068          } else {
2069            String name = urltail(element.getType().get(0).getProfile());
2070            left.getPieces().get(0).setText(name);
2071            // left.getPieces().get(0).setReference((String) extDefn.getExtensionStructure().getTag("filename"));
2072            left.getPieces().get(0).setHint(translate("sd.table", "Extension URL")+" = "+extDefn.getUrl());
2073            genCardinality(gen, element, row, hasDef, used, extDefn.getElement());
2074            ElementDefinition valueDefn = extDefn.getExtensionValueDefinition();
2075            if (valueDefn != null && !"0".equals(valueDefn.getMax()))
2076               genTypes(gen, row, valueDefn, profileBaseFileName, profile, corePath, imagePath);
2077             else // if it's complex, we just call it nothing
2078                // genTypes(gen, row, extDefn.getSnapshot().getElement().get(0), profileBaseFileName, profile);
2079              row.getCells().add(gen.new Cell(null, null, "("+translate("sd.table", "Complex")+")", null, null));
2080            generateDescription(gen, row, element, extDefn.getElement(), used.used, null, extDefn.getUrl(), profile, corePath, imagePath, root, logicalModel, allInvariants, valueDefn);
2081          }
2082        } else {
2083          genCardinality(gen, element, row, hasDef, used, null);
2084          if ("0".equals(element.getMax()))
2085            row.getCells().add(gen.new Cell());            
2086          else
2087            genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath);
2088          generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants);
2089        }
2090      } else {
2091        genCardinality(gen, element, row, hasDef, used, null);
2092        if (hasDef && !"0".equals(element.getMax()))
2093          genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath);
2094        else
2095          row.getCells().add(gen.new Cell());
2096        generateDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, logicalModel, allInvariants);
2097      }
2098      if (element.hasSlicing()) {
2099        if (standardExtensionSlicing(element)) {
2100          used.used = element.hasType() && element.getType().get(0).hasProfile();
2101          showMissing = false;
2102        } else {
2103          row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE);
2104          row.getCells().get(2).getPieces().clear();
2105          for (Cell cell : row.getCells())
2106            for (Piece p : cell.getPieces()) {
2107              p.addStyle("font-style: italic");
2108            }
2109        }
2110      }
2111      if (used.used || showMissing)
2112        rows.add(row);
2113      if (!used.used && !element.hasSlicing()) {
2114        for (Cell cell : row.getCells())
2115          for (Piece p : cell.getPieces()) {
2116            p.setStyle("text-decoration:line-through");
2117            p.setReference(null);
2118          }
2119      } else{
2120        for (ElementDefinition child : children)
2121          if (logicalModel || !child.getPath().endsWith(".id") || (child.getPath().endsWith(".id") && (profile != null) && (profile.getDerivation() == TypeDerivationRule.CONSTRAINT)))  
2122            genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, snapshot, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants);
2123        if (!snapshot && (extensions == null || !extensions))
2124          for (ElementDefinition child : children)
2125            if (child.getPath().endsWith(".extension") || child.getPath().endsWith(".modifierExtension"))
2126              genElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, true, false, corePath, imagePath, false, logicalModel, isConstraintMode, allInvariants);
2127      }
2128    } 
2129  }
2130
2131  private void genGridElement(String defPath, HierarchicalTableGenerator gen, List<Row> rows, ElementDefinition element, List<ElementDefinition> all, List<StructureDefinition> profiles, boolean showMissing, String profileBaseFileName, Boolean extensions, String corePath, String imagePath, boolean root, boolean isConstraintMode) throws IOException {
2132    StructureDefinition profile = profiles == null ? null : profiles.get(profiles.size()-1);
2133    String s = tail(element.getPath());
2134    List<ElementDefinition> children = getChildren(all, element);
2135    boolean isExtension = (s.equals("extension") || s.equals("modifierExtension"));
2136
2137    if (!onlyInformationIsMapping(all, element)) {
2138      Row row = gen.new Row();
2139      row.setAnchor(element.getPath());
2140      row.setColor(getRowColor(element, isConstraintMode));
2141      boolean hasDef = element != null;
2142      String ref = defPath == null ? null : defPath + element.getId();
2143      UnusedTracker used = new UnusedTracker();
2144      used.used = true;
2145      Cell left = gen.new Cell();
2146      if (element.getType().size() == 1 && element.getType().get(0).isPrimitive())
2147        left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())).addStyle("font-weight:bold"));
2148      else
2149        left.getPieces().add(gen.new Piece(ref, "\u00A0\u00A0" + s, !hasDef ? null : gt(element.getDefinitionElement())));
2150      if (element.hasSliceName()) {
2151        left.getPieces().add(gen.new Piece("br"));
2152        String indent = StringUtils.repeat('\u00A0', 1+2*(element.getPath().split("\\.").length));
2153        left.getPieces().add(gen.new Piece(null, indent + "("+element.getSliceName() + ")", null));
2154      }
2155      row.getCells().add(left);
2156
2157      ExtensionContext extDefn = null;
2158      genCardinality(gen, element, row, hasDef, used, null);
2159      if (hasDef && !"0".equals(element.getMax()))
2160        genTypes(gen, row, element, profileBaseFileName, profile, corePath, imagePath);
2161      else
2162        row.getCells().add(gen.new Cell());
2163      generateGridDescription(gen, row, element, null, used.used, null, null, profile, corePath, imagePath, root, null);
2164/*      if (element.hasSlicing()) {
2165        if (standardExtensionSlicing(element)) {
2166          used.used = element.hasType() && element.getType().get(0).hasProfile();
2167          showMissing = false;
2168        } else {
2169          row.setIcon("icon_slice.png", HierarchicalTableGenerator.TEXT_ICON_SLICE);
2170          row.getCells().get(2).getPieces().clear();
2171          for (Cell cell : row.getCells())
2172            for (Piece p : cell.getPieces()) {
2173              p.addStyle("font-style: italic");
2174            }
2175        }
2176      }*/
2177      rows.add(row);
2178      for (ElementDefinition child : children)
2179        if (child.getMustSupport())
2180          genGridElement(defPath, gen, row.getSubRows(), child, all, profiles, showMissing, profileBaseFileName, isExtension, corePath, imagePath, false, isConstraintMode);
2181    }
2182  }
2183
2184
2185  private ExtensionContext locateExtension(Class<StructureDefinition> class1, String value)  {
2186    if (value.contains("#")) {
2187      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
2188      if (ext == null)
2189        return null;
2190      String tail = value.substring(value.indexOf("#")+1);
2191      ElementDefinition ed = null;
2192      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
2193        if (tail.equals(ted.getSliceName())) {
2194          ed = ted;
2195          return new ExtensionContext(ext, ed);
2196        }
2197      }
2198      return null;
2199    } else {
2200      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value);
2201      if (ext == null)
2202        return null;
2203      else 
2204        return new ExtensionContext(ext, ext.getSnapshot().getElement().get(0));
2205    }
2206  }
2207
2208
2209  private boolean extensionIsComplex(String value) {
2210    if (value.contains("#")) {
2211      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
2212    if (ext == null)
2213      return false;
2214      String tail = value.substring(value.indexOf("#")+1);
2215      ElementDefinition ed = null;
2216      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
2217        if (tail.equals(ted.getSliceName())) {
2218          ed = ted;
2219          break;
2220        }
2221      }
2222      if (ed == null)
2223        return false;
2224      int i = ext.getSnapshot().getElement().indexOf(ed);
2225      int j = i+1;
2226      while (j < ext.getSnapshot().getElement().size() && !ext.getSnapshot().getElement().get(j).getPath().equals(ed.getPath()))
2227        j++;
2228      return j - i > 5;
2229    } else {
2230      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value);
2231      return ext != null && ext.getSnapshot().getElement().size() > 5;
2232    }
2233  }
2234
2235
2236  private String getRowColor(ElementDefinition element, boolean isConstraintMode) {
2237    switch (element.getUserInt(UD_ERROR_STATUS)) {
2238    case STATUS_HINT: return ROW_COLOR_HINT;
2239    case STATUS_WARNING: return ROW_COLOR_WARNING;
2240    case STATUS_ERROR: return ROW_COLOR_ERROR;
2241    case STATUS_FATAL: return ROW_COLOR_FATAL;
2242    }
2243    if (isConstraintMode && !element.getMustSupport() && !element.getIsModifier() && element.getPath().contains("."))
2244      return null; // ROW_COLOR_NOT_MUST_SUPPORT;
2245    else
2246      return null;
2247  }
2248
2249
2250  private String urltail(String path) {
2251    if (path.contains("#"))
2252      return path.substring(path.lastIndexOf('#')+1);
2253    if (path.contains("/"))
2254      return path.substring(path.lastIndexOf('/')+1);
2255    else
2256      return path;
2257
2258  }
2259
2260  private boolean standardExtensionSlicing(ElementDefinition element) {
2261    String t = tail(element.getPath());
2262    return (t.equals("extension") || t.equals("modifierExtension"))
2263          && element.getSlicing().getRules() != SlicingRules.CLOSED && element.getSlicing().getDiscriminator().size() == 1 && element.getSlicing().getDiscriminator().get(0).getPath().equals("url") && element.getSlicing().getDiscriminator().get(0).getType().equals(DiscriminatorType.VALUE);
2264  }
2265
2266  private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants) throws IOException {
2267    return generateDescription(gen, row, definition, fallback, used, baseURL, url, profile, corePath, imagePath, root, logicalModel, allInvariants, null);
2268  }
2269  
2270  private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, boolean logicalModel, boolean allInvariants, ElementDefinition valueDefn) throws IOException {
2271    Cell c = gen.new Cell();
2272    row.getCells().add(c);
2273
2274    if (used) {
2275      if (logicalModel && ToolingExtensions.hasExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) {
2276        if (root) {
2277          c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold"));
2278          c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null));        
2279        } else if (!root && ToolingExtensions.hasExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace") && 
2280            !ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace").equals(ToolingExtensions.readStringExtension(profile, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))) {
2281          c.getPieces().add(gen.new Piece(null, translate("sd.table", "XML Namespace")+": ", null).addStyle("font-weight:bold"));
2282          c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"), null));        
2283        }
2284      }
2285      
2286      if (definition.hasContentReference()) {
2287        ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference());
2288        if (ed == null)
2289          c.getPieces().add(gen.new Piece(null, translate("sd.table", "Unknown reference to %s", definition.getContentReference()), null));
2290        else
2291          c.getPieces().add(gen.new Piece("#"+ed.getPath(), translate("sd.table", "See %s", ed.getPath()), null));
2292      }
2293      if (definition.getPath().endsWith("url") && definition.hasFixed()) {
2294        c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen")));
2295      } else {
2296        if (definition != null && definition.hasShort()) {
2297          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2298          c.addPiece(checkForNoChange(definition.getShortElement(), gen.new Piece(null, gt(definition.getShortElement()), null)));
2299        } else if (fallback != null && fallback.hasShort()) {
2300          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2301          c.addPiece(checkForNoChange(fallback.getShortElement(), gen.new Piece(null, gt(fallback.getShortElement()), null)));
2302        }
2303        if (url != null) {
2304          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2305          String fullUrl = url.startsWith("#") ? baseURL+url : url;
2306          StructureDefinition ed = context.fetchResource(StructureDefinition.class, url);
2307          String ref = null;
2308          if (ed != null) {
2309            String p = ed.getUserString("path");
2310            if (p != null) {
2311              ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p);
2312            }
2313          }
2314          c.getPieces().add(gen.new Piece(null, translate("sd.table", "URL")+": ", null).addStyle("font-weight:bold"));
2315          c.getPieces().add(gen.new Piece(ref, fullUrl, null));
2316        }
2317
2318        if (definition.hasSlicing()) {
2319          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2320          c.getPieces().add(gen.new Piece(null, translate("sd.table", "Slice")+": ", null).addStyle("font-weight:bold"));
2321          c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null));
2322        }
2323        if (definition != null) {
2324          ElementDefinitionBindingComponent binding = null;
2325          if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty())
2326            binding = valueDefn.getBinding();
2327          else if (definition.hasBinding())
2328            binding = definition.getBinding();
2329          if (binding!=null && !binding.isEmpty()) {
2330            if (!c.getPieces().isEmpty()) 
2331              c.addPiece(gen.new Piece("br"));
2332            BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath());
2333            c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, translate("sd.table", "Binding")+": ", null).addStyle("font-weight:bold")));
2334            c.getPieces().add(checkForNoChange(binding, gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath+br.url, br.display, null)));
2335            if (binding.hasStrength()) {
2336              c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null)));
2337              c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), egt(binding.getStrengthElement()), binding.getStrength().getDefinition())));              
2338              c.getPieces().add(gen.new Piece(null, ")", null));
2339            }
2340          }
2341          for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
2342            if (!inv.hasSource() || allInvariants) {
2343              if (!c.getPieces().isEmpty()) 
2344                c.addPiece(gen.new Piece("br"));
2345              c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold")));
2346              c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, gt(inv.getHumanElement()), null)));
2347            }
2348          }
2349          if ((definition.hasBase() && definition.getBase().getMax().equals("*")) || (definition.hasMax() && definition.getMax().equals("*"))) {
2350            if (c.getPieces().size() > 0)
2351              c.addPiece(gen.new Piece("br"));
2352            if (definition.hasOrderMeaning()) {
2353              c.getPieces().add(gen.new Piece(null, "This repeating element order: "+definition.getOrderMeaning(), null));
2354            } else {
2355              // don't show this, this it's important: c.getPieces().add(gen.new Piece(null, "This repeating element has no defined order", null));
2356            }           
2357          }
2358
2359          if (definition.hasFixed()) {
2360            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2361            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, translate("sd.table", "Fixed Value")+": ", null).addStyle("font-weight:bold")));
2362            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, buildJson(definition.getFixed()), null).addStyle("color: darkgreen")));
2363            if (isCoded(definition.getFixed()) && !hasDescription(definition.getFixed())) {
2364              Piece p = describeCoded(gen, definition.getFixed());
2365              if (p != null)
2366                c.getPieces().add(p);
2367            }
2368          } else if (definition.hasPattern()) {
2369            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2370            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, translate("sd.table", "Required Pattern")+": ", null).addStyle("font-weight:bold")));
2371            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen")));
2372          } else if (definition.hasExample()) {
2373            for (ElementDefinitionExampleComponent ex : definition.getExample()) {
2374              if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2375              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, translate("sd.table", "Example")+("".equals("General")? "" : " "+ex.getLabel()+"'")+": ", null).addStyle("font-weight:bold")));
2376              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen")));
2377            }
2378          }
2379          if (definition.hasMaxLength() && definition.getMaxLength()!=0) {
2380            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2381            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold")));
2382            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen")));
2383          }
2384          if (profile != null) {
2385            for (StructureDefinitionMappingComponent md : profile.getMapping()) {
2386              if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) {
2387                ElementDefinitionMappingComponent map = null;
2388                for (ElementDefinitionMappingComponent m : definition.getMapping()) 
2389                  if (m.getIdentity().equals(md.getIdentity()))
2390                    map = m;
2391                if (map != null) {
2392                  for (int i = 0; i<definition.getMapping().size(); i++){
2393                    c.addPiece(gen.new Piece("br"));
2394                    c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null));
2395                  }
2396                }
2397              }
2398            }
2399          }
2400        }
2401      }
2402    }
2403    return c;
2404  }
2405
2406  private Piece describeCoded(HierarchicalTableGenerator gen, Type fixed) {
2407    if (fixed instanceof Coding) {
2408      Coding c = (Coding) fixed;
2409      ValidationResult vr = context.validateCode(c.getSystem(), c.getCode(), c.getDisplay());
2410      if (vr.getDisplay() != null)
2411        return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen");
2412    } else if (fixed instanceof CodeableConcept) {
2413      CodeableConcept cc = (CodeableConcept) fixed;
2414      for (Coding c : cc.getCoding()) {
2415        ValidationResult vr = context.validateCode(c.getSystem(), c.getCode(), c.getDisplay());
2416        if (vr.getDisplay() != null)
2417          return gen.new Piece(null, " ("+vr.getDisplay()+")", null).addStyle("color: darkgreen");
2418      }
2419    }
2420    return null;
2421  }
2422
2423
2424  private boolean hasDescription(Type fixed) {
2425    if (fixed instanceof Coding) {
2426      return ((Coding) fixed).hasDisplay();
2427    } else if (fixed instanceof CodeableConcept) {
2428      CodeableConcept cc = (CodeableConcept) fixed;
2429      if (cc.hasText())
2430        return true;
2431      for (Coding c : cc.getCoding())
2432        if (c.hasDisplay())
2433         return true;
2434    } // (fixed instanceof CodeType) || (fixed instanceof Quantity);
2435    return false;
2436  }
2437
2438
2439  private boolean isCoded(Type fixed) {
2440    return (fixed instanceof Coding) || (fixed instanceof CodeableConcept) || (fixed instanceof CodeType) || (fixed instanceof Quantity);
2441  }
2442
2443
2444  private Cell generateGridDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath, String imagePath, boolean root, ElementDefinition valueDefn) throws IOException {
2445    Cell c = gen.new Cell();
2446    row.getCells().add(c);
2447
2448    if (used) {
2449      if (definition.hasContentReference()) {
2450        ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), definition.getContentReference());
2451        if (ed == null)
2452          c.getPieces().add(gen.new Piece(null, "Unknown reference to "+definition.getContentReference(), null));
2453        else
2454          c.getPieces().add(gen.new Piece("#"+ed.getPath(), "See "+ed.getPath(), null));
2455      }
2456      if (definition.getPath().endsWith("url") && definition.hasFixed()) {
2457        c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen")));
2458      } else {
2459        if (url != null) {
2460          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2461          String fullUrl = url.startsWith("#") ? baseURL+url : url;
2462          StructureDefinition ed = context.fetchResource(StructureDefinition.class, url);
2463          String ref = null;
2464          if (ed != null) {
2465            String p = ed.getUserString("path");
2466            if (p != null) {
2467              ref = p.startsWith("http:") || igmode ? p : Utilities.pathURL(corePath, p);
2468            }
2469          }
2470          c.getPieces().add(gen.new Piece(null, "URL: ", null).addStyle("font-weight:bold"));
2471          c.getPieces().add(gen.new Piece(ref, fullUrl, null));
2472        }
2473
2474        if (definition.hasSlicing()) {
2475          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2476          c.getPieces().add(gen.new Piece(null, "Slice: ", null).addStyle("font-weight:bold"));
2477          c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null));
2478        }
2479        if (definition != null) {
2480          ElementDefinitionBindingComponent binding = null;
2481          if (valueDefn != null && valueDefn.hasBinding() && !valueDefn.getBinding().isEmpty())
2482            binding = valueDefn.getBinding();
2483          else if (definition.hasBinding())
2484            binding = definition.getBinding();
2485          if (binding!=null && !binding.isEmpty()) {
2486            if (!c.getPieces().isEmpty()) 
2487              c.addPiece(gen.new Piece("br"));
2488            BindingResolution br = pkp.resolveBinding(profile, binding, definition.getPath());
2489            c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, "Binding: ", null).addStyle("font-weight:bold")));
2490            c.getPieces().add(checkForNoChange(binding, gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !pkp.prependLinks() ? br.url : corePath+br.url, br.display, null)));
2491            if (binding.hasStrength()) {
2492              c.getPieces().add(checkForNoChange(binding, gen.new Piece(null, " (", null)));
2493              c.getPieces().add(checkForNoChange(binding, gen.new Piece(corePath+"terminologies.html#"+binding.getStrength().toCode(), binding.getStrength().toCode(), binding.getStrength().getDefinition())));              c.getPieces().add(gen.new Piece(null, ")", null));
2494            }
2495          }
2496          for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
2497            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2498            c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold")));
2499            c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null)));
2500          }
2501          if (definition.hasFixed()) {
2502            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2503            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight:bold")));
2504            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, buildJson(definition.getFixed()), null).addStyle("color: darkgreen")));
2505          } else if (definition.hasPattern()) {
2506            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2507            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "Required Pattern: ", null).addStyle("font-weight:bold")));
2508            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen")));
2509          } else if (definition.hasExample()) {
2510            for (ElementDefinitionExampleComponent ex : definition.getExample()) {
2511              if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2512              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, "Example'"+("".equals("General")? "" : " "+ex.getLabel()+"'")+": ", null).addStyle("font-weight:bold")));
2513              c.getPieces().add(checkForNoChange(ex, gen.new Piece(null, buildJson(ex.getValue()), null).addStyle("color: darkgreen")));
2514            }
2515          }
2516          if (definition.hasMaxLength() && definition.getMaxLength()!=0) {
2517            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2518            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, "Max Length: ", null).addStyle("font-weight:bold")));
2519            c.getPieces().add(checkForNoChange(definition.getMaxLengthElement(), gen.new Piece(null, Integer.toString(definition.getMaxLength()), null).addStyle("color: darkgreen")));
2520          }
2521          if (profile != null) {
2522            for (StructureDefinitionMappingComponent md : profile.getMapping()) {
2523              if (md.hasExtension(ToolingExtensions.EXT_TABLE_NAME)) {
2524                ElementDefinitionMappingComponent map = null;
2525                for (ElementDefinitionMappingComponent m : definition.getMapping()) 
2526                  if (m.getIdentity().equals(md.getIdentity()))
2527                    map = m;
2528                if (map != null) {
2529                  for (int i = 0; i<definition.getMapping().size(); i++){
2530                    c.addPiece(gen.new Piece("br"));
2531                    c.getPieces().add(gen.new Piece(null, ToolingExtensions.readStringExtension(md, ToolingExtensions.EXT_TABLE_NAME)+": " + map.getMap(), null));
2532                  }
2533                }
2534              }
2535            }
2536          }
2537          if (definition.getComment()!=null) {
2538            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
2539            c.getPieces().add(gen.new Piece(null, "Comments: ", null).addStyle("font-weight:bold"));
2540            c.addPiece(gen.new Piece("br"));
2541            c.addMarkdown(definition.getComment());
2542//            c.getPieces().add(checkForNoChange(definition.getCommentElement(), gen.new Piece(null, definition.getComment(), null)));
2543          }
2544        }
2545      }
2546    }
2547    return c;
2548  }
2549  /*
2550  private List<Piece> markdownToPieces(String markdown) throws FHIRException {
2551    String htmlString = Processor.process(markdown);
2552    XhtmlParser parser = new XhtmlParser();
2553    try {
2554      XhtmlNode node = parser.parseFragment(htmlString);
2555      return htmlToPieces(node);
2556    } catch (IOException e) {
2557    }
2558    return null;
2559  }
2560
2561  private List<Piece> htmlToPieces(XhtmlNode n) {
2562    
2563  }*/
2564
2565  private String buildJson(Type value) throws IOException {
2566    if (value instanceof PrimitiveType)
2567      return ((PrimitiveType) value).asStringValue();
2568
2569    IParser json = context.newJsonParser();
2570    return json.composeString(value, null);
2571  }
2572
2573
2574  public String describeSlice(ElementDefinitionSlicingComponent slicing) {
2575    return translate("sd.table", "%s, %s by %s", slicing.getOrdered() ? translate("sd.table", "Ordered") : translate("sd.table", "Unordered"), describe(slicing.getRules()), commas(slicing.getDiscriminator()));
2576  }
2577
2578  private String commas(List<ElementDefinitionSlicingDiscriminatorComponent> list) {
2579    CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder();
2580    for (ElementDefinitionSlicingDiscriminatorComponent id : list)
2581      c.append(id.getType().toCode()+":"+id.getPath());
2582    return c.toString();
2583  }
2584
2585
2586  private String describe(SlicingRules rules) {
2587    if (rules == null)
2588      return translate("sd.table", "Unspecified");
2589    switch (rules) {
2590    case CLOSED : return translate("sd.table", "Closed");
2591    case OPEN : return translate("sd.table", "Open");
2592    case OPENATEND : return translate("sd.table", "Open At End");
2593    default:
2594      return "??";
2595    }
2596  }
2597
2598  private boolean onlyInformationIsMapping(List<ElementDefinition> list, ElementDefinition e) {
2599    return (!e.hasSliceName() && !e.hasSlicing() && (onlyInformationIsMapping(e))) &&
2600        getChildren(list, e).isEmpty();
2601  }
2602
2603  private boolean onlyInformationIsMapping(ElementDefinition d) {
2604    return !d.hasShort() && !d.hasDefinition() &&
2605        !d.hasRequirements() && !d.getAlias().isEmpty() && !d.hasMinElement() &&
2606        !d.hasMax() && !d.getType().isEmpty() && !d.hasContentReference() &&
2607        !d.hasExample() && !d.hasFixed() && !d.hasMaxLengthElement() &&
2608        !d.getCondition().isEmpty() && !d.getConstraint().isEmpty() && !d.hasMustSupportElement() &&
2609        !d.hasBinding();
2610  }
2611
2612  private boolean allTypesAre(List<TypeRefComponent> types, String name) {
2613    for (TypeRefComponent t : types) {
2614      if (!t.getCode().equals(name))
2615        return false;
2616    }
2617    return true;
2618  }
2619
2620  private List<ElementDefinition> getChildren(List<ElementDefinition> all, ElementDefinition element) {
2621    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
2622    int i = all.indexOf(element)+1;
2623    while (i < all.size() && all.get(i).getPath().length() > element.getPath().length()) {
2624      if ((all.get(i).getPath().substring(0, element.getPath().length()+1).equals(element.getPath()+".")) && !all.get(i).getPath().substring(element.getPath().length()+1).contains("."))
2625        result.add(all.get(i));
2626      i++;
2627    }
2628    return result;
2629  }
2630
2631  private String tail(String path) {
2632    if (path.contains("."))
2633      return path.substring(path.lastIndexOf('.')+1);
2634    else
2635      return path;
2636  }
2637
2638  private boolean isDataType(String value) {
2639    StructureDefinition sd = context.fetchTypeDefinition(value);
2640    return sd != null && sd.getKind() == StructureDefinitionKind.COMPLEXTYPE;
2641  }
2642
2643  private boolean isReference(String value) {
2644    return "Reference".equals(value);
2645  }
2646
2647  public boolean isPrimitive(String value) {
2648    StructureDefinition sd = context.fetchTypeDefinition(value);
2649    return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
2650  }
2651
2652//  private static String listStructures(StructureDefinition p) {
2653//    StringBuilder b = new StringBuilder();
2654//    boolean first = true;
2655//    for (ProfileStructureComponent s : p.getStructure()) {
2656//      if (first)
2657//        first = false;
2658//      else
2659//        b.append(", ");
2660//      if (pkp != null && pkp.hasLinkFor(s.getType()))
2661//        b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>");
2662//      else
2663//        b.append(s.getType());
2664//    }
2665//    return b.toString();
2666//  }
2667
2668
2669  public StructureDefinition getProfile(StructureDefinition source, String url) {
2670        StructureDefinition profile = null;
2671        String code = null;
2672        if (url.startsWith("#")) {
2673                profile = source;
2674                code = url.substring(1);
2675        } else if (context != null) {
2676                String[] parts = url.split("\\#");
2677                profile = context.fetchResource(StructureDefinition.class, parts[0]);
2678      code = parts.length == 1 ? null : parts[1];
2679        }         
2680        if (profile == null)
2681                return null;
2682        if (code == null)
2683                return profile;
2684        for (Resource r : profile.getContained()) {
2685                if (r instanceof StructureDefinition && r.getId().equals(code))
2686                        return (StructureDefinition) r;
2687        }
2688        return null;
2689  }
2690
2691
2692
2693  public static class ElementDefinitionHolder {
2694    private String name;
2695    private ElementDefinition self;
2696    private int baseIndex = 0;
2697    private List<ElementDefinitionHolder> children;
2698
2699    public ElementDefinitionHolder(ElementDefinition self) {
2700      super();
2701      this.self = self;
2702      this.name = self.getPath();
2703      children = new ArrayList<ElementDefinitionHolder>();
2704    }
2705
2706    public ElementDefinition getSelf() {
2707      return self;
2708    }
2709
2710    public List<ElementDefinitionHolder> getChildren() {
2711      return children;
2712    }
2713
2714    public int getBaseIndex() {
2715      return baseIndex;
2716    }
2717
2718    public void setBaseIndex(int baseIndex) {
2719      this.baseIndex = baseIndex;
2720    }
2721
2722    @Override
2723    public String toString() {
2724      if (self.hasSliceName())
2725        return self.getPath()+"("+self.getSliceName()+")";
2726      else
2727        return self.getPath();
2728    }
2729  }
2730
2731  public static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> {
2732
2733    private boolean inExtension;
2734    private List<ElementDefinition> snapshot;
2735    private int prefixLength;
2736    private String base;
2737    private String name;
2738    private Set<String> errors = new HashSet<String>();
2739
2740    public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, int prefixLength, String name) {
2741      this.inExtension = inExtension;
2742      this.snapshot = snapshot;
2743      this.prefixLength = prefixLength;
2744      this.base = base;
2745      this.name = name;
2746    }
2747
2748    @Override
2749    public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) {
2750      if (o1.getBaseIndex() == 0)
2751        o1.setBaseIndex(find(o1.getSelf().getPath()));
2752      if (o2.getBaseIndex() == 0)
2753        o2.setBaseIndex(find(o2.getSelf().getPath()));
2754      return o1.getBaseIndex() - o2.getBaseIndex();
2755    }
2756
2757    private int find(String path) {
2758      String actual = base+path.substring(prefixLength);
2759      for (int i = 0; i < snapshot.size(); i++) {
2760        String p = snapshot.get(i).getPath();
2761        if (p.equals(actual)) {
2762          return i;
2763        }
2764        if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length()-3)) && !(actual.endsWith("[x]")) && !actual.substring(p.length()-3).contains(".")) {
2765          return i;
2766        }
2767        if (path.startsWith(p+".") && snapshot.get(i).hasContentReference()) {
2768          actual = base+(snapshot.get(i).getContentReference().substring(1)+"."+path.substring(p.length()+1)).substring(prefixLength);
2769          i = 0;
2770        }
2771      }
2772      if (prefixLength == 0)
2773        errors.add("Differential contains path "+path+" which is not found in the base");
2774      else
2775        errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the base");
2776      return 0;
2777    }
2778
2779    public void checkForErrors(List<String> errorList) {
2780      if (errors.size() > 0) {
2781//        CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
2782//        for (String s : errors)
2783//          b.append("StructureDefinition "+name+": "+s);
2784//        throw new DefinitionException(b.toString());
2785        for (String s : errors)
2786          if (s.startsWith("!"))
2787            errorList.add("!StructureDefinition "+name+": "+s.substring(1));
2788          else
2789            errorList.add("StructureDefinition "+name+": "+s);
2790      }
2791    }
2792  }
2793
2794
2795  public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors) throws FHIRException  {
2796
2797    final List<ElementDefinition> diffList = diff.getDifferential().getElement();
2798    // first, we move the differential elements into a tree
2799    if (diffList.isEmpty())
2800      return;
2801    ElementDefinitionHolder edh = new ElementDefinitionHolder(diffList.get(0));
2802
2803    boolean hasSlicing = false;
2804    List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly
2805    for(ElementDefinition elt : diffList) {
2806      if (elt.hasSlicing() || paths.contains(elt.getPath())) {
2807        hasSlicing = true;
2808        break;
2809      }
2810      paths.add(elt.getPath());
2811    }
2812    if(!hasSlicing) {
2813      // if Differential does not have slicing then safe to pre-sort the list
2814      // so elements and subcomponents are together
2815      Collections.sort(diffList, new ElementNameCompare());
2816    }
2817
2818    int i = 1;
2819    processElementsIntoTree(edh, i, diff.getDifferential().getElement());
2820
2821    // now, we sort the siblings throughout the tree
2822    ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name);
2823    sortElements(edh, cmp, errors);
2824
2825    // now, we serialise them back to a list
2826    diffList.clear();
2827    writeElements(edh, diffList);
2828  }
2829
2830  private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) {
2831    String path = edh.getSelf().getPath();
2832    final String prefix = path + ".";
2833    while (i < list.size() && list.get(i).getPath().startsWith(prefix)) {
2834      ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i));
2835      edh.getChildren().add(child);
2836      i = processElementsIntoTree(child, i+1, list);
2837    }
2838    return i;
2839  }
2840
2841  private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) throws FHIRException {
2842    if (edh.getChildren().size() == 1)
2843      // special case - sort needsto allocate base numbers, but there'll be no sort if there's only 1 child. So in that case, we just go ahead and allocated base number directly
2844      edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath());
2845    else
2846      Collections.sort(edh.getChildren(), cmp);
2847    cmp.checkForErrors(errors);
2848
2849    for (ElementDefinitionHolder child : edh.getChildren()) {
2850      if (child.getChildren().size() > 0) {
2851        // what we have to check for here is running off the base profile into a data type profile
2852        ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex());
2853        ElementDefinitionComparer ccmp;
2854        if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getCode()) || ed.getType().get(0).getCode().equals(ed.getPath())) {
2855          ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name);
2856        } else if (ed.getType().get(0).getCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) {
2857          StructureDefinition profile = context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile());
2858          if (profile==null)
2859            ccmp = null; // this might happen before everything is loaded. And we don't so much care about sot order in this case
2860          else
2861          ccmp = new ElementDefinitionComparer(true, profile.getSnapshot().getElement(), ed.getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name);
2862        } else if (ed.getType().size() == 1 && !ed.getType().get(0).getCode().equals("*")) {
2863          StructureDefinition profile = context.fetchTypeDefinition(ed.getType().get(0).getCode());
2864          if (profile==null)
2865            throw new FHIRException("Unable to resolve profile " + "http://hl7.org/fhir/StructureDefinition/"+ed.getType().get(0).getCode() + " in element " + ed.getPath());
2866          ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), ed.getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name);
2867        } else if (child.getSelf().getType().size() == 1) {
2868          StructureDefinition profile = context.fetchTypeDefinition(child.getSelf().getType().get(0).getCode());
2869          if (profile==null)
2870            throw new FHIRException("Unable to resolve profile " + "http://hl7.org/fhir/StructureDefinition/"+ed.getType().get(0).getCode() + " in element " + ed.getPath());
2871          ccmp = new ElementDefinitionComparer(false, profile.getSnapshot().getElement(), child.getSelf().getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name);
2872        } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) {
2873          String edLastNode = ed.getPath().replaceAll("(.*\\.)*(.*)", "$2");
2874          String childLastNode = child.getSelf().getPath().replaceAll("(.*\\.)*(.*)", "$2");
2875          String p = childLastNode.substring(edLastNode.length()-3);
2876          StructureDefinition sd = context.fetchTypeDefinition(p);
2877          if (sd == null)
2878            throw new Error("Unable to find profile "+p);
2879          ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name);
2880        } else {
2881          throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")");
2882        }
2883        if (ccmp != null)
2884        sortElements(child, ccmp, errors);
2885      }
2886    }
2887  }
2888
2889  private boolean isAbstract(String code) {
2890    return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") || code.equals("DomainResource");
2891  }
2892
2893
2894  private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) {
2895    list.add(edh.getSelf());
2896    for (ElementDefinitionHolder child : edh.getChildren()) {
2897      writeElements(child, list);
2898    }
2899  }
2900
2901  /**
2902   * First compare element by path then by name if same
2903   */
2904  private static class ElementNameCompare implements Comparator<ElementDefinition> {
2905
2906    @Override
2907    public int compare(ElementDefinition o1, ElementDefinition o2) {
2908      String path1 = normalizePath(o1);
2909      String path2 = normalizePath(o2);
2910      int cmp = path1.compareTo(path2);
2911      if (cmp == 0) {
2912        String name1 = o1.hasSliceName() ? o1.getSliceName() : "";
2913        String name2 = o2.hasSliceName() ? o2.getSliceName() : "";
2914        cmp = name1.compareTo(name2);
2915      }
2916      return cmp;
2917    }
2918
2919    private static String normalizePath(ElementDefinition e) {
2920      if (!e.hasPath()) return "";
2921      String path = e.getPath();
2922      // if sorting element names make sure onset[x] appears before onsetAge, onsetDate, etc.
2923      // so strip off the [x] suffix when comparing the path names.
2924      if (path.endsWith("[x]")) {
2925        path = path.substring(0, path.length()-3);
2926      }
2927      return path;
2928    }
2929
2930  }
2931
2932
2933  // generate schematrons for the rules in a structure definition
2934  public void generateSchematrons(OutputStream dest, StructureDefinition structure) throws IOException, DefinitionException {
2935    if (structure.getDerivation() != TypeDerivationRule.CONSTRAINT)
2936      throw new DefinitionException("not the right kind of structure to generate schematrons for");
2937    if (!structure.hasSnapshot())
2938      throw new DefinitionException("needs a snapshot");
2939
2940        StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBaseDefinition());
2941
2942        SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName());
2943
2944    ElementDefinition ed = structure.getSnapshot().getElement().get(0);
2945    generateForChildren(sch, "f:"+ed.getPath(), ed, structure, base);
2946    sch.dump();
2947  }
2948
2949  // generate a CSV representation of the structure definition
2950  public void generateCsvs(OutputStream dest, StructureDefinition structure, boolean asXml) throws IOException, DefinitionException, Exception {
2951    if (!structure.hasSnapshot())
2952      throw new DefinitionException("needs a snapshot");
2953
2954    CSVWriter csv = new CSVWriter(dest, structure, asXml);
2955
2956    for (ElementDefinition child : structure.getSnapshot().getElement()) {
2957      csv.processElement(child);
2958    }
2959    csv.dump();
2960  }
2961  
2962  private class Slicer extends ElementDefinitionSlicingComponent {
2963    String criteria = "";
2964    String name = "";   
2965    boolean check;
2966    public Slicer(boolean cantCheck) {
2967      super();
2968      this.check = cantCheck;
2969    }
2970  }
2971  
2972  private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, StructureDefinition structure) {
2973    // given a child in a structure, it's sliced. figure out the slicing xpath
2974    if (child.getPath().endsWith(".extension")) {
2975      ElementDefinition ued = getUrlFor(structure, child);
2976      if ((ued == null || !ued.hasFixed()) && !(child.hasType() && (child.getType().get(0).hasProfile())))
2977        return new Slicer(false);
2978      else {
2979      Slicer s = new Slicer(true);
2980      String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile() : ((UriType) ued.getFixed()).asStringValue();
2981      s.name = " with URL = '"+url+"'";
2982      s.criteria = "[@url = '"+url+"']";
2983      return s;
2984      }
2985    } else
2986      return new Slicer(false);
2987  }
2988
2989  private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, StructureDefinition structure, StructureDefinition base) throws IOException {
2990    //    generateForChild(txt, structure, child);
2991    List<ElementDefinition> children = getChildList(structure, ed);
2992    String sliceName = null;
2993    ElementDefinitionSlicingComponent slicing = null;
2994    for (ElementDefinition child : children) {
2995      String name = tail(child.getPath());
2996      if (child.hasSlicing()) {
2997        sliceName = name;
2998        slicing = child.getSlicing();        
2999      } else if (!name.equals(sliceName))
3000        slicing = null;
3001      
3002      ElementDefinition based = getByPath(base, child.getPath());
3003      boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin()));
3004      boolean doMax = child.hasMax() && !child.getMax().equals("*") && (based == null || (!child.getMax().equals(based.getMax())));
3005      Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure);
3006      if (slicer.check) {
3007        if (doMin || doMax) {
3008          Section s = sch.section(xpath);
3009          Rule r = s.rule(xpath);
3010          if (doMin) 
3011            r.assrt("count(f:"+name+slicer.criteria+") >= "+Integer.toString(child.getMin()), name+slicer.name+": minimum cardinality of '"+name+"' is "+Integer.toString(child.getMin()));
3012          if (doMax) 
3013            r.assrt("count(f:"+name+slicer.criteria+") <= "+child.getMax(), name+slicer.name+": maximum cardinality of '"+name+"' is "+child.getMax());
3014          }
3015        }
3016      }
3017    for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
3018      if (inv.hasXpath()) {
3019        Section s = sch.section(ed.getPath());
3020        Rule r = s.rule(xpath);
3021        r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId()+": " : "")+inv.getHuman()+(inv.hasUserData(IS_DERIVED) ? " (inherited)" : ""));
3022      }
3023    }
3024    for (ElementDefinition child : children) {
3025      String name = tail(child.getPath());
3026      generateForChildren(sch, xpath+"/f:"+name, child, structure, base);
3027    }
3028  }
3029
3030
3031
3032
3033  private ElementDefinition getByPath(StructureDefinition base, String path) {
3034                for (ElementDefinition ed : base.getSnapshot().getElement()) {
3035                        if (ed.getPath().equals(path))
3036                                return ed;
3037                        if (ed.getPath().endsWith("[x]") && ed.getPath().length() <= path.length()-3 &&  ed.getPath().substring(0, ed.getPath().length()-3).equals(path.substring(0, ed.getPath().length()-3)))
3038                                return ed;
3039    }
3040          return null;
3041  }
3042
3043
3044  public void setIds(StructureDefinition sd, boolean checkFirst) throws DefinitionException  {
3045    if (!checkFirst || !sd.hasDifferential() || hasMissingIds(sd.getDifferential().getElement())) {
3046      if (!sd.hasDifferential())
3047        sd.setDifferential(new StructureDefinitionDifferentialComponent());
3048      generateIds(sd.getDifferential().getElement(), sd.getName());
3049    }
3050    if (!checkFirst || !sd.hasSnapshot() || hasMissingIds(sd.getSnapshot().getElement())) {
3051      if (!sd.hasSnapshot())
3052        sd.setSnapshot(new StructureDefinitionSnapshotComponent());
3053      generateIds(sd.getSnapshot().getElement(), sd.getName());
3054    }
3055  }
3056
3057
3058  private boolean hasMissingIds(List<ElementDefinition> list) {
3059    for (ElementDefinition ed : list) {
3060      if (!ed.hasId())
3061        return true;
3062    }    
3063    return false;
3064  }
3065
3066
3067  private void generateIds(List<ElementDefinition> list, String name) throws DefinitionException  {
3068    if (list.isEmpty())
3069      return;
3070    
3071    Map<String, String> idMap = new HashMap<String, String>();
3072    
3073    List<String> paths = new ArrayList<String>();
3074    // first pass, update the element ids
3075    for (ElementDefinition ed : list) {
3076      if (!ed.hasPath())
3077        throw new DefinitionException("No path on element Definition "+Integer.toString(list.indexOf(ed))+" in "+name);
3078      int depth = charCount(ed.getPath(), '.');
3079      String tail = tail(ed.getPath());
3080
3081      if (depth > paths.size()) {
3082        // this means that we've jumped into a sparse thing. 
3083        String[] pl = ed.getPath().split("\\.");
3084        for (int i = paths.size(); i < pl.length-1; i++) // -1 because the last path is in focus
3085          paths.add(pl[i]);
3086      }
3087      while (depth < paths.size() && paths.size() > 0)
3088        paths.remove(paths.size() - 1);
3089      
3090      String t = ed.hasSliceName() ? tail+":"+checkName(ed.getSliceName()) : /* why do this? name != null ? tail + ":"+checkName(name) : */ tail;
3091//      if (isExtension(ed))
3092//        t = t + describeExtension(ed);
3093      name = null;
3094      StringBuilder b = new StringBuilder();
3095      for (String s : paths) {
3096        b.append(s);
3097        b.append(".");
3098      }
3099      b.append(t);
3100      String bs = b.toString();
3101      idMap.put(ed.hasId() ? ed.getId() : ed.getPath(), bs);
3102      ed.setId(bs);
3103      paths.add(t);
3104      if (ed.hasContentReference()) {
3105        String s = ed.getContentReference().substring(1);
3106        if (idMap.containsKey(s))
3107          ed.setContentReference("#"+idMap.get(s));
3108        
3109      }
3110    }  
3111    // second path - fix up any broken path based id references
3112    
3113  }
3114
3115
3116//  private String describeExtension(ElementDefinition ed) {
3117//    if (!ed.hasType() || !ed.getTypeFirstRep().hasProfile())
3118//      return "";
3119//    return "$"+urlTail(ed.getTypeFirstRep().getProfile());
3120//  }
3121//
3122
3123  private String urlTail(String profile) {
3124    return profile.contains("/") ? profile.substring(profile.lastIndexOf("/")+1) : profile;
3125  }
3126
3127
3128  private String checkName(String name) {
3129//    if (name.contains("."))
3130////      throw new Exception("Illegal name "+name+": no '.'");
3131//    if (name.contains(" "))
3132//      throw new Exception("Illegal name "+name+": no spaces");
3133    StringBuilder b = new StringBuilder();
3134    for (char c : name.toCharArray()) {
3135      if (!Utilities.existsInList(c, '.', ' ', ':', '"', '\'', '(', ')', '&', '[', ']'))
3136        b.append(c);
3137    }
3138    return b.toString().toLowerCase();
3139  }
3140
3141
3142  private int charCount(String path, char t) {
3143    int res = 0;
3144    for (char ch : path.toCharArray()) {
3145      if (ch == t)
3146        res++;
3147    }
3148    return res;
3149  }
3150
3151//
3152//private void generateForChild(TextStreamWriter txt,
3153//    StructureDefinition structure, ElementDefinition child) {
3154//  // TODO Auto-generated method stub
3155//
3156//}
3157
3158  private interface ExampleValueAccessor {
3159    Type getExampleValue(ElementDefinition ed);
3160    String getId();
3161  }
3162
3163  private class BaseExampleValueAccessor implements ExampleValueAccessor {
3164    @Override
3165    public Type getExampleValue(ElementDefinition ed) {
3166      if (ed.hasFixed())
3167        return ed.getFixed();
3168      if (ed.hasExample())
3169        return ed.getExample().get(0).getValue();
3170      else
3171        return null;
3172    }
3173
3174    @Override
3175    public String getId() {
3176      return "-genexample";
3177    }
3178  }
3179  
3180  private class ExtendedExampleValueAccessor implements ExampleValueAccessor {
3181    private String index;
3182
3183    public ExtendedExampleValueAccessor(String index) {
3184      this.index = index;
3185    }
3186    @Override
3187    public Type getExampleValue(ElementDefinition ed) {
3188      if (ed.hasFixed())
3189        return ed.getFixed();
3190      for (Extension ex : ed.getExtension()) {
3191       String ndx = ToolingExtensions.readStringExtension(ex, "index");
3192       Type value = ToolingExtensions.getExtension(ex, "exValue").getValue();
3193       if (index.equals(ndx) && value != null)
3194         return value;
3195      }
3196      return null;
3197    }
3198    @Override
3199    public String getId() {
3200      return "-genexample-"+index;
3201    }
3202  }
3203  
3204  public List<org.hl7.fhir.dstu3.elementmodel.Element> generateExamples(StructureDefinition sd, boolean evenWhenNoExamples) throws FHIRException {
3205    List<org.hl7.fhir.dstu3.elementmodel.Element> examples = new ArrayList<org.hl7.fhir.dstu3.elementmodel.Element>();
3206    if (sd.hasSnapshot()) {
3207      if (evenWhenNoExamples || hasAnyExampleValues(sd)) 
3208        examples.add(generateExample(sd, new BaseExampleValueAccessor()));
3209      for (int i = 1; i <= 50; i++) {
3210        if (hasAnyExampleValues(sd, Integer.toString(i))) 
3211          examples.add(generateExample(sd, new ExtendedExampleValueAccessor(Integer.toString(i))));
3212      }
3213    }
3214    return examples;
3215  }
3216
3217  private org.hl7.fhir.dstu3.elementmodel.Element generateExample(StructureDefinition profile, ExampleValueAccessor accessor) throws FHIRException {
3218    ElementDefinition ed = profile.getSnapshot().getElementFirstRep();
3219    org.hl7.fhir.dstu3.elementmodel.Element r = new org.hl7.fhir.dstu3.elementmodel.Element(ed.getPath(), new Property(context, ed, profile));
3220    List<ElementDefinition> children = getChildMap(profile, ed);
3221    for (ElementDefinition child : children) {
3222      if (child.getPath().endsWith(".id")) {
3223        org.hl7.fhir.dstu3.elementmodel.Element id = new org.hl7.fhir.dstu3.elementmodel.Element("id", new Property(context, child, profile));
3224        id.setValue(profile.getId()+accessor.getId());
3225        r.getChildren().add(id);
3226      } else { 
3227        org.hl7.fhir.dstu3.elementmodel.Element e = createExampleElement(profile, child, accessor);
3228        if (e != null)
3229          r.getChildren().add(e);
3230      }
3231    }
3232    return r;
3233  }
3234
3235  private org.hl7.fhir.dstu3.elementmodel.Element createExampleElement(StructureDefinition profile, ElementDefinition ed, ExampleValueAccessor accessor) throws FHIRException {
3236    Type v = accessor.getExampleValue(ed);
3237    if (v != null) {
3238      return new ObjectConverter(context).convert(new Property(context, ed, profile), v);
3239    } else {
3240      org.hl7.fhir.dstu3.elementmodel.Element res = new org.hl7.fhir.dstu3.elementmodel.Element(tail(ed.getPath()), new Property(context, ed, profile));
3241      boolean hasValue = false;
3242      List<ElementDefinition> children = getChildMap(profile, ed);
3243      for (ElementDefinition child : children) {
3244        if (!child.hasContentReference()) {
3245        org.hl7.fhir.dstu3.elementmodel.Element e = createExampleElement(profile, child, accessor);
3246        if (e != null) {
3247          hasValue = true;
3248          res.getChildren().add(e);
3249        }
3250      }
3251      }
3252      if (hasValue)
3253        return res;
3254      else
3255        return null;
3256    }
3257  }
3258
3259  private boolean hasAnyExampleValues(StructureDefinition sd, String index) {
3260    for (ElementDefinition ed : sd.getSnapshot().getElement())
3261      for (Extension ex : ed.getExtension()) {
3262        String ndx = ToolingExtensions.readStringExtension(ex, "index");
3263        Extension exv = ToolingExtensions.getExtension(ex, "exValue");
3264        if (exv != null) {
3265          Type value = exv.getValue();
3266        if (index.equals(ndx) && value != null)
3267          return true;
3268        }
3269       }
3270    return false;
3271  }
3272
3273
3274  private boolean hasAnyExampleValues(StructureDefinition sd) {
3275    for (ElementDefinition ed : sd.getSnapshot().getElement())
3276      if (ed.hasExample())
3277        return true;
3278    return false;
3279  }
3280
3281
3282  public void populateLogicalSnapshot(StructureDefinition sd) throws FHIRException {
3283    sd.getSnapshot().getElement().add(sd.getDifferential().getElementFirstRep().copy());
3284    
3285    if (sd.hasBaseDefinition()) {
3286    StructureDefinition base = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition());
3287    if (base == null)
3288      throw new FHIRException("Unable to find base definition for logical model: "+sd.getBaseDefinition()+" from "+sd.getUrl());
3289    copyElements(sd, base.getSnapshot().getElement());
3290    }
3291    copyElements(sd, sd.getDifferential().getElement());
3292  }
3293
3294
3295  private void copyElements(StructureDefinition sd, List<ElementDefinition> list) {
3296    for (ElementDefinition ed : list) {
3297      if (ed.getPath().contains(".")) {
3298        ElementDefinition n = ed.copy();
3299        n.setPath(sd.getSnapshot().getElementFirstRep().getPath()+"."+ed.getPath().substring(ed.getPath().indexOf(".")+1));
3300        sd.getSnapshot().addElement(n);
3301      }
3302    }
3303  }
3304
3305    
3306  public void cleanUpDifferential(StructureDefinition sd) {
3307    if (sd.getDifferential().getElement().size() > 1)
3308      cleanUpDifferential(sd, 1);
3309  }
3310  
3311  private void cleanUpDifferential(StructureDefinition sd, int start) {
3312    int level = Utilities.charCount(sd.getDifferential().getElement().get(start).getPath(), '.');
3313    int c = start;
3314    int len = sd.getDifferential().getElement().size();
3315    HashSet<String> paths = new HashSet<String>();
3316    while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') == level) {
3317      ElementDefinition ed = sd.getDifferential().getElement().get(c);
3318      if (!paths.contains(ed.getPath())) {
3319        paths.add(ed.getPath());
3320        int ic = c+1; 
3321        while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 
3322          ic++;
3323        ElementDefinition slicer = null;
3324        List<ElementDefinition> slices = new ArrayList<ElementDefinition>();
3325        slices.add(ed);
3326        while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') == level) {
3327          ElementDefinition edi = sd.getDifferential().getElement().get(ic);
3328          if (ed.getPath().equals(edi.getPath())) {
3329            if (slicer == null) {
3330              slicer = new ElementDefinition();
3331              slicer.setPath(edi.getPath());
3332              slicer.getSlicing().setRules(SlicingRules.OPEN);
3333              sd.getDifferential().getElement().add(c, slicer);
3334              c++;
3335              ic++;
3336            }
3337            slices.add(edi);
3338          }
3339          ic++;
3340          while (ic < len && Utilities.charCount(sd.getDifferential().getElement().get(ic).getPath(), '.') > level) 
3341            ic++;
3342        }
3343        // now we're at the end, we're going to figure out the slicing discriminator
3344        if (slicer != null)
3345          determineSlicing(slicer, slices);
3346      }
3347      c++;
3348      if (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) {
3349        cleanUpDifferential(sd, c);
3350        c++;
3351        while (c < len && Utilities.charCount(sd.getDifferential().getElement().get(c).getPath(), '.') > level) 
3352          c++;
3353      }
3354  }
3355  }
3356
3357
3358  private void determineSlicing(ElementDefinition slicer, List<ElementDefinition> slices) {
3359    // first, name them
3360    int i = 0;
3361    for (ElementDefinition ed : slices) {
3362      if (ed.hasUserData("slice-name")) {
3363        ed.setSliceName(ed.getUserString("slice-name"));
3364      } else {
3365        i++;
3366        ed.setSliceName("slice-"+Integer.toString(i));
3367      }
3368    }
3369    // now, the hard bit, how are they differentiated? 
3370    // right now, we hard code this...
3371    if (slicer.getPath().endsWith(".extension") || slicer.getPath().endsWith(".modifierExtension"))
3372      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("url");
3373    else if (slicer.getPath().equals("DiagnosticReport.result"))
3374      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("reference.code");
3375    else if (slicer.getPath().equals("Observation.related"))
3376      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("target.reference.code");
3377    else if (slicer.getPath().equals("Bundle.entry"))
3378      slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.VALUE).setPath("resource.@profile");
3379    else  
3380      throw new Error("No slicing for "+slicer.getPath()); 
3381  }
3382
3383  public class SpanEntry {
3384    private List<SpanEntry> children = new ArrayList<SpanEntry>();
3385    private boolean profile;
3386    private String id;
3387    private String name;
3388    private String resType;
3389    private String cardinality;
3390    private String description;
3391    private String profileLink;
3392    private String resLink;
3393    private String type;
3394    
3395    public String getName() {
3396      return name;
3397    }
3398    public void setName(String name) {
3399      this.name = name;
3400    }
3401    public String getResType() {
3402      return resType;
3403    }
3404    public void setResType(String resType) {
3405      this.resType = resType;
3406    }
3407    public String getCardinality() {
3408      return cardinality;
3409    }
3410    public void setCardinality(String cardinality) {
3411      this.cardinality = cardinality;
3412    }
3413    public String getDescription() {
3414      return description;
3415    }
3416    public void setDescription(String description) {
3417      this.description = description;
3418    }
3419    public String getProfileLink() {
3420      return profileLink;
3421    }
3422    public void setProfileLink(String profileLink) {
3423      this.profileLink = profileLink;
3424    }
3425    public String getResLink() {
3426      return resLink;
3427    }
3428    public void setResLink(String resLink) {
3429      this.resLink = resLink;
3430    }
3431    public String getId() {
3432      return id;
3433    }
3434    public void setId(String id) {
3435      this.id = id;
3436    }
3437    public boolean isProfile() {
3438      return profile;
3439    }
3440    public void setProfile(boolean profile) {
3441      this.profile = profile;
3442    }
3443    public List<SpanEntry> getChildren() {
3444      return children;
3445    }
3446    public String getType() {
3447      return type;
3448    }
3449    public void setType(String type) {
3450      this.type = type;
3451    }
3452    
3453  }
3454
3455  public XhtmlNode generateSpanningTable(StructureDefinition profile, String imageFolder, boolean onlyConstraints, String constraintPrefix, Set<String> outputTracker) throws IOException, FHIRException {
3456    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(imageFolder, false);
3457    gen.setTranslator(getTranslator());
3458    TableModel model = initSpanningTable(gen, "", false, profile.getId());
3459    Set<String> processed = new HashSet<String>();
3460    SpanEntry span = buildSpanningTable("(focus)", "", profile, processed, onlyConstraints, constraintPrefix);
3461    
3462    genSpanEntry(gen, model.getRows(), span);
3463    return gen.generate(model, "", 0, outputTracker);
3464  }
3465
3466  private SpanEntry buildSpanningTable(String name, String cardinality, StructureDefinition profile, Set<String> processed, boolean onlyConstraints, String constraintPrefix) throws IOException {
3467    SpanEntry res = buildSpanEntryFromProfile(name, cardinality, profile);
3468    boolean wantProcess = !processed.contains(profile.getUrl());
3469    processed.add(profile.getUrl());
3470    if (wantProcess && profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
3471      for (ElementDefinition ed : profile.getSnapshot().getElement()) {
3472        if (!"0".equals(ed.getMax()) && ed.getType().size() > 0) {
3473          String card = getCardinality(ed, profile.getSnapshot().getElement());
3474          if (!card.endsWith(".0")) {
3475            List<String> refProfiles = listReferenceProfiles(ed);
3476            if (refProfiles.size() > 0) {
3477              String uri = refProfiles.get(0);
3478              if (uri != null) {
3479                StructureDefinition sd = context.fetchResource(StructureDefinition.class, uri);
3480                if (sd != null && (!onlyConstraints || (sd.getDerivation() == TypeDerivationRule.CONSTRAINT && (constraintPrefix == null || sd.getUrl().startsWith(constraintPrefix))))) {
3481                  res.getChildren().add(buildSpanningTable(nameForElement(ed), card, sd, processed, onlyConstraints, constraintPrefix));
3482                }
3483              }
3484            }
3485          }
3486        } 
3487      }
3488    }
3489    return res;
3490  }
3491
3492
3493  private String getCardinality(ElementDefinition ed, List<ElementDefinition> list) {
3494    int min = ed.getMin();
3495    int max = !ed.hasMax() || ed.getMax().equals("*") ? Integer.MAX_VALUE : Integer.parseInt(ed.getMax());
3496    while (ed != null && ed.getPath().contains(".")) {
3497      ed = findParent(ed, list);
3498      if (ed.getMax().equals("0"))
3499        max = 0;
3500      else if (!ed.getMax().equals("1") && !ed.hasSlicing())
3501        max = Integer.MAX_VALUE;
3502      if (ed.getMin() == 0)
3503        min = 0;
3504    }
3505    return Integer.toString(min)+".."+(max == Integer.MAX_VALUE ? "*" : Integer.toString(max));
3506  }
3507
3508
3509  private ElementDefinition findParent(ElementDefinition ed, List<ElementDefinition> list) {
3510    int i = list.indexOf(ed)-1;
3511    while (i >= 0 && !ed.getPath().startsWith(list.get(i).getPath()+"."))
3512      i--;
3513    if (i == -1)
3514      return null;
3515    else
3516      return list.get(i);
3517  }
3518
3519
3520  private List<String> listReferenceProfiles(ElementDefinition ed) {
3521    List<String> res = new ArrayList<String>();
3522    for (TypeRefComponent tr : ed.getType()) {
3523      // code is null if we're dealing with "value" and profile is null if we just have Reference()
3524      if (tr.getCode()!= null && "Reference".equals(tr.getCode()) && tr.getTargetProfile() != null)
3525        res.add(tr.getTargetProfile());
3526    }
3527    return res ;
3528  }
3529
3530
3531  private String nameForElement(ElementDefinition ed) {
3532    return ed.getPath().substring(ed.getPath().indexOf(".")+1);
3533  }
3534
3535
3536  private SpanEntry buildSpanEntryFromProfile(String name, String cardinality, StructureDefinition profile) throws IOException {
3537    SpanEntry res = new SpanEntry();
3538    res.setName(name);
3539    res.setCardinality(cardinality);
3540    res.setProfileLink(profile.getUserString("path"));
3541    res.setResType(profile.getType());
3542    StructureDefinition base = context.fetchResource(StructureDefinition.class, res.getResType());
3543    if (base != null)
3544      res.setResLink(base.getUserString("path"));
3545    res.setId(profile.getId());
3546    res.setProfile(profile.getDerivation() == TypeDerivationRule.CONSTRAINT);
3547    StringBuilder b = new StringBuilder();
3548    b.append(res.getResType());
3549    boolean first = true;
3550    boolean open = false;
3551    if (profile.getDerivation() == TypeDerivationRule.CONSTRAINT) {
3552      res.setDescription(profile.getName());
3553      for (ElementDefinition ed : profile.getSnapshot().getElement()) {
3554        if (isKeyProperty(ed.getBase().getPath()) && ed.hasFixed()) {
3555          if (first) {
3556            open = true;
3557            first = false;
3558            b.append("[");
3559          } else {
3560            b.append(", ");
3561          }
3562          b.append(tail(ed.getBase().getPath()));
3563          b.append("=");
3564          b.append(summarise(ed.getFixed()));
3565        }
3566      }
3567      if (open)
3568        b.append("]");
3569    } else
3570      res.setDescription("Base FHIR "+profile.getName());
3571    res.setType(b.toString());
3572    return res ;
3573  }
3574
3575
3576  private String summarise(Type value) throws IOException {
3577    if (value instanceof Coding)
3578      return summariseCoding((Coding) value);
3579    else if (value instanceof CodeableConcept)
3580      return summariseCodeableConcept((CodeableConcept) value);
3581    else
3582      return buildJson(value);
3583  }
3584
3585
3586  private String summariseCoding(Coding value) {
3587    String uri = value.getSystem();
3588    String system = NarrativeGenerator.describeSystem(uri);
3589    if (Utilities.isURL(system)) {
3590      if (system.equals("http://cap.org/protocols"))
3591        system = "CAP Code";
3592    }
3593    return system+" "+value.getCode();
3594  }
3595
3596
3597  private String summariseCodeableConcept(CodeableConcept value) {
3598    if (value.hasCoding())
3599      return summariseCoding(value.getCodingFirstRep());
3600    else
3601      return value.getText();
3602  }
3603
3604
3605  private boolean isKeyProperty(String path) {
3606    return Utilities.existsInList(path, "Observation.code");
3607  }
3608
3609
3610  public TableModel initSpanningTable(HierarchicalTableGenerator gen, String prefix, boolean isLogical, String id) {
3611    TableModel model = gen.new TableModel(id, false);
3612    
3613    model.setDocoImg(prefix+"help16.png");
3614    model.setDocoRef(prefix+"formats.html#table"); // todo: change to graph definition
3615    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Property", "A profiled resource", null, 0));
3616    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Card.", "Minimum and Maximum # of times the the element can appear in the instance", null, 0));
3617    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Content", "What goes here", null, 0));
3618    model.getTitles().add(gen.new Title(null, model.getDocoRef(), "Description", "Description of the profile", null, 0));
3619    return model;
3620  }
3621
3622  private void genSpanEntry(HierarchicalTableGenerator gen, List<Row> rows, SpanEntry span) throws IOException {
3623    Row row = gen.new Row();
3624    rows.add(row);
3625    row.setAnchor(span.getId());
3626    //row.setColor(..?);
3627    if (span.isProfile()) 
3628      row.setIcon("icon_profile.png", HierarchicalTableGenerator.TEXT_ICON_PROFILE);
3629    else
3630      row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
3631    
3632    row.getCells().add(gen.new Cell(null, null, span.getName(), null, null));
3633    row.getCells().add(gen.new Cell(null, null, span.getCardinality(), null, null));
3634    row.getCells().add(gen.new Cell(null, span.getProfileLink(), span.getType(), null, null));
3635    row.getCells().add(gen.new Cell(null, null, span.getDescription(), null, null));
3636
3637    for (SpanEntry child : span.getChildren())
3638      genSpanEntry(gen, row.getSubRows(), child);
3639  }
3640
3641
3642  public static ElementDefinitionSlicingDiscriminatorComponent interpretR2Discriminator(String discriminator) {
3643    if (discriminator.endsWith("@pattern"))
3644      return makeDiscriminator(DiscriminatorType.PATTERN, discriminator.length() == 8 ? "" : discriminator.substring(0,discriminator.length()-9)); 
3645    if (discriminator.endsWith("@profile"))
3646      return makeDiscriminator(DiscriminatorType.PROFILE, discriminator.length() == 8 ? "" : discriminator.substring(discriminator.length()-9)); 
3647    if (discriminator.endsWith("@type")) 
3648      return makeDiscriminator(DiscriminatorType.TYPE, discriminator.length() == 5 ? "" : discriminator.substring(discriminator.length()-6)); 
3649    return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(discriminator);
3650  }
3651
3652
3653  public static ElementDefinitionSlicingDiscriminatorComponent makeDiscriminator(DiscriminatorType profile, String str) {
3654    return new ElementDefinitionSlicingDiscriminatorComponent().setType(DiscriminatorType.VALUE).setPath(Utilities.noString(str)? "$this" : str);
3655  }
3656
3657
3658  public static String buildR2Discriminator(ElementDefinitionSlicingDiscriminatorComponent t) throws FHIRException {
3659    switch (t.getType()) {
3660    case PROFILE: return t.getPath()+"/@profile";
3661    case PATTERN: return t.getPath()+"/@pattern";
3662    case TYPE: return t.getPath()+"/@type";
3663    case VALUE: return t.getPath();
3664    case EXISTS: return t.getPath(); // determination of value vs. exists is based on whether there's only 2 slices - one with minOccurs=1 and other with maxOccur=0
3665    default: throw new FHIRException("Unable to represent "+t.getType().toCode()+":"+t.getPath()+" in R2");    
3666    }
3667  }
3668
3669
3670
3671
3672
3673
3674}