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