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