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