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