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