001package org.hl7.fhir.r4b.comparison;
002
003import java.io.IOException;
004import java.util.ArrayList;
005import java.util.Collection;
006import java.util.Date;
007import java.util.List;
008
009import org.hl7.fhir.exceptions.DefinitionException;
010import org.hl7.fhir.exceptions.FHIRException;
011import org.hl7.fhir.exceptions.FHIRFormatError;
012import org.hl7.fhir.r4b.comparison.ResourceComparer.MessageCounts;
013import org.hl7.fhir.r4b.comparison.ValueSetComparer.ValueSetComparison;
014import org.hl7.fhir.r4b.conformance.ProfileUtilities;
015import org.hl7.fhir.r4b.conformance.ProfileUtilities.UnusedTracker;
016import org.hl7.fhir.r4b.context.IWorkerContext;
017import org.hl7.fhir.r4b.formats.IParser;
018import org.hl7.fhir.r4b.model.Base;
019import org.hl7.fhir.r4b.model.Coding;
020import org.hl7.fhir.r4b.model.DataType;
021import org.hl7.fhir.r4b.model.ElementDefinition;
022import org.hl7.fhir.r4b.model.ElementDefinition.ElementDefinitionBindingComponent;
023import org.hl7.fhir.r4b.model.ElementDefinition.ElementDefinitionConstraintComponent;
024import org.hl7.fhir.r4b.model.ElementDefinition.ElementDefinitionMappingComponent;
025import org.hl7.fhir.r4b.model.ElementDefinition.TypeRefComponent;
026import org.hl7.fhir.r4b.model.Enumerations.BindingStrength;
027import org.hl7.fhir.r4b.model.IntegerType;
028import org.hl7.fhir.r4b.model.PrimitiveType;
029import org.hl7.fhir.r4b.model.StringType;
030import org.hl7.fhir.r4b.model.StructureDefinition;
031import org.hl7.fhir.r4b.model.StructureDefinition.TypeDerivationRule;
032import org.hl7.fhir.r4b.model.ValueSet;
033import org.hl7.fhir.r4b.utils.DefinitionNavigator;
034import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
035import org.hl7.fhir.utilities.Utilities;
036import org.hl7.fhir.utilities.validation.ValidationMessage;
037import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
038import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
039import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
040import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
041import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
042import org.hl7.fhir.utilities.xhtml.XhtmlNode;
043
044public class ProfileComparer extends CanonicalResourceComparer {
045
046  public class ProfileComparison extends CanonicalResourceComparison<StructureDefinition> {
047
048    private StructuralMatch<ElementDefinition> combined;                                             
049
050    public ProfileComparison(StructureDefinition left, StructureDefinition right) {
051      super(left, right);
052      combined = new StructuralMatch<ElementDefinition>(); // base
053    }
054
055    public StructuralMatch<ElementDefinition> getCombined() {
056      return combined;
057    }
058
059    @Override
060    protected String abbreviation() {
061      return "sd";
062    }
063
064    @Override
065    protected String summary() {
066      return "Profile: "+left.present()+" vs "+right.present();
067    }
068
069    @Override
070    protected String fhirType() {
071      return "StructureDefinition";
072    }
073    @Override
074    protected void countMessages(MessageCounts cnts) {
075      super.countMessages(cnts);
076      combined.countMessages(cnts);
077    }
078
079  }
080
081
082
083
084  private ProfileUtilities utilsLeft;
085  private ProfileUtilities utilsRight;
086
087  public ProfileComparer(ComparisonSession session, ProfileUtilities utilsLeft, ProfileUtilities utilsRight) {
088    super(session);
089    this.utilsLeft = utilsLeft;
090    this.utilsRight = utilsRight;
091  }
092
093  @Override
094  protected String fhirType() {
095    return "StructureDefinition";
096  }
097
098  public ProfileComparison compare(StructureDefinition left, StructureDefinition right) throws DefinitionException, FHIRFormatError, IOException {
099    check(left, "left");
100    check(right, "right");
101
102    ProfileComparison res = new ProfileComparison(left, right);
103    session.identify(res);
104    StructureDefinition sd = new StructureDefinition();
105    res.setUnion(sd);
106    session.identify(sd);
107    sd.setName("Union"+left.getName()+"And"+right.getName());
108    sd.setTitle("Union of "+left.getTitle()+" And "+right.getTitle());
109    sd.setStatus(left.getStatus());
110    sd.setDate(new Date());
111
112    StructureDefinition sd1 = new StructureDefinition();
113    res.setIntersection(sd1);
114    session.identify(sd1);
115    sd1.setName("Intersection"+left.getName()+"And"+right.getName());
116    sd1.setTitle("Intersection of "+left.getTitle()+" And "+right.getTitle());
117    sd1.setStatus(left.getStatus());
118    sd1.setDate(new Date());
119
120    compareMetadata(left, right, res.getMetadata(), res);
121    comparePrimitives("fhirVersion", left.getFhirVersionElement(), right.getFhirVersionElement(), res.getMetadata(), IssueSeverity.WARNING, res);
122    comparePrimitives("kind", left.getKindElement(), right.getKindElement(), res.getMetadata(), IssueSeverity.WARNING, res);
123    comparePrimitives("abstract", left.getAbstractElement(), right.getAbstractElement(), res.getMetadata(), IssueSeverity.WARNING, res);
124    comparePrimitives("type", left.getTypeElement(), right.getTypeElement(), res.getMetadata(), IssueSeverity.ERROR, res);
125    comparePrimitives("baseDefinition", left.getBaseDefinitionElement(), right.getBaseDefinitionElement(), res.getMetadata(), IssueSeverity.ERROR, res);
126
127    if (left.getType().equals(right.getType())) {
128      DefinitionNavigator ln = new DefinitionNavigator(session.getContextLeft(), left);
129      DefinitionNavigator rn = new DefinitionNavigator(session.getContextRight(), right);
130      StructuralMatch<ElementDefinition> sm = new StructuralMatch<ElementDefinition>(ln.current(), rn.current());
131      compareElements(res, sm, ln.path(), null, ln, rn);
132      res.combined = sm;
133    }
134    return res;
135  }
136
137  private void check(StructureDefinition sd, String name) {
138    if (sd == null)
139      throw new DefinitionException("No StructureDefinition provided ("+name+": "+sd.getName()+")");
140//    if (sd.getType().equals("Extension")) {
141//      throw new DefinitionException("StructureDefinition is for an extension - use ExtensionComparer instead ("+name+": "+sd.getName()+")");
142//    }
143    if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
144      throw new DefinitionException("StructureDefinition is not for an profile - can't be compared ("+name+": "+sd.getName()+")");
145    }
146    if (sd.getSnapshot().getElement().isEmpty())
147      throw new DefinitionException("StructureDefinition snapshot is empty ("+name+": "+sd.getName()+")");
148  }
149
150  private void compareElements(ProfileComparison comp, StructuralMatch<ElementDefinition> res,  String path, String sliceName, DefinitionNavigator left, DefinitionNavigator right) throws DefinitionException, FHIRFormatError, IOException {
151    assert(path != null);  
152    assert(left != null);
153    assert(right != null);
154    assert(left.path().equals(right.path()));
155
156    if (session.isDebug()) {
157      System.out.println("Compare elements at "+path);
158    }
159    
160    // not allowed to be different:   
161//    ruleEqual(comp, res, left.current().getDefaultValue(), right.current().getDefaultValue(), "defaultValue", path);
162//    ruleEqual(comp, res, left.current().getMeaningWhenMissingElement(), right.current().getMeaningWhenMissingElement(), "meaningWhenMissing", path);
163//    ruleEqual(comp, res, left.current().getIsModifierElement(), right.current().getIsModifierElement(), "isModifier", path); - this check belongs in the core
164//    ruleEqual(comp, res, left.current().getIsSummaryElement(), right.current().getIsSummaryElement(), "isSummary", path); - so does this
165
166    // we ignore slicing right now - we're going to clone the root one anyway, and then think about clones 
167    // simple stuff
168    ElementDefinition subset = new ElementDefinition();
169    subset.setPath(left.path());
170    if (sliceName != null)
171      subset.setSliceName(sliceName);
172
173
174    subset.getRepresentation().addAll(left.current().getRepresentation()); // can't be bothered even testing this one
175    subset.setDefaultValue(left.current().getDefaultValue());
176    subset.setMeaningWhenMissing(left.current().getMeaningWhenMissing());
177    subset.setIsModifier(left.current().getIsModifier());
178    subset.setIsSummary(left.current().getIsSummary());
179
180    // descriptive properties from ElementDefinition - merge them:
181    subset.setLabel(mergeText(comp, res, path, "label", left.current().getLabel(), right.current().getLabel(), false));
182    subset.setShort(mergeText(comp, res, path, "short", left.current().getShort(), right.current().getShort(), false));
183    subset.setDefinition(mergeText(comp, res, path, "definition", left.current().getDefinition(), right.current().getDefinition(), false));
184    subset.setComment(mergeText(comp, res, path, "comments", left.current().getComment(), right.current().getComment(), false));
185    subset.setRequirements(mergeText(comp, res, path, "requirements", left.current().getRequirements(), right.current().getRequirements(), false));
186    subset.getCode().addAll(mergeCodings(left.current().getCode(), right.current().getCode()));
187    subset.getAlias().addAll(mergeStrings(left.current().getAlias(), right.current().getAlias()));
188    subset.getMapping().addAll(mergeMappings(left.current().getMapping(), right.current().getMapping()));
189    // left will win for example
190    subset.setExample(left.current().hasExample() ? left.current().getExample() : right.current().getExample());
191
192    if (left.current().getMustSupport() != right.current().getMustSupport()) {
193      vm(IssueSeverity.ERROR, "Elements differ in definition for mustSupport:\r\n  \""+left.current().getMustSupport()+"\"\r\n  \""+right.current().getMustSupport()+"\"", path, comp.getMessages(), res.getMessages());
194
195    }
196    subset.setMustSupport(left.current().getMustSupport() || right.current().getMustSupport());
197    ElementDefinition superset = subset.copy();
198
199
200    // compare and intersect
201    superset.setMin(unionMin(left.current().getMin(), right.current().getMin()));
202    superset.setMax(unionMax(left.current().getMax(), right.current().getMax()));
203    subset.setMin(intersectMin(left.current().getMin(), right.current().getMin()));
204    subset.setMax(intersectMax(left.current().getMax(), right.current().getMax()));
205    rule(comp, res, subset.getMax().equals("*") || Integer.parseInt(subset.getMax()) >= subset.getMin(), path, "Cardinality Mismatch: "+card(left)+"/"+card(right));
206
207    superset.getType().addAll(unionTypes(comp, res, path, left.current().getType(), right.current().getType()));
208    subset.getType().addAll(intersectTypes(comp, res, subset, path, left.current().getType(), right.current().getType()));
209    rule(comp, res, !subset.getType().isEmpty() || (!left.current().hasType() && !right.current().hasType()), path, "Type Mismatch:\r\n  "+typeCode(left)+"\r\n  "+typeCode(right));
210    //    <fixed[x]><!-- ?? 0..1 * Value must be exactly this --></fixed[x]>
211    //    <pattern[x]><!-- ?? 0..1 * Value must have at least these property values --></pattern[x]>
212    superset.setMaxLengthElement(unionMaxLength(left.current().getMaxLength(), right.current().getMaxLength()));
213    subset.setMaxLengthElement(intersectMaxLength(left.current().getMaxLength(), right.current().getMaxLength()));
214    if (left.current().hasBinding() || right.current().hasBinding()) {
215      compareBindings(comp, res, subset, superset, path, left.current(), right.current());
216    }
217    // note these are backwards
218    superset.getConstraint().addAll(intersectConstraints(path, left.current().getConstraint(), right.current().getConstraint()));
219    subset.getConstraint().addAll(unionConstraints(comp, res, path, left.current().getConstraint(), right.current().getConstraint()));
220    comp.getIntersection().getSnapshot().getElement().add(subset);
221    comp.getUnion().getSnapshot().getElement().add(superset);
222
223    // add the children
224    compareChildren(comp, res, path, left, right);
225//
226//    // now process the slices
227//    if (left.current().hasSlicing() || right.current().hasSlicing()) {
228//      assert sliceName == null;
229//      if (isExtension(left.path()))
230//        return compareExtensions(outcome, path, superset, subset, left, right);
231//      //      return true;
232//      else {
233//        ElementDefinitionSlicingComponent slicingL = left.current().getSlicing();
234//        ElementDefinitionSlicingComponent slicingR = right.current().getSlicing();
235//        // 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.
236//        if (left.current().hasSlicing() && !right.current().hasSlicing()) { 
237//          // 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)
238//          // the minimum set is the slicing specified in the slicer
239//          subset.setSlicing(slicingL);
240//          // stick everything from the right to do with the slices to the subset 
241//          copySlices(outcome.subset.getSnapshot().getElement(), left.getStructure().getSnapshot().getElement(), left.slices());
242//        } else if (!left.current().hasSlicing() && right.current().hasSlicing()) { 
243//          // 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)
244//          // the minimum set is the slicing specified in the slicer
245//          subset.setSlicing(slicingR);
246//          // stick everything from the right to do with the slices to the subset 
247//          copySlices(outcome.subset.getSnapshot().getElement(), right.getStructure().getSnapshot().getElement(), right.slices());
248//        } else if (isTypeSlicing(slicingL) || isTypeSlicing(slicingR)) {
249//          superset.getSlicing().setRules(SlicingRules.OPEN).setOrdered(false).addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
250//          subset.getSlicing().setRules(slicingL.getRules() == SlicingRules.CLOSED || slicingR.getRules() == SlicingRules.CLOSED ? SlicingRules.OPEN : SlicingRules.CLOSED).setOrdered(false).addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this");
251//
252//          // the superset is the union of the types 
253//          // the subset is the intersection of them 
254//          List<DefinitionNavigator> handled = new ArrayList<>();
255//          for (DefinitionNavigator t : left.slices()) {
256//            DefinitionNavigator r = findMatchingSlice(right.slices(), t);
257//            if (r == null) {
258//              copySlice(outcome.superset.getSnapshot().getElement(), left.getStructure().getSnapshot().getElement(), t);              
259//            } else {
260//              handled.add(r);
261//              ret = compareElements(outcome, path+":"+t.current().getSliceName(), t, r, t.current().getSliceName()) && ret;
262//            }
263//          }
264//          for (DefinitionNavigator t : right.slices()) {
265//            if (!handled.contains(t)) {
266//              copySlice(outcome.superset.getSnapshot().getElement(), right.getStructure().getSnapshot().getElement(), t);
267//            }
268//          }
269//        } else if (slicingMatches(slicingL, slicingR)) {
270//          // if it's the same, we can try matching the slices - though we might have to give up without getting matches correct
271//          // there amy be implied consistency we can't reason about 
272//          throw new DefinitionException("Slicing matches but is not handled yet at "+left.current().getId()+": ("+ProfileUtilities.summarizeSlicing(slicingL)+")");
273//        } else  {
274//          // if the slicing is different, we can't compare them - or can we?
275//          throw new DefinitionException("Slicing doesn't match at "+left.current().getId()+": ("+ProfileUtilities.summarizeSlicing(slicingL)+" / "+ProfileUtilities.summarizeSlicing(slicingR)+")");
276//        }
277//      }
278//      // todo: name 
279//    }
280//    return ret;
281//
282//    // TODO Auto-generated method stub
283//    return null;
284  }
285
286  private void compareChildren(ProfileComparison comp, StructuralMatch<ElementDefinition> res, String path, DefinitionNavigator left, DefinitionNavigator right) throws DefinitionException, IOException, FHIRFormatError {
287    List<DefinitionNavigator> lc = left.children();
288    List<DefinitionNavigator> rc = right.children();
289    // it's possible that one of these profiles walks into a data type and the other doesn't
290    // if it does, we have to load the children for that data into the profile that doesn't 
291    // walk into it
292    if (lc.isEmpty() && !rc.isEmpty() && right.current().getType().size() == 1 && left.hasTypeChildren(right.current().getType().get(0)))
293      lc = left.childrenFromType(right.current().getType().get(0));
294    if (rc.isEmpty() && !lc.isEmpty() && left.current().getType().size() == 1 && right.hasTypeChildren(left.current().getType().get(0)))
295      rc = right.childrenFromType(left.current().getType().get(0));
296    
297    List<DefinitionNavigator> matchR = new ArrayList<>();
298    for (DefinitionNavigator l : lc) {
299      DefinitionNavigator r = findInList(rc, l);
300      if (r == null) {
301        comp.getUnion().getSnapshot().getElement().add(l.current().copy());
302        res.getChildren().add(new StructuralMatch<ElementDefinition>(l.current(), vmI(IssueSeverity.INFORMATION, "Removed this element", path)));
303      } else {
304        matchR.add(r);
305        StructuralMatch<ElementDefinition> sm = new StructuralMatch<ElementDefinition>(l.current(), r.current());
306        res.getChildren().add(sm);
307        compareElements(comp, sm, l.path(), null, l, r);
308      }
309    }
310    for (DefinitionNavigator r : rc) {
311      if (!matchR.contains(r)) {
312        comp.getUnion().getSnapshot().getElement().add(r.current().copy());
313        res.getChildren().add(new StructuralMatch<ElementDefinition>(vmI(IssueSeverity.INFORMATION, "Added this element", path), r.current()));        
314      }
315    }
316  }
317
318  private DefinitionNavigator findInList(List<DefinitionNavigator> rc, DefinitionNavigator l) {
319    for (DefinitionNavigator t : rc) {
320      if (tail(t.current().getPath()).equals(tail(l.current().getPath()))) {
321        return t;
322      }
323    }
324    return null;
325  }
326
327  private void ruleEqual(ProfileComparison comp, StructuralMatch<ElementDefinition> res, DataType vLeft, DataType vRight, String name, String path) throws IOException {
328    if (vLeft == null && vRight == null) {
329      // nothing
330    } else if (vLeft == null) {
331      vm(IssueSeverity.ERROR, "Added "+name, path, comp.getMessages(), res.getMessages());
332    } else if (vRight == null) {
333      vm(IssueSeverity.ERROR, "Removed "+name, path, comp.getMessages(), res.getMessages());
334    } else if (!Base.compareDeep(vLeft, vRight, false)) {
335      vm(IssueSeverity.ERROR, name+" must be the same ("+toString(vLeft, true)+"/"+toString(vRight, false)+")", path, comp.getMessages(), res.getMessages());
336    }
337  }
338
339  private String toString(DataType val, boolean left) throws IOException {
340    if (val instanceof PrimitiveType) 
341      return "\"" + ((PrimitiveType) val).getValueAsString()+"\"";
342    
343    IParser jp = left ? session.getContextLeft().newJsonParser() : session.getContextRight().newJsonParser();
344    return jp.composeString(val, "value");
345  }
346  
347  private String stripLinks(String s) {
348    while (s.contains("](")) {
349      int i = s.indexOf("](");
350      int j = s.substring(i).indexOf(")");
351      if (j == -1)
352        return s;
353      else
354        s = s.substring(0, i+1)+s.substring(i+j+1);
355    }
356    return s;
357  }
358  
359  private boolean rule(ProfileComparison comp, StructuralMatch<ElementDefinition> res, boolean test, String path, String message) {
360    if (!test)  {
361      vm(IssueSeverity.ERROR, message, path, comp.getMessages(), res.getMessages());
362    }
363    return test;
364  }
365
366  private String mergeText(ProfileComparison comp, StructuralMatch<ElementDefinition> res, String path, String name, String left, String right, boolean isError) {
367    if (left == null && right == null)
368      return null;
369    if (left == null)
370      return right;
371    if (right == null)
372      return left;
373    left = stripLinks(left);
374    right = stripLinks(right);
375    if (left.equalsIgnoreCase(right))
376      return left;
377    if (path != null) {
378      vm(isError ? IssueSeverity.ERROR : IssueSeverity.WARNING, "Elements differ in "+name+":\r\n  \""+left+"\"\r\n  \""+right+"\"", path, comp.getMessages(), res.getMessages());
379    }
380    return "left: "+left+"; right: "+right;
381  }
382
383  private List<Coding> mergeCodings(List<Coding> left, List<Coding> right) {
384    List<Coding> result = new ArrayList<Coding>();
385    result.addAll(left);
386    for (Coding c : right) {
387      boolean found = false;
388      for (Coding ct : left)
389        if (Utilities.equals(c.getSystem(), ct.getSystem()) && Utilities.equals(c.getCode(), ct.getCode()))
390          found = true;
391      if (!found)
392        result.add(c);
393    }
394    return result;
395  }
396
397  private List<StringType> mergeStrings(List<StringType> left, List<StringType> right) {
398    List<StringType> result = new ArrayList<StringType>();
399    result.addAll(left);
400    for (StringType c : right) {
401      boolean found = false;
402      for (StringType ct : left)
403        if (Utilities.equals(c.getValue(), ct.getValue()))
404          found = true;
405      if (!found)
406        result.add(c);
407    }
408    return result;
409  }
410
411  private List<ElementDefinitionMappingComponent> mergeMappings(List<ElementDefinitionMappingComponent> left, List<ElementDefinitionMappingComponent> right) {
412    List<ElementDefinitionMappingComponent> result = new ArrayList<ElementDefinitionMappingComponent>();
413    result.addAll(left);
414    for (ElementDefinitionMappingComponent c : right) {
415      boolean found = false;
416      for (ElementDefinitionMappingComponent ct : left)
417        if (Utilities.equals(c.getIdentity(), ct.getIdentity()) && Utilities.equals(c.getLanguage(), ct.getLanguage()) && Utilities.equals(c.getMap(), ct.getMap()))
418          found = true;
419      if (!found)
420        result.add(c);
421    }
422    return result;
423  }
424
425  private int intersectMin(int left, int right) {
426    if (left > right)
427      return left;
428    else
429      return right;
430  }
431
432  private int unionMin(int left, int right) {
433    if (left > right)
434      return right;
435    else
436      return left;
437  }
438
439  private String intersectMax(String left, String right) {
440    int l = "*".equals(left) ? Integer.MAX_VALUE : Integer.parseInt(left);
441    int r = "*".equals(right) ? Integer.MAX_VALUE : Integer.parseInt(right);
442    if (l < r)
443      return left;
444    else
445      return right;
446  }
447
448  private String unionMax(String left, String right) {
449    int l = "*".equals(left) ? Integer.MAX_VALUE : Integer.parseInt(left);
450    int r = "*".equals(right) ? Integer.MAX_VALUE : Integer.parseInt(right);
451    if (l < r)
452      return right;
453    else
454      return left;
455  }
456
457  private IntegerType intersectMaxLength(int left, int right) {
458    if (left == 0) 
459      left = Integer.MAX_VALUE;
460    if (right == 0) 
461      right = Integer.MAX_VALUE;
462    if (left < right)
463      return left == Integer.MAX_VALUE ? null : new IntegerType(left);
464    else
465      return right == Integer.MAX_VALUE ? null : new IntegerType(right);
466  }
467
468  private IntegerType unionMaxLength(int left, int right) {
469    if (left == 0) 
470      left = Integer.MAX_VALUE;
471    if (right == 0) 
472      right = Integer.MAX_VALUE;
473    if (left < right)
474      return right == Integer.MAX_VALUE ? null : new IntegerType(right);
475    else
476      return left == Integer.MAX_VALUE ? null : new IntegerType(left);
477  }
478
479  private String card(DefinitionNavigator defn) {
480    return Integer.toString(defn.current().getMin())+".."+defn.current().getMax();
481  }
482
483  private Collection<? extends TypeRefComponent> unionTypes(ProfileComparison comp, StructuralMatch<ElementDefinition> res, String path, List<TypeRefComponent> left, List<TypeRefComponent> right) throws DefinitionException, IOException, FHIRFormatError {
484    List<TypeRefComponent> result = new ArrayList<TypeRefComponent>();
485    for (TypeRefComponent l : left) 
486      checkAddTypeUnion(comp, res, path, result, l, session.getContextLeft());
487    for (TypeRefComponent r : right) 
488      checkAddTypeUnion(comp, res, path, result, r, session.getContextRight());
489    return result;
490  }    
491
492  private void checkAddTypeUnion(ProfileComparison comp, StructuralMatch<ElementDefinition> res, String path, List<TypeRefComponent> results, TypeRefComponent nw, IWorkerContext ctxt) throws DefinitionException, IOException, FHIRFormatError {
493    boolean pfound = false;
494    boolean tfound = false;
495    nw = nw.copy();
496    if (nw.hasAggregation())
497      throw new DefinitionException("Aggregation not supported: "+path);
498    for (TypeRefComponent ex : results) {
499      if (Utilities.equals(ex.getWorkingCode(), nw.getWorkingCode())) {
500        if (!ex.hasProfile() && !nw.hasProfile())
501          pfound = true;
502        else if (!ex.hasProfile()) {
503          pfound = true; 
504        } else if (!nw.hasProfile()) {
505          pfound = true;
506          ex.setProfile(null);
507        } else {
508          // both have profiles. Is one derived from the other? 
509          StructureDefinition sdex = ((IWorkerContext) ex.getUserData("ctxt")).fetchResource(StructureDefinition.class, ex.getProfile().get(0).getValue());
510          StructureDefinition sdnw = ctxt.fetchResource(StructureDefinition.class, nw.getProfile().get(0).getValue());
511          if (sdex != null && sdnw != null) {
512            if (sdex.getUrl().equals(sdnw.getUrl())) {
513              pfound = true;
514            } else if (derivesFrom(sdex, sdnw, ((IWorkerContext) ex.getUserData("ctxt")))) {
515              ex.setProfile(nw.getProfile());
516              pfound = true;
517            } else if (derivesFrom(sdnw, sdex, ctxt)) {
518              pfound = true;
519            } else if (sdnw.getSnapshot().getElement().get(0).getPath().equals(sdex.getSnapshot().getElement().get(0).getPath())) {
520              ProfileComparison compP = (ProfileComparison) session.compare(sdex, sdnw);
521              if (compP != null && compP.getUnion() != null) { // might be null if circular
522                pfound = true;
523                ex.addProfile("#"+compP.getId());
524              }
525            }
526          }
527        }        
528        if (!ex.hasTargetProfile() && !nw.hasTargetProfile())
529          tfound = true;
530        else if (!ex.hasTargetProfile()) {
531          tfound = true; 
532        } else if (!nw.hasTargetProfile()) {
533          tfound = true;
534          ex.setTargetProfile(null);
535        } else {
536          // both have profiles. Is one derived from the other? 
537          StructureDefinition sdex = ((IWorkerContext) ex.getUserData("ctxt")).fetchResource(StructureDefinition.class, ex.getTargetProfile().get(0).getValue());
538          StructureDefinition sdnw = ctxt.fetchResource(StructureDefinition.class, nw.getTargetProfile().get(0).getValue());
539          if (sdex != null && sdnw != null) {
540            if (matches(sdex, sdnw)) {
541              tfound = true;
542            } else if (derivesFrom(sdex, sdnw, ((IWorkerContext) ex.getUserData("ctxt")))) {
543              ex.setTargetProfile(nw.getTargetProfile());
544              tfound = true;
545            } else if (derivesFrom(sdnw, sdex, ctxt)) {
546              tfound = true;
547            } else if (sdnw.getSnapshot().getElement().get(0).getPath().equals(sdex.getSnapshot().getElement().get(0).getPath())) {
548              ProfileComparison compP = (ProfileComparison) session.compare(sdex, sdnw);
549              if (compP.getUnion() != null) {
550                tfound = true;
551                ex.addTargetProfile("#"+compP.getId());
552              }
553            }
554          }
555        }        
556      }
557    }
558    if (!tfound || !pfound) {
559      nw.setUserData("ctxt", ctxt);
560      results.add(nw);      
561    }
562  }
563
564  private boolean matches(StructureDefinition s1, StructureDefinition s2) {
565    if (!s1.getUrl().equals(s2.getUrl())) {
566      return false;
567    }
568    if (s1.getDerivation() == TypeDerivationRule.SPECIALIZATION && s2.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
569      return true; // arbitrary; we're just not interested in pursuing cross version differences
570    }
571    if (s1.hasVersion()) {
572      return  s1.getVersion().equals(s2.getVersion());
573    } else {
574      return !s2.hasVersion();
575    }
576  }
577
578  private boolean derivesFrom(StructureDefinition left, StructureDefinition right, IWorkerContext ctxt) {
579    StructureDefinition sd = left;
580    while (sd != null) {
581      if (right.getUrl().equals(sd.getBaseDefinition())) {
582        return true;
583      }
584      sd = sd.hasBaseDefinition() ? ctxt.fetchResource(StructureDefinition.class, sd.getBaseDefinition()) : null;
585    }
586    return false;
587  }
588
589  private Collection<? extends TypeRefComponent> intersectTypes(ProfileComparison comp, StructuralMatch<ElementDefinition> res, ElementDefinition ed, String path, List<TypeRefComponent> left, List<TypeRefComponent> right) throws DefinitionException, IOException, FHIRFormatError {
590    List<TypeRefComponent> result = new ArrayList<TypeRefComponent>();
591    for (TypeRefComponent l : left) {
592      if (l.hasAggregation())
593        throw new DefinitionException("Aggregation not supported: "+path);
594      boolean pfound = false;
595      boolean tfound = false;
596      TypeRefComponent c = l.copy();
597      for (TypeRefComponent r : right) {
598        if (r.hasAggregation())
599          throw new DefinitionException("Aggregation not supported: "+path);
600        if (!l.hasProfile() && !r.hasProfile()) {
601          pfound = true;    
602        } else if (!r.hasProfile()) {
603          pfound = true; 
604        } else if (!l.hasProfile()) {
605          pfound = true;
606          c.setProfile(r.getProfile());
607        } else {
608          StructureDefinition sdl = resolveProfile(comp, res, path, l.getProfile().get(0).getValue(), comp.getLeft().getName(), session.getContextLeft());
609          StructureDefinition sdr = resolveProfile(comp, res, path, r.getProfile().get(0).getValue(), comp.getRight().getName(), session.getContextRight());
610          if (sdl != null && sdr != null) {
611            if (sdl == sdr) {
612              pfound = true;
613            } else if (derivesFrom(sdl, sdr, session.getContextLeft())) {
614              pfound = true;
615            } else if (derivesFrom(sdr, sdl, session.getContextRight())) {
616              c.setProfile(r.getProfile());
617              pfound = true;
618            } else if (sdl.getType().equals(sdr.getType())) {
619              ProfileComparison compP = (ProfileComparison) session.compare(sdl, sdr);
620              if (compP != null && compP.getIntersection() != null) {
621                pfound = true;
622                c.addProfile("#"+compP.getId());
623              }
624            }
625          }
626        }
627        if (!l.hasTargetProfile() && !r.hasTargetProfile()) {
628          tfound = true;    
629        } else if (!r.hasTargetProfile()) {
630          tfound = true; 
631        } else if (!l.hasTargetProfile()) {
632          tfound = true;
633          c.setTargetProfile(r.getTargetProfile());
634        } else {
635          StructureDefinition sdl = resolveProfile(comp, res, path, l.getTargetProfile().get(0).getValue(), comp.getLeft().getName(), session.getContextLeft());
636          StructureDefinition sdr = resolveProfile(comp, res, path, r.getTargetProfile().get(0).getValue(), comp.getRight().getName(), session.getContextRight());
637          if (sdl != null && sdr != null) {
638            if (matches(sdl, sdr)) {
639              tfound = true;
640            } else if (derivesFrom(sdl, sdr, session.getContextLeft())) {
641              tfound = true;
642            } else if (derivesFrom(sdr, sdl, session.getContextRight())) {
643              c.setTargetProfile(r.getTargetProfile());
644              tfound = true;
645            } else if (sdl.getType().equals(sdr.getType())) {
646              ProfileComparison compP = (ProfileComparison) session.compare(sdl, sdr);
647              if (compP != null && compP.getIntersection() != null) {
648                tfound = true;
649                c.addTargetProfile("#"+compP.getId());
650              }
651            }
652          }
653        }
654      }
655      if (pfound && tfound)
656        result.add(c);
657    }
658    return result;
659  }
660
661  private String typeCode(DefinitionNavigator defn) {
662    CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
663    for (TypeRefComponent t : defn.current().getType())
664      b.append(t.getWorkingCode()+(t.hasProfile() ? "("+t.getProfile()+")" : "")+(t.hasTargetProfile() ? "("+t.getTargetProfile()+")" : "")); // todo: other properties
665    return b.toString();
666  }
667
668  private boolean compareBindings(ProfileComparison comp, StructuralMatch<ElementDefinition> res, ElementDefinition subset, ElementDefinition superset, String path, ElementDefinition lDef, ElementDefinition rDef) throws FHIRFormatError, DefinitionException, IOException {
669    assert(lDef.hasBinding() || rDef.hasBinding());
670    if (!lDef.hasBinding()) {
671      subset.setBinding(rDef.getBinding());
672      // technically, the super set is unbound, but that's not very useful - so we use the provided on as an example
673      superset.setBinding(rDef.getBinding().copy());
674      superset.getBinding().setStrength(BindingStrength.EXAMPLE);
675      return true;
676    }
677    if (!rDef.hasBinding()) {
678      subset.setBinding(lDef.getBinding());
679      superset.setBinding(lDef.getBinding().copy());
680      superset.getBinding().setStrength(BindingStrength.EXAMPLE);
681      return true;
682    }
683    ElementDefinitionBindingComponent left = lDef.getBinding();
684    ElementDefinitionBindingComponent right = rDef.getBinding();
685    if (Base.compareDeep(left, right, false)) {
686      subset.setBinding(left);
687      superset.setBinding(right);      
688    }
689
690    // if they're both examples/preferred then:
691    // subset: left wins if they're both the same
692    // superset: 
693    if (isPreferredOrExample(left) && isPreferredOrExample(right)) {
694      if (right.getStrength() == BindingStrength.PREFERRED && left.getStrength() == BindingStrength.EXAMPLE && !Base.compareDeep(left.getValueSet(), right.getValueSet(), false)) {
695        vm(IssueSeverity.INFORMATION, "Example/preferred bindings differ at "+path+" using binding from "+comp.getRight().getName(), path, comp.getMessages(), res.getMessages());
696        subset.setBinding(right);
697        superset.setBinding(unionBindings(comp, res, path, left, right));
698      } else {
699        if ((right.getStrength() != BindingStrength.EXAMPLE || left.getStrength() != BindingStrength.EXAMPLE) && !Base.compareDeep(left.getValueSet(), right.getValueSet(), false) ) { 
700          vm(IssueSeverity.INFORMATION, "Example/preferred bindings differ at "+path+" using binding from "+comp.getLeft().getName(), path, comp.getMessages(), res.getMessages());
701        }
702        subset.setBinding(left);
703        superset.setBinding(unionBindings(comp, res, path, left, right));
704      }
705      return true;
706    }
707    // if either of them are extensible/required, then it wins
708    if (isPreferredOrExample(left)) {
709      subset.setBinding(right);
710      superset.setBinding(unionBindings(comp, res, path, left, right));
711      return true;
712    }
713    if (isPreferredOrExample(right)) {
714      subset.setBinding(left);
715      superset.setBinding(unionBindings(comp, res, path, left, right));
716      return true;
717    }
718
719    // ok, both are extensible or required.
720    ElementDefinitionBindingComponent subBinding = new ElementDefinitionBindingComponent();
721    subset.setBinding(subBinding);
722    ElementDefinitionBindingComponent superBinding = new ElementDefinitionBindingComponent();
723    superset.setBinding(superBinding);
724    subBinding.setDescription(mergeText(comp, res, path, "description", left.getDescription(), right.getDescription(), false));
725    superBinding.setDescription(mergeText(comp, res, path, "description", left.getDescription(), right.getDescription(), false));
726    if (left.getStrength() == BindingStrength.REQUIRED || right.getStrength() == BindingStrength.REQUIRED)
727      subBinding.setStrength(BindingStrength.REQUIRED);
728    else
729      subBinding.setStrength(BindingStrength.EXTENSIBLE);
730    if (left.getStrength() == BindingStrength.EXTENSIBLE || right.getStrength() == BindingStrength.EXTENSIBLE)
731      superBinding.setStrength(BindingStrength.EXTENSIBLE);
732    else
733      superBinding.setStrength(BindingStrength.REQUIRED);
734
735    if (Base.compareDeep(left.getValueSet(), right.getValueSet(), false)) {
736      subBinding.setValueSet(left.getValueSet());
737      superBinding.setValueSet(left.getValueSet());
738      return true;
739    } else if (!left.hasValueSet()) {
740      vm(IssueSeverity.ERROR, "No left Value set at "+path, path, comp.getMessages(), res.getMessages());
741      return true;      
742    } else if (!right.hasValueSet()) {
743      vm(IssueSeverity.ERROR, "No right Value set at "+path, path, comp.getMessages(), res.getMessages());
744      return true;      
745    } else {
746      // ok, now we compare the value sets. This may be unresolvable. 
747      ValueSet lvs = resolveVS(comp.getLeft(), left.getValueSet(), session.getContextLeft());
748      ValueSet rvs = resolveVS(comp.getRight(), right.getValueSet(), session.getContextRight());
749      if (lvs == null) {
750        vm(IssueSeverity.ERROR, "Unable to resolve left value set "+left.getValueSet().toString()+" at "+path, path, comp.getMessages(), res.getMessages());
751        return true;
752      } else if (rvs == null) {
753        vm(IssueSeverity.ERROR, "Unable to resolve right value set "+right.getValueSet().toString()+" at "+path, path, comp.getMessages(), res.getMessages());
754        return true;        
755      } else if (sameValueSets(lvs, rvs)) {
756        subBinding.setValueSet(lvs.getUrl());
757        superBinding.setValueSet(lvs.getUrl());
758      } else {
759        ValueSetComparison compP = (ValueSetComparison) session.compare(lvs, rvs);
760        if (compP != null) {
761          subBinding.setValueSet(compP.getIntersection().getUrl());
762          superBinding.setValueSet(compP.getUnion().getUrl());
763        }
764      }
765    }
766    return false;
767  }
768
769  private boolean sameValueSets(ValueSet lvs, ValueSet rvs) {
770    if (!lvs.getUrl().equals(rvs.getUrl())) {
771      return false;
772    }
773    if (isCore(lvs) && isCore(rvs)) {
774      return true;
775    }
776    if (lvs.hasVersion()) {
777      if (!lvs.getVersion().equals(rvs.getVersion())) {
778        return false;
779      } else if (!rvs.hasVersion()) {
780        return false;
781      }
782    }
783    return true;
784  }
785
786  private boolean isCore(ValueSet vs) {
787    return vs.getUrl().startsWith("http://hl7.org/fhir/ValueSet");
788  }
789
790  private List<ElementDefinitionConstraintComponent> intersectConstraints(String path, List<ElementDefinitionConstraintComponent> left, List<ElementDefinitionConstraintComponent> right) {
791    List<ElementDefinitionConstraintComponent> result = new ArrayList<ElementDefinitionConstraintComponent>();
792    for (ElementDefinitionConstraintComponent l : left) {
793      boolean found = false;
794      for (ElementDefinitionConstraintComponent r : right)
795        if (Utilities.equals(r.getId(), l.getId()) || (Utilities.equals(r.getXpath(), l.getXpath()) && r.getSeverity() == l.getSeverity()))
796          found = true;
797      if (found)
798        result.add(l);
799    }
800    return result;
801  }
802
803  // we can't really know about constraints. We create warnings, and collate them 
804  private List<ElementDefinitionConstraintComponent> unionConstraints(ProfileComparison comp, StructuralMatch<ElementDefinition> res, String path, List<ElementDefinitionConstraintComponent> left, List<ElementDefinitionConstraintComponent> right) {
805    List<ElementDefinitionConstraintComponent> result = new ArrayList<ElementDefinitionConstraintComponent>();
806    for (ElementDefinitionConstraintComponent l : left) {
807      boolean found = false;
808      for (ElementDefinitionConstraintComponent r : right)
809        if (Utilities.equals(r.getId(), l.getId()) || (Utilities.equals(r.getXpath(), l.getXpath()) && r.getSeverity() == l.getSeverity()))
810          found = true;
811      if (!found) {
812        if (!Utilities.existsInList(l.getExpression(), "hasValue() or (children().count() > id.count())", "extension.exists() != value.exists()")) {
813          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());
814        }
815      }
816      result.add(l);
817    }
818    for (ElementDefinitionConstraintComponent r : right) {
819      boolean found = false;
820      for (ElementDefinitionConstraintComponent l : left)
821        if (Utilities.equals(r.getId(), l.getId()) || (Utilities.equals(r.getXpath(), l.getXpath()) && r.getSeverity() == l.getSeverity()))
822          found = true;
823      if (!found) {
824        if (!Utilities.existsInList(r.getExpression(), "hasValue() or (children().count() > id.count())", "extension.exists() != value.exists()")) {
825          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());
826        }
827      }
828    }
829    return result;
830  }
831
832  private StructureDefinition resolveProfile(ProfileComparison comp, StructuralMatch<ElementDefinition> res, String path, String url, String name, IWorkerContext ctxt) {
833    StructureDefinition sd = ctxt.fetchResource(StructureDefinition.class, url);
834    if (sd == null) {
835      ValidationMessage vm = vmI(IssueSeverity.WARNING, "Unable to resolve profile "+url+" in profile "+name, path);
836    }
837    return sd;
838  }
839
840  private boolean isPreferredOrExample(ElementDefinitionBindingComponent binding) {
841    return binding.getStrength() == BindingStrength.EXAMPLE || binding.getStrength() == BindingStrength.PREFERRED;
842  }
843
844  private ElementDefinitionBindingComponent unionBindings(ProfileComparison comp, StructuralMatch<ElementDefinition> res, String path, ElementDefinitionBindingComponent left, ElementDefinitionBindingComponent right) throws FHIRFormatError, DefinitionException, IOException {
845    ElementDefinitionBindingComponent union = new ElementDefinitionBindingComponent();
846    if (left.getStrength().compareTo(right.getStrength()) < 0)
847      union.setStrength(left.getStrength());
848    else
849      union.setStrength(right.getStrength());
850    union.setDescription(mergeText(comp, res, path, "binding.description", left.getDescription(), right.getDescription(), false));
851    if (Base.compareDeep(left.getValueSet(), right.getValueSet(), false))
852      union.setValueSet(left.getValueSet());
853    else {
854      ValueSet lvs = resolveVS(comp.getLeft(), left.getValueSet(), session.getContextLeft());
855      ValueSet rvs = resolveVS(comp.getRight(), right.getValueSet(), session.getContextRight());
856      if (lvs != null && rvs != null) {
857        ValueSetComparison compP = (ValueSetComparison) session.compare(lvs, rvs);
858        if (compP != null) {
859          union.setValueSet(compP.getUnion().getUrl());
860        }
861      } else if (lvs != null) {
862        union.setValueSet(lvs.getUrl());
863      } else if (rvs != null) {
864        union.setValueSet(rvs.getUrl());
865      }
866    }
867    return union;
868  }
869
870  private ValueSet resolveVS(StructureDefinition ctxtLeft, String vsRef, IWorkerContext ctxt) {
871    if (vsRef == null)
872      return null;
873    return ctxt.fetchResource(ValueSet.class, vsRef);
874  }
875
876  public XhtmlNode renderStructure(ProfileComparison comp, String id, String prefix, String corePath) throws FHIRException, IOException {
877    HierarchicalTableGenerator gen = new HierarchicalTableGenerator(Utilities.path("[tmp]", "compare"), false, true);
878    gen.setTranslator(session.getContextRight().translator());
879    TableModel model = gen.initComparisonTable(corePath, id);
880    genElementComp(null /* oome back to this later */, gen, model.getRows(), comp.combined, corePath, prefix, null, true);
881    return gen.generate(model, prefix, 0, null);
882  }
883
884  private void genElementComp(String defPath, HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<ElementDefinition> combined, String corePath, String prefix, Row slicingRow, boolean root) throws IOException {
885    Row originalRow = slicingRow;
886    Row typesRow = null;
887    
888    List<StructuralMatch<ElementDefinition>> children = combined.getChildren();
889
890    Row row = gen.new Row();
891    rows.add(row);
892    String path = combined.either().getPath();
893    row.setAnchor(path);
894      row.setColor(utilsRight.getRowColor(combined.either(), false));
895      if (eitherHasSlicing(combined))
896        row.setLineColor(1);
897      else if (eitherHasSliceName(combined))
898        row.setLineColor(2);
899      else
900        row.setLineColor(0);
901      boolean ext = false;
902      if (tail(path).equals("extension")) {
903        if (elementIsComplex(combined))
904          row.setIcon("icon_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
905        else
906          row.setIcon("icon_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
907        ext = true;
908      } else if (tail(path).equals("modifierExtension")) {
909        if (elementIsComplex(combined))
910          row.setIcon("icon_modifier_extension_complex.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_COMPLEX);
911        else
912          row.setIcon("icon_modifier_extension_simple.png", HierarchicalTableGenerator.TEXT_ICON_EXTENSION_SIMPLE);
913      } else if (hasChoice(combined)) {
914        if (allAreReference(combined))
915          row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
916        else {
917          row.setIcon("icon_choice.gif", HierarchicalTableGenerator.TEXT_ICON_CHOICE);
918          typesRow = row;
919        }
920      } else if (combined.either().hasContentReference())
921        row.setIcon("icon_reuse.png", HierarchicalTableGenerator.TEXT_ICON_REUSE);
922      else if (isPrimitive(combined))
923        row.setIcon("icon_primitive.png", HierarchicalTableGenerator.TEXT_ICON_PRIMITIVE);
924      else if (hasTarget(combined))
925        row.setIcon("icon_reference.png", HierarchicalTableGenerator.TEXT_ICON_REFERENCE);
926      else if (isDataType(combined))
927        row.setIcon("icon_datatype.gif", HierarchicalTableGenerator.TEXT_ICON_DATATYPE);
928      else
929        row.setIcon("icon_resource.png", HierarchicalTableGenerator.TEXT_ICON_RESOURCE);
930      String ref = defPath == null ? null : defPath + combined.either().getId();
931      String sName = tail(path);
932      String sn = getSliceName(combined);
933      if (sn != null)
934        sName = sName +":"+sn;
935      UnusedTracker used = new UnusedTracker();
936      Cell nc;
937      String leftColor = !combined.hasLeft() ? COLOR_NO_ROW_LEFT : combined.hasErrors() ? COLOR_DIFFERENT : null;
938      String rightColor = !combined.hasRight() ? COLOR_NO_ROW_LEFT : combined.hasErrors() ? COLOR_DIFFERENT : null;
939      if (combined.hasLeft()) {
940        nc = utilsRight.genElementNameCell(gen, combined.getLeft(),  "??", true, corePath, prefix, root, false, false, null, typesRow, row, false, ext, used , ref, sName);
941      } else {
942        nc = utilsRight.genElementNameCell(gen, combined.getRight(),  "??", true, corePath, prefix, root, false, false, null, typesRow, row, false, ext, used , ref, sName);
943      }
944      if (combined.hasLeft()) {
945        frame(utilsRight.genElementCells(gen, combined.getLeft(),  "??", true, corePath, prefix, root, false, false, null, typesRow, row, false, ext, used , ref, sName, nc, false), leftColor);
946      } else {
947        frame(spacers(row, 4, gen), leftColor);
948      }
949      if (combined.hasRight()) {
950        frame(utilsRight.genElementCells(gen, combined.getRight(), "??", true, corePath, prefix, root, false, false, null, typesRow, row, false, ext, used, ref, sName, nc, false), rightColor);
951      } else {
952        frame(spacers(row, 4, gen), rightColor);
953      }
954      row.getCells().add(cellForMessages(gen, combined.getMessages()));
955
956      for (StructuralMatch<ElementDefinition> child : children) {
957        genElementComp(defPath, gen, row.getSubRows(), child, corePath, prefix, originalRow, false);
958      }
959    }
960
961  private void frame(List<Cell> cells, String color) {
962    for (Cell cell : cells) {
963      if (color != null) {
964        cell.setStyle("background-color: "+color);
965      }
966    }
967    cells.get(0).setStyle("border-left: 1px grey solid"+(color == null ? "" : "; background-color: "+color));
968    cells.get(cells.size()-1).setStyle("border-right: 1px grey solid"+(color == null ? "" : "; background-color: "+color));
969  }
970
971  private List<Cell> spacers(Row row, int count, HierarchicalTableGenerator gen) {
972    List<Cell> res = new ArrayList<>();
973    for (int i = 0; i < count; i++) {
974      Cell c = gen.new Cell();
975      res.add(c);
976      row.getCells().add(c);
977    }
978    return res;
979  }
980
981  private String getSliceName(StructuralMatch<ElementDefinition> combined) {
982    // TODO Auto-generated method stub
983    return null;
984  }
985
986  private boolean isDataType(StructuralMatch<ElementDefinition> combined) {
987    // TODO Auto-generated method stub
988    return false;
989  }
990
991  private boolean hasTarget(StructuralMatch<ElementDefinition> combined) {
992    // TODO Auto-generated method stub
993    return false;
994  }
995
996  private boolean isPrimitive(StructuralMatch<ElementDefinition> combined) {
997    // TODO Auto-generated method stub
998    return false;
999  }
1000
1001  private boolean allAreReference(StructuralMatch<ElementDefinition> combined) {
1002    // TODO Auto-generated method stub
1003    return false;
1004  }
1005
1006  private boolean hasChoice(StructuralMatch<ElementDefinition> combined) {
1007    // TODO Auto-generated method stub
1008    return false;
1009  }
1010
1011  private boolean elementIsComplex(StructuralMatch<ElementDefinition> combined) {
1012    // TODO Auto-generated method stub velement.hasType() && element.getType().get(0).hasProfile() && extensionIsComplex(element.getType().get(0).getProfile().get(0).getValue()
1013    return false;
1014  }
1015
1016  private boolean eitherHasSliceName(StructuralMatch<ElementDefinition> combined) {
1017    // TODO Auto-generated method stub
1018    return false;
1019  }
1020
1021  private boolean eitherHasSlicing(StructuralMatch<ElementDefinition> combined) {
1022    // TODO Auto-generated method stub
1023    return false;
1024  }
1025  
1026
1027  
1028
1029private String tail(String path) {
1030  if (path.contains("."))
1031    return path.substring(path.lastIndexOf('.')+1);
1032  else
1033    return path;
1034}
1035  
1036
1037}