001package org.hl7.fhir.r5.comparison; 002 003import java.io.IOException; 004import java.util.ArrayList; 005import java.util.Date; 006import java.util.List; 007 008import org.hl7.fhir.exceptions.DefinitionException; 009import org.hl7.fhir.exceptions.FHIRException; 010import org.hl7.fhir.r5.comparison.ResourceComparer.MessageCounts; 011import org.hl7.fhir.r5.context.IWorkerContext; 012import org.hl7.fhir.r5.model.CanonicalType; 013import org.hl7.fhir.r5.model.Element; 014import org.hl7.fhir.r5.model.ValueSet; 015import org.hl7.fhir.r5.model.ValueSet.ConceptReferenceComponent; 016import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; 017import org.hl7.fhir.r5.model.ValueSet.ConceptSetFilterComponent; 018import org.hl7.fhir.r5.model.ValueSet.ValueSetComposeComponent; 019import org.hl7.fhir.r5.model.ValueSet.ValueSetExpansionContainsComponent; 020import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome; 021import org.hl7.fhir.utilities.Utilities; 022import org.hl7.fhir.utilities.validation.ValidationMessage; 023import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 024import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 025import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 026import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 027import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row; 028import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel; 029import org.hl7.fhir.utilities.xhtml.NodeType; 030import org.hl7.fhir.utilities.xhtml.XhtmlNode; 031 032public class ValueSetComparer extends CanonicalResourceComparer { 033 034 public class ValueSetComparison extends CanonicalResourceComparison<ValueSet> { 035 036 public ValueSetComparison(ValueSet left, ValueSet right) { 037 super(left, right); 038 } 039 040 private StructuralMatch<Element> includes = new StructuralMatch<>(); 041 private StructuralMatch<Element> excludes = new StructuralMatch<>(); 042 private StructuralMatch<ValueSetExpansionContainsComponent> expansion; 043 044 public StructuralMatch<Element> getIncludes() { 045 return includes; 046 } 047 048 public StructuralMatch<Element> getExcludes() { 049 return excludes; 050 } 051 052 public StructuralMatch<ValueSetExpansionContainsComponent> getExpansion() { 053 return expansion; 054 } 055 056 public StructuralMatch<ValueSetExpansionContainsComponent> forceExpansion() { 057 if (expansion == null) { 058 expansion = new StructuralMatch<>(); 059 } 060 return expansion; 061 } 062 063 @Override 064 protected String abbreviation() { 065 return "vs"; 066 } 067 068 @Override 069 protected String summary() { 070 return "ValueSet: "+left.present()+" vs "+right.present(); 071 } 072 073 @Override 074 protected String fhirType() { 075 return "ValueSet"; 076 } 077 @Override 078 protected void countMessages(MessageCounts cnts) { 079 super.countMessages(cnts); 080 if (includes != null) { 081 includes.countMessages(cnts); 082 } 083 if (excludes != null) { 084 excludes.countMessages(cnts); 085 } 086 if (expansion != null) { 087 expansion.countMessages(cnts); 088 } 089 } 090 091 } 092 093 public ValueSetComparer(ComparisonSession session) { 094 super(session); 095 } 096 097 public ValueSetComparison compare(ValueSet left, ValueSet right) { 098 if (left == null) 099 throw new DefinitionException("No ValueSet provided (left)"); 100 if (right == null) 101 throw new DefinitionException("No ValueSet provided (right)"); 102 103 ValueSetComparison res = new ValueSetComparison(left, right); 104 session.identify(res); 105 ValueSet vs = new ValueSet(); 106 res.setUnion(vs); 107 session.identify(vs); 108 vs.setName("Union"+left.getName()+"And"+right.getName()); 109 vs.setTitle("Union of "+left.getTitle()+" And "+right.getTitle()); 110 vs.setStatus(left.getStatus()); 111 vs.setDate(new Date()); 112 113 ValueSet vs1 = new ValueSet(); 114 res.setIntersection(vs1); 115 session.identify(vs1); 116 vs1.setName("Intersection"+left.getName()+"And"+right.getName()); 117 vs1.setTitle("Intersection of "+left.getTitle()+" And "+right.getTitle()); 118 vs1.setStatus(left.getStatus()); 119 vs1.setDate(new Date()); 120 121 compareMetadata(left, right, res.getMetadata(), res); 122 comparePrimitives("immutable", left.getImmutableElement(), right.getImmutableElement(), res.getMetadata(), IssueSeverity.WARNING, res); 123 if (left.hasCompose() || right.hasCompose()) { 124 comparePrimitives("compose.lockedDate", left.getCompose().getLockedDateElement(), right.getCompose().getLockedDateElement(), res.getMetadata(), IssueSeverity.WARNING, res); 125 comparePrimitives("compose.inactive", left.getCompose().getInactiveElement(), right.getCompose().getInactiveElement(), res.getMetadata(), IssueSeverity.WARNING, res); 126 } 127 128 compareCompose(left.getCompose(), right.getCompose(), res, res.getUnion().getCompose(), res.getIntersection().getCompose()); 129 compareExpansions(left, right, res); 130 return res; 131 } 132 133 134 135 private void compareCompose(ValueSetComposeComponent left, ValueSetComposeComponent right, ValueSetComparison res, ValueSetComposeComponent union, ValueSetComposeComponent intersection) { 136 // first, the includes 137 List<ConceptSetComponent> matchR = new ArrayList<>(); 138 for (ConceptSetComponent l : left.getInclude()) { 139 ConceptSetComponent r = findInList(right.getInclude(), l, left.getInclude()); 140 if (r == null) { 141 union.getInclude().add(l); 142 res.getIncludes().getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed Include", "ValueSet.compose.include"))); 143 } else { 144 matchR.add(r); 145 ConceptSetComponent csM = new ConceptSetComponent(); 146 ConceptSetComponent csI = new ConceptSetComponent(); 147 union.getInclude().add(csM); 148 intersection.getInclude().add(csI); 149 StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r); 150 res.getIncludes().getChildren().add(sm); 151 compareDefinitions(l, r, sm, csM, csI); 152 } 153 } 154 for (ConceptSetComponent r : right.getInclude()) { 155 if (!matchR.contains(r)) { 156 union.getInclude().add(r); 157 res.getIncludes().getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added Include", "ValueSet.compose.include"), r)); 158 } 159 } 160 161 // now. the excludes 162 matchR.clear(); 163 for (ConceptSetComponent l : left.getExclude()) { 164 ConceptSetComponent r = findInList(right.getExclude(), l, left.getExclude()); 165 if (r == null) { 166 union.getExclude().add(l); 167 res.getExcludes().getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed Exclude", "ValueSet.compose.exclude"))); 168 } else { 169 matchR.add(r); 170 ConceptSetComponent csM = new ConceptSetComponent(); 171 ConceptSetComponent csI = new ConceptSetComponent(); 172 union.getExclude().add(csM); 173 intersection.getExclude().add(csI); 174 StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r); 175 res.getExcludes().getChildren().add(sm); 176 compareDefinitions(l, r, sm, csM, csI); 177 } 178 } 179 for (ConceptSetComponent r : right.getExclude()) { 180 if (!matchR.contains(r)) { 181 union.getExclude().add(r); 182 res.getExcludes().getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added Exclude", "ValueSet.compose.exclude"), r)); 183 } 184 } 185 } 186 187 private ConceptSetComponent findInList(List<ConceptSetComponent> matches, ConceptSetComponent item, List<ConceptSetComponent> source) { 188 if (matches.size() == 1 && source.size() == 1) { 189 return matches.get(0); 190 } 191 int matchCount = countMatchesBySystem(matches, item); 192 int sourceCount = countMatchesBySystem(source, item); 193 194 if (matchCount == 1 && sourceCount == 1) { 195 for (ConceptSetComponent t : matches) { 196 if (t.getSystem().equals(item.getSystem())) { 197 return t; 198 } 199 } 200 } 201 // if there's more than one candidate match by system, then we look for a full match 202 for (ConceptSetComponent t : matches) { 203 if (t.equalsDeep(item)) { 204 return t; 205 } 206 } 207 return null; 208 } 209 210 private int countMatchesBySystem(List<ConceptSetComponent> list, ConceptSetComponent item) { 211 int c = 0; 212 for (ConceptSetComponent t : list) { 213 if (t.hasSystem() && t.getSystem().equals(item.getSystem())) { 214 c++; 215 } 216 } 217 return c; 218 } 219 220 221 private void compareDefinitions(ConceptSetComponent left, ConceptSetComponent right, StructuralMatch<Element> combined, ConceptSetComponent union, ConceptSetComponent intersection) { 222 // system must match, but the rest might not. we're going to do the full comparison whatever, so the outcome looks consistent to the user 223 List<CanonicalType> matchVSR = new ArrayList<>(); 224 for (CanonicalType l : left.getValueSet()) { 225 CanonicalType r = findInList(right.getValueSet(), l, left.getValueSet()); 226 if (r == null) { 227 union.getValueSet().add(l); 228 combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed ValueSet", "ValueSet.compose.include.valueSet"))); 229 } else { 230 matchVSR.add(r); 231 if (l.getValue().equals(r.getValue())) { 232 union.getValueSet().add(l); 233 intersection.getValueSet().add(l); 234 StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, null); 235 combined.getChildren().add(sm); 236 } else { 237 union.getValueSet().add(l); 238 union.getValueSet().add(r); 239 StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, vmI(IssueSeverity.INFORMATION, "Values are different", "ValueSet.compose.include.valueSet")); 240 combined.getChildren().add(sm); 241 } 242 } 243 } 244 for (CanonicalType r : right.getValueSet()) { 245 if (!matchVSR.contains(r)) { 246 union.getValueSet().add(r); 247 combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Add ValueSet", "ValueSet.compose.include.valueSet"), r)); 248 } 249 } 250 251 List<ConceptReferenceComponent> matchCR = new ArrayList<>(); 252 for (ConceptReferenceComponent l : left.getConcept()) { 253 ConceptReferenceComponent r = findInList(right.getConcept(), l, left.getConcept()); 254 if (r == null) { 255 union.getConcept().add(l); 256 combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed this Concept", "ValueSet.compose.include.concept"))); 257 } else { 258 matchCR.add(r); 259 if (l.getCode().equals(r.getCode())) { 260 ConceptReferenceComponent cu = new ConceptReferenceComponent(); 261 ConceptReferenceComponent ci = new ConceptReferenceComponent(); 262 union.getConcept().add(cu); 263 intersection.getConcept().add(ci); 264 StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r); 265 combined.getChildren().add(sm); 266 compareConcepts(l, r, sm, cu, ci); 267 } else { 268 union.getConcept().add(l); 269 union.getConcept().add(r); 270 StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, vmI(IssueSeverity.INFORMATION, "Concepts are different", "ValueSet.compose.include.concept")); 271 combined.getChildren().add(sm); 272 compareConcepts(l, r, sm, null, null); 273 } 274 } 275 } 276 for (ConceptReferenceComponent r : right.getConcept()) { 277 if (!matchCR.contains(r)) { 278 union.getConcept().add(r); 279 combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this Concept", "ValueSet.compose.include.concept"), r)); 280 } 281 } 282 283 List<ConceptSetFilterComponent> matchFR = new ArrayList<>(); 284 for (ConceptSetFilterComponent l : left.getFilter()) { 285 ConceptSetFilterComponent r = findInList(right.getFilter(), l, left.getFilter()); 286 if (r == null) { 287 union.getFilter().add(l); 288 combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed this item", "ValueSet.compose.include.filter"))); 289 } else { 290 matchFR.add(r); 291 if (l.getProperty().equals(r.getProperty()) && l.getOp().equals(r.getOp())) { 292 ConceptSetFilterComponent cu = new ConceptSetFilterComponent(); 293 ConceptSetFilterComponent ci = new ConceptSetFilterComponent(); 294 union.getFilter().add(cu); 295 intersection.getFilter().add(ci); 296 StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r); 297 combined.getChildren().add(sm); 298 compareFilters(l, r, sm, cu, ci); 299 } else { 300 union.getFilter().add(l); 301 union.getFilter().add(r); 302 StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, vmI(IssueSeverity.INFORMATION, "Codes are different", "ValueSet.compose.include.filter")); 303 combined.getChildren().add(sm); 304 compareFilters(l, r, sm, null, null); 305 } 306 } 307 } 308 for (ConceptSetFilterComponent r : right.getFilter()) { 309 if (!matchFR.contains(r)) { 310 union.getFilter().add(r); 311 combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this item", "ValueSet.compose.include.filter"), r)); 312 } 313 } 314 } 315 316 private void compareConcepts(ConceptReferenceComponent l, ConceptReferenceComponent r, StructuralMatch<Element> sm, ConceptReferenceComponent cu, ConceptReferenceComponent ci) { 317 sm.getChildren().add(new StructuralMatch<Element>(l.getCodeElement(), r.getCodeElement(), l.getCode().equals(r.getCode()) ? null : vmI(IssueSeverity.INFORMATION, "Codes do not match", "ValueSet.compose.include.concept"))); 318 if (ci != null) { 319 ci.setCode(l.getCode()); 320 cu.setCode(l.getCode()); 321 } 322 if (l.hasDisplay() && r.hasDisplay()) { 323 sm.getChildren().add(new StructuralMatch<Element>(l.getDisplayElement(), r.getDisplayElement(), l.getDisplay().equals(r.getDisplay()) ? null : vmI(IssueSeverity.INFORMATION, "Displays do not match", "ValueSet.compose.include.concept"))); 324 if (ci != null) { 325 ci.setDisplay(r.getDisplay()); 326 cu.setDisplay(r.getDisplay()); 327 } 328 } else if (l.hasDisplay()) { 329 sm.getChildren().add(new StructuralMatch<Element>(l.getDisplayElement(), null, vmI(IssueSeverity.INFORMATION, "Display Removed", "ValueSet.compose.include.concept"))); 330 if (ci != null) { 331 ci.setDisplay(l.getDisplay()); 332 cu.setDisplay(l.getDisplay()); 333 } 334 } else if (r.hasDisplay()) { 335 sm.getChildren().add(new StructuralMatch<Element>(null, r.getDisplayElement(), vmI(IssueSeverity.INFORMATION, "Display added", "ValueSet.compose.include.concept"))); 336 if (ci != null) { 337 ci.setDisplay(r.getDisplay()); 338 cu.setDisplay(r.getDisplay()); 339 } 340 } else { 341 sm.getChildren().add(new StructuralMatch<Element>(null, null, vmI(IssueSeverity.INFORMATION, "No Display", "ValueSet.compose.include.concept"))); 342 } 343 } 344 345 private void compareFilters(ConceptSetFilterComponent l, ConceptSetFilterComponent r, StructuralMatch<Element> sm, ConceptSetFilterComponent cu, ConceptSetFilterComponent ci) { 346 sm.getChildren().add(new StructuralMatch<Element>(l.getPropertyElement(), r.getPropertyElement(), l.getProperty().equals(r.getProperty()) ? null : vmI(IssueSeverity.INFORMATION, "Properties do not match", "ValueSet.compose.include.concept"))); 347 sm.getChildren().add(new StructuralMatch<Element>(l.getOpElement(), r.getOpElement(), l.getOp().equals(r.getOp()) ? null : vmI(IssueSeverity.INFORMATION, "Filter Operations do not match", "ValueSet.compose.include.concept"))); 348 sm.getChildren().add(new StructuralMatch<Element>(l.getValueElement(), r.getValueElement(), l.getValue().equals(r.getValue()) ? null : vmI(IssueSeverity.INFORMATION, "Values do not match", "ValueSet.compose.include.concept"))); 349 if (ci != null) { 350 ci.setProperty(l.getProperty()); 351 ci.setOp(l.getOp()); 352 ci.setValue(l.getValue()); 353 cu.setProperty(l.getProperty()); 354 cu.setOp(l.getOp()); 355 cu.setValue(l.getValue()); 356 } 357 } 358 359 private CanonicalType findInList(List<CanonicalType> matches, CanonicalType item, List<CanonicalType> source) { 360 if (matches.size() == 1 && source.size() == 1) { 361 return matches.get(0); 362 } 363 for (CanonicalType t : matches) { 364 if (t.getValue().equals(item.getValue())) { 365 return t; 366 } 367 } 368 return null; 369 } 370 371 private ConceptReferenceComponent findInList(List<ConceptReferenceComponent> matches, ConceptReferenceComponent item, List<ConceptReferenceComponent> source) { 372 if (matches.size() == 1 && source.size() == 1) { 373 return matches.get(0); 374 } 375 for (ConceptReferenceComponent t : matches) { 376 if (t.getCode().equals(item.getCode())) { 377 return t; 378 } 379 } 380 return null; 381 } 382 383 private ConceptSetFilterComponent findInList(List<ConceptSetFilterComponent> matches, ConceptSetFilterComponent item, List<ConceptSetFilterComponent> source) { 384 if (matches.size() == 1 && source.size() == 1) { 385 return matches.get(0); 386 } 387 for (ConceptSetFilterComponent t : matches) { 388 if (t.getProperty().equals(item.getProperty()) && t.getOp().equals(item.getOp()) ) { 389 return t; 390 } 391 } 392 return null; 393 } 394 395 private void compareExpansions(ValueSet left, ValueSet right, ValueSetComparison res) { 396 ValueSet expL = left.hasExpansion() ? left : expand(left, res, "left", session.getContextLeft()); 397 ValueSet expR = right.hasExpansion() ? right : expand(right, res, "right", session.getContextRight()); 398 if (expL != null && expR != null) { 399 // ignore the parameters for now 400 compareConcepts(expL.getExpansion().getContains(), expR.getExpansion().getContains(), res.forceExpansion(), res.getUnion().getExpansion().getContains(), res.getIntersection().getExpansion().getContains(), "ValueSet.expansion.contains", res); 401 } 402 } 403 404 private ValueSet expand(ValueSet vs, ValueSetComparison res, String name, IWorkerContext ctxt) { 405 ValueSetExpansionOutcome vse = ctxt.expandVS(vs, true, false); 406 if (vse.getValueset() != null) { 407 return vse.getValueset(); 408 } else { 409 res.getMessages().add(new ValidationMessage(Source.TerminologyEngine, IssueType.EXCEPTION, "ValueSet", "Error Expanding "+name+":"+vse.getError(), IssueSeverity.ERROR)); 410 return null; 411 } 412 } 413 414 private void compareConcepts(List<ValueSetExpansionContainsComponent> left, List<ValueSetExpansionContainsComponent> right, StructuralMatch<ValueSetExpansionContainsComponent> combined, List<ValueSetExpansionContainsComponent> union, List<ValueSetExpansionContainsComponent> intersection, String path, ValueSetComparison res) { 415 List<ValueSetExpansionContainsComponent> matchR = new ArrayList<>(); 416 for (ValueSetExpansionContainsComponent l : left) { 417 ValueSetExpansionContainsComponent r = findInList(right, l); 418 if (r == null) { 419 union.add(l); 420 combined.getChildren().add(new StructuralMatch<ValueSetExpansionContainsComponent>(l, vmI(IssueSeverity.INFORMATION, "Removed from expansion", path))); 421 } else { 422 matchR.add(r); 423 ValueSetExpansionContainsComponent ccU = merge(l, r); 424 ValueSetExpansionContainsComponent ccI = intersect(l, r); 425 union.add(ccU); 426 intersection.add(ccI); 427 StructuralMatch<ValueSetExpansionContainsComponent> sm = new StructuralMatch<ValueSetExpansionContainsComponent>(l, r); 428 compareItem(sm.getMessages(), path, l, r, res); 429 combined.getChildren().add(sm); 430 compareConcepts(l.getContains(), r.getContains(), sm, ccU.getContains(), ccI.getContains(), path+".where(code = '"+l.getCode()+"').contains", res); 431 } 432 } 433 for (ValueSetExpansionContainsComponent r : right) { 434 if (!matchR.contains(r)) { 435 union.add(r); 436 combined.getChildren().add(new StructuralMatch<ValueSetExpansionContainsComponent>(vmI(IssueSeverity.INFORMATION, "Added to expansion", path), r)); 437 } 438 } 439 } 440 441 private void compareItem(List<ValidationMessage> msgs, String path, ValueSetExpansionContainsComponent l, ValueSetExpansionContainsComponent r, ValueSetComparison res) { 442 compareStrings(path, msgs, l.getDisplay(), r.getDisplay(), "display", IssueSeverity.WARNING, res); 443 } 444 445 private void compareStrings(String path, List<ValidationMessage> msgs, String left, String right, String name, IssueSeverity level, ValueSetComparison res) { 446 if (!Utilities.noString(right)) { 447 if (Utilities.noString(left)) { 448 msgs.add(vmI(level, "Value for "+name+" added", path)); 449 } else if (!left.equals(right)) { 450 if (level != IssueSeverity.NULL) { 451 res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path+".name", "Changed value for "+name+": '"+left+"' vs '"+right+"'", level)); 452 } 453 msgs.add(vmI(level, name+" changed from left to right", path)); 454 } 455 } else if (!Utilities.noString(left)) { 456 msgs.add(vmI(level, "Value for "+name+" removed", path)); 457 } 458 } 459 460 private ValueSetExpansionContainsComponent findInList(List<ValueSetExpansionContainsComponent> list, ValueSetExpansionContainsComponent item) { 461 for (ValueSetExpansionContainsComponent t : list) { 462 if (t.getSystem().equals(item.getSystem()) && t.getCode().equals(item.getCode())) { 463 return t; 464 } 465 } 466 return null; 467 } 468 469 private ValueSetExpansionContainsComponent intersect(ValueSetExpansionContainsComponent l, ValueSetExpansionContainsComponent r) { 470 ValueSetExpansionContainsComponent res = new ValueSetExpansionContainsComponent(); 471 if (l.hasAbstract() && r.hasAbstract()) { 472 res.setAbstract(l.getAbstract()); 473 } 474 if (l.hasCode() && r.hasCode()) { 475 res.setCode(l.getCode()); 476 } 477 if (l.hasSystem() && r.hasSystem()) { 478 res.setSystem(l.getSystem()); 479 } 480 if (l.hasVersion() && r.hasVersion()) { 481 res.setVersion(l.getVersion()); 482 } 483 if (l.hasDisplay() && r.hasDisplay()) { 484 res.setDisplay(l.getDisplay()); 485 } 486 return res; 487 } 488 489 private ValueSetExpansionContainsComponent merge(ValueSetExpansionContainsComponent l, ValueSetExpansionContainsComponent r) { 490 ValueSetExpansionContainsComponent res = new ValueSetExpansionContainsComponent(); 491 if (l.hasAbstract()) { 492 res.setAbstract(l.getAbstract()); 493 } else if (r.hasAbstract()) { 494 res.setAbstract(r.getAbstract()); 495 } 496 if (l.hasCode()) { 497 res.setCode(l.getCode()); 498 } else if (r.hasCode()) { 499 res.setCode(r.getCode()); 500 } 501 if (l.hasSystem()) { 502 res.setSystem(l.getSystem()); 503 } else if (r.hasSystem()) { 504 res.setSystem(r.getSystem()); 505 } 506 if (l.hasVersion()) { 507 res.setVersion(l.getVersion()); 508 } else if (r.hasVersion()) { 509 res.setVersion(r.getVersion()); 510 } 511 if (l.hasDisplay()) { 512 res.setDisplay(l.getDisplay()); 513 } else if (r.hasDisplay()) { 514 res.setDisplay(r.getDisplay()); 515 } 516 return res; 517 } 518 519 @Override 520 protected String fhirType() { 521 return "ValueSet"; 522 } 523 524 public XhtmlNode renderCompose(ValueSetComparison csc, String id, String prefix) throws FHIRException, IOException { 525 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(Utilities.path("[tmp]", "comparison"), false); 526 TableModel model = gen.new TableModel(id, true); 527 model.setAlternating(true); 528 model.getTitles().add(gen.new Title(null, null, "Item", "The type of item being compared", null, 100)); 529 model.getTitles().add(gen.new Title(null, null, "Property", "The system for the concept", null, 100, 2)); 530 model.getTitles().add(gen.new Title(null, null, "Value", "The display for the concept", null, 200, 2)); 531 model.getTitles().add(gen.new Title(null, null, "Comments", "Additional information about the comparison", null, 200)); 532 for (StructuralMatch<Element> t : csc.getIncludes().getChildren()) { 533 addComposeRow(gen, model.getRows(), t, "include"); 534 } 535 for (StructuralMatch<Element> t : csc.getExcludes().getChildren()) { 536 addComposeRow(gen, model.getRows(), t, "exclude"); 537 } 538 return gen.generate(model, prefix, 0, null); 539 } 540 541 private void addComposeRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t, String name) { 542 Row r = gen.new Row(); 543 rows.add(r); 544 r.getCells().add(gen.new Cell(null, null, name, null, null)); 545 if (t.hasLeft() && t.hasRight()) { 546 ConceptSetComponent csL = (ConceptSetComponent) t.getLeft(); 547 ConceptSetComponent csR = (ConceptSetComponent) t.getRight(); 548 if (csL.hasSystem() && csL.getSystem().equals(csR.getSystem())) { 549 r.getCells().add(gen.new Cell(null, null, csL.getSystem(), null, null).span(2).center()); 550 } else { 551 r.getCells().add(gen.new Cell(null, null, csL.getSystem(), null, null).setStyle("background-color: "+COLOR_DIFFERENT)); 552 r.getCells().add(gen.new Cell(null, null, csR.getSystem(), null, null).setStyle("background-color: "+COLOR_DIFFERENT)); 553 } 554 555 if (csL.hasVersion() && csR.hasVersion()) { 556 if (csL.getVersion().equals(csR.getVersion())) { 557 r.getCells().add(gen.new Cell(null, null, csL.getVersion(), null, null).span(2).center()); 558 } else { 559 r.getCells().add(gen.new Cell(null, null, csL.getVersion(), null, null).setStyle("background-color: "+COLOR_DIFFERENT)); 560 r.getCells().add(gen.new Cell(null, null, csR.getVersion(), null, null).setStyle("background-color: "+COLOR_DIFFERENT)); 561 } 562 } else if (csL.hasVersion()) { 563 r.getCells().add(gen.new Cell(null, null, csL.getVersion(), null, null)); 564 r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT)); 565 } else if (csR.hasVersion()) { 566 r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT)); 567 r.getCells().add(gen.new Cell(null, null, csR.getVersion(), null, null)); 568 } else { 569 r.getCells().add(missingCell(gen).span(2).center()); 570 } 571 572 } else if (t.hasLeft()) { 573 r.setColor(COLOR_NO_ROW_RIGHT); 574 ConceptSetComponent cs = (ConceptSetComponent) t.getLeft(); 575 r.getCells().add(gen.new Cell(null, null, cs.getSystem(), null, null)); 576 r.getCells().add(missingCell(gen)); 577 r.getCells().add(gen.new Cell(null, null, cs.hasVersion() ? "Version: "+cs.getVersion() : "", null, null)); 578 r.getCells().add(missingCell(gen)); 579 } else { 580 r.setColor(COLOR_NO_ROW_LEFT); 581 ConceptSetComponent cs = (ConceptSetComponent) t.getRight(); 582 r.getCells().add(missingCell(gen)); 583 r.getCells().add(gen.new Cell(null, null, cs.getSystem(), null, null)); 584 r.getCells().add(missingCell(gen)); 585 r.getCells().add(gen.new Cell(null, null, cs.hasVersion() ? "Version: "+cs.getVersion() : "", null, null)); 586 } 587 r.getCells().add(cellForMessages(gen, t.getMessages())); 588 for (StructuralMatch<Element> c : t.getChildren()) { 589 if (c.either() instanceof ConceptReferenceComponent) { 590 addSetConceptRow(gen, r.getSubRows(), c); 591 } else { 592 addSetFilterRow(gen, r.getSubRows(), c); 593 } 594 } 595 } 596 597 private void addSetConceptRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t) { 598 Row r = gen.new Row(); 599 rows.add(r); 600 r.getCells().add(gen.new Cell(null, null, "Concept", null, null)); 601 if (t.hasLeft() && t.hasRight()) { 602 ConceptReferenceComponent csL = (ConceptReferenceComponent) t.getLeft(); 603 ConceptReferenceComponent csR = (ConceptReferenceComponent) t.getRight(); 604 // we assume both have codes 605 if (csL.getCode().equals(csR.getCode())) { 606 r.getCells().add(gen.new Cell(null, null, csL.getCode(), null, null).span(2).center()); 607 } else { 608 r.getCells().add(gen.new Cell(null, null, csL.getCode(), null, null).setStyle("background-color: "+COLOR_DIFFERENT)); 609 r.getCells().add(gen.new Cell(null, null, csR.getCode(), null, null).setStyle("background-color: "+COLOR_DIFFERENT)); 610 } 611 612 if (csL.hasDisplay() && csR.hasDisplay()) { 613 if (csL.getDisplay().equals(csR.getDisplay())) { 614 r.getCells().add(gen.new Cell(null, null, csL.getDisplay(), null, null).span(2).center()); 615 } else { 616 r.getCells().add(gen.new Cell(null, null, csL.getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT)); 617 r.getCells().add(gen.new Cell(null, null, csR.getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT)); 618 } 619 } else if (csL.hasDisplay()) { 620 r.getCells().add(gen.new Cell(null, null, csL.getDisplay(), null, null)); 621 r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT)); 622 } else if (csR.hasDisplay()) { 623 r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT)); 624 r.getCells().add(gen.new Cell(null, null, csR.getDisplay(), null, null)); 625 } else { 626 r.getCells().add(missingCell(gen).span(2).center()); 627 } 628 629 } else if (t.hasLeft()) { 630 r.setColor(COLOR_NO_ROW_RIGHT); 631 ConceptReferenceComponent cs = (ConceptReferenceComponent) t.getLeft(); 632 r.getCells().add(gen.new Cell(null, null, cs.getCode(), null, null)); 633 r.getCells().add(missingCell(gen)); 634 r.getCells().add(gen.new Cell(null, null, cs.hasDisplay() ? "Version: "+cs.getDisplay() : "", null, null)); 635 r.getCells().add(missingCell(gen)); 636 } else { 637 r.setColor(COLOR_NO_ROW_LEFT); 638 ConceptReferenceComponent cs = (ConceptReferenceComponent) t.getRight(); 639 r.getCells().add(missingCell(gen)); 640 r.getCells().add(gen.new Cell(null, null, cs.getCode(), null, null)); 641 r.getCells().add(missingCell(gen)); 642 r.getCells().add(gen.new Cell(null, null, cs.hasDisplay() ? "Version: "+cs.getDisplay() : "", null, null)); 643 } 644 r.getCells().add(cellForMessages(gen, t.getMessages())); 645 646 } 647 648 private void addSetFilterRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t) { 649// Row r = gen.new Row(); 650// rows.add(r); 651// r.getCells().add(gen.new Cell(null, null, "Filter", null, null)); 652// if (t.hasLeft() && t.hasRight()) { 653// ConceptSetComponent csL = (ConceptSetComponent) t.getLeft(); 654// ConceptSetComponent csR = (ConceptSetComponent) t.getRight(); 655// // we assume both have systems 656// if (csL.getSystem().equals(csR.getSystem())) { 657// r.getCells().add(gen.new Cell(null, null, csL.getSystem(), null, null).span(2).center()); 658// } else { 659// r.getCells().add(gen.new Cell(null, null, csL.getSystem(), null, null).setStyle("background-color: "+COLOR_DIFFERENT)); 660// r.getCells().add(gen.new Cell(null, null, csR.getSystem(), null, null).setStyle("background-color: "+COLOR_DIFFERENT)); 661// } 662// 663// if (csL.hasVersion() && csR.hasVersion()) { 664// if (csL.getVersion().equals(csR.getVersion())) { 665// r.getCells().add(gen.new Cell(null, null, csL.getVersion(), null, null).span(2).center()); 666// } else { 667// r.getCells().add(gen.new Cell(null, null, csL.getVersion(), null, null).setStyle("background-color: "+COLOR_DIFFERENT)); 668// r.getCells().add(gen.new Cell(null, null, csR.getVersion(), null, null).setStyle("background-color: "+COLOR_DIFFERENT)); 669// } 670// } else if (csL.hasVersion()) { 671// r.getCells().add(gen.new Cell(null, null, csL.getVersion(), null, null)); 672// r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT)); 673// } else if (csR.hasVersion()) { 674// r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT)); 675// r.getCells().add(gen.new Cell(null, null, csR.getVersion(), null, null)); 676// } else { 677// r.getCells().add(missingCell(gen).span(2).center()); 678// } 679// 680// } else if (t.hasLeft()) { 681// r.setColor(COLOR_NO_ROW_RIGHT); 682// ConceptSetComponent cs = (ConceptSetComponent) t.getLeft(); 683// r.getCells().add(gen.new Cell(null, null, cs.getSystem(), null, null)); 684// r.getCells().add(missingCell(gen)); 685// r.getCells().add(gen.new Cell(null, null, cs.hasVersion() ? "Version: "+cs.getVersion() : "", null, null)); 686// r.getCells().add(missingCell(gen)); 687// } else { 688// r.setColor(COLOR_NO_ROW_LEFT); 689// ConceptSetComponent cs = (ConceptSetComponent) t.getRight(); 690// r.getCells().add(missingCell(gen)); 691// r.getCells().add(gen.new Cell(null, null, cs.getSystem(), null, null)); 692// r.getCells().add(missingCell(gen)); 693// r.getCells().add(gen.new Cell(null, null, cs.hasVersion() ? "Version: "+cs.getVersion() : "", null, null)); 694// } 695// r.getCells().add(gen.new Cell(null, null, t.getError(), null, null)); 696 697 } 698 699 public XhtmlNode renderExpansion(ValueSetComparison csc, String id, String prefix) throws IOException { 700 if (csc.getExpansion() == null) { 701 XhtmlNode p = new XhtmlNode(NodeType.Element, "p"); 702 p.tx("Unable to generate expansion - see errors"); 703 return p; 704 } 705 if (csc.getExpansion().getChildren().isEmpty()) { 706 XhtmlNode p = new XhtmlNode(NodeType.Element, "p"); 707 p.tx("Expansion is empty"); 708 return p; 709 } 710 // columns: code(+system), version, display , abstract, inactive, 711 boolean hasSystem = csc.getExpansion().getChildren().isEmpty() ? false : getSystemVaries(csc.getExpansion(), csc.getExpansion().getChildren().get(0).either().getSystem()); 712 boolean hasVersion = findVersion(csc.getExpansion()); 713 boolean hasAbstract = findAbstract(csc.getExpansion()); 714 boolean hasInactive = findInactive(csc.getExpansion()); 715 716 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(Utilities.path("[tmp]", "comparison"), false); 717 TableModel model = gen.new TableModel(id, true); 718 model.setAlternating(true); 719 if (hasSystem) { 720 model.getTitles().add(gen.new Title(null, null, "System", "The code for the concept", null, 100)); 721 } 722 model.getTitles().add(gen.new Title(null, null, "Code", "The system for the concept", null, 100)); 723 model.getTitles().add(gen.new Title(null, null, "Display", "The display for the concept", null, 200, 2)); 724// if (hasVersion) { 725// model.getTitles().add(gen.new Title(null, null, "Version", "The version for the concept", null, 200, 2)); 726// } 727// if (hasAbstract) { 728// model.getTitles().add(gen.new Title(null, null, "Abstract", "The abstract flag for the concept", null, 200, 2)); 729// } 730// if (hasInactive) { 731// model.getTitles().add(gen.new Title(null, null, "Inactive", "The inactive flag for the concept", null, 200, 2)); 732// } 733 model.getTitles().add(gen.new Title(null, null, "Comments", "Additional information about the comparison", null, 200)); 734 for (StructuralMatch<ValueSetExpansionContainsComponent> t : csc.getExpansion().getChildren()) { 735 addExpansionRow(gen, model.getRows(), t, hasSystem, hasVersion, hasAbstract, hasInactive); 736 } 737 return gen.generate(model, prefix, 0, null); 738 } 739 740 private void addExpansionRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<ValueSetExpansionContainsComponent> t, boolean hasSystem, boolean hasVersion, boolean hasAbstract, boolean hasInactive) { 741 Row r = gen.new Row(); 742 rows.add(r); 743 if (hasSystem) { 744 r.getCells().add(gen.new Cell(null, null, t.either().getSystem(), null, null)); 745 } 746 r.getCells().add(gen.new Cell(null, null, t.either().getCode(), null, null)); 747 if (t.hasLeft() && t.hasRight()) { 748 if (t.getLeft().hasDisplay() && t.getRight().hasDisplay()) { 749 if (t.getLeft().getDisplay().equals(t.getRight().getDisplay())) { 750 r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).span(2).center()); 751 } else { 752 r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT)); 753 r.getCells().add(gen.new Cell(null, null, t.getRight().getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT)); 754 } 755 } else if (t.getLeft().hasDisplay()) { 756 r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null)); 757 r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT)); 758 } else if (t.getRight().hasDisplay()) { 759 r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT)); 760 r.getCells().add(gen.new Cell(null, null, t.getRight().getDisplay(), null, null)); 761 } else { 762 r.getCells().add(missingCell(gen).span(2).center()); 763 } 764 765 } else if (t.hasLeft()) { 766 r.setColor(COLOR_NO_ROW_RIGHT); 767 r.getCells().add(gen.new Cell(null, null, t.either().getDisplay(), null, null)); 768 r.getCells().add(missingCell(gen)); 769 } else { 770 r.setColor(COLOR_NO_ROW_LEFT); 771 r.getCells().add(missingCell(gen)); 772 r.getCells().add(gen.new Cell(null, null, t.either().getDisplay(), null, null)); 773 } 774 r.getCells().add(cellForMessages(gen, t.getMessages())); 775 for (StructuralMatch<ValueSetExpansionContainsComponent> c : t.getChildren()) { 776 addExpansionRow(gen, r.getSubRows(), c, hasSystem, hasVersion, hasAbstract, hasInactive); 777 } 778 } 779 780 private boolean getSystemVaries(StructuralMatch<ValueSetExpansionContainsComponent> list, String system) { 781 for (StructuralMatch<ValueSetExpansionContainsComponent> t : list.getChildren()) { 782 if (t.hasLeft() && !system.equals(t.getLeft().getSystem())) { 783 return true; 784 } 785 if (t.hasRight() && !system.equals(t.getRight().getSystem())) { 786 return true; 787 } 788 if (getSystemVaries(t, system)) { 789 return true; 790 } 791 } 792 return false; 793 } 794 795 private boolean findInactive(StructuralMatch<ValueSetExpansionContainsComponent> list) { 796 for (StructuralMatch<ValueSetExpansionContainsComponent> t : list.getChildren()) { 797 if (t.hasLeft() && t.getLeft().getInactive()) { 798 return true; 799 } 800 if (t.hasRight() && t.getRight().getInactive()) { 801 return true; 802 } 803 if (findInactive(t)) { 804 return true; 805 } 806 } 807 return false; 808 } 809 810 private boolean findAbstract(StructuralMatch<ValueSetExpansionContainsComponent> list) { 811 for (StructuralMatch<ValueSetExpansionContainsComponent> t : list.getChildren()) { 812 if (t.hasLeft() && t.getLeft().getAbstract()) { 813 return true; 814 } 815 if (t.hasRight() && t.getRight().getAbstract()) { 816 return true; 817 } 818 if (findAbstract(t)) { 819 return true; 820 } 821 } 822 return false; 823 } 824 825 private boolean findVersion(StructuralMatch<ValueSetExpansionContainsComponent> list) { 826 for (StructuralMatch<ValueSetExpansionContainsComponent> t : list.getChildren()) { 827 if (t.hasLeft() && t.getLeft().hasVersion()) { 828 return true; 829 } 830 if (t.hasRight() && t.getRight().hasVersion()) { 831 return true; 832 } 833 if (findVersion(t)) { 834 return true; 835 } 836 } 837 return false; 838 } 839 840}