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