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