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}