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