001package org.hl7.fhir.r5.comparison;
002
003import java.io.IOException;
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.Date;
007import java.util.List;
008
009import org.hl7.fhir.exceptions.DefinitionException;
010import org.hl7.fhir.exceptions.FHIRException;
011import org.hl7.fhir.exceptions.FHIRFormatError;
012import org.hl7.fhir.r5.comparison.ValueSetComparer.ValueSetComparison;
013import org.hl7.fhir.r5.conformance.profile.BindingResolution;
014import org.hl7.fhir.r5.conformance.profile.ProfileKnowledgeProvider;
015import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
016import org.hl7.fhir.r5.context.IWorkerContext;
017import org.hl7.fhir.r5.formats.IParser;
018import org.hl7.fhir.r5.formats.JsonParser;
019import org.hl7.fhir.r5.model.Base;
020import org.hl7.fhir.r5.model.Coding;
021import org.hl7.fhir.r5.model.DataType;
022import org.hl7.fhir.r5.model.ElementDefinition;
023import org.hl7.fhir.r5.model.ElementDefinition.AggregationMode;
024import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent;
025import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionConstraintComponent;
026import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionMappingComponent;
027import org.hl7.fhir.r5.model.ElementDefinition.TypeRefComponent;
028import org.hl7.fhir.r5.model.Enumeration;
029import org.hl7.fhir.r5.model.Enumerations.BindingStrength;
030import org.hl7.fhir.r5.model.IntegerType;
031import org.hl7.fhir.r5.model.PrimitiveType;
032import org.hl7.fhir.r5.model.Resource;
033import org.hl7.fhir.r5.model.StringType;
034import org.hl7.fhir.r5.model.StructureDefinition;
035import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
036import org.hl7.fhir.r5.model.ValueSet;
037import org.hl7.fhir.r5.renderers.StructureDefinitionRenderer;
038import org.hl7.fhir.r5.renderers.utils.RenderingContext;
039import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules;
040import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode;
041import org.hl7.fhir.r5.utils.DefinitionNavigator;
042import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
043import org.hl7.fhir.utilities.Utilities;
044import org.hl7.fhir.utilities.validation.ValidationMessage;
045import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
046import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
047import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
048import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
049import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
050import org.hl7.fhir.utilities.xhtml.XhtmlNode;
051
052import kotlin.NotImplementedError;
053
054public class StructureDefinitionComparer extends CanonicalResourceComparer implements ProfileKnowledgeProvider {
055
056  public class ProfileComparison extends CanonicalResourceComparison<StructureDefinition> {
057
058    private StructuralMatch<ElementDefinitionNode> combined;                                             
059
060    public ProfileComparison(StructureDefinition left, StructureDefinition right) {
061      super(left, right);
062      combined = new StructuralMatch<ElementDefinitionNode>(); // base
063    }
064
065    public StructuralMatch<ElementDefinitionNode> getCombined() {
066      return combined;
067    }
068
069    @Override
070    protected String abbreviation() {
071      return "sd";
072    }
073
074    @Override
075    protected String summary() {
076      return "Profile: "+left.present()+" vs "+right.present();
077    }
078
079    @Override
080    protected String fhirType() {
081      return "StructureDefinition";
082    }
083    @Override
084    protected void countMessages(MessageCounts cnts) {
085      super.countMessages(cnts);
086      combined.countMessages(cnts);
087    }
088
089  }
090
091
092  private class ElementDefinitionNode {
093    private ElementDefinition def;
094    private StructureDefinition src;
095    private ElementDefinitionNode(StructureDefinition src, ElementDefinition def) {
096      super();
097      this.src = src;
098      this.def = def;
099    }
100    public ElementDefinition getDef() {
101      return def;
102    }
103    public StructureDefinition getSrc() {
104      return src;
105    }
106  }
107
108  private ProfileUtilities utilsLeft;
109  private ProfileUtilities utilsRight;
110
111  public StructureDefinitionComparer(ComparisonSession session, ProfileUtilities utilsLeft, ProfileUtilities utilsRight) {
112    super(session);
113    this.utilsLeft = utilsLeft;
114    this.utilsRight = utilsRight;
115  }
116
117  @Override
118  protected String fhirType() {
119    return "StructureDefinition";
120  }
121
122  public ProfileComparison compare(StructureDefinition left, StructureDefinition right) throws DefinitionException, FHIRFormatError, IOException {
123    check(left, "left");
124    check(right, "right");
125
126    ProfileComparison res = new ProfileComparison(left, right);
127    session.identify(res);
128    StructureDefinition sd = new StructureDefinition();
129    res.setUnion(sd);
130    session.identify(sd);
131    sd.setName("Union"+left.getName()+"And"+right.getName());
132    sd.setTitle("Union of "+left.getTitle()+" And "+right.getTitle());
133    sd.setStatus(left.getStatus());
134    sd.setDate(new Date());
135
136    StructureDefinition sd1 = new StructureDefinition();
137    res.setIntersection(sd1);
138    session.identify(sd1);
139    sd1.setName("Intersection"+left.getName()+"And"+right.getName());
140    sd1.setTitle("Intersection of "+left.getTitle()+" And "+right.getTitle());
141    sd1.setStatus(left.getStatus());
142    sd1.setDate(new Date());
143
144    List<String> chMetadata = new ArrayList<>();
145    boolean ch = compareMetadata(left, right, res.getMetadata(), res, chMetadata, right);
146    if (comparePrimitives("fhirVersion", left.getFhirVersionElement(), right.getFhirVersionElement(), res.getMetadata(), IssueSeverity.WARNING, res)) {
147      ch = true;
148      chMetadata.add("fhirVersion");
149    }
150    if (comparePrimitives("kind", left.getKindElement(), right.getKindElement(), res.getMetadata(), IssueSeverity.WARNING, res)) {
151      ch = true;
152      chMetadata.add("kind");
153    }
154    if (comparePrimitives("abstract", left.getAbstractElement(), right.getAbstractElement(), res.getMetadata(), IssueSeverity.WARNING, res)) {
155      ch = true;
156      chMetadata.add("abstract");
157    }
158    res.updatedMetadataState(ch, chMetadata);
159    
160    ch = false;
161    ch = comparePrimitives("type", left.getTypeElement(), right.getTypeElement(), res.getMetadata(), IssueSeverity.ERROR, res) || ch;
162    ch = comparePrimitives("baseDefinition", left.getBaseDefinitionElement(), right.getBaseDefinitionElement(), res.getMetadata(), IssueSeverity.ERROR, res) || ch;
163    if (left.getType().equals(right.getType())) {
164      DefinitionNavigator ln = new DefinitionNavigator(session.getContextLeft(), left, false);
165      DefinitionNavigator rn = new DefinitionNavigator(session.getContextRight(), right, false);
166      StructuralMatch<ElementDefinitionNode> sm = new StructuralMatch<ElementDefinitionNode>(new ElementDefinitionNode(left, ln.current()), new ElementDefinitionNode(right, rn.current()));
167      compareElements(res, sm, ln.path(), null, ln, rn);
168      res.combined = sm;
169      ln = new DefinitionNavigator(session.getContextLeft(), left, true);
170      rn = new DefinitionNavigator(session.getContextRight(), right, true);
171      ch = compareDiff(ln.path(), null, ln, rn, res, right) || ch;
172      // we don't preserve the differences - we only want the annotations
173    }
174    res.updateDefinitionsState(ch);
175
176    session.annotate(right, res);
177    return res;
178  }
179
180  private void check(StructureDefinition sd, String name) {
181    if (sd == null)
182      throw new DefinitionException("No StructureDefinition provided ("+name+": "+sd.getName()+")");
183//    if (sd.getType().equals("Extension")) {
184//      throw new DefinitionException("StructureDefinition is for an extension - use ExtensionComparer instead ("+name+": "+sd.getName()+")");
185//    }
186    if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
187      throw new DefinitionException("StructureDefinition is not for an profile - can't be compared ("+name+": "+sd.getName()+")");
188    }
189    if (sd.getSnapshot().getElement().isEmpty())
190      throw new DefinitionException("StructureDefinition snapshot is empty ("+name+": "+sd.getName()+")");
191  }
192
193  private boolean compareElements(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res,  String path, String sliceName, DefinitionNavigator left, DefinitionNavigator right) throws DefinitionException, FHIRFormatError, IOException {
194    assert(path != null);  
195    assert(left != null);
196    assert(right != null);
197    assert(left.path().equals(right.path()));
198
199    boolean def = false;
200    
201    if (session.isDebug()) {
202      System.out.println("Compare elements at "+path);
203    }
204    
205    // not allowed to be different:   
206//    ruleEqual(comp, res, left.current().getDefaultValue(), right.current().getDefaultValue(), "defaultValue", path);
207//    ruleEqual(comp, res, left.current().getMeaningWhenMissingElement(), right.current().getMeaningWhenMissingElement(), "meaningWhenMissing", path);
208//    ruleEqual(comp, res, left.current().getIsModifierElement(), right.current().getIsModifierElement(), "isModifier", path); - this check belongs in the core
209//    ruleEqual(comp, res, left.current().getIsSummaryElement(), right.current().getIsSummaryElement(), "isSummary", path); - so does this
210
211    // we ignore slicing right now - we're going to clone the root one anyway, and then think about clones 
212    // simple stuff
213    ElementDefinition subset = new ElementDefinition();
214    subset.setPath(left.path());
215    if (sliceName != null)
216      subset.setSliceName(sliceName);
217
218    subset.getRepresentation().addAll(left.current().getRepresentation()); // can't be bothered even testing this one
219    subset.setDefaultValue(left.current().getDefaultValue());
220    subset.setMeaningWhenMissing(left.current().getMeaningWhenMissing());
221    subset.setIsModifier(left.current().getIsModifier());
222    subset.setIsSummary(left.current().getIsSummary());
223
224    // descriptive properties from ElementDefinition - merge them:
225    subset.setLabel(mergeText(comp, res, path, "label", left.current().getLabel(), right.current().getLabel(), false));
226    comparePrimitivesWithTracking("label", left.current().getLabelElement(), right.current().getLabelElement(), null, IssueSeverity.INFORMATION, comp, right.current());
227
228    subset.setShort(mergeText(comp, res, path, "short", left.current().getShort(), right.current().getShort(), false));
229    def = comparePrimitivesWithTracking("short", left.current().getShortElement(), right.current().getShortElement(), null, IssueSeverity.INFORMATION, comp, right.current()) || def;
230    
231    subset.setDefinition(mergeText(comp, res, path, "definition", left.current().getDefinition(), right.current().getDefinition(), false));
232    def = comparePrimitivesWithTracking("definition", left.current().getDefinitionElement(), right.current().getDefinitionElement(), null, IssueSeverity.INFORMATION, comp, right.current()) || def;
233
234    subset.setComment(mergeText(comp, res, path, "comments", left.current().getComment(), right.current().getComment(), false));
235    def = comparePrimitivesWithTracking("comment", left.current().getCommentElement(), right.current().getCommentElement(), null, IssueSeverity.INFORMATION, comp, right.current()) || def;
236
237    subset.setRequirements(mergeText(comp, res, path, "requirements", left.current().getRequirements(), right.current().getRequirements(), false));
238    def = comparePrimitivesWithTracking("requirements", left.current().getRequirementsElement(), right.current().getRequirementsElement(), null, IssueSeverity.INFORMATION, comp, right.current()) || def;
239
240    subset.getCode().addAll(mergeCodings(left.current().getCode(), right.current().getCode()));
241    subset.getAlias().addAll(mergeStrings(left.current().getAlias(), right.current().getAlias()));
242    subset.getMapping().addAll(mergeMappings(left.current().getMapping(), right.current().getMapping()));
243    // left will win for example
244    subset.setExample(left.current().hasExample() ? left.current().getExample() : right.current().getExample());
245
246    if (left.current().getMustSupport() != right.current().getMustSupport()) {
247      vm(IssueSeverity.WARNING, "Elements differ in definition for mustSupport: '"+left.current().getMustSupport()+"' vs '"+right.current().getMustSupport()+"'", path, comp.getMessages(), res.getMessages());
248
249    }
250    subset.setMustSupport(left.current().getMustSupport() || right.current().getMustSupport());
251    def = comparePrimitivesWithTracking("mustSupport", left.current().getMustSupportElement(), right.current().getMustSupportElement(), null, IssueSeverity.INFORMATION, null, right.current()) || def;
252
253    ElementDefinition superset = subset.copy();
254
255    def = comparePrimitivesWithTracking("min", left.current().getMinElement(), right.current().getMinElement(), null, IssueSeverity.INFORMATION, null, right.current()) || def;
256    def = comparePrimitivesWithTracking("max", left.current().getMaxElement(), right.current().getMaxElement(), null, IssueSeverity.INFORMATION, null, right.current()) || def;
257
258    // compare and intersect
259    int leftMin = left.current().getMin();
260    int rightMin = right.current().getMin();
261    int leftMax = "*".equals(left.current().getMax()) ? Integer.MAX_VALUE : Utilities.parseInt(left.current().getMax(), -1);
262    int rightMax = "*".equals(right.current().getMax()) ? Integer.MAX_VALUE : Utilities.parseInt(right.current().getMax(), -1);
263    
264    checkMinMax(comp, res, path, leftMin, rightMin, leftMax, rightMax);
265    superset.setMin(unionMin(leftMin, rightMin));
266    superset.setMax(unionMax(leftMax, rightMax, left.current().getMax(), right.current().getMax()));
267    subset.setMin(intersectMin(leftMin, rightMin));
268    subset.setMax(intersectMax(leftMax, rightMax, left.current().getMax(), right.current().getMax()));
269
270    superset.getType().addAll(unionTypes(comp, res, path, left.current().getType(), right.current().getType(), left.getStructure(), right.getStructure()));
271    subset.getType().addAll(intersectTypes(comp, res, subset, path, left.current().getType(), right.current().getType()));
272    rule(comp, res, !subset.getType().isEmpty() || (!left.current().hasType() && !right.current().hasType()), path, "Type Mismatch: "+typeCode(left)+" vs "+typeCode(right));
273    //    <fixed[x]><!-- ?? 0..1 * Value must be exactly this --></fixed[x]>
274    //    <pattern[x]><!-- ?? 0..1 * Value must have at least these property values --></pattern[x]>
275    superset.setMaxLengthElement(unionMaxLength(left.current().getMaxLength(), right.current().getMaxLength()));
276    subset.setMaxLengthElement(intersectMaxLength(left.current().getMaxLength(), right.current().getMaxLength()));
277    if (left.current().hasBinding() || right.current().hasBinding()) {
278      compareBindings(comp, res, subset, superset, path, left.current(), right.current(), left.getStructure(), right.getStructure());
279    }
280    // note these are backwards
281    superset.getConstraint().addAll(intersectConstraints(path, left.current().getConstraint(), right.current().getConstraint()));
282    subset.getConstraint().addAll(unionConstraints(comp, res, path, left.current().getConstraint(), right.current().getConstraint()));
283    comp.getIntersection().getSnapshot().getElement().add(subset);
284    comp.getUnion().getSnapshot().getElement().add(superset);
285
286    // add the children
287    def = compareChildren(comp, res, path, left, right) || def;
288//
289//    // now process the slices
290//    if (left.current().hasSlicing() || right.current().hasSlicing()) {
291//      assert sliceName == null;
292//      if (isExtension(left.path()))
293//        return compareExtensions(outcome, path, superset, subset, left, right);
294//      //      return true;
295//      else {
296//        ElementDefinitionSlicingComponent slicingL = left.current().getSlicing();
297//        ElementDefinitionSlicingComponent slicingR = right.current().getSlicing();
298//        // well, this is tricky. If one is sliced, and the other is not, then in general, the union just ignores the slices, and the intersection is the slices.
299//        if (left.current().hasSlicing() && !right.current().hasSlicing()) { 
300//          // the super set is done. Any restrictions in the slices are irrelevant to what the super set says, except that we're going sum up the value sets if we can (for documentation purposes) (todo)
301//          // the minimum set is the slicing specified in the slicer
302//          subset.setSlicing(slicingL);
303//          // stick everything from the right to do with the slices to the subset 
304//          copySlices(outcome.subset.getSnapshot().getElement(), left.getStructure().getSnapshot().getElement(), left.slices());
305//        } else if (!left.current().hasSlicing() && right.current().hasSlicing()) { 
306//          // the super set is done. Any restrictions in the slices are irrelevant to what the super set says, except that we're going sum up the value sets if we can (for documentation purposes) (todo)
307//          // the minimum set is the slicing specified in the slicer
308//          subset.setSlicing(slicingR);
309//          // stick everything from the right to do with the slices to the subset 
310//          copySlices(outcome.subset.getSnapshot().getElement(), right.getStructure().getSnapshot().getElement(), right.slices());
311//        } else if (isTypeSlicing(slicingL) || isTypeSlicing(slicingR)) {
312//          superset.getSlicing().setRules(SlicingRules.OPEN).setOrdered(false).addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
313//          subset.getSlicing().setRules(slicingL.getRules() == SlicingRules.CLOSED || slicingR.getRules() == SlicingRules.CLOSED ? SlicingRules.OPEN : SlicingRules.CLOSED).setOrdered(false).addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
314//
315//          // the superset is the union of the types 
316//          // the subset is the intersection of them 
317//          List<DefinitionNavigator> handled = new ArrayList<>();
318//          for (DefinitionNavigator t : left.slices()) {
319//            DefinitionNavigator r = findMatchingSlice(right.slices(), t);
320//            if (r == null) {
321//              copySlice(outcome.superset.getSnapshot().getElement(), left.getStructure().getSnapshot().getElement(), t);              
322//            } else {
323//              handled.add(r);
324//              ret = compareElements(outcome, path+":"+t.current().getSliceName(), t, r, t.current().getSliceName()) && ret;
325//            }
326//          }
327//          for (DefinitionNavigator t : right.slices()) {
328//            if (!handled.contains(t)) {
329//              copySlice(outcome.superset.getSnapshot().getElement(), right.getStructure().getSnapshot().getElement(), t);
330//            }
331//          }
332//        } else if (slicingMatches(slicingL, slicingR)) {
333//          // if it's the same, we can try matching the slices - though we might have to give up without getting matches correct
334//          // there amy be implied consistency we can't reason about 
335//          throw new DefinitionException("Slicing matches but is not handled yet at "+left.current().getId()+": ("+ProfileUtilities.summarizeSlicing(slicingL)+")");
336//        } else  {
337//          // if the slicing is different, we can't compare them - or can we?
338//          throw new DefinitionException("Slicing doesn't match at "+left.current().getId()+": ("+ProfileUtilities.summarizeSlicing(slicingL)+" / "+ProfileUtilities.summarizeSlicing(slicingR)+")");
339//        }
340//      }
341//      // todo: name 
342//    }
343//    return ret;
344//
345//    // TODO Auto-generated method stub
346//    return null;
347    return def;
348  }
349  
350
351  private boolean compareChildren(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, DefinitionNavigator left, DefinitionNavigator right) throws DefinitionException, IOException, FHIRFormatError {
352    boolean def = false;
353    
354    List<DefinitionNavigator> lc = left.children();
355    List<DefinitionNavigator> rc = right.children();
356    // it's possible that one of these profiles walks into a data type and the other doesn't
357    // if it does, we have to load the children for that data into the profile that doesn't 
358    // walk into it
359    if (lc.isEmpty() && !rc.isEmpty() && right.current().getType().size() == 1 && left.hasTypeChildren(right.current().getType().get(0), left.getStructure()))
360      lc = left.childrenFromType(right.current().getType().get(0), right.getStructure());
361    if (rc.isEmpty() && !lc.isEmpty() && left.current().getType().size() == 1 && right.hasTypeChildren(left.current().getType().get(0), right.getStructure()))
362      rc = right.childrenFromType(left.current().getType().get(0), left.getStructure());
363    
364    List<DefinitionNavigator> matchR = new ArrayList<>();
365    for (DefinitionNavigator l : lc) {
366      DefinitionNavigator r = findInList(rc, l);
367      if (r == null) {
368        comp.getUnion().getSnapshot().getElement().add(l.current().copy());
369        res.getChildren().add(new StructuralMatch<ElementDefinitionNode>(new ElementDefinitionNode(l.getStructure(), l.current()), vmI(IssueSeverity.INFORMATION, "Removed this element", path)));
370      } else {
371        matchR.add(r);
372        StructuralMatch<ElementDefinitionNode> sm = new StructuralMatch<ElementDefinitionNode>(new ElementDefinitionNode(l.getStructure(), l.current()), new ElementDefinitionNode(r.getStructure(), r.current()));
373        res.getChildren().add(sm);
374        def = compareElements(comp, sm, l.path(), null, l, r) || def;
375      }
376    }
377    for (DefinitionNavigator r : rc) {
378      if (!matchR.contains(r)) {
379        comp.getUnion().getSnapshot().getElement().add(r.current().copy());
380        res.getChildren().add(new StructuralMatch<ElementDefinitionNode>(vmI(IssueSeverity.INFORMATION, "Added this element", path), new ElementDefinitionNode(r.getStructure(), r.current())));        
381      }
382    }
383    return def;
384  }
385
386
387  private boolean compareDiff(String path, String sliceName, DefinitionNavigator left, DefinitionNavigator right, ProfileComparison res, Base parent) throws DefinitionException, FHIRFormatError, IOException {
388    assert(path != null);  
389    assert(left != null);
390    assert(right != null);
391    assert(left.path().equals(right.path()));
392
393    boolean def = false;
394    boolean ch = false;
395    
396    // not allowed to be different:   
397//    ruleEqual(comp, res, left.current().getDefaultValue(), right.current().getDefaultValue(), "defaultValue", path);
398//    ruleEqual(comp, res, left.current().getMeaningWhenMissingElement(), right.current().getMeaningWhenMissingElement(), "meaningWhenMissing", path);
399//    ruleEqual(comp, res, left.current().getIsModifierElement(), right.current().getIsModifierElement(), "isModifier", path); - this check belongs in the core
400//    ruleEqual(comp, res, left.current().getIsSummaryElement(), right.current().getIsSummaryElement(), "isSummary", path); - so does this
401
402    ElementDefinition edl = left.current();
403    ElementDefinition edr = right.current();
404    if (edl == null && edr == null) {
405      // both are sparse at this point, do nothing
406    } else if (edl == null) {
407      session.markAdded(edr);      
408    } else if (edr == null) {
409      session.markDeleted(right.parent(), "element", edl);            
410    } else {
411      // descriptive properties from ElementDefinition
412      comparePrimitivesWithTracking("label", edl.getLabelElement(), edr.getLabelElement(), null, IssueSeverity.INFORMATION, null, edr);
413      comparePrimitivesWithTracking("sliceName", edl.getSliceNameElement(), edr.getSliceNameElement(), null, IssueSeverity.INFORMATION, null, edr);
414      comparePrimitivesWithTracking("sliceIsConstraining", edl.getSliceIsConstrainingElement(), edr.getSliceIsConstrainingElement(), null, IssueSeverity.INFORMATION, null, edr);
415      comparePrimitivesWithTracking("alias", edl.getAlias(), edr.getAlias(), null, IssueSeverity.INFORMATION, null, edr);
416      compareDataTypesWithTracking("code", edl.getCode(), edr.getCode(), null, IssueSeverity.INFORMATION, null, edr);
417      
418      def = comparePrimitivesWithTracking("short", edl.getShortElement(), edr.getShortElement(), null, IssueSeverity.INFORMATION, null, edr) || def;
419      def = comparePrimitivesWithTracking("definition", edl.getDefinitionElement(), edr.getDefinitionElement(), null, IssueSeverity.INFORMATION, null, edr) || def;
420      def = comparePrimitivesWithTracking("comment", edl.getCommentElement(), edr.getCommentElement(), null, IssueSeverity.INFORMATION, null, edr) || def;
421      def = comparePrimitivesWithTracking("requirements", edl.getRequirementsElement(), edr.getRequirementsElement(), null, IssueSeverity.INFORMATION, null, edr) || def;
422      def = comparePrimitivesWithTracking("mustSupport", edl.getMustSupportElement(), edr.getMustSupportElement(), null, IssueSeverity.INFORMATION, null, edr) || def;
423      def = comparePrimitivesWithTracking("meaningWhenMissing", edl.getMeaningWhenMissingElement(), edr.getMeaningWhenMissingElement(), null, IssueSeverity.INFORMATION, null, edr) || def;
424      def = comparePrimitivesWithTracking("isModifierReason", edl.getIsModifierReasonElement(), edr.getIsModifierReasonElement(), null, IssueSeverity.INFORMATION, null, edr) || def;
425      
426      ch = comparePrimitivesWithTracking("min", edl.getMinElement(), edr.getMinElement(), null, IssueSeverity.ERROR, null, edr) || ch;
427      ch = comparePrimitivesWithTracking("max", edl.getMaxElement(), edr.getMaxElement(), null, IssueSeverity.ERROR, null, edr) || ch;
428      ch = compareDataTypesWithTracking("defaultValue", edl.getDefaultValue(), edr.getDefaultValue(), null, IssueSeverity.ERROR, null, edr) || ch;
429      ch = compareDataTypesWithTracking("fixed", edl.getFixed(), edr.getFixed(), null, IssueSeverity.ERROR, null, edr) || ch;
430      ch = compareDataTypesWithTracking("pattern", edl.getPattern(), edr.getPattern(), null, IssueSeverity.ERROR, null, edr) || ch;
431      ch = compareDataTypesWithTracking("minValue", edl.getMinValue(), edr.getMinValue(), null, IssueSeverity.ERROR, null, edr) || ch;
432      ch = compareDataTypesWithTracking("maxValue", edl.getMaxValue(), edr.getMaxValue(), null, IssueSeverity.ERROR, null, edr) || ch;
433      ch = comparePrimitivesWithTracking("maxLength", edl.getMaxLengthElement(), edr.getMaxLengthElement(), null, IssueSeverity.INFORMATION, null, edr) || def;
434      ch = comparePrimitivesWithTracking("mustHaveValue", edl.getMustHaveValueElement(), edr.getMustHaveValueElement(), null, IssueSeverity.INFORMATION, null, edr) || def;
435      ch = comparePrimitivesWithTracking("valueAlternatives", edl.getValueAlternatives(), edr.getValueAlternatives(), null, IssueSeverity.INFORMATION, null, edr) || ch;
436      ch = comparePrimitivesWithTracking("isModifier", edl.getIsModifierElement(), edr.getIsModifierElement(), null, IssueSeverity.INFORMATION, null, edr) || def;
437      
438      def = compareTypes(path, sliceName, edl, edr, res) || def;
439      
440      
441      ElementDefinitionBindingComponent bl = edl.getBinding();
442      ElementDefinitionBindingComponent br = edr.getBinding();
443      if (bl == null && br == null) {
444        // both are sparse at this point, do nothing
445      } else if (bl == null) {
446        session.markAdded(edr);      
447      } else if (br == null) {
448        session.markDeleted(right.parent(), "element", edl);            
449      } else {
450        ch = comparePrimitivesWithTracking("strength", bl.getStrengthElement(), br.getStrengthElement(), null, IssueSeverity.ERROR, null, edr) || ch;
451        def = comparePrimitivesWithTracking("description", bl.getDescriptionElement(), br.getDescriptionElement(), null, IssueSeverity.ERROR, null, edr) || def;
452        ch = comparePrimitivesWithTracking("valueSet", bl.getValueSetElement(), br.getValueSetElement(), null, IssueSeverity.ERROR, null, edr) || ch;
453        // todo: additional
454      }
455
456      def = compareInvariants(path, sliceName, edl, edr, res) || def;
457      
458      // main todos:
459      //  invariants, slicing
460      // mappings 
461    }
462    // add the children
463    if (ch) {
464      res.updateContentState(true);
465    }
466    def = compareDiffChildren(path, left, right, edr == null ? parent : edr, res) || def;
467//
468//    // now process the slices
469//    if (left.current().hasSlicing() || right.current().hasSlicing()) {
470//      assert sliceName == null;
471//      if (isExtension(left.path()))
472//        return compareExtensions(outcome, path, superset, subset, left, right);
473//      //      return true;
474//      else {
475//        ElementDefinitionSlicingComponent slicingL = left.current().getSlicing();
476//        ElementDefinitionSlicingComponent slicingR = right.current().getSlicing();
477//        // well, this is tricky. If one is sliced, and the other is not, then in general, the union just ignores the slices, and the intersection is the slices.
478//        if (left.current().hasSlicing() && !right.current().hasSlicing()) { 
479//          // the super set is done. Any restrictions in the slices are irrelevant to what the super set says, except that we're going sum up the value sets if we can (for documentation purposes) (todo)
480//          // the minimum set is the slicing specified in the slicer
481//          subset.setSlicing(slicingL);
482//          // stick everything from the right to do with the slices to the subset 
483//          copySlices(outcome.subset.getSnapshot().getElement(), left.getStructure().getSnapshot().getElement(), left.slices());
484//        } else if (!left.current().hasSlicing() && right.current().hasSlicing()) { 
485//          // the super set is done. Any restrictions in the slices are irrelevant to what the super set says, except that we're going sum up the value sets if we can (for documentation purposes) (todo)
486//          // the minimum set is the slicing specified in the slicer
487//          subset.setSlicing(slicingR);
488//          // stick everything from the right to do with the slices to the subset 
489//          copySlices(outcome.subset.getSnapshot().getElement(), right.getStructure().getSnapshot().getElement(), right.slices());
490//        } else if (isTypeSlicing(slicingL) || isTypeSlicing(slicingR)) {
491//          superset.getSlicing().setRules(SlicingRules.OPEN).setOrdered(false).addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
492//          subset.getSlicing().setRules(slicingL.getRules() == SlicingRules.CLOSED || slicingR.getRules() == SlicingRules.CLOSED ? SlicingRules.OPEN : SlicingRules.CLOSED).setOrdered(false).addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
493//
494//          // the superset is the union of the types 
495//          // the subset is the intersection of them 
496//          List<DefinitionNavigator> handled = new ArrayList<>();
497//          for (DefinitionNavigator t : left.slices()) {
498//            DefinitionNavigator r = findMatchingSlice(right.slices(), t);
499//            if (r == null) {
500//              copySlice(outcome.superset.getSnapshot().getElement(), left.getStructure().getSnapshot().getElement(), t);              
501//            } else {
502//              handled.add(r);
503//              ret = compareElements(outcome, path+":"+t.current().getSliceName(), t, r, t.current().getSliceName()) && ret;
504//            }
505//          }
506//          for (DefinitionNavigator t : right.slices()) {
507//            if (!handled.contains(t)) {
508//              copySlice(outcome.superset.getSnapshot().getElement(), right.getStructure().getSnapshot().getElement(), t);
509//            }
510//          }
511//        } else if (slicingMatches(slicingL, slicingR)) {
512//          // if it's the same, we can try matching the slices - though we might have to give up without getting matches correct
513//          // there amy be implied consistency we can't reason about 
514//          throw new DefinitionException("Slicing matches but is not handled yet at "+left.current().getId()+": ("+ProfileUtilities.summarizeSlicing(slicingL)+")");
515//        } else  {
516//          // if the slicing is different, we can't compare them - or can we?
517//          throw new DefinitionException("Slicing doesn't match at "+left.current().getId()+": ("+ProfileUtilities.summarizeSlicing(slicingL)+" / "+ProfileUtilities.summarizeSlicing(slicingR)+")");
518//        }
519//      }
520//      // todo: name 
521//    }
522//    return ret;
523//
524//    // TODO Auto-generated method stub
525//    return null;
526    return def;
527  }
528
529  private boolean compareDiffChildren(String path, DefinitionNavigator left, DefinitionNavigator right, Base parent, ProfileComparison res) throws DefinitionException, IOException, FHIRFormatError {
530    boolean def = false;
531    
532    List<DefinitionNavigator> lc = left.children();
533    List<DefinitionNavigator> rc = right.children();
534
535    List<DefinitionNavigator> matchR = new ArrayList<>();
536    for (DefinitionNavigator l : lc) {
537      DefinitionNavigator r = findInList(rc, l);
538      if (r == null) {
539        session.markDeleted(parent, "element", l.current());
540        res.updateContentState(true);
541      } else {
542        matchR.add(r);
543        def = compareDiff(l.path(), null, l, r, res, parent) || def;
544      }
545    }
546    for (DefinitionNavigator r : rc) {
547      if (!matchR.contains(r)) {
548        session.markAdded(r.current());
549        res.updateContentState(true);
550      }
551    }
552    return def;
553  }
554
555  private DefinitionNavigator findInList(List<DefinitionNavigator> rc, DefinitionNavigator l) {
556    String s = l.getId(); 
557    for (DefinitionNavigator t : rc) {
558      String ts = t.getId(); 
559      if (tail(ts).equals(tail(s))) {
560        return t;
561      }
562    }
563    return null;
564  }
565
566  private boolean compareTypes(String path, String sliceName, ElementDefinition left, ElementDefinition right, ProfileComparison res) {
567    boolean def = false;
568
569    List<TypeRefComponent> matchR = new ArrayList<>();
570    for (TypeRefComponent l : left.getType()) {
571      TypeRefComponent r = findInList(right.getType(), l);
572      if (r == null) {
573        session.markDeleted(right, "type", l);
574        res.updateContentState(true);
575      } else {
576        matchR.add(r);
577        def = compareType(path+".type", l, r, res) || def;
578      }
579    }
580    for (TypeRefComponent r : right.getType()) {
581      if (!matchR.contains(r)) {
582        session.markAdded(r);
583        res.updateContentState(true);
584      }
585    }
586    return def;
587  }
588
589  private TypeRefComponent findInList(List<TypeRefComponent> rc, TypeRefComponent l) {
590    for (TypeRefComponent t : rc) {
591      if (t.getCodeElement().equalsDeep(l.getCodeElement())) {
592        return t;
593      }
594    }
595    return null;
596  }
597  
598
599  private boolean compareType(String string, TypeRefComponent l, TypeRefComponent r, ProfileComparison res) {
600    boolean def = false;
601    boolean ch = false;
602    // codes must match
603    ch = comparePrimitivesWithTracking("profile", l.getProfile(), r.getProfile(), null, IssueSeverity.ERROR, null, r) || ch;
604    ch = comparePrimitivesWithTracking("targetProfile", l.getTargetProfile(), r.getTargetProfile(), null, IssueSeverity.ERROR, null, r) || ch;
605    ch = comparePrimitivesWithTracking("aggregation", l.getAggregation(), r.getAggregation(), null, IssueSeverity.ERROR, null, r) || ch;
606    def = comparePrimitivesWithTracking("versioning", l.getVersioningElement(), r.getVersioningElement(), null, IssueSeverity.INFORMATION, null, r) || def;    
607    if (ch) {
608      res.updateContentState(true);
609    }
610    return def;
611  }
612
613
614  private boolean compareInvariants(String path, String sliceName, ElementDefinition left, ElementDefinition right, ProfileComparison res) {
615    boolean def = false;
616
617    List<ElementDefinitionConstraintComponent> matchR = new ArrayList<>();
618    for (ElementDefinitionConstraintComponent l : left.getConstraint()) {
619      ElementDefinitionConstraintComponent r = findInList(right.getConstraint(), l);
620      if (r == null) {
621        session.markDeleted(right, "invariant", l);
622        res.updateContentState(true);
623      } else {
624        matchR.add(r);
625        def = compareInvariant(path+".type", l, r, res) || def;
626      }
627    }
628    for (ElementDefinitionConstraintComponent r : right.getConstraint()) {
629      if (!matchR.contains(r)) {
630        session.markAdded(r);
631        res.updateContentState(true);
632      }
633    }
634    return def;
635  }
636
637  private ElementDefinitionConstraintComponent findInList(List<ElementDefinitionConstraintComponent> rc, ElementDefinitionConstraintComponent l) {
638    for (ElementDefinitionConstraintComponent t : rc) {
639      if (t.getKeyElement().equalsDeep(l.getKeyElement())) {
640        return t;
641      }
642    }
643    return null;
644  }
645  
646
647  private boolean compareInvariant(String string, ElementDefinitionConstraintComponent l, ElementDefinitionConstraintComponent r, ProfileComparison res) {
648    boolean def = false;
649    boolean ch = false;
650    // codes must match
651    def = comparePrimitivesWithTracking("requirements", l.getRequirementsElement(), r.getRequirementsElement(), null, IssueSeverity.INFORMATION, null, r) || def;
652    ch = comparePrimitivesWithTracking("severity", l.getSeverityElement(), r.getSeverityElement(), null, IssueSeverity.ERROR, null, r) || ch;
653    comparePrimitivesWithTracking("suppress", l.getSuppressElement(), r.getSuppressElement(), null, IssueSeverity.INFORMATION, null, r);
654    def = comparePrimitivesWithTracking("human", l.getHumanElement(), r.getHumanElement(), null, IssueSeverity.INFORMATION, null, r) || def;    
655    ch = comparePrimitivesWithTracking("expression", l.getExpressionElement(), r.getExpressionElement(), null, IssueSeverity.ERROR, null, r) || ch;
656    if (ch) {
657      res.updateContentState(true);
658    }
659    return def;
660  }
661
662//  private void ruleEqual(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, DataType vLeft, DataType vRight, String name, String path) throws IOException {
663//    if (vLeft == null && vRight == null) {
664//      // nothing
665//    } else if (vLeft == null) {
666//      vm(IssueSeverity.ERROR, "Added "+name, path, comp.getMessages(), res.getMessages());
667//    } else if (vRight == null) {
668//      vm(IssueSeverity.ERROR, "Removed "+name, path, comp.getMessages(), res.getMessages());
669//    } else if (!Base.compareDeep(vLeft, vRight, false)) {
670//      vm(IssueSeverity.ERROR, name+" must be the same ("+toString(vLeft, true)+"/"+toString(vRight, false)+")", path, comp.getMessages(), res.getMessages());
671//    }
672//  }
673//
674  private String toString(DataType val, boolean left) throws IOException {
675    if (val instanceof PrimitiveType) 
676      return "'" + ((PrimitiveType) val).getValueAsString()+"'";
677    
678    IParser jp = new JsonParser();
679    return jp.composeString(val, "value");
680  }
681  
682  private String stripLinks(String s) {
683    while (s.contains("](")) {
684      int i = s.indexOf("](");
685      int j = s.substring(i).indexOf(")");
686      if (j == -1)
687        return s;
688      else
689        s = s.substring(0, i+1)+s.substring(i+j+1);
690    }
691    return s;
692  }
693  
694  private boolean rule(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, boolean test, String path, String message) {
695    if (!test)  {
696      vm(IssueSeverity.ERROR, message, path, comp.getMessages(), res.getMessages());
697    }
698    return test;
699  }
700
701  private String mergeText(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, String name, String left, String right, boolean isError) {
702    if (left == null && right == null)
703      return null;
704    if (left == null)
705      return right;
706    if (right == null)
707      return left;
708    left = stripLinks(left);
709    right = stripLinks(right);
710    if (left.equalsIgnoreCase(right))
711      return left;
712    return "left: "+left+"; right: "+right;
713  }
714
715  private List<Coding> mergeCodings(List<Coding> left, List<Coding> right) {
716    List<Coding> result = new ArrayList<Coding>();
717    result.addAll(left);
718    for (Coding c : right) {
719      boolean found = false;
720      for (Coding ct : left)
721        if (Utilities.equals(c.getSystem(), ct.getSystem()) && Utilities.equals(c.getCode(), ct.getCode()))
722          found = true;
723      if (!found)
724        result.add(c);
725    }
726    return result;
727  }
728
729  private List<StringType> mergeStrings(List<StringType> left, List<StringType> right) {
730    List<StringType> result = new ArrayList<StringType>();
731    result.addAll(left);
732    for (StringType c : right) {
733      boolean found = false;
734      for (StringType ct : left)
735        if (Utilities.equals(c.getValue(), ct.getValue()))
736          found = true;
737      if (!found)
738        result.add(c);
739    }
740    return result;
741  }
742
743  private List<ElementDefinitionMappingComponent> mergeMappings(List<ElementDefinitionMappingComponent> left, List<ElementDefinitionMappingComponent> right) {
744    List<ElementDefinitionMappingComponent> result = new ArrayList<ElementDefinitionMappingComponent>();
745    result.addAll(left);
746    for (ElementDefinitionMappingComponent c : right) {
747      boolean found = false;
748      for (ElementDefinitionMappingComponent ct : left)
749        if (Utilities.equals(c.getIdentity(), ct.getIdentity()) && Utilities.equals(c.getLanguage(), ct.getLanguage()) && Utilities.equals(c.getMap(), ct.getMap()))
750          found = true;
751      if (!found)
752        result.add(c);
753    }
754    return result;
755  }
756
757  private int intersectMin(int left, int right) {
758    if (left > right)
759      return left;
760    else
761      return right;
762  }
763
764  private void checkMinMax(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, int leftMin, int rightMin, int leftMax, int rightMax) {
765    if (leftMin != rightMin) {
766      if (leftMin == 0) {
767        vm(IssueSeverity.INFORMATION, "Element minimum cardinalities differ:  '"+leftMin+"' vs '"+rightMin+"'", path, comp.getMessages(), res.getMessages());
768      } else if (rightMin == 0) { 
769        vm(IssueSeverity.INFORMATION, "Element minimum cardinalities differ:  '"+leftMin+"' vs '"+rightMin+"'", path, comp.getMessages(), res.getMessages());
770      } else {
771        vm(IssueSeverity.INFORMATION, "Element minimum cardinalities differ:  '"+leftMin+"' vs '"+rightMin+"'", path, comp.getMessages(), res.getMessages());
772      }
773    }    
774    if (leftMax != rightMax) {
775      if (leftMax == Integer.MAX_VALUE) {
776        vm(IssueSeverity.INFORMATION, "Element maximum cardinalities differ:  '"+leftMax+"' vs '"+rightMax+"'", path, comp.getMessages(), res.getMessages());
777      } else if (rightMax == Integer.MAX_VALUE) { 
778        vm(IssueSeverity.INFORMATION, "Element maximum cardinalities differ:  '"+leftMax+"' vs '"+rightMax+"'", path, comp.getMessages(), res.getMessages());
779      } else {
780        vm(IssueSeverity.INFORMATION, "Element maximum cardinalities differ:  '"+leftMax+"' vs '"+rightMax+"'", path, comp.getMessages(), res.getMessages());
781      }
782    }    
783//    rule(comp, res, subset.getMax().equals("*") || Integer.parseInt(subset.getMax()) >= subset.getMin(), path, "Cardinality Mismatch: "+card(left)+"/"+card(right));
784
785    // cross comparison - if max > min in either direction, there can be no instances that are valid against both
786    if (leftMax < rightMin) {
787      vm(IssueSeverity.ERROR, "Element minimum cardinalities conflict:  '"+leftMin+".."+leftMax+"' vs '"+rightMin+".."+rightMax+"': No instances can be valid against both profiles", path, comp.getMessages(), res.getMessages());      
788    }
789    if (rightMax < leftMin) {
790      vm(IssueSeverity.ERROR, "Element minimum cardinalities conflict:  '"+leftMin+".."+leftMax+"' vs '"+rightMin+".."+rightMax+"': No instances can be valid against both profiles", path, comp.getMessages(), res.getMessages());            
791    }
792  }
793  
794  private int unionMin(int left, int right) {
795    if (left > right)
796      return right;
797    else
798      return left;
799  }
800
801  private String intersectMax(int l, int r, String left, String right) {
802    if (l < r)
803      return left;
804    else
805      return right;
806  }
807
808  private String unionMax(int l, int r, String left, String right) {
809    if (l < r)
810      return right;
811    else
812      return left;
813  }
814
815  private IntegerType intersectMaxLength(int left, int right) {
816    if (left == 0) 
817      left = Integer.MAX_VALUE;
818    if (right == 0) 
819      right = Integer.MAX_VALUE;
820    if (left < right)
821      return left == Integer.MAX_VALUE ? null : new IntegerType(left);
822    else
823      return right == Integer.MAX_VALUE ? null : new IntegerType(right);
824  }
825
826  private IntegerType unionMaxLength(int left, int right) {
827    if (left == 0) 
828      left = Integer.MAX_VALUE;
829    if (right == 0) 
830      right = Integer.MAX_VALUE;
831    if (left < right)
832      return right == Integer.MAX_VALUE ? null : new IntegerType(right);
833    else
834      return left == Integer.MAX_VALUE ? null : new IntegerType(left);
835  }
836
837  private String card(DefinitionNavigator defn) {
838    return Integer.toString(defn.current().getMin())+".."+defn.current().getMax();
839  }
840
841  private Collection<? extends TypeRefComponent> unionTypes(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, List<TypeRefComponent> left, List<TypeRefComponent> right, Resource leftSrc, Resource rightSrc) throws DefinitionException, IOException, FHIRFormatError {
842    List<TypeRefComponent> result = new ArrayList<TypeRefComponent>();
843    for (TypeRefComponent l : left) 
844      checkAddTypeUnion(comp, res, path, result, l, session.getContextLeft(), leftSrc);
845    for (TypeRefComponent r : right) 
846      checkAddTypeUnion(comp, res, path, result, r, session.getContextRight(), rightSrc);
847    return result;
848  }    
849
850  private void checkAddTypeUnion(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, List<TypeRefComponent> results, TypeRefComponent nw, IWorkerContext ctxt, Resource nwSource) throws DefinitionException, IOException, FHIRFormatError {
851    boolean pfound = false;
852    boolean tfound = false;
853    nw = nw.copy();
854    for (TypeRefComponent ex : results) {
855      if (Utilities.equals(ex.getWorkingCode(), nw.getWorkingCode())) {
856        for (Enumeration<AggregationMode> a : nw.getAggregation()) {
857          if (!ex.hasAggregation(a.getValue())) {
858            ex.addAggregation(a.getValue());
859          }
860        }
861        if (!ex.hasProfile() && !nw.hasProfile())
862          pfound = true;
863        else if (!ex.hasProfile()) {
864          pfound = true; 
865        } else if (!nw.hasProfile()) {
866          pfound = true;
867          ex.setProfile(null);
868        } else {
869          // both have profiles. Is one derived from the other? 
870          StructureDefinition sdex = ((IWorkerContext) ex.getUserData("ctxt")).fetchResource(StructureDefinition.class, ex.getProfile().get(0).getValue(), nwSource);
871          StructureDefinition sdnw = ctxt.fetchResource(StructureDefinition.class, nw.getProfile().get(0).getValue(), nwSource);
872          if (sdex != null && sdnw != null) {
873            if (sdex.getUrl().equals(sdnw.getUrl())) {
874              pfound = true;
875            } else if (derivesFrom(sdex, sdnw, ((IWorkerContext) ex.getUserData("ctxt")))) {
876              ex.setProfile(nw.getProfile());
877              pfound = true;
878            } else if (derivesFrom(sdnw, sdex, ctxt)) {
879              pfound = true;
880            } else if (sdnw.getSnapshot().getElement().get(0).getPath().equals(sdex.getSnapshot().getElement().get(0).getPath())) {
881              ProfileComparison compP = (ProfileComparison) session.compare(sdex, sdnw);
882              if (compP != null && compP.getUnion() != null) { // might be null if circular
883                pfound = true;
884                ex.addProfile("#"+compP.getId());
885              }
886            }
887          }
888        }        
889        if (!ex.hasTargetProfile() && !nw.hasTargetProfile())
890          tfound = true;
891        else if (!ex.hasTargetProfile()) {
892          tfound = true; 
893        } else if (!nw.hasTargetProfile()) {
894          tfound = true;
895          ex.setTargetProfile(null);
896        } else {
897          // both have profiles. Is one derived from the other? 
898          StructureDefinition sdex = ((IWorkerContext) ex.getUserData("ctxt")).fetchResource(StructureDefinition.class, ex.getTargetProfile().get(0).getValue(), nwSource);
899          StructureDefinition sdnw = ctxt.fetchResource(StructureDefinition.class, nw.getTargetProfile().get(0).getValue(), nwSource);
900          if (sdex != null && sdnw != null) {
901            if (matches(sdex, sdnw)) {
902              tfound = true;
903            } else if (derivesFrom(sdex, sdnw, ((IWorkerContext) ex.getUserData("ctxt")))) {
904              ex.setTargetProfile(nw.getTargetProfile());
905              tfound = true;
906            } else if (derivesFrom(sdnw, sdex, ctxt)) {
907              tfound = true;
908            } else if (sdnw.getSnapshot().getElement().get(0).getPath().equals(sdex.getSnapshot().getElement().get(0).getPath())) {
909              ProfileComparison compP = (ProfileComparison) session.compare(sdex, sdnw);
910              if (compP.getUnion() != null) {
911                tfound = true;
912                ex.addTargetProfile("#"+compP.getId());
913              }
914            }
915          }
916        }        
917      }
918    }
919    if (!tfound || !pfound) {
920      nw.setUserData("ctxt", ctxt);
921      results.add(nw);      
922    }
923  }
924
925  private boolean matches(StructureDefinition s1, StructureDefinition s2) {
926    if (!s1.getUrl().equals(s2.getUrl())) {
927      return false;
928    }
929    if (s1.getDerivation() == TypeDerivationRule.SPECIALIZATION && s2.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
930      return true; // arbitrary; we're just not interested in pursuing cross version differences
931    }
932    if (s1.hasVersion()) {
933      return  s1.getVersion().equals(s2.getVersion());
934    } else {
935      return !s2.hasVersion();
936    }
937  }
938
939  private boolean derivesFrom(StructureDefinition left, StructureDefinition right, IWorkerContext ctxt) {
940    StructureDefinition sd = left;
941    while (sd != null) {
942      if (right.getUrl().equals(sd.getBaseDefinition())) {
943        return true;
944      }
945      sd = sd.hasBaseDefinition() ? ctxt.fetchResource(StructureDefinition.class, sd.getBaseDefinition(), sd) : null;
946    }
947    return false;
948  }
949
950  private Collection<? extends TypeRefComponent> intersectTypes(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, ElementDefinition ed, String path, List<TypeRefComponent> left, List<TypeRefComponent> right) throws DefinitionException, IOException, FHIRFormatError {
951    List<TypeRefComponent> result = new ArrayList<TypeRefComponent>();
952    for (TypeRefComponent l : left) {
953      boolean pfound = false;
954      boolean tfound = false;
955      TypeRefComponent c = l.copy();
956      for (TypeRefComponent r : right) {
957        if (!l.hasProfile() && !r.hasProfile()) {
958          pfound = true;    
959        } else if (!r.hasProfile()) {
960          pfound = true; 
961        } else if (!l.hasProfile()) {
962          pfound = true;
963          c.setProfile(r.getProfile());
964        } else {
965          StructureDefinition sdl = resolveProfile(comp, res, path, l.getProfile().get(0).getValue(), comp.getLeft().getName(), session.getContextLeft(), comp.getLeft());
966          StructureDefinition sdr = resolveProfile(comp, res, path, r.getProfile().get(0).getValue(), comp.getRight().getName(), session.getContextRight(), comp.getRight());
967          if (sdl != null && sdr != null) {
968            if (sdl == sdr) {
969              pfound = true;
970            } else if (derivesFrom(sdl, sdr, session.getContextLeft())) {
971              pfound = true;
972            } else if (derivesFrom(sdr, sdl, session.getContextRight())) {
973              c.setProfile(r.getProfile());
974              pfound = true;
975            } else if (sdl.getType().equals(sdr.getType())) {
976              ProfileComparison compP = (ProfileComparison) session.compare(sdl, sdr);
977              if (compP != null && compP.getIntersection() != null) {
978                pfound = true;
979                c.addProfile("#"+compP.getId());
980              }
981            }
982          }
983        }
984        if (!l.hasTargetProfile() && !r.hasTargetProfile()) {
985          tfound = true;    
986        } else if (!r.hasTargetProfile()) {
987          tfound = true; 
988        } else if (!l.hasTargetProfile()) {
989          tfound = true;
990          c.setTargetProfile(r.getTargetProfile());
991        } else {
992          StructureDefinition sdl = resolveProfile(comp, res, path, l.getTargetProfile().get(0).getValue(), comp.getLeft().getName(), session.getContextLeft(), comp.getLeft());
993          StructureDefinition sdr = resolveProfile(comp, res, path, r.getTargetProfile().get(0).getValue(), comp.getRight().getName(), session.getContextRight(), comp.getRight());
994          if (sdl != null && sdr != null) {
995            if (matches(sdl, sdr)) {
996              tfound = true;
997            } else if (derivesFrom(sdl, sdr, session.getContextLeft())) {
998              tfound = true;
999            } else if (derivesFrom(sdr, sdl, session.getContextRight())) {
1000              c.setTargetProfile(r.getTargetProfile());
1001              tfound = true;
1002            } else if (sdl.getType().equals(sdr.getType())) {
1003              ProfileComparison compP = (ProfileComparison) session.compare(sdl, sdr);
1004              if (compP != null && compP.getIntersection() != null) {
1005                tfound = true;
1006                c.addTargetProfile("#"+compP.getId());
1007              }
1008            }
1009          }
1010        }
1011        if (pfound && tfound) {
1012          for (Enumeration<AggregationMode> a : l.getAggregation()) {
1013            if (!r.hasAggregation(a.getValue())) {
1014              c.getAggregation().removeIf(n -> n.getValue() == a.getValue());
1015            }
1016          }
1017        }
1018      }
1019      if (pfound && tfound) {
1020        result.add(c);
1021      }
1022    }
1023    return result;
1024  }
1025
1026  private String typeCode(DefinitionNavigator defn) {
1027    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
1028    for (TypeRefComponent t : defn.current().getType())
1029      b.append(t.getWorkingCode()+(t.hasProfile() ? "("+t.getProfile()+")" : "")+(t.hasTargetProfile() ? "("+t.getTargetProfile()+")" : "")); // todo: other properties
1030    return b.toString();
1031  }
1032
1033  private boolean compareBindings(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, ElementDefinition subset, ElementDefinition superset, String path, ElementDefinition lDef, ElementDefinition rDef, Resource leftSrc, Resource rightSrc) throws FHIRFormatError, DefinitionException, IOException {
1034    assert(lDef.hasBinding() || rDef.hasBinding());
1035    if (!lDef.hasBinding()) {
1036      subset.setBinding(rDef.getBinding());
1037      // technically, the super set is unbound, but that's not very useful - so we use the provided on as an example
1038      superset.setBinding(rDef.getBinding().copy());
1039      superset.getBinding().setStrength(BindingStrength.EXAMPLE);
1040      return true;
1041    }
1042    if (!rDef.hasBinding()) {
1043      subset.setBinding(lDef.getBinding());
1044      superset.setBinding(lDef.getBinding().copy());
1045      superset.getBinding().setStrength(BindingStrength.EXAMPLE);
1046      return true;
1047    }
1048    ElementDefinitionBindingComponent left = lDef.getBinding();
1049    ElementDefinitionBindingComponent right = rDef.getBinding();
1050    if (Base.compareDeep(left, right, false)) {
1051      subset.setBinding(left);
1052      superset.setBinding(right);      
1053    }
1054
1055    // if they're both examples/preferred then:
1056    // subset: left wins if they're both the same
1057    // superset: 
1058    if (isPreferredOrExample(left) && isPreferredOrExample(right)) {
1059      if (right.getStrength() == BindingStrength.PREFERRED && left.getStrength() == BindingStrength.EXAMPLE && !Base.compareDeep(left.getValueSet(), right.getValueSet(), false)) {
1060        vm(IssueSeverity.INFORMATION, "Example/preferred bindings differ at "+path+" using binding from "+comp.getRight().getName(), path, comp.getMessages(), res.getMessages());
1061        subset.setBinding(right);
1062        superset.setBinding(unionBindings(comp, res, path, left, right, leftSrc, rightSrc));
1063      } else {
1064        if ((right.getStrength() != BindingStrength.EXAMPLE || left.getStrength() != BindingStrength.EXAMPLE) && !Base.compareDeep(left.getValueSet(), right.getValueSet(), false) ) { 
1065          vm(IssueSeverity.INFORMATION, "Example/preferred bindings differ at "+path+" using binding from "+comp.getLeft().getName(), path, comp.getMessages(), res.getMessages());
1066        }
1067        subset.setBinding(left);
1068        superset.setBinding(unionBindings(comp, res, path, left, right, leftSrc, rightSrc));
1069      }
1070      return true;
1071    }
1072    // if either of them are extensible/required, then it wins
1073    if (isPreferredOrExample(left)) {
1074      subset.setBinding(right);
1075      superset.setBinding(unionBindings(comp, res, path, left, right, leftSrc, rightSrc));
1076      return true;
1077    }
1078    if (isPreferredOrExample(right)) {
1079      subset.setBinding(left);
1080      superset.setBinding(unionBindings(comp, res, path, left, right, leftSrc, rightSrc));
1081      return true;
1082    }
1083
1084    // ok, both are extensible or required.
1085    ElementDefinitionBindingComponent subBinding = new ElementDefinitionBindingComponent();
1086    subset.setBinding(subBinding);
1087    ElementDefinitionBindingComponent superBinding = new ElementDefinitionBindingComponent();
1088    superset.setBinding(superBinding);
1089    subBinding.setDescription(mergeText(comp, res, path, "description", left.getDescription(), right.getDescription(), false));
1090    superBinding.setDescription(mergeText(comp, res, path, "description", left.getDescription(), right.getDescription(), false));
1091    if (left.getStrength() == BindingStrength.REQUIRED || right.getStrength() == BindingStrength.REQUIRED)
1092      subBinding.setStrength(BindingStrength.REQUIRED);
1093    else
1094      subBinding.setStrength(BindingStrength.EXTENSIBLE);
1095    if (left.getStrength() == BindingStrength.EXTENSIBLE || right.getStrength() == BindingStrength.EXTENSIBLE)
1096      superBinding.setStrength(BindingStrength.EXTENSIBLE);
1097    else
1098      superBinding.setStrength(BindingStrength.REQUIRED);
1099
1100    if (Base.compareDeep(left.getValueSet(), right.getValueSet(), false)) {
1101      subBinding.setValueSet(left.getValueSet());
1102      superBinding.setValueSet(left.getValueSet());
1103      return true;
1104    } else if (!left.hasValueSet()) {
1105      vm(IssueSeverity.ERROR, "No left Value set at "+path, path, comp.getMessages(), res.getMessages());
1106      return true;      
1107    } else if (!right.hasValueSet()) {
1108      vm(IssueSeverity.ERROR, "No right Value set at "+path, path, comp.getMessages(), res.getMessages());
1109      return true;      
1110    } else {
1111      // ok, now we compare the value sets. This may be unresolvable. 
1112      ValueSet lvs = resolveVS(comp.getLeft(), left.getValueSet(), leftSrc, session.getContextLeft());
1113      ValueSet rvs = resolveVS(comp.getRight(), right.getValueSet(), rightSrc, session.getContextRight());
1114      if (lvs == null) {
1115        vm(IssueSeverity.ERROR, "Unable to resolve left value set "+left.getValueSet().toString()+" at "+path, path, comp.getMessages(), res.getMessages());
1116        return true;
1117      } else if (rvs == null) {
1118        vm(IssueSeverity.ERROR, "Unable to resolve right value set "+right.getValueSet().toString()+" at "+path, path, comp.getMessages(), res.getMessages());
1119        return true;        
1120      } else if (sameValueSets(lvs, rvs)) {
1121        subBinding.setValueSet(lvs.getUrl());
1122        superBinding.setValueSet(lvs.getUrl());
1123      } else {
1124        ValueSetComparison compP = (ValueSetComparison) session.compare(lvs, rvs);
1125        if (compP != null) {
1126          subBinding.setValueSet(compP.getIntersection().getUrl());
1127          superBinding.setValueSet(compP.getUnion().getUrl());
1128        }
1129      }
1130    }
1131    return false;
1132  }
1133
1134  private boolean sameValueSets(ValueSet lvs, ValueSet rvs) {
1135    if (!lvs.getUrl().equals(rvs.getUrl())) {
1136      return false;
1137    }
1138    if (isCore(lvs) && isCore(rvs)) {
1139      return true;
1140    }
1141    if (lvs.hasVersion()) {
1142      if (!lvs.getVersion().equals(rvs.getVersion())) {
1143        return false;
1144      } else if (!rvs.hasVersion()) {
1145        return false;
1146      }
1147    }
1148    return true;
1149  }
1150
1151  private boolean isCore(ValueSet vs) {
1152    return vs.getUrl().startsWith("http://hl7.org/fhir/ValueSet");
1153  }
1154
1155  private List<ElementDefinitionConstraintComponent> intersectConstraints(String path, List<ElementDefinitionConstraintComponent> left, List<ElementDefinitionConstraintComponent> right) {
1156    List<ElementDefinitionConstraintComponent> result = new ArrayList<ElementDefinitionConstraintComponent>();
1157    for (ElementDefinitionConstraintComponent l : left) {
1158      boolean found = false;
1159      for (ElementDefinitionConstraintComponent r : right)
1160        if (Utilities.equals(r.getId(), l.getId()) || (Utilities.equals(r.getExpression(), l.getExpression()) && r.getSeverity() == l.getSeverity()))
1161          found = true;
1162      if (found)
1163        result.add(l);
1164    }
1165    return result;
1166  }
1167
1168  // we can't really know about constraints. We create warnings, and collate them 
1169  private List<ElementDefinitionConstraintComponent> unionConstraints(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, List<ElementDefinitionConstraintComponent> left, List<ElementDefinitionConstraintComponent> right) {
1170    List<ElementDefinitionConstraintComponent> result = new ArrayList<ElementDefinitionConstraintComponent>();
1171    for (ElementDefinitionConstraintComponent l : left) {
1172      boolean found = false;
1173      for (ElementDefinitionConstraintComponent r : right)
1174        if (Utilities.equals(r.getId(), l.getId()) || (Utilities.equals(r.getExpression(), l.getExpression()) && r.getSeverity() == l.getSeverity()))
1175          found = true;
1176      if (!found) {
1177        if (!Utilities.existsInList(l.getExpression(), "hasValue() or (children().count() > id.count())", "extension.exists() != value.exists()")) {
1178          vm(IssueSeverity.INFORMATION,  "StructureDefinition "+comp.getLeft().getName()+" has a constraint that is removed in "+comp.getRight().getName()+" and it is uncertain whether they are compatible ("+l.getExpression()+")", path, comp.getMessages(), res.getMessages());
1179        }
1180      }
1181      result.add(l);
1182    }
1183    for (ElementDefinitionConstraintComponent r : right) {
1184      boolean found = false;
1185      for (ElementDefinitionConstraintComponent l : left)
1186        if (Utilities.equals(r.getId(), l.getId()) || (Utilities.equals(r.getExpression(), l.getExpression()) && r.getSeverity() == l.getSeverity()))
1187          found = true;
1188      if (!found) {
1189        if (!Utilities.existsInList(r.getExpression(), "hasValue() or (children().count() > id.count())", "extension.exists() != value.exists()")) {
1190          vm(IssueSeverity.INFORMATION,  "StructureDefinition "+comp.getRight().getName()+" has added constraint that is not found in "+comp.getLeft().getName()+" and it is uncertain whether they are compatible ("+r.getExpression()+")", path, comp.getMessages(), res.getMessages());
1191        }
1192      }
1193    }
1194    return result;
1195  }
1196
1197  private StructureDefinition resolveProfile(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, String url, String name, IWorkerContext ctxt, Resource urlSource) {
1198    StructureDefinition sd = ctxt.fetchResource(StructureDefinition.class, url, urlSource);
1199    if (sd == null) {
1200      ValidationMessage vm = vmI(IssueSeverity.WARNING, "Unable to resolve profile "+url+" in profile "+name, path);
1201    }
1202    return sd;
1203  }
1204
1205  private boolean isPreferredOrExample(ElementDefinitionBindingComponent binding) {
1206    return binding.getStrength() == BindingStrength.EXAMPLE || binding.getStrength() == BindingStrength.PREFERRED;
1207  }
1208
1209  private ElementDefinitionBindingComponent unionBindings(ProfileComparison comp, StructuralMatch<ElementDefinitionNode> res, String path, ElementDefinitionBindingComponent left, ElementDefinitionBindingComponent right, Resource leftSrc, Resource rightSrc) throws FHIRFormatError, DefinitionException, IOException {
1210    ElementDefinitionBindingComponent union = new ElementDefinitionBindingComponent();
1211    if (left.getStrength().compareTo(right.getStrength()) < 0)
1212      union.setStrength(left.getStrength());
1213    else
1214      union.setStrength(right.getStrength());
1215    union.setDescription(mergeText(comp, res, path, "binding.description", left.getDescription(), right.getDescription(), false));
1216    if (Base.compareDeep(left.getValueSet(), right.getValueSet(), false))
1217      union.setValueSet(left.getValueSet());
1218    else {
1219      ValueSet lvs = resolveVS(comp.getLeft(), left.getValueSet(), leftSrc, session.getContextLeft());
1220      ValueSet rvs = resolveVS(comp.getRight(), right.getValueSet(), rightSrc, session.getContextRight());
1221      if (lvs != null && rvs != null) {
1222        ValueSetComparison compP = (ValueSetComparison) session.compare(lvs, rvs);
1223        if (compP != null) {
1224          union.setValueSet(compP.getUnion().getUrl());
1225        }
1226      } else if (lvs != null) {
1227        union.setValueSet(lvs.getUrl());
1228      } else if (rvs != null) {
1229        union.setValueSet(rvs.getUrl());
1230      }
1231    }
1232    return union;
1233  }
1234
1235  private ValueSet resolveVS(StructureDefinition ctxtLeft, String vsRef, Resource src, IWorkerContext ctxt) {
1236    if (vsRef == null)
1237      return null;
1238    return ctxt.fetchResource(ValueSet.class, vsRef, src);
1239  }
1240
1241  public XhtmlNode renderStructure(ProfileComparison comp, String id, String prefix, String corePath) throws FHIRException, IOException {
1242    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(Utilities.path("[tmp]", "compare"), false, true);
1243    gen.setTranslator(session.getContextRight().translator());
1244    TableModel model = gen.initComparisonTable(corePath, id);
1245    genElementComp(null /* oome back to this later */, gen, model.getRows(), comp.combined, corePath, prefix, null, true);
1246    return gen.generate(model, prefix, 0, null);
1247  }
1248
1249  private void genElementComp(String defPath, HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<ElementDefinitionNode> combined, String corePath, String prefix, Row slicingRow, boolean root) throws IOException {
1250    Row originalRow = slicingRow;
1251    Row typesRow = null;
1252    
1253    List<StructuralMatch<ElementDefinitionNode>> children = combined.getChildren();
1254
1255    Row row = gen.new Row();
1256    rows.add(row);
1257    String path = combined.either().getDef().getPath();
1258    row.setAnchor(path);
1259      row.setColor(utilsRight.getRowColor(combined.either().getDef(), false));
1260      if (eitherHasSlicing(combined))
1261        row.setLineColor(1);
1262      else if (eitherHasSliceName(combined))
1263        row.setLineColor(2);
1264      else
1265        row.setLineColor(0);
1266      boolean ext = false;
1267      if (tail(path).equals("extension")) {
1268        if (elementIsComplex(combined))
1269          row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
1270        else
1271          row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
1272        ext = true;
1273      } else if (tail(path).equals("modifierExtension")) {
1274        if (elementIsComplex(combined))
1275          row.setIcon("icon_modifier_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
1276        else
1277          row.setIcon("icon_modifier_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
1278      } else if (hasChoice(combined)) {
1279        if (allAreReference(combined))
1280          row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
1281        else {
1282          row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE);
1283          typesRow = row;
1284        }
1285      } else if (combined.either().getDef().hasContentReference())
1286        row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE);
1287      else if (isPrimitive(combined))
1288        row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
1289      else if (hasTarget(combined))
1290        row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
1291      else if (isDataType(combined))
1292        row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
1293      else
1294        row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
1295      String ref = defPath == null ? null : defPath + combined.either().getDef().getId();
1296      String sName = tail(path);
1297      String sn = getSliceName(combined);
1298      if (sn != null)
1299        sName = sName +":"+sn;
1300      StructureDefinitionRenderer.UnusedTracker used = new StructureDefinitionRenderer.UnusedTracker();
1301      StructureDefinitionRenderer sdrLeft = new StructureDefinitionRenderer(new RenderingContext(utilsLeft.getContext(), null, utilsLeft.getTerminologyServiceOptions(), corePath, prefix, null, ResourceRendererMode.TECHNICAL, GenerationRules.IG_PUBLISHER).setPkp(this));
1302      StructureDefinitionRenderer sdrRight= new StructureDefinitionRenderer(new RenderingContext(utilsRight.getContext(), null, utilsRight.getTerminologyServiceOptions(), corePath, prefix, null, ResourceRendererMode.TECHNICAL, GenerationRules.IG_PUBLISHER).setPkp(this));
1303
1304
1305        
1306      Cell nc;
1307      String leftColor = !combined.hasLeft() ? COLOR_NO_ROW_LEFT : combined.hasErrors() ? COLOR_DIFFERENT : null;
1308      String rightColor = !combined.hasRight() ? COLOR_NO_ROW_LEFT : combined.hasErrors() ? COLOR_DIFFERENT : null;
1309      if (combined.hasLeft()) {
1310        nc = sdrLeft.genElementNameCell(gen, combined.getLeft().getDef(),  "??", true, corePath, prefix, root, false, false, combined.getLeft().getSrc(), typesRow, row, false, ext, used , ref, sName, null);
1311      } else {
1312        nc = sdrRight.genElementNameCell(gen, combined.getRight().getDef(),  "??", true, corePath, prefix, root, false, false, combined.getRight().getSrc(), typesRow, row, false, ext, used , ref, sName, null);
1313      }
1314      if (combined.hasLeft()) {
1315        frame(sdrLeft.genElementCells(gen, combined.getLeft().getDef(),  "??", true, corePath, prefix, root, false, false, combined.getLeft().getSrc(), typesRow, row, true, ext, used , ref, sName, nc, false, false, null), leftColor);
1316      } else {
1317        frame(spacers(row, 4, gen), leftColor);
1318      }
1319      if (combined.hasRight()) {
1320        frame(sdrRight.genElementCells(gen, combined.getRight().getDef(), "??", true, corePath, prefix, root, false, false, combined.getRight().getSrc(), typesRow, row, true, ext, used, ref, sName, nc, false, false, null), rightColor);
1321      } else {
1322        frame(spacers(row, 4, gen), rightColor);
1323      }
1324      row.getCells().add(cellForMessages(gen, combined.getMessages()));
1325
1326      for (StructuralMatch<ElementDefinitionNode> child : children) {
1327        genElementComp(defPath, gen, row.getSubRows(), child, corePath, prefix, originalRow, false);
1328      }
1329    }
1330
1331  private void frame(List<Cell> cells, String color) {
1332    for (Cell cell : cells) {
1333      if (color != null) {
1334        cell.setStyle("background-color: "+color);
1335      }
1336    }
1337    cells.get(0).setStyle("border-left: 1px grey solid"+(color == null ? "" : "; background-color: "+color));
1338    cells.get(cells.size()-1).setStyle("border-right: 1px grey solid"+(color == null ? "" : "; background-color: "+color));
1339  }
1340
1341  private List<Cell> spacers(Row row, int count, HierarchicalTableGenerator gen) {
1342    List<Cell> res = new ArrayList<>();
1343    for (int i = 0; i < count; i++) {
1344      Cell c = gen.new Cell();
1345      res.add(c);
1346      row.getCells().add(c);
1347    }
1348    return res;
1349  }
1350
1351  private String getSliceName(StructuralMatch<ElementDefinitionNode> combined) {
1352    // TODO Auto-generated method stub
1353    return null;
1354  }
1355
1356  private boolean isDataType(StructuralMatch<ElementDefinitionNode> combined) {
1357    // TODO Auto-generated method stub
1358    return false;
1359  }
1360
1361  private boolean hasTarget(StructuralMatch<ElementDefinitionNode> combined) {
1362    // TODO Auto-generated method stub
1363    return false;
1364  }
1365
1366  private boolean isPrimitive(StructuralMatch<ElementDefinitionNode> combined) {
1367    // TODO Auto-generated method stub
1368    return false;
1369  }
1370
1371  private boolean allAreReference(StructuralMatch<ElementDefinitionNode> combined) {
1372    // TODO Auto-generated method stub
1373    return false;
1374  }
1375
1376  private boolean hasChoice(StructuralMatch<ElementDefinitionNode> combined) {
1377    // TODO Auto-generated method stub
1378    return false;
1379  }
1380
1381  private boolean elementIsComplex(StructuralMatch<ElementDefinitionNode> combined) {
1382    // TODO Auto-generated method stub velement.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue()
1383    return false;
1384  }
1385
1386  private boolean eitherHasSliceName(StructuralMatch<ElementDefinitionNode> combined) {
1387    // TODO Auto-generated method stub
1388    return false;
1389  }
1390
1391  private boolean eitherHasSlicing(StructuralMatch<ElementDefinitionNode> combined) {
1392    // TODO Auto-generated method stub
1393    return false;
1394  }
1395  
1396
1397  
1398
1399private String tail(String path) {
1400  if (path.contains("."))
1401    return path.substring(path.lastIndexOf('.')+1);
1402  else
1403    return path;
1404}
1405
1406@Override
1407public boolean isDatatype(String typeSimple) {
1408  // TODO Auto-generated method stub
1409  return false;
1410}
1411
1412@Override
1413public boolean isPrimitiveType(String typeSimple) {
1414  // TODO Auto-generated method stub
1415  return false;
1416}
1417
1418@Override
1419public boolean isResource(String typeSimple) {
1420//  return false;
1421  throw new NotImplementedError();
1422}
1423
1424@Override
1425public boolean hasLinkFor(String typeSimple) {
1426  return false;
1427}
1428
1429@Override
1430public String getLinkFor(String corePath, String typeSimple) {
1431  return "??|??";
1432}
1433
1434@Override
1435public BindingResolution resolveBinding(StructureDefinition def, ElementDefinitionBindingComponent binding, String path)
1436    throws FHIRException {
1437  return new BindingResolution("??", "??");
1438}
1439
1440@Override
1441public BindingResolution resolveBinding(StructureDefinition def, String url, String path) throws FHIRException {
1442  return new BindingResolution("??", "??");
1443}
1444
1445@Override
1446public String getLinkForProfile(StructureDefinition profile, String url) {
1447  return "??|??";
1448}
1449
1450@Override
1451public boolean prependLinks() {
1452  return false;
1453}
1454
1455@Override
1456public String getLinkForUrl(String corePath, String s) {
1457  return null;
1458}
1459  
1460
1461}