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