001package org.hl7.fhir.instance.utils;
002
003import java.io.IOException;
004import java.io.OutputStream;
005import java.util.ArrayList;
006import java.util.Collections;
007import java.util.Comparator;
008import java.util.HashSet;
009import java.util.List;
010import java.util.Set;
011
012import org.hl7.fhir.instance.formats.IParser;
013import org.hl7.fhir.instance.model.Base;
014import org.hl7.fhir.instance.model.BooleanType;
015import org.hl7.fhir.instance.model.Coding;
016import org.hl7.fhir.instance.model.Element;
017import org.hl7.fhir.instance.model.ElementDefinition;
018import org.hl7.fhir.instance.model.ElementDefinition.ElementDefinitionBindingComponent;
019import org.hl7.fhir.instance.model.ElementDefinition.ElementDefinitionConstraintComponent;
020import org.hl7.fhir.instance.model.ElementDefinition.ElementDefinitionMappingComponent;
021import org.hl7.fhir.instance.model.ElementDefinition.ElementDefinitionSlicingComponent;
022import org.hl7.fhir.instance.model.ElementDefinition.SlicingRules;
023import org.hl7.fhir.instance.model.ElementDefinition.TypeRefComponent;
024import org.hl7.fhir.instance.model.Enumerations.BindingStrength;
025import org.hl7.fhir.instance.model.IntegerType;
026import org.hl7.fhir.instance.model.PrimitiveType;
027import org.hl7.fhir.instance.model.Reference;
028import org.hl7.fhir.instance.model.Resource;
029import org.hl7.fhir.instance.model.StringType;
030import org.hl7.fhir.instance.model.StructureDefinition;
031import org.hl7.fhir.instance.model.StructureDefinition.StructureDefinitionDifferentialComponent;
032import org.hl7.fhir.instance.model.StructureDefinition.StructureDefinitionSnapshotComponent;
033import org.hl7.fhir.instance.model.Type;
034import org.hl7.fhir.instance.model.UriType;
035import org.hl7.fhir.instance.model.ValueSet;
036import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionComponent;
037import org.hl7.fhir.instance.model.ValueSet.ValueSetExpansionContainsComponent;
038import org.hl7.fhir.instance.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
039import org.hl7.fhir.instance.utils.ProfileUtilities.ProfileKnowledgeProvider.BindingResolution;
040import org.hl7.fhir.utilities.validation.ValidationMessage;
041import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
042import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
043import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
044import org.hl7.fhir.exceptions.DefinitionException;
045import org.hl7.fhir.exceptions.FHIRException;
046import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
047import org.hl7.fhir.utilities.Utilities;
048import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
049import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
050import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
051import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
052import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
053import org.hl7.fhir.utilities.xhtml.XhtmlNode;
054import org.hl7.fhir.utilities.xml.SchematronWriter;
055import org.hl7.fhir.utilities.xml.SchematronWriter.Rule;
056import org.hl7.fhir.utilities.xml.SchematronWriter.SchematronType;
057import org.hl7.fhir.utilities.xml.SchematronWriter.Section;
058
059/**
060 * This class provides a set of utility operations for working with Profiles.
061 * Key functionality:
062 *  * getChildMap --?
063 *  * getChildList
064 *  * generateSnapshot: Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
065 *  * generateExtensionsTable: generate the HTML for a hierarchical table presentation of the extensions
066 *  * generateTable: generate  the HTML for a hierarchical table presentation of a structure
067 *  * summarise: describe the contents of a profile
068 * @author Grahame
069 *
070 */
071public class ProfileUtilities {
072
073  public static final int STATUS_OK = 0;
074  public static final int STATUS_HINT = 1;
075  public static final int STATUS_WARNING = 2;
076  public static final int STATUS_ERROR = 3;
077  public static final int STATUS_FATAL = 4;
078  public static final String DERIVATION_POINTER = "derived.pointer";
079  public static final String IS_DERIVED = "derived.fact";
080  public static final String UD_ERROR_STATUS = "error-status";
081  private static final String ROW_COLOR_ERROR = "#ffcccc";
082  private static final String ROW_COLOR_FATAL = "#ff9999";
083  private static final String ROW_COLOR_WARNING = "#ffebcc";
084  private static final String ROW_COLOR_HINT = "#ebf5ff";
085  private static final String DERIVATION_EQUALS = "derivation.equals";
086  private final boolean ADD_REFERENCE_TO_TABLE = true;
087  // note that ProfileUtilities are used re-entrantly internally, so nothing with process state can be here
088  private final IWorkerContext context;
089  private List<ValidationMessage> messages;
090  private List<String> snapshotStack = new ArrayList<String>();
091  private ProfileKnowledgeProvider pkp;
092  public ProfileUtilities(IWorkerContext context, List<ValidationMessage> messages, ProfileKnowledgeProvider pkp) {
093    super();
094    this.context = context;
095    this.messages = messages;
096    this.pkp = pkp;
097  }
098
099  private boolean allTypesAre(List<TypeRefComponent> types, String name) {
100    for (TypeRefComponent t : types) {
101      if (!t.getCode().equals(name))
102        return false;
103    }
104    return true;
105  }
106
107  private String buildJson(Type value) throws Exception {
108    if (value instanceof PrimitiveType)
109      return ((PrimitiveType) value).asStringValue();
110
111    IParser json = context.newJsonParser();
112    return json.composeString(value, null);
113  }
114
115  private boolean checkExtensionDoco(ElementDefinition base) {
116    // see task 3970. For an extension, there's no point copying across all the underlying definitional stuff
117    boolean isExtension = base.getPath().equals("Extension") || base.getPath().endsWith(".extension") || base.getPath().endsWith(".modifierExtension");
118    if (isExtension) {
119      base.setDefinition("An Extension");
120      base.setShort("Extension");
121      base.setCommentsElement(null);
122      base.setRequirementsElement(null);
123      base.getAlias().clear();
124      base.getMapping().clear();
125    }
126    return isExtension;
127  }
128
129  private Piece checkForNoChange(Element source, Piece piece) {
130    if (source.hasUserData(DERIVATION_EQUALS)) {
131      piece.addStyle("opacity: 0.4");
132    }
133    return piece;
134  }
135
136  private Piece checkForNoChange(Element src1, Element src2, Piece piece) {
137    if (src1.hasUserData(DERIVATION_EQUALS) && src2.hasUserData(DERIVATION_EQUALS)) {
138      piece.addStyle("opacity: 0.5");
139    }
140    return piece;
141  }
142
143  private boolean codesInExpansion(List<ValueSetExpansionContainsComponent> contains, ValueSetExpansionComponent expansion) {
144    for (ValueSetExpansionContainsComponent cc : contains) {
145      if (!inExpansion(cc, expansion.getContains()))
146        return false;
147      if (!codesInExpansion(cc.getContains(), expansion))
148        return false;
149    }
150    return true;
151  }
152
153  private String commas(List<StringType> discriminator) {
154    CommaSeparatedStringBuilder c = new CommaSeparatedStringBuilder();
155    for (StringType id : discriminator)
156      c.append(id.asStringValue());
157    return c.toString();
158  }
159
160  private String describe(SlicingRules rules) {
161    switch (rules) {
162    case CLOSED : return "Closed";
163    case OPEN : return "Open";
164    case OPENATEND : return "Open At End";
165    default:
166      return "??";
167    }
168  }
169
170  private String describeCardinality(ElementDefinition definition, ElementDefinition fallback, UnusedTracker tracker) {
171    IntegerType min = definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
172    StringType max = definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
173    if (min.isEmpty() && fallback != null)
174      min = fallback.getMinElement();
175    if (max.isEmpty() && fallback != null)
176      max = fallback.getMaxElement();
177
178    tracker.used = !max.isEmpty() && !max.getValue().equals("0");
179
180    if (min.isEmpty() && max.isEmpty())
181      return null;
182    else
183      return (!min.hasValue() ? "" : Integer.toString(min.getValue())) + ".." + (!max.hasValue() ? "" : max.getValue());
184  }
185
186  public String describeSlice(ElementDefinitionSlicingComponent slicing) {
187    return (slicing.getOrdered() ? "Ordered, " : "Unordered, ")+describe(slicing.getRules())+", by "+commas(slicing.getDiscriminator());
188  }
189
190  private boolean discriiminatorMatches(List<StringType> diff, List<StringType> base) {
191    if (diff.isEmpty() || base.isEmpty())
192        return true;
193    if (diff.size() != base.size())
194        return false;
195    for (int i = 0; i < diff.size(); i++)
196        if (!diff.get(i).getValue().equals(base.get(i).getValue()))
197                return false;
198    return true;
199  }
200
201  private boolean extensionIsComplex(String value) {
202    if (value.contains("#")) {
203      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
204    if (ext == null)
205      return false;
206      String tail = value.substring(value.indexOf("#")+1);
207      ElementDefinition ed = null;
208      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
209        if (tail.equals(ted.getName())) {
210          ed = ted;
211          break;
212        }
213      }
214      if (ed == null)
215        return false;
216      int i = ext.getSnapshot().getElement().indexOf(ed);
217      int j = i+1;
218      while (j < ext.getSnapshot().getElement().size() && !ext.getSnapshot().getElement().get(j).getPath().equals(ed.getPath()))
219        j++;
220      return j - i > 5;
221    } else {
222      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value);
223      return ext != null && ext.getSnapshot().getElement().size() > 5;
224    }
225  }
226
227  private int findEndOfElement(StructureDefinitionDifferentialComponent context, int cursor) {
228            int result = cursor;
229            String path = context.getElement().get(cursor).getPath()+".";
230            while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path))
231              result++;
232            return result;
233          }
234
235  private int findEndOfElement(StructureDefinitionSnapshotComponent context, int cursor) {
236            int result = cursor;
237            String path = context.getElement().get(cursor).getPath()+".";
238            while (result < context.getElement().size()- 1 && context.getElement().get(result+1).getPath().startsWith(path))
239              result++;
240            return result;
241          }
242
243  private String fixedPath(String contextPath, String pathSimple) {
244    if (contextPath == null)
245      return pathSimple;
246    return contextPath+"."+pathSimple.substring(pathSimple.indexOf(".")+1);
247  }
248
249  private void genCardinality(HierarchicalTableGenerator gen, ElementDefinition definition, Row row, boolean hasDef, UnusedTracker tracker, ElementDefinition fallback) {
250    IntegerType min = !hasDef ? new IntegerType() : definition.hasMinElement() ? definition.getMinElement() : new IntegerType();
251    StringType max = !hasDef ? new StringType() : definition.hasMaxElement() ? definition.getMaxElement() : new StringType();
252    if (min.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) {
253      ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER);
254      min = base.getMinElement().copy();
255      min.setUserData(DERIVATION_EQUALS, true);
256    }
257    if (max.isEmpty() && definition.getUserData(DERIVATION_POINTER) != null) {
258      ElementDefinition base = (ElementDefinition) definition.getUserData(DERIVATION_POINTER);
259      max = base.getMaxElement().copy();
260      max.setUserData(DERIVATION_EQUALS, true);
261    }
262    if (min.isEmpty() && fallback != null)
263      min = fallback.getMinElement();
264    if (max.isEmpty() && fallback != null)
265      max = fallback.getMaxElement();
266
267    if (!max.isEmpty())
268      tracker.used = !max.getValue().equals("0");
269
270    Cell cell = gen.new Cell(null, null, null, null, null);
271    row.getCells().add(cell);
272    if (!min.isEmpty() || !max.isEmpty()) {
273      cell.addPiece(checkForNoChange(min, gen.new Piece(null, !min.hasValue() ? "" : Integer.toString(min.getValue()), null)));
274      cell.addPiece(checkForNoChange(min, max, gen.new Piece(null, "..", null)));
275      cell.addPiece(checkForNoChange(min, gen.new Piece(null, !max.hasValue() ? "" : max.getValue(), null)));
276    }
277  }
278
279  private Cell genTypes(HierarchicalTableGenerator gen, Row r, ElementDefinition e, String profileBaseFileName, StructureDefinition profile, String corePath) {
280    Cell c = gen.new Cell();
281    r.getCells().add(c);
282    List<TypeRefComponent> types = e.getType();
283    if (!e.hasType()) {
284      if (e.hasNameReference()) {
285        ElementDefinition ed = getElementByName(profile.getSnapshot().getElement(), e.getNameReference());
286        if (ed == null)
287          c.getPieces().add(gen.new Piece(null, "Unknown reference to "+e.getNameReference(), null));
288        else
289          c.getPieces().add(gen.new Piece("#"+ed.getPath(), "See "+ed.getPath(), null));
290        return c;
291      } else {
292      ElementDefinition d = (ElementDefinition) e.getUserData(DERIVATION_POINTER);
293      if (d != null && d.hasType()) {
294        types = new ArrayList<ElementDefinition.TypeRefComponent>();
295        for (TypeRefComponent tr : d.getType()) {
296          TypeRefComponent tt = tr.copy();
297          tt.setUserData(DERIVATION_EQUALS, true);
298          types.add(tt);
299        }
300      } else
301        return c;
302    }
303    }
304
305    boolean first = true;
306    Element source = types.get(0); // either all types are the same, or we don't consider any of them the same
307
308    boolean allReference = ADD_REFERENCE_TO_TABLE && !types.isEmpty();
309    for (TypeRefComponent t : types) {
310      if (!(t.getCode().equals("Reference") && t.hasProfile()))
311        allReference = false;
312    }
313    if (allReference) {
314      c.getPieces().add(gen.new Piece(corePath+"references.html", "Reference", null));
315      c.getPieces().add(gen.new Piece(null, "(", null));
316    }
317    TypeRefComponent tl = null;
318    for (TypeRefComponent t : types) {
319      if (first)
320        first = false;
321      else if (allReference)
322        c.addPiece(checkForNoChange(tl, gen.new Piece(null," | ", null)));
323      else
324        c.addPiece(checkForNoChange(tl, gen.new Piece(null,", ", null)));
325      tl = t;
326      if (t.getCode().equals("Reference") || (t.getCode().equals("Resource") && t.hasProfile())) {
327        if (ADD_REFERENCE_TO_TABLE && !allReference) {
328          c.getPieces().add(gen.new Piece(corePath+"references.html", "Reference", null));
329          c.getPieces().add(gen.new Piece(null, "(", null));
330        }
331        if (t.hasProfile() && t.getProfile().get(0).getValue().startsWith("http://hl7.org/fhir/StructureDefinition/")) {
332          StructureDefinition sd = context.fetchResource(StructureDefinition.class, t.getProfile().get(0).getValue());
333          if (sd != null) {
334            String disp = sd.hasDisplay() ? sd.getDisplay() : sd.getName();
335            c.addPiece(checkForNoChange(t, gen.new Piece(corePath+sd.getUserString("path"), disp, null)));
336          } else {
337            String rn = t.getProfile().get(0).getValue().substring(40);
338            c.addPiece(checkForNoChange(t, gen.new Piece(corePath+pkp.getLinkFor(rn), rn, null)));
339          }
340        } else if (t.getProfile().size() == 0) {
341          c.addPiece(checkForNoChange(t, gen.new Piece(null, t.getCode(), null)));
342        } else if (t.getProfile().get(0).getValue().startsWith("#"))
343          c.addPiece(checkForNoChange(t, gen.new Piece(corePath+profileBaseFileName+"."+t.getProfile().get(0).getValue().substring(1).toLowerCase()+".html", t.getProfile().get(0).getValue(), null)));
344        else
345          c.addPiece(checkForNoChange(t, gen.new Piece(corePath+t.getProfile().get(0).getValue(), t.getProfile().get(0).getValue(), null)));
346        if (ADD_REFERENCE_TO_TABLE && !allReference) {
347          c.getPieces().add(gen.new Piece(null, ")", null));
348        }
349      } else if (t.hasProfile()) { // a profiled type
350        String ref;
351        ref = pkp.getLinkForProfile(profile, t.getProfile().get(0).getValue());
352        if (ref != null) {
353          String[] parts = ref.split("\\|");
354          c.addPiece(checkForNoChange(t, gen.new Piece(corePath+parts[0], parts[1], t.getCode())));
355        } else
356          c.addPiece(checkForNoChange(t, gen.new Piece(corePath+ref, t.getCode(), null)));
357      } else if (pkp.hasLinkFor(t.getCode())) {
358        c.addPiece(checkForNoChange(t, gen.new Piece(corePath+pkp.getLinkFor(t.getCode()), t.getCode(), null)));
359      } else
360        c.addPiece(checkForNoChange(t, gen.new Piece(null, t.getCode(), null)));
361    }
362    if (allReference) {
363      c.getPieces().add(gen.new Piece(null, ")", null));
364    }
365    return c;
366  }
367
368  private Cell generateDescription(HierarchicalTableGenerator gen, Row row, ElementDefinition definition, ElementDefinition fallback, boolean used, String baseURL, String url, StructureDefinition profile, String corePath) throws Exception {
369    Cell c = gen.new Cell();
370    row.getCells().add(c);
371
372    if (used) {
373      if (definition.getPath().endsWith("url") && definition.hasFixed()) {
374        c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "\""+buildJson(definition.getFixed())+"\"", null).addStyle("color: darkgreen")));
375      } else {
376        if (definition != null && definition.hasShort()) {
377          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
378          c.addPiece(checkForNoChange(definition.getShortElement(), gen.new Piece(null, definition.getShort(), null)));
379        } else if (fallback != null && fallback.hasShort()) {
380          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
381          c.addPiece(checkForNoChange(fallback.getShortElement(), gen.new Piece(null, fallback.getShort(), null)));
382        }
383        if (url != null) {
384          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
385          String fullUrl = url.startsWith("#") ? baseURL+url : url;
386          StructureDefinition ed = context.fetchResource(StructureDefinition.class, url);
387          String ref = ed == null ? null : (String) corePath+ed.getUserData("path");
388          c.getPieces().add(gen.new Piece(null, "URL: ", null).addStyle("font-weight:bold"));
389          c.getPieces().add(gen.new Piece(ref, fullUrl, null));
390        }
391
392        if (definition.hasSlicing()) {
393          if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
394          c.getPieces().add(gen.new Piece(null, "Slice: ", null).addStyle("font-weight:bold"));
395          c.getPieces().add(gen.new Piece(null, describeSlice(definition.getSlicing()), null));
396        }
397        if (definition != null) {
398          if (definition.hasBinding()) {
399            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
400            BindingResolution br = pkp.resolveBinding(definition.getBinding());
401            c.getPieces().add(checkForNoChange(definition.getBinding(), gen.new Piece(null, "Binding: ", null).addStyle("font-weight:bold")));
402            c.getPieces().add(checkForNoChange(definition.getBinding(), gen.new Piece(br.url == null ? null : Utilities.isAbsoluteUrl(br.url)? br.url : corePath+br.url, br.display, null)));
403            if (definition.getBinding().hasStrength()) {
404              c.getPieces().add(checkForNoChange(definition.getBinding(), gen.new Piece(null, " (", null)));
405              c.getPieces().add(checkForNoChange(definition.getBinding(), gen.new Piece(corePath+"terminologies.html#"+definition.getBinding().getStrength().toCode(), definition.getBinding().getStrength().toCode(), definition.getBinding().getStrength().getDefinition())));
406              c.getPieces().add(gen.new Piece(null, ")", null));
407            }
408          }
409          for (ElementDefinitionConstraintComponent inv : definition.getConstraint()) {
410            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
411            c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getKey()+": ", null).addStyle("font-weight:bold")));
412            c.getPieces().add(checkForNoChange(inv, gen.new Piece(null, inv.getHuman(), null)));
413          }
414          if (definition.hasFixed()) {
415            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
416            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, "Fixed Value: ", null).addStyle("font-weight:bold")));
417            c.getPieces().add(checkForNoChange(definition.getFixed(), gen.new Piece(null, buildJson(definition.getFixed()), null).addStyle("color: darkgreen")));
418          } else if (definition.hasPattern()) {
419            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
420            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, "Required Pattern: ", null).addStyle("font-weight:bold")));
421            c.getPieces().add(checkForNoChange(definition.getPattern(), gen.new Piece(null, buildJson(definition.getPattern()), null).addStyle("color: darkgreen")));
422          } else if (definition.hasExample()) {
423            if (!c.getPieces().isEmpty()) c.addPiece(gen.new Piece("br"));
424            c.getPieces().add(checkForNoChange(definition.getExample(), gen.new Piece(null, "Example: ", null).addStyle("font-weight:bold")));
425            c.getPieces().add(checkForNoChange(definition.getExample(), gen.new Piece(null, buildJson(definition.getExample()), null).addStyle("color: darkgreen")));
426          }
427        }
428      }
429    }
430    return c;
431  }
432
433  private void generateForChildren(SchematronWriter sch, String xpath, ElementDefinition ed, StructureDefinition structure, StructureDefinition base) throws IOException {
434    //    generateForChild(txt, structure, child);
435    List<ElementDefinition> children = getChildList(structure, ed);
436    String sliceName = null;
437    ElementDefinitionSlicingComponent slicing = null;
438    for (ElementDefinition child : children) {
439      String name = tail(child.getPath());
440      if (child.hasSlicing()) {
441        sliceName = name;
442        slicing = child.getSlicing();
443      } else if (!name.equals(sliceName))
444        slicing = null;
445
446      ElementDefinition based = getByPath(base, child.getPath());
447      boolean doMin = (child.getMin() > 0) && (based == null || (child.getMin() != based.getMin()));
448      boolean doMax =  !child.getMax().equals("*") && (based == null || (!child.getMax().equals(based.getMax())));
449      Slicer slicer = slicing == null ? new Slicer(true) : generateSlicer(child, slicing, structure);
450      if (slicer.check) {
451        if (doMin || doMax) {
452          Section s = sch.section(xpath);
453          Rule r = s.rule(xpath);
454          if (doMin)
455            r.assrt("count(f:"+name+slicer.criteria+") >= "+Integer.toString(child.getMin()), name+slicer.name+": minimum cardinality of '"+name+"' is "+Integer.toString(child.getMin()));
456          if (doMax)
457            r.assrt("count(f:"+name+slicer.criteria+") <= "+child.getMax(), name+slicer.name+": maximum cardinality of '"+name+"' is "+child.getMax());
458          }
459        }
460      }
461    for (ElementDefinitionConstraintComponent inv : ed.getConstraint()) {
462      if (inv.hasXpath()) {
463        Section s = sch.section(ed.getPath());
464        Rule r = s.rule(xpath);
465        r.assrt(inv.getXpath(), (inv.hasId() ? inv.getId()+": " : "")+inv.getHuman()+(inv.hasUserData(IS_DERIVED) ? " (inherited)" : ""));
466      }
467    }
468    for (ElementDefinition child : children) {
469      String name = tail(child.getPath());
470      generateForChildren(sch, xpath+"/f:"+name, child, structure, base);
471    }
472  }
473
474  public void generateSchematrons(OutputStream dest, StructureDefinition structure) throws IOException, DefinitionException {
475    if (!structure.hasConstrainedType())
476      throw new DefinitionException("not the right kind of structure to generate schematrons for ("+structure.getUrl()+")");
477    if (!structure.hasSnapshot())
478      throw new DefinitionException("needs a snapshot for ("+structure.getUrl()+")");
479
480        StructureDefinition base = context.fetchResource(StructureDefinition.class, structure.getBase());
481
482        SchematronWriter sch = new SchematronWriter(dest, SchematronType.PROFILE, base.getName());
483
484    ElementDefinition ed = structure.getSnapshot().getElement().get(0);
485    generateForChildren(sch, "f:"+ed.getPath(), ed, structure, base);
486    sch.dump();
487  }
488
489  private Slicer generateSlicer(ElementDefinition child, ElementDefinitionSlicingComponent slicing, StructureDefinition structure) {
490    // given a child in a structure, it's sliced. figure out the slicing xpath
491    if (child.getPath().endsWith(".extension")) {
492      ElementDefinition ued = getUrlFor(structure, child);
493      if ((ued == null || !ued.hasFixed()) && !(child.getType().get(0).hasProfile()))
494        return new Slicer(false);
495      else {
496      Slicer s = new Slicer(true);
497      String url = (ued == null || !ued.hasFixed()) ? child.getType().get(0).getProfile().get(0).asStringValue() : ((UriType) ued.getFixed()).asStringValue();
498      s.name = " with URL = '"+url+"'";
499      s.criteria = "[@url = '"+url+"']";
500      return s;
501      }
502    } else
503      return new Slicer(false);
504  }
505
506  /**
507   * Given a base (snapshot) profile structure, and a differential profile, generate a new snapshot profile
508   *
509   * @param base - the base structure on which the differential will be applied
510   * @param differential - the differential to apply to the base
511   * @param url - where the base has relative urls for profile references, these need to be converted to absolutes by prepending this URL
512   * @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
513   * @return
514   * @throws Exception
515   */
516  public void generateSnapshot(StructureDefinition base, StructureDefinition derived, String url, String profileName) throws Exception {
517    if (base == null)
518      throw new DefinitionException("no base profile provided");
519    if (derived == null)
520      throw new DefinitionException("no derived structure provided");
521
522    if (snapshotStack.contains(derived.getUrl()))
523      throw new DefinitionException("Circular snapshot references detected; cannot generate snapshot (stack = "+snapshotStack.toString()+")");
524    snapshotStack.add(derived.getUrl());
525
526//    System.out.println("Generate Snapshot for "+derived.getUrl());
527
528    derived.setSnapshot(new StructureDefinitionSnapshotComponent());
529
530    // so we have two lists - the base list, and the differential list
531    // the differential list is only allowed to include things that are in the base list, but
532    // is allowed to include them multiple times - thereby slicing them
533
534    // our approach is to walk through the base list, and see whether the differential
535    // says anything about them.
536    int baseCursor = 0;
537    int diffCursor = 0; // we need a diff cursor because we can only look ahead, in the bound scoped by longer paths
538
539    // we actually delegate the work to a subroutine so we can re-enter it with a different cursors
540    processPaths(derived.getSnapshot(), base.getSnapshot(), derived.getDifferential(), baseCursor, diffCursor, base.getSnapshot().getElement().size()-1, derived.getDifferential().getElement().size()-1, url, derived.getId(), null, false, base.getUrl(), null, false);
541  }
542
543  private ElementDefinition getByPath(StructureDefinition base, String path) {
544                for (ElementDefinition ed : base.getSnapshot().getElement()) {
545                        if (ed.getPath().equals(path))
546                                return ed;
547                        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)))
548                                return ed;
549    }
550          return null;
551  }
552
553  private List<ElementDefinition> getChildren(List<ElementDefinition> all, ElementDefinition element) {
554    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
555    int i = all.indexOf(element)+1;
556    while (i < all.size() && all.get(i).getPath().length() > element.getPath().length()) {
557      if ((all.get(i).getPath().substring(0, element.getPath().length()+1).equals(element.getPath()+".")) && !all.get(i).getPath().substring(element.getPath().length()+1).contains("."))
558        result.add(all.get(i));
559      i++;
560    }
561    return result;
562  }
563
564  private List<ElementDefinition> getDiffMatches(StructureDefinitionDifferentialComponent context, String path, int start, int end, String profileName) {
565    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
566    for (int i = start; i <= end; i++) {
567      String statedPath = context.getElement().get(i).getPath();
568      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.substring(path.length()).contains("."))) {
569        result.add(context.getElement().get(i));
570      } else if (result.isEmpty()) {
571//        System.out.println("ignoring "+statedPath+" in differential of "+profileName);
572      }
573    }
574    return result;
575  }
576
577  private ElementDefinition getElementByName(List<ElementDefinition> elements, String nameReference) {
578    for (ElementDefinition ed : elements)
579      if (ed.hasName() && ed.getName().equals(nameReference))
580        return ed;
581    return null;
582  }
583
584  public StructureDefinition getProfile(StructureDefinition source, String url) {
585        StructureDefinition profile;
586        String code;
587        if (url.startsWith("#")) {
588                profile = source;
589                code = url.substring(1);
590        } else {
591                String[] parts = url.split("\\#");
592                profile = context.fetchResource(StructureDefinition.class, parts[0]);
593      code = parts.length == 1 ? null : parts[1];
594        }
595        if (profile == null)
596                return null;
597        if (code == null)
598                return profile;
599        for (Resource r : profile.getContained()) {
600                if (r instanceof StructureDefinition && r.getId().equals(code))
601                        return (StructureDefinition) r;
602        }
603        return null;
604  }
605
606  private StructureDefinition getProfileForDataType(TypeRefComponent type)  {
607    StructureDefinition sd = null;
608    if (type.hasProfile())
609      sd = context.fetchResource(StructureDefinition.class, type.getProfile().get(0).asStringValue());
610    if (sd == null)
611      sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+type.getCode());
612    if (sd == null)
613      System.out.println("XX: failed to find profle for type: " + type.getCode()); // debug GJM
614    return sd;
615  }
616
617  private String getRowColor(ElementDefinition element) {
618    switch (element.getUserInt(UD_ERROR_STATUS)) {
619    case STATUS_OK: return null;
620    case STATUS_HINT: return ROW_COLOR_HINT;
621    case STATUS_WARNING: return ROW_COLOR_WARNING;
622    case STATUS_ERROR: return ROW_COLOR_ERROR;
623    case STATUS_FATAL: return ROW_COLOR_FATAL;
624    default: return null;
625    }
626  }
627
628  private List<ElementDefinition> getSiblings(List<ElementDefinition> list, ElementDefinition current) {
629    List<ElementDefinition> result = new ArrayList<ElementDefinition>();
630    String path = current.getPath();
631    int cursor = list.indexOf(current)+1;
632    while (cursor < list.size() && list.get(cursor).getPath().length() >= path.length()) {
633      if (pathMatches(list.get(cursor).getPath(), path))
634        result.add(list.get(cursor));
635      cursor++;
636    }
637    return result;
638  }
639
640  private ElementDefinition getUrlFor(StructureDefinition ed, ElementDefinition c) {
641    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
642    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
643      if (ed.getSnapshot().getElement().get(i).getPath().equals(c.getPath()+".url"))
644        return ed.getSnapshot().getElement().get(i);
645      i++;
646    }
647    return null;
648  }
649
650  private ElementDefinition getValueFor(StructureDefinition ed, ElementDefinition c) {
651    int i = ed.getSnapshot().getElement().indexOf(c) + 1;
652    while (i < ed.getSnapshot().getElement().size() && ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".")) {
653      if (ed.getSnapshot().getElement().get(i).getPath().startsWith(c.getPath()+".value"))
654        return ed.getSnapshot().getElement().get(i);
655      i++;
656    }
657    return null;
658  }
659
660  private boolean inExpansion(ValueSetExpansionContainsComponent cc, List<ValueSetExpansionContainsComponent> contains) {
661    for (ValueSetExpansionContainsComponent cc1 : contains) {
662      if (cc.getSystem().equals(cc1.getSystem()) && cc.getCode().equals(cc1.getCode()))
663        return true;
664      if (inExpansion(cc,  cc1.getContains()))
665        return true;
666    }
667    return false;
668  }
669
670  private boolean isAbstract(String code) {
671    return code.equals("Element") || code.equals("BackboneElement") || code.equals("Resource") || code.equals("DomainResource");
672  }
673
674  private boolean isDataType(List<TypeRefComponent> types) {
675    if (types.isEmpty())
676      return false;
677    for (TypeRefComponent type : types) {
678      String t = type.getCode();
679      if (!isDataType(t) && !t.equals("Reference") && !t.equals("Narrative") && !t.equals("Extension") && !t.equals("ElementDefinition") && !isPrimitive(t))
680        return false;
681    }
682    return true;
683  }
684
685  private boolean isDataType(String value) {
686    return Utilities.existsInList(value, "Identifier", "HumanName", "Address", "ContactPoint", "Timing", "SimpleQuantity", "Quantity", "Attachment", "Range",
687          "Period", "Ratio", "CodeableConcept", "Coding", "SampledData", "Age", "Distance", "Duration", "Count", "Money");
688  }
689
690  private boolean isExtension(ElementDefinition currentBase) {
691    return currentBase.getPath().endsWith(".extension") || currentBase.getPath().endsWith(".modifierExtension");
692  }
693
694  private boolean isLargerMax(String derived, String base) {
695    if ("*".equals(base))
696      return false;
697    if ("*".equals(derived))
698      return true;
699    return Integer.parseInt(derived) > Integer.parseInt(base);
700  }
701
702  private boolean isReference(String value) {
703    return value.equals("Reference");
704  }
705
706  private boolean isSlicedToOneOnly(ElementDefinition e) {
707    return (e.hasSlicing() && e.hasMaxElement() && e.getMax().equals("1"));
708  }
709
710  private boolean isSubset(ValueSet expBase, ValueSet expDerived) {
711    return codesInExpansion(expDerived.getExpansion().getContains(), expBase.getExpansion());
712  }
713
714  private ExtensionContext locateExtension(Class<StructureDefinition> class1, String value)  {
715    if (value.contains("#")) {
716      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value.substring(0, value.indexOf("#")));
717      if (ext == null)
718        return null;
719      String tail = value.substring(value.indexOf("#")+1);
720      ElementDefinition ed = null;
721      for (ElementDefinition ted : ext.getSnapshot().getElement()) {
722        if (tail.equals(ted.getName())) {
723          ed = ted;
724          return new ExtensionContext(ext, ed);
725        }
726      }
727      return null;
728    } else {
729      StructureDefinition ext = context.fetchResource(StructureDefinition.class, value);
730      if (ext == null)
731        return null;
732      else
733        return new ExtensionContext(ext, ext.getSnapshot().getElement().get(0));
734    }
735  }
736
737  private ElementDefinitionSlicingComponent makeExtensionSlicing() {
738        ElementDefinitionSlicingComponent slice = new ElementDefinitionSlicingComponent();
739    slice.addDiscriminator("url");
740    slice.setOrdered(false);
741    slice.setRules(SlicingRules.OPEN);
742    return slice;
743  }
744
745  private String makePathLink(ElementDefinition element) {
746    if (!element.hasName())
747      return element.getPath();
748    if (!element.getPath().contains("."))
749      return element.getName();
750    return element.getPath().substring(0, element.getPath().lastIndexOf("."))+"."+element.getName();
751
752  }
753
754  private void markDerived(ElementDefinition outcome) {
755    for (ElementDefinitionConstraintComponent inv : outcome.getConstraint())
756      inv.setUserData(IS_DERIVED, true);
757  }
758
759  private boolean onlyInformationIsMapping(List<ElementDefinition> list, ElementDefinition e) {
760    return (!e.hasName() && !e.hasSlicing() && (onlyInformationIsMapping(e))) &&
761        getChildren(list, e).isEmpty();
762  }
763
764  private boolean onlyInformationIsMapping(ElementDefinition d) {
765    return !d.hasShort() && !d.hasDefinition() &&
766        !d.hasRequirements() && !d.getAlias().isEmpty() && !d.hasMinElement() &&
767        !d.hasMax() && !d.getType().isEmpty() && !d.hasNameReference() &&
768        !d.hasExample() && !d.hasFixed() && !d.hasMaxLengthElement() &&
769        !d.getCondition().isEmpty() && !d.getConstraint().isEmpty() && !d.hasMustSupportElement() &&
770        !d.hasBinding();
771  }
772
773  private boolean orderMatches(BooleanType diff, BooleanType base) {
774    return (diff == null) || (base == null) || (diff.getValue() == base.getValue());
775  }
776
777  private ElementDefinition overWriteWithCurrent(ElementDefinition profile, ElementDefinition usage) {
778    ElementDefinition res = profile.copy();
779    if (usage.hasName())
780      res.setName(usage.getName());
781    if (usage.hasLabel())
782      res.setLabel(usage.getLabel());
783    for (Coding c : usage.getCode())
784      res.addCode(c);
785
786    if (usage.hasDefinition())
787      res.setDefinition(usage.getDefinition());
788    if (usage.hasShort())
789      res.setShort(usage.getShort());
790    if (usage.hasComments())
791      res.setComments(usage.getComments());
792    if (usage.hasRequirements())
793      res.setRequirements(usage.getRequirements());
794    for (StringType c : usage.getAlias())
795      res.addAlias(c.getValue());
796    if (usage.hasMin())
797      res.setMin(usage.getMin());
798    if (usage.hasMax())
799      res.setMax(usage.getMax());
800
801    if (usage.hasFixed())
802      res.setFixed(usage.getFixed());
803    if (usage.hasPattern())
804      res.setPattern(usage.getPattern());
805    if (usage.hasExample())
806      res.setExample(usage.getExample());
807    if (usage.hasMinValue())
808      res.setMinValue(usage.getMinValue());
809    if (usage.hasMaxValue())
810      res.setMaxValue(usage.getMaxValue());
811    if (usage.hasMaxLength())
812      res.setMaxLength(usage.getMaxLength());
813    if (usage.hasMustSupport())
814      res.setMustSupport(usage.getMustSupport());
815    if (usage.hasBinding())
816      res.setBinding(usage.getBinding().copy());
817    for (ElementDefinitionConstraintComponent c : usage.getConstraint())
818      res.addConstraint(c);
819
820    return res;
821  }
822
823  private boolean pathMatches(String p1, String p2) {
824    return p1.equals(p2) || (p2.endsWith("[x]") && p1.startsWith(p2.substring(0, p2.length()-3)) && !p1.substring(p2.length()-3).contains("."));
825  }
826
827  private boolean pathStartsWith(String p1, String p2) {
828    return p1.startsWith(p2);
829  }
830
831  private String pathTail(List<ElementDefinition> diffMatches, int i) {
832
833    ElementDefinition d = diffMatches.get(i);
834    String s = d.getPath().contains(".") ? d.getPath().substring(d.getPath().lastIndexOf(".")+1) : d.getPath();
835    return "."+s + (d.hasType() && d.getType().get(0).hasProfile() ? "["+d.getType().get(0).getProfile().get(0).asStringValue()+"]" : "");
836  }
837
838  private int processElementsIntoTree(ElementDefinitionHolder edh, int i, List<ElementDefinition> list) {
839    String path = edh.getSelf().getPath();
840    final String prefix = path + ".";
841    while (i < list.size() && list.get(i).getPath().startsWith(prefix)) {
842      ElementDefinitionHolder child = new ElementDefinitionHolder(list.get(i));
843      edh.getChildren().add(child);
844      i = processElementsIntoTree(child, i+1, list);
845    }
846    return i;
847  }
848
849  /**
850   * @param trimDifferential
851   * @throws Exception
852   */
853  private void processPaths(StructureDefinitionSnapshotComponent result, StructureDefinitionSnapshotComponent base, StructureDefinitionDifferentialComponent differential, int baseCursor, int diffCursor, int baseLimit,
854      int diffLimit, String url, String profileName, String contextPath, boolean trimDifferential, String contextName, String resultPathBase, boolean slicingDone) throws Exception {
855
856    // just repeat processing entries until we run out of our allowed scope (1st entry, the allowed scope is all the entries)
857    while (baseCursor <= baseLimit) {
858      // get the current focus of the base, and decide what to do
859      ElementDefinition currentBase = base.getElement().get(baseCursor);
860      String cpath = fixedPath(contextPath, currentBase.getPath());
861      List<ElementDefinition> diffMatches = getDiffMatches(differential, cpath, diffCursor, diffLimit, profileName); // get a list of matching elements in scope
862
863      // in the simple case, source is not sliced.
864      if (!currentBase.hasSlicing()) {
865        if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item
866          // so we just copy it in
867          ElementDefinition outcome = updateURLs(url, currentBase.copy());
868          outcome.setPath(fixedPath(contextPath, outcome.getPath()));
869          updateFromBase(outcome, currentBase);
870          markDerived(outcome);
871          if (resultPathBase == null)
872            resultPathBase = outcome.getPath();
873          else if (!outcome.getPath().startsWith(resultPathBase))
874            throw new DefinitionException("Adding wrong path");
875          result.getElement().add(outcome);
876          baseCursor++;
877        } else if (diffMatches.size() == 1 && (!diffMatches.get(0).hasSlicing() || slicingDone)) {// one matching element in the differential
878          ElementDefinition template = null;
879          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")) {
880            String p = diffMatches.get(0).getType().get(0).getProfile().get(0).asStringValue();
881            StructureDefinition sd = context.fetchResource(StructureDefinition.class, p);
882            if (sd != null) {
883              if (!sd.hasSnapshot()) {
884                StructureDefinition sdb = context.fetchResource(StructureDefinition.class, sd.getBase());
885                if (sdb == null)
886                  throw new DefinitionException("no base for "+sd.getBase());
887                generateSnapshot(sdb, sd, sd.getUrl(), sd.getName());
888              }
889              template = sd.getSnapshot().getElement().get(0).copy().setPath(currentBase.getPath());
890              // temporary work around
891              if (!diffMatches.get(0).getType().get(0).getCode().equals("Extension")) {
892                template.setMin(currentBase.getMin());
893                template.setMax(currentBase.getMax());
894              }
895            }
896          }
897          if (template == null)
898            template = currentBase.copy();
899          else
900            // some of what's in currentBase overrides template
901            template = overWriteWithCurrent(template, currentBase);
902          ElementDefinition outcome = updateURLs(url, template);
903          outcome.setPath(fixedPath(contextPath, outcome.getPath()));
904          updateFromBase(outcome, currentBase);
905          if (diffMatches.get(0).hasName())
906          outcome.setName(diffMatches.get(0).getName());
907          outcome.setSlicing(null);
908          updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url);
909          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
910            outcome.setPath(outcome.getPath().substring(0, outcome.getPath().length()-3)+Utilities.capitalize(outcome.getType().get(0).getCode()));
911          if (resultPathBase == null)
912            resultPathBase = outcome.getPath();
913          else if (!outcome.getPath().startsWith(resultPathBase))
914            throw new DefinitionException("Adding wrong path");
915          result.getElement().add(outcome);
916          baseCursor++;
917          diffCursor = differential.getElement().indexOf(diffMatches.get(0))+1;
918          if (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
919            if (pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+".")) {
920              if (outcome.getType().size() > 1)
921                throw new DefinitionException(diffMatches.get(0).getPath()+" has children ("+differential.getElement().get(diffCursor).getPath()+") and multiple types ("+typeCode(outcome.getType())+") in profile "+profileName);
922              StructureDefinition dt = getProfileForDataType(outcome.getType().get(0));
923              if (dt == null)
924                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");
925              contextName = dt.getUrl();
926              int start = diffCursor;
927              while (differential.getElement().size() > diffCursor && pathStartsWith(differential.getElement().get(diffCursor).getPath(), diffMatches.get(0).getPath()+"."))
928                diffCursor++;
929              processPaths(result, dt.getSnapshot(), differential, 1 /* starting again on the data type, but skip the root */, start-1, dt.getSnapshot().getElement().size()-1,
930                  diffCursor - 1, url, profileName+pathTail(diffMatches, 0), diffMatches.get(0).getPath(), trimDifferential, contextName, resultPathBase, false);
931            }
932          }
933        } else {
934          // ok, the differential slices the item. Let's check our pre-conditions to ensure that this is correct
935          if (!unbounded(currentBase) && !isSlicedToOneOnly(diffMatches.get(0)))
936            // you can only slice an element that doesn't repeat if the sum total of your slices is limited to 1
937            // (but you might do that in order to split up constraints by type)
938            throw new DefinitionException("Attempt to a slice an element that does not repeat: "+currentBase.getPath()+"/"+currentBase.getName()+" from "+contextName);
939          if (!diffMatches.get(0).hasSlicing() && !isExtension(currentBase)) // well, the diff has set up a slice, but hasn't defined it. this is an error
940            throw new DefinitionException("differential does not have a slice: "+currentBase.getPath());
941
942          // well, if it passed those preconditions then we slice the dest.
943          // we're just going to accept the differential slicing at face value
944          ElementDefinition outcome = updateURLs(url, currentBase.copy());
945          outcome.setPath(fixedPath(contextPath, outcome.getPath()));
946          updateFromBase(outcome, currentBase);
947
948          if (!diffMatches.get(0).hasSlicing())
949            outcome.setSlicing(makeExtensionSlicing());
950          else
951            outcome.setSlicing(diffMatches.get(0).getSlicing().copy());
952          if (!outcome.getPath().startsWith(resultPathBase))
953            throw new DefinitionException("Adding wrong path");
954          result.getElement().add(outcome);
955
956          // 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.
957          int start = 0;
958          if (!diffMatches.get(0).hasName()) {
959            updateFromDefinition(outcome, diffMatches.get(0), profileName, trimDifferential, url);
960            if (!outcome.hasType()) {
961              throw new DefinitionException("not done yet");
962            }
963            start = 1;
964          } else
965            checkExtensionDoco(outcome);
966
967          // now, for each entry in the diff matches, we're going to process the base item
968          // our processing scope for base is all the children of the current path
969          int nbl = findEndOfElement(base, baseCursor);
970          int ndc = diffCursor;
971          int ndl = diffCursor;
972          for (int i = start; i < diffMatches.size(); i++) {
973            // our processing scope for the differential is the item in the list, and all the items before the next one in the list
974            ndc = differential.getElement().indexOf(diffMatches.get(i));
975            ndl = findEndOfElement(differential, ndc);
976            // now we process the base scope repeatedly for each instance of the item in the differential list
977            processPaths(result, base, differential, baseCursor, ndc, nbl, ndl, url, profileName+pathTail(diffMatches, i), contextPath, trimDifferential, contextName, resultPathBase, true);
978          }
979          // ok, done with that - next in the base list
980          baseCursor = nbl+1;
981          diffCursor = ndl+1;
982        }
983      } else {
984        // the item is already sliced in the base profile.
985        // here's the rules
986        //  1. irrespective of whether the slicing is ordered or not, the definition order must be maintained
987        //  2. slice element names have to match.
988        //  3. new slices must be introduced at the end
989        // corallory: you can't re-slice existing slices. is that ok?
990
991        // we're going to need this:
992        String path = currentBase.getPath();
993        ElementDefinition original = currentBase;
994
995        if (diffMatches.isEmpty()) { // the differential doesn't say anything about this item
996          // copy across the currentbase, and all of it's children and siblings
997          while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path)) {
998            ElementDefinition outcome = updateURLs(url, base.getElement().get(baseCursor).copy());
999            if (!outcome.getPath().startsWith(resultPathBase))
1000              throw new DefinitionException("Adding wrong path");
1001            result.getElement().add(outcome); // so we just copy it in
1002            baseCursor++;
1003          }
1004        } else {
1005          // first - check that the slicing is ok
1006          boolean closed = currentBase.getSlicing().getRules() == SlicingRules.CLOSED;
1007          int diffpos = 0;
1008          boolean isExtension = cpath.endsWith(".extension") || cpath.endsWith(".modifierExtension");
1009          if (diffMatches.get(0).hasSlicing()) { // it might be null if the differential doesn't want to say anything about slicing
1010            if (!isExtension)
1011            diffpos++; // if there's a slice on the first, we'll ignore any content it has
1012            ElementDefinitionSlicingComponent dSlice = diffMatches.get(0).getSlicing();
1013            ElementDefinitionSlicingComponent bSlice = currentBase.getSlicing();
1014            if (!orderMatches(dSlice.getOrderedElement(), bSlice.getOrderedElement()))
1015              throw new DefinitionException("Slicing rules on differential ("+summariseSlicing(dSlice)+") do not match those on base ("+summariseSlicing(bSlice)+") - order @ "+path+" ("+contextName+")");
1016            if (!discriiminatorMatches(dSlice.getDiscriminator(), bSlice.getDiscriminator()))
1017             throw new DefinitionException("Slicing rules on differential ("+summariseSlicing(dSlice)+") do not match those on base ("+summariseSlicing(bSlice)+") - disciminator @ "+path+" ("+contextName+")");
1018            if (!ruleMatches(dSlice.getRules(), bSlice.getRules()))
1019             throw new DefinitionException("Slicing rules on differential ("+summariseSlicing(dSlice)+") do not match those on base ("+summariseSlicing(bSlice)+") - rule @ "+path+" ("+contextName+")");
1020          }
1021          ElementDefinition outcome = updateURLs(url, currentBase.copy());
1022          outcome.setPath(fixedPath(contextPath, outcome.getPath()));
1023          updateFromBase(outcome, currentBase);
1024          if (diffMatches.get(0).hasSlicing() && !isExtension) {
1025            updateFromSlicing(outcome.getSlicing(), diffMatches.get(0).getSlicing());
1026            updateFromDefinition(outcome, diffMatches.get(0), profileName, closed, url); // if there's no slice, we don't want to update the unsliced description
1027          }
1028          if (diffMatches.get(0).hasSlicing() && !diffMatches.get(0).hasName())
1029            diffpos++;
1030
1031          result.getElement().add(outcome);
1032
1033          // now, we have two lists, base and diff. we're going to work through base, looking for matches in diff.
1034          List<ElementDefinition> baseMatches = getSiblings(base.getElement(), currentBase);
1035          for (ElementDefinition baseItem : baseMatches) {
1036            baseCursor = base.getElement().indexOf(baseItem);
1037            outcome = updateURLs(url, baseItem.copy());
1038            updateFromBase(outcome, currentBase);
1039            outcome.setPath(fixedPath(contextPath, outcome.getPath()));
1040            outcome.setSlicing(null);
1041            if (!outcome.getPath().startsWith(resultPathBase))
1042              throw new DefinitionException("Adding wrong path");
1043            if (diffpos < diffMatches.size() && diffMatches.get(diffpos).getName().equals(outcome.getName())) {
1044              // if there's a diff, we update the outcome with diff
1045              // no? updateFromDefinition(outcome, diffMatches.get(diffpos), profileName, closed, url);
1046              //then process any children
1047              int nbl = findEndOfElement(base, baseCursor);
1048              int ndc = differential.getElement().indexOf(diffMatches.get(diffpos));
1049              int ndl = findEndOfElement(differential, ndc);
1050              // now we process the base scope repeatedly for each instance of the item in the differential list
1051              processPaths(result, base, differential, baseCursor, ndc, nbl, ndl, url, profileName+pathTail(diffMatches, diffpos), contextPath, closed, contextName, resultPathBase, true);
1052              // ok, done with that - now set the cursors for if this is the end
1053              baseCursor = nbl+1;
1054              diffCursor = ndl+1;
1055              diffpos++;
1056            } else {
1057              result.getElement().add(outcome);
1058              baseCursor++;
1059              // just copy any children on the base
1060              while (baseCursor < base.getElement().size() && base.getElement().get(baseCursor).getPath().startsWith(path) && !base.getElement().get(baseCursor).getPath().equals(path)) {
1061                outcome = updateURLs(url, currentBase.copy());
1062                outcome.setPath(fixedPath(contextPath, outcome.getPath()));
1063                if (!outcome.getPath().startsWith(resultPathBase))
1064                  throw new DefinitionException("Adding wrong path");
1065                result.getElement().add(outcome);
1066                baseCursor++;
1067              }
1068            }
1069          }
1070          // finally, we process any remaining entries in diff, which are new (and which are only allowed if the base wasn't closed
1071          if (closed && diffpos < diffMatches.size())
1072            throw new DefinitionException("The base snapshot marks a slicing as closed, but the differential tries to extend it in "+profileName+" at "+path+" ("+cpath+")");
1073          while (diffpos < diffMatches.size()) {
1074            ElementDefinition diffItem = diffMatches.get(diffpos);
1075            for (ElementDefinition baseItem : baseMatches)
1076              if (baseItem.getName().equals(diffItem.getName()))
1077                throw new DefinitionException("Named items are out of order in the slice");
1078            outcome = updateURLs(url, original.copy());
1079            outcome.setPath(fixedPath(contextPath, outcome.getPath()));
1080            updateFromBase(outcome, currentBase);
1081            outcome.setSlicing(null);
1082            if (!outcome.getPath().startsWith(resultPathBase))
1083              throw new DefinitionException("Adding wrong path");
1084            result.getElement().add(outcome);
1085            updateFromDefinition(outcome, diffItem, profileName, trimDifferential, url);
1086            diffpos++;
1087          }
1088        }
1089      }
1090    }
1091  }
1092
1093  private boolean ruleMatches(SlicingRules diff, SlicingRules base) {
1094    return (diff == null) || (base == null) || (diff == base) || (diff == SlicingRules.OPEN) ||
1095        ((diff == SlicingRules.OPENATEND && base == SlicingRules.CLOSED));
1096  }
1097
1098  public void sortDifferential(StructureDefinition base, StructureDefinition diff, String name, List<String> errors)  {
1099
1100    final List<ElementDefinition> diffList = diff.getDifferential().getElement();
1101    // first, we move the differential elements into a tree
1102    ElementDefinitionHolder edh = new ElementDefinitionHolder(diffList.get(0));
1103
1104    boolean hasSlicing = false;
1105    List<String> paths = new ArrayList<String>(); // in a differential, slicing may not be stated explicitly
1106    for(ElementDefinition elt : diffList) {
1107      if (elt.hasSlicing() || paths.contains(elt.getPath())) {
1108        hasSlicing = true;
1109        break;
1110      }
1111      paths.add(elt.getPath());
1112    }
1113    if(!hasSlicing) {
1114      // if Differential does not have slicing then safe to pre-sort the list
1115      // so elements and subcomponents are together
1116      Collections.sort(diffList, new ElementNameCompare());
1117    }
1118
1119    int i = 1;
1120    processElementsIntoTree(edh, i, diff.getDifferential().getElement());
1121
1122    // now, we sort the siblings throughout the tree
1123    ElementDefinitionComparer cmp = new ElementDefinitionComparer(true, base.getSnapshot().getElement(), "", 0, name);
1124    sortElements(edh, cmp, errors);
1125
1126    // now, we serialise them back to a list
1127    diffList.clear();
1128    writeElements(edh, diffList);
1129  }
1130
1131  private void sortElements(ElementDefinitionHolder edh, ElementDefinitionComparer cmp, List<String> errors) {
1132    if (edh.getChildren().size() == 1)
1133      // 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
1134      edh.getChildren().get(0).baseIndex = cmp.find(edh.getChildren().get(0).getSelf().getPath());
1135    else
1136      Collections.sort(edh.getChildren(), cmp);
1137    cmp.checkForErrors(errors);
1138
1139    for (ElementDefinitionHolder child : edh.getChildren()) {
1140      if (child.getChildren().size() > 0) {
1141        // what we have to check for here is running off the base profile into a data type profile
1142        ElementDefinition ed = cmp.snapshot.get(child.getBaseIndex());
1143        ElementDefinitionComparer ccmp;
1144        if (ed.getType().isEmpty() || isAbstract(ed.getType().get(0).getCode()) || ed.getType().get(0).getCode().equals(ed.getPath())) {
1145          ccmp = new ElementDefinitionComparer(true, cmp.snapshot, cmp.base, cmp.prefixLength, cmp.name);
1146        } else if (ed.getType().get(0).getCode().equals("Extension") && child.getSelf().getType().size() == 1 && child.getSelf().getType().get(0).hasProfile()) {
1147          ccmp = new ElementDefinitionComparer(true, context.fetchResource(StructureDefinition.class, child.getSelf().getType().get(0).getProfile().get(0).getValue()).getSnapshot().getElement(), ed.getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name);
1148        } else if (ed.getType().size() == 1 && !ed.getType().get(0).getCode().equals("*")) {
1149          ccmp = new ElementDefinitionComparer(false, context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+ed.getType().get(0).getCode()).getSnapshot().getElement(), ed.getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name);
1150        } else if (child.getSelf().getType().size() == 1) {
1151          ccmp = new ElementDefinitionComparer(false, context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+child.getSelf().getType().get(0).getCode()).getSnapshot().getElement(), child.getSelf().getType().get(0).getCode(), child.getSelf().getPath().length(), cmp.name);
1152        } else if (ed.getPath().endsWith("[x]") && !child.getSelf().getPath().endsWith("[x]")) {
1153          String p = child.getSelf().getPath().substring(ed.getPath().length()-3);
1154          StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+p);
1155          if (sd == null)
1156            throw new Error("Unable to find profile "+p);
1157          ccmp = new ElementDefinitionComparer(false, sd.getSnapshot().getElement(), p, child.getSelf().getPath().length(), cmp.name);
1158        } else {
1159          throw new Error("Not handled yet (sortElements: "+ed.getPath()+":"+typeCode(ed.getType())+")");
1160        }
1161        sortElements(child, ccmp, errors);
1162      }
1163    }
1164  }
1165
1166  private boolean standardExtensionSlicing(ElementDefinition element) {
1167    String t = tail(element.getPath());
1168    return (t.equals("extension") || t.equals("modifierExtension"))
1169          && element.getSlicing().getRules() != SlicingRules.CLOSED && element.getSlicing().getDiscriminator().size() == 1 && element.getSlicing().getDiscriminator().get(0).getValue().equals("url");
1170  }
1171
1172  private String summariseSlicing(ElementDefinitionSlicingComponent slice) {
1173    StringBuilder b = new StringBuilder();
1174    boolean first = true;
1175    for (StringType d : slice.getDiscriminator()) {
1176      if (first)
1177        first = false;
1178      else
1179        b.append(", ");
1180      b.append(d);
1181    }
1182    b.append("(");
1183    if (slice.hasOrdered())
1184      b.append(slice.getOrderedElement().asStringValue());
1185    b.append("/");
1186    if (slice.hasRules())
1187      b.append(slice.getRules().toCode());
1188    b.append(")");
1189    if (slice.hasDescription()) {
1190      b.append(" \"");
1191      b.append(slice.getDescription());
1192      b.append("\"");
1193    }
1194    return b.toString();
1195  }
1196
1197  private String tail(String path) {
1198    if (path.contains("."))
1199      return path.substring(path.lastIndexOf('.')+1);
1200    else
1201      return path;
1202  }
1203
1204  private boolean unbounded(ElementDefinition definition) {
1205    StringType max = definition.getMaxElement();
1206    if (max == null)
1207      return false; // this is not valid
1208    if (max.getValue().equals("1"))
1209      return false;
1210    if (max.getValue().equals("0"))
1211      return false;
1212    return true;
1213  }
1214
1215  private void updateFromBase(ElementDefinition derived, ElementDefinition base) {
1216    if (base.hasBase()) {
1217      derived.getBase().setPath(base.getBase().getPath());
1218      derived.getBase().setMin(base.getBase().getMin());
1219      derived.getBase().setMax(base.getBase().getMax());
1220    } else {
1221      derived.getBase().setPath(base.getPath());
1222      derived.getBase().setMin(base.getMin());
1223      derived.getBase().setMax(base.getMax());
1224    }
1225  }
1226
1227  private void updateFromDefinition(ElementDefinition dest, ElementDefinition source, String pn, boolean trimDifferential, String purl) throws Exception {
1228    // we start with a clone of the base profile ('dest') and we copy from the profile ('source')
1229    // over the top for anything the source has
1230    ElementDefinition base = dest;
1231    ElementDefinition derived = source;
1232    derived.setUserData(DERIVATION_POINTER, base);
1233
1234    if (derived != null) {
1235      boolean isExtension = checkExtensionDoco(base);
1236
1237      if (derived.hasShortElement()) {
1238        if (!Base.compareDeep(derived.getShortElement(), base.getShortElement(), false))
1239          base.setShortElement(derived.getShortElement().copy());
1240        else if (trimDifferential)
1241          derived.setShortElement(null);
1242        else if (derived.hasShortElement())
1243          derived.getShortElement().setUserData(DERIVATION_EQUALS, true);
1244      }
1245
1246      if (derived.hasDefinitionElement()) {
1247        if (derived.getDefinition().startsWith("..."))
1248          base.setDefinition(base.getDefinition()+"\r\n"+derived.getDefinition().substring(3));
1249        else if (!Base.compareDeep(derived.getDefinitionElement(), base.getDefinitionElement(), false))
1250          base.setDefinitionElement(derived.getDefinitionElement().copy());
1251        else if (trimDifferential)
1252          derived.setDefinitionElement(null);
1253        else if (derived.hasDefinitionElement())
1254          derived.getDefinitionElement().setUserData(DERIVATION_EQUALS, true);
1255      }
1256
1257      if (derived.hasCommentsElement()) {
1258        if (derived.getComments().startsWith("..."))
1259          base.setComments(base.getComments()+"\r\n"+derived.getComments().substring(3));
1260        else if (!Base.compareDeep(derived.getCommentsElement(), base.getCommentsElement(), false))
1261          base.setCommentsElement(derived.getCommentsElement().copy());
1262        else if (trimDifferential)
1263          base.setCommentsElement(derived.getCommentsElement().copy());
1264        else if (derived.hasCommentsElement())
1265          derived.getCommentsElement().setUserData(DERIVATION_EQUALS, true);
1266      }
1267
1268      if (derived.hasLabelElement()) {
1269        if (derived.getLabel().startsWith("..."))
1270          base.setLabel(base.getLabel()+"\r\n"+derived.getLabel().substring(3));
1271        else if (!Base.compareDeep(derived.getLabelElement(), base.getLabelElement(), false))
1272          base.setLabelElement(derived.getLabelElement().copy());
1273        else if (trimDifferential)
1274          base.setLabelElement(derived.getLabelElement().copy());
1275        else if (derived.hasLabelElement())
1276          derived.getLabelElement().setUserData(DERIVATION_EQUALS, true);
1277      }
1278
1279      if (derived.hasRequirementsElement()) {
1280        if (derived.getRequirements().startsWith("..."))
1281          base.setRequirements(base.getRequirements()+"\r\n"+derived.getRequirements().substring(3));
1282        else if (!Base.compareDeep(derived.getRequirementsElement(), base.getRequirementsElement(), false))
1283          base.setRequirementsElement(derived.getRequirementsElement().copy());
1284        else if (trimDifferential)
1285          base.setRequirementsElement(derived.getRequirementsElement().copy());
1286        else if (derived.hasRequirementsElement())
1287          derived.getRequirementsElement().setUserData(DERIVATION_EQUALS, true);
1288      }
1289      // sdf-9
1290      if (derived.hasRequirements() && !base.getPath().contains("."))
1291        derived.setRequirements(null);
1292      if (base.hasRequirements() && !base.getPath().contains("."))
1293        base.setRequirements(null);
1294
1295      if (derived.hasAlias()) {
1296        if (!Base.compareDeep(derived.getAlias(), base.getAlias(), false))
1297          for (StringType s : derived.getAlias()) {
1298            if (!base.hasAlias(s.getValue()))
1299              base.getAlias().add(s.copy());
1300          }
1301        else if (trimDifferential)
1302          derived.getAlias().clear();
1303        else
1304          for (StringType t : derived.getAlias())
1305            t.setUserData(DERIVATION_EQUALS, true);
1306      }
1307
1308      if (derived.hasMinElement()) {
1309        if (!Base.compareDeep(derived.getMinElement(), base.getMinElement(), false)) {
1310          if (derived.getMin() < base.getMin())
1311            messages.add(new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Derived min  ("+Integer.toString(derived.getMin())+") cannot be less than base min ("+Integer.toString(base.getMin())+")", IssueSeverity.ERROR));
1312          base.setMinElement(derived.getMinElement().copy());
1313        } else if (trimDifferential)
1314          derived.setMinElement(null);
1315        else
1316          derived.getMinElement().setUserData(DERIVATION_EQUALS, true);
1317      }
1318
1319      if (derived.hasMaxElement()) {
1320        if (!Base.compareDeep(derived.getMaxElement(), base.getMaxElement(), false)) {
1321          if (isLargerMax(derived.getMax(), base.getMax()))
1322            messages.add(new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Derived max ("+derived.getMax()+") cannot be greater than base max ("+base.getMax()+")", IssueSeverity.ERROR));
1323          base.setMaxElement(derived.getMaxElement().copy());
1324        } else if (trimDifferential)
1325          derived.setMaxElement(null);
1326        else
1327          derived.getMaxElement().setUserData(DERIVATION_EQUALS, true);
1328      }
1329
1330      if (derived.hasFixed()) {
1331        if (!Base.compareDeep(derived.getFixed(), base.getFixed(), true)) {
1332          base.setFixed(derived.getFixed().copy());
1333        } else if (trimDifferential)
1334          derived.setFixed(null);
1335        else
1336          derived.getFixed().setUserData(DERIVATION_EQUALS, true);
1337      }
1338
1339      if (derived.hasPattern()) {
1340        if (!Base.compareDeep(derived.getPattern(), base.getPattern(), false)) {
1341          base.setPattern(derived.getPattern().copy());
1342        } else
1343          if (trimDifferential)
1344            derived.setPattern(null);
1345          else
1346            derived.getPattern().setUserData(DERIVATION_EQUALS, true);
1347      }
1348
1349      if (derived.hasExample()) {
1350        if (!Base.compareDeep(derived.getExample(), base.getExample(), false))
1351          base.setExample(derived.getExample().copy());
1352        else if (trimDifferential)
1353          derived.setExample(null);
1354        else
1355          derived.getExample().setUserData(DERIVATION_EQUALS, true);
1356      }
1357
1358      if (derived.hasMaxLengthElement()) {
1359        if (!Base.compareDeep(derived.getMaxLengthElement(), base.getMaxLengthElement(), false))
1360          base.setMaxLengthElement(derived.getMaxLengthElement().copy());
1361        else if (trimDifferential)
1362          derived.setMaxLengthElement(null);
1363        else
1364          derived.getMaxLengthElement().setUserData(DERIVATION_EQUALS, true);
1365      }
1366
1367      // todo: what to do about conditions?
1368      // condition : id 0..*
1369
1370      if (derived.hasMustSupportElement()) {
1371        if (!Base.compareDeep(derived.getMustSupportElement(), base.getMustSupportElement(), false))
1372          base.setMustSupportElement(derived.getMustSupportElement().copy());
1373        else if (trimDifferential)
1374          derived.setMustSupportElement(null);
1375        else
1376          derived.getMustSupportElement().setUserData(DERIVATION_EQUALS, true);
1377      }
1378
1379
1380      // profiles cannot change : isModifier, defaultValue, meaningWhenMissing
1381      // but extensions can change isModifier
1382      if (isExtension) {
1383        if (!Base.compareDeep(derived.getIsModifierElement(), base.getIsModifierElement(), false))
1384          base.setIsModifierElement(derived.getIsModifierElement().copy());
1385        else if (trimDifferential)
1386          derived.setIsModifierElement(null);
1387        else
1388          derived.getIsModifierElement().setUserData(DERIVATION_EQUALS, true);
1389      }
1390
1391      if (derived.hasBinding()) {
1392        if (!Base.compareDeep(derived.getBinding(), base.getBinding(), false)) {
1393          if (base.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED && derived.getBinding().getStrength() != BindingStrength.REQUIRED)
1394            messages.add(new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode(), IssueSeverity.ERROR));
1395//            throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal attempt to change a binding from "+base.getBinding().getStrength().toCode()+" to "+derived.getBinding().getStrength().toCode());
1396          else if (base.hasBinding() && derived.hasBinding() && base.getBinding().getStrength() == BindingStrength.REQUIRED) {
1397            ValueSetExpansionOutcome expBase = context.expandVS(context.fetchResource(ValueSet.class, base.getBinding().getValueSetReference().getReference()), true);
1398            ValueSetExpansionOutcome expDerived = context.expandVS(context.fetchResource(ValueSet.class, derived.getBinding().getValueSetReference().getReference()), true);
1399            if (expBase.getValueset() == null)
1400              messages.add(new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, pn+"."+base.getPath(), "Binding "+base.getBinding().getValueSetReference().getReference()+" could not be expanded", IssueSeverity.WARNING));
1401            else if (expDerived.getValueset() == null)
1402              messages.add(new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSetReference().getReference()+" could not be expanded", IssueSeverity.WARNING));
1403            else if (!isSubset(expBase.getValueset(), expDerived.getValueset()))
1404              messages.add(new ValidationMessage(Source.ProfileValidator, IssueType.BUSINESSRULE, pn+"."+derived.getPath(), "Binding "+derived.getBinding().getValueSetReference().getReference()+" is not a subset of binding "+base.getBinding().getValueSetReference().getReference(), IssueSeverity.ERROR));
1405          }
1406          base.setBinding(derived.getBinding().copy());
1407        } else if (trimDifferential)
1408          derived.setBinding(null);
1409        else
1410          derived.getBinding().setUserData(DERIVATION_EQUALS, true);
1411      } // else if (base.hasBinding() && doesn't have bindable type )
1412        //  base
1413
1414      if (derived.hasIsSummaryElement()) {
1415        if (!Base.compareDeep(derived.getIsSummaryElement(), base.getIsSummaryElement(), false))
1416          base.setIsSummaryElement(derived.getIsSummaryElement().copy());
1417        else if (trimDifferential)
1418          derived.setIsSummaryElement(null);
1419        else
1420          derived.getIsSummaryElement().setUserData(DERIVATION_EQUALS, true);
1421      }
1422
1423      if (derived.hasType()) {
1424        if (!Base.compareDeep(derived.getType(), base.getType(), false)) {
1425          if (base.hasType()) {
1426            for (TypeRefComponent ts : derived.getType()) {
1427              boolean ok = false;
1428              CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1429              for (TypeRefComponent td : base.getType()) {
1430                b.append(td.getCode());
1431                if (td.hasCode() && (td.getCode().equals(ts.getCode()) || td.getCode().equals("Extension") ||
1432                    td.getCode().equals("Element") || td.getCode().equals("*") ||
1433                    ((td.getCode().equals("Resource") || (td.getCode().equals("DomainResource")) && pkp.isResource(ts.getCode())))))
1434                  ok = true;
1435              }
1436              if (!ok)
1437                throw new DefinitionException("StructureDefinition "+pn+" at "+derived.getPath()+": illegal constrained type "+ts.getCode()+" from "+b.toString());
1438            }
1439          }
1440          base.getType().clear();
1441          for (TypeRefComponent t : derived.getType()) {
1442            TypeRefComponent tt = t.copy();
1443//            tt.setUserData(DERIVATION_EQUALS, true);
1444            base.getType().add(tt);
1445          }
1446        }
1447        else if (trimDifferential)
1448          derived.getType().clear();
1449        else
1450          for (TypeRefComponent t : derived.getType())
1451            t.setUserData(DERIVATION_EQUALS, true);
1452      }
1453
1454      if (derived.hasMapping()) {
1455        // todo: mappings are not cumulative - one replaces another
1456        if (!Base.compareDeep(derived.getMapping(), base.getMapping(), false)) {
1457          for (ElementDefinitionMappingComponent s : derived.getMapping()) {
1458            boolean found = false;
1459            for (ElementDefinitionMappingComponent d : base.getMapping()) {
1460              found = found || (d.getIdentity().equals(s.getIdentity()) && d.getMap().equals(s.getMap()));
1461            }
1462            if (!found)
1463              base.getMapping().add(s);
1464          }
1465        }
1466        else if (trimDifferential)
1467          derived.getMapping().clear();
1468        else
1469          for (ElementDefinitionMappingComponent t : derived.getMapping())
1470            t.setUserData(DERIVATION_EQUALS, true);
1471      }
1472
1473      // todo: constraints are cumulative. there is no replacing
1474      for (ElementDefinitionConstraintComponent s : base.getConstraint())
1475        s.setUserData(IS_DERIVED, true);
1476      if (derived.hasConstraint()) {
1477        for (ElementDefinitionConstraintComponent s : derived.getConstraint()) {
1478          base.getConstraint().add(s.copy());
1479        }
1480      }
1481    }
1482  }
1483
1484  private void updateFromSlicing(ElementDefinitionSlicingComponent dst, ElementDefinitionSlicingComponent src) {
1485    if (src.hasOrderedElement())
1486      dst.setOrderedElement(src.getOrderedElement().copy());
1487    if (src.hasDiscriminator())
1488      dst.getDiscriminator().addAll(src.getDiscriminator());
1489    if (src.hasRulesElement())
1490      dst.setRulesElement(src.getRulesElement().copy());
1491  }
1492
1493  /**
1494   * Finds internal references in an Element's Binding and StructureDefinition references (in TypeRef) and bases them on the given url
1495   * @param url - the base url to use to turn internal references into absolute references
1496   * @param element - the Element to update
1497   * @return - the updated Element
1498   */
1499  private ElementDefinition updateURLs(String url, ElementDefinition element) {
1500    if (element != null) {
1501      ElementDefinition defn = element;
1502      if (defn.hasBinding() && defn.getBinding().getValueSet() instanceof Reference && ((Reference)defn.getBinding().getValueSet()).getReference().startsWith("#"))
1503        ((Reference)defn.getBinding().getValueSet()).setReference(url+((Reference)defn.getBinding().getValueSet()).getReference());
1504      for (TypeRefComponent t : defn.getType()) {
1505        for (UriType tp : t.getProfile()) {
1506                if (tp.getValue().startsWith("#"))
1507            tp.setValue(url+t.getProfile());
1508        }
1509      }
1510    }
1511    return element;
1512  }
1513
1514  private String urltail(String path) {
1515    if (path.contains("#"))
1516      return path.substring(path.lastIndexOf('#')+1);
1517    if (path.contains("/"))
1518      return path.substring(path.lastIndexOf('/')+1);
1519    else
1520      return path;
1521
1522  }
1523
1524  private void writeElements(ElementDefinitionHolder edh, List<ElementDefinition> list) {
1525    list.add(edh.getSelf());
1526    for (ElementDefinitionHolder child : edh.getChildren()) {
1527      writeElements(child, list);
1528    }
1529  }
1530
1531//  private static String listStructures(StructureDefinition p) {
1532//    StringBuilder b = new StringBuilder();
1533//    boolean first = true;
1534//    for (ProfileStructureComponent s : p.getStructure()) {
1535//      if (first)
1536//        first = false;
1537//      else
1538//        b.append(", ");
1539//      if (pkp != null && pkp.hasLinkFor(s.getType()))
1540//        b.append("<a href=\""+pkp.getLinkFor(s.getType())+"\">"+s.getType()+"</a>");
1541//      else
1542//        b.append(s.getType());
1543//    }
1544//    return b.toString();
1545//  }
1546
1547  public static String describeExtensionContext(StructureDefinition ext) {
1548    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1549    for (StringType t : ext.getContext())
1550      b.append(t.getValue());
1551    if (!ext.hasContextType())
1552      throw new Error("no context type on "+ext.getUrl());
1553    switch (ext.getContextType()) {
1554    case DATATYPE: return "Use on data type: "+b.toString();
1555    case EXTENSION: return "Use on extension: "+b.toString();
1556    case RESOURCE: return "Use on element: "+b.toString();
1557    case MAPPING: return "Use where element has mapping: "+b.toString();
1558    default:
1559      return "??";
1560    }
1561  }
1562
1563  public static List<ElementDefinition> getChildList(StructureDefinition structure, ElementDefinition element) {
1564                return getChildList(structure, element.getPath());
1565          }
1566
1567  /**
1568   * Given a Structure, navigate to the element given by the path and return the direct children of that element
1569   *
1570   * @param structure The structure to navigate into
1571   * @param path The path of the element within the structure to get the children for
1572   * @return A List containing the element children (all of them are Elements)
1573   */
1574  public static List<ElementDefinition> getChildList(StructureDefinition profile, String path) {
1575    List<ElementDefinition> res = new ArrayList<ElementDefinition>();
1576
1577    for (ElementDefinition e : profile.getSnapshot().getElement())
1578    {
1579      String p = e.getPath();
1580
1581      if (!Utilities.noString(e.getNameReference()) && path.startsWith(p))
1582      {
1583        if (path.length() > p.length())
1584          return getChildList(profile, e.getNameReference()+"."+path.substring(p.length()+1));
1585        else
1586          return getChildList(profile, e.getNameReference());
1587      }
1588      else if (p.startsWith(path+".") && !p.equals(path))
1589      {
1590          String tail = p.substring(path.length()+1);
1591          if (!tail.contains(".")) {
1592            res.add(e);
1593          }
1594        }
1595
1596      }
1597
1598    return res;
1599  }
1600
1601  public static List<ElementDefinition> getChildMap(StructureDefinition profile, ElementDefinition element) throws DefinitionException {
1602                return getChildMap(profile, element.getName(), element.getPath(), null);
1603  }
1604
1605/**
1606 * Given a Structure, navigate to the element given by the path and return the direct children of that element
1607 *
1608 * @param structure The structure to navigate into
1609 * @param path The path of the element within the structure to get the children for
1610 * @return A Map containing the name of the element child (not the path) and the child itself (an Element)
1611 * @throws DefinitionException
1612 * @throws Exception
1613 */
1614  public static List<ElementDefinition> getChildMap(StructureDefinition profile, String name, String path, String nameReference) throws DefinitionException {
1615    List<ElementDefinition> res = new ArrayList<ElementDefinition>();
1616
1617    // if we have a name reference, we have to find it, and iterate it's children
1618    if (nameReference != null) {
1619        boolean found = false;
1620      for (ElementDefinition e : profile.getSnapshot().getElement()) {
1621        if (nameReference.equals(e.getName())) {
1622                found = true;
1623                path = e.getPath();
1624        }
1625      }
1626      if (!found)
1627        throw new DefinitionException("Unable to resolve name reference "+nameReference+" at path "+path);
1628    }
1629
1630    for (ElementDefinition e : profile.getSnapshot().getElement())
1631    {
1632      String p = e.getPath();
1633
1634      if (path != null && !Utilities.noString(e.getNameReference()) && path.startsWith(p))
1635      {
1636        /* The path we are navigating to is on or below this element, but the element defers its definition to another named part of the
1637         * structure.
1638         */
1639        if (path.length() > p.length())
1640        {
1641          // The path navigates further into the referenced element, so go ahead along the path over there
1642          return getChildMap(profile, name, e.getNameReference()+"."+path.substring(p.length()+1), null);
1643        }
1644        else
1645        {
1646          // The path we are looking for is actually this element, but since it defers it definition, go get the referenced element
1647          return getChildMap(profile, name, e.getNameReference(), null);
1648        }
1649      }
1650      else if (p.startsWith(path+"."))
1651      {
1652          // The path of the element is a child of the path we're looking for (i.e. the parent),
1653          // so add this element to the result.
1654          String tail = p.substring(path.length()+1);
1655
1656          // Only add direct children, not any deeper paths
1657          if (!tail.contains(".")) {
1658            res.add(e);
1659          }
1660        }
1661      }
1662
1663    return res;
1664  }
1665
1666  public static boolean isPrimitive(String value) {
1667    return value == null || Utilities.existsInListNC(value, "boolean", "integer", "decimal", "base64Binary", "instant", "string", "date", "dateTime", "code", "oid", "uuid", "id", "uri");
1668  }
1669
1670  public static String typeCode(List<TypeRefComponent> types) {
1671    StringBuilder b = new StringBuilder();
1672    boolean first = true;
1673    for (TypeRefComponent type : types) {
1674      if (first) first = false; else b.append(", ");
1675      b.append(type.getCode());
1676      if (type.hasProfile())
1677        b.append("{"+type.getProfile()+"}");
1678    }
1679    return b.toString();
1680  }
1681
1682
1683  public interface ProfileKnowledgeProvider {
1684    String getLinkFor(String typeSimple);
1685
1686    String getLinkForProfile(StructureDefinition profile, String url);
1687
1688    boolean hasLinkFor(String typeSimple);
1689
1690    boolean isDatatype(String typeSimple);
1691
1692    boolean isResource(String typeSimple);
1693
1694    BindingResolution resolveBinding(ElementDefinitionBindingComponent binding);
1695
1696    public class BindingResolution {
1697      public String display;
1698      public String url;
1699    }
1700  }
1701
1702  public class ExtensionContext {
1703
1704    private ElementDefinition element;
1705    private StructureDefinition defn;
1706
1707    public ExtensionContext(StructureDefinition ext, ElementDefinition ed) {
1708      this.defn = ext;
1709      this.element = ed;
1710    }
1711
1712    public StructureDefinition getDefn() {
1713      return defn;
1714    }
1715
1716    public ElementDefinition getElement() {
1717      return element;
1718    }
1719
1720    public ElementDefinition getExtensionValueDefinition() {
1721      int i = defn.getSnapshot().getElement().indexOf(element)+1;
1722      while (i < defn.getSnapshot().getElement().size()) {
1723        ElementDefinition ed = defn.getSnapshot().getElement().get(i);
1724        if (ed.getPath().equals(element.getPath()))
1725          return null;
1726        if (ed.getPath().startsWith(element.getPath()+".value"))
1727          return ed;
1728        i++;
1729      }
1730      return null;
1731    }
1732
1733    public String getUrl() {
1734      if (element == defn.getSnapshot().getElement().get(0))
1735        return defn.getUrl();
1736      else
1737        return element.getName();
1738    }
1739
1740  }
1741
1742  // generate schematroins for the rules in a structure definition
1743
1744  private class UnusedTracker {
1745    private boolean used;
1746  }
1747
1748  private class Slicer extends ElementDefinitionSlicingComponent {
1749    String criteria = "";
1750    String name = "";   
1751    boolean check;
1752    public Slicer(boolean cantCheck) {
1753      super();
1754      this.check = cantCheck;
1755    }
1756  }
1757  
1758  public static class ElementDefinitionHolder {
1759    private String name;
1760    private ElementDefinition self;
1761    private int baseIndex = 0;
1762    private List<ElementDefinitionHolder> children;
1763
1764    public ElementDefinitionHolder(ElementDefinition self) {
1765      super();
1766      this.self = self;
1767      this.name = self.getPath();
1768      children = new ArrayList<ElementDefinitionHolder>();
1769    }
1770
1771    public int getBaseIndex() {
1772      return baseIndex;
1773    }
1774
1775    public void setBaseIndex(int baseIndex) {
1776      this.baseIndex = baseIndex;
1777    }
1778
1779    public List<ElementDefinitionHolder> getChildren() {
1780      return children;
1781    }
1782
1783    public ElementDefinition getSelf() {
1784      return self;
1785    }
1786
1787  }
1788
1789  public static class ElementDefinitionComparer implements Comparator<ElementDefinitionHolder> {
1790
1791    private boolean inExtension;
1792    private List<ElementDefinition> snapshot;
1793    private int prefixLength;
1794    private String base;
1795    private String name;
1796    private Set<String> errors = new HashSet<String>();
1797
1798    public ElementDefinitionComparer(boolean inExtension, List<ElementDefinition> snapshot, String base, int prefixLength, String name) {
1799      this.inExtension = inExtension;
1800      this.snapshot = snapshot;
1801      this.prefixLength = prefixLength;
1802      this.base = base;
1803      this.name = name;
1804    }
1805
1806    public void checkForErrors(List<String> errorList) {
1807      if (errors.size() > 0) {
1808//        CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1809//        for (String s : errors)
1810//          b.append("StructureDefinition "+name+": "+s);
1811//        throw new DefinitionException(b.toString());
1812        for (String s : errors)
1813          if (s.startsWith("!"))
1814            errorList.add("!StructureDefinition "+name+": "+s.substring(1));
1815          else
1816            errorList.add("StructureDefinition "+name+": "+s);
1817      }
1818    }
1819
1820    @Override
1821    public int compare(ElementDefinitionHolder o1, ElementDefinitionHolder o2) {
1822      if (o1.getBaseIndex() == 0)
1823        o1.setBaseIndex(find(o1.getSelf().getPath()));
1824      if (o2.getBaseIndex() == 0)
1825        o2.setBaseIndex(find(o2.getSelf().getPath()));
1826      return o1.getBaseIndex() - o2.getBaseIndex();
1827    }
1828
1829    private int find(String path) {
1830      String actual = base+path.substring(prefixLength);
1831      for (int i = 0; i < snapshot.size(); i++) {
1832        String p = snapshot.get(i).getPath();
1833        if (p.equals(actual))
1834          return i;
1835        if (p.endsWith("[x]") && actual.startsWith(p.substring(0, p.length()-3)) && !(actual.endsWith("[x]")) && !actual.substring(p.length()-3).contains("."))
1836          return i;
1837      }
1838      if (prefixLength == 0)
1839        errors.add("Differential contains path "+path+" which is not found in the base");
1840      else
1841        errors.add("Differential contains path "+path+" which is actually "+actual+", which is not found in the base");
1842      return 0;
1843    }
1844  }
1845
1846  /**
1847   * First compare element by path then by name if same
1848   */
1849  private static class ElementNameCompare implements Comparator<ElementDefinition> {
1850
1851    @Override
1852    public int compare(ElementDefinition o1, ElementDefinition o2) {
1853      String path1 = normalizePath(o1);
1854      String path2 = normalizePath(o2);
1855      int cmp = path1.compareTo(path2);
1856      if (cmp == 0) {
1857        String name1 = o1.hasName() ? o1.getName() : "";
1858        String name2 = o2.hasName() ? o2.getName() : "";
1859        cmp = name1.compareTo(name2);
1860      }
1861      return cmp;
1862    }
1863
1864    private static String normalizePath(ElementDefinition e) {
1865      if (!e.hasPath()) return "";
1866      String path = e.getPath();
1867      // if sorting element names make sure onset[x] appears before onsetAge, onsetDate, etc.
1868      // so strip off the [x] suffix when comparing the path names.
1869      if (path.endsWith("[x]")) {
1870        path = path.substring(0, path.length()-3);
1871      }
1872      return path;
1873    }
1874
1875  }
1876
1877//
1878//private void generateForChild(TextStreamWriter txt,
1879//    StructureDefinition structure, ElementDefinition child) {
1880//  // TODO Auto-generated method stub
1881//
1882//}
1883
1884
1885}