001package org.hl7.fhir.r5.comparison; 002 003import java.io.IOException; 004import java.util.ArrayList; 005import java.util.Date; 006import java.util.HashMap; 007import java.util.List; 008import java.util.Map; 009 010import org.hl7.fhir.exceptions.DefinitionException; 011import org.hl7.fhir.exceptions.FHIRException; 012import org.hl7.fhir.r5.model.Base; 013import org.hl7.fhir.r5.model.CodeSystem; 014import org.hl7.fhir.r5.model.CodeSystem.CodeSystemFilterComponent; 015import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; 016import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionDesignationComponent; 017import org.hl7.fhir.r5.model.CodeSystem.ConceptPropertyComponent; 018import org.hl7.fhir.r5.model.CodeSystem.PropertyComponent; 019import org.hl7.fhir.utilities.Utilities; 020import org.hl7.fhir.utilities.validation.ValidationMessage; 021import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; 022import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType; 023import org.hl7.fhir.utilities.validation.ValidationMessage.Source; 024import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 025import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell; 026import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row; 027import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel; 028import org.hl7.fhir.utilities.xhtml.XhtmlNode; 029 030public class CodeSystemComparer extends CanonicalResourceComparer { 031 032 033 public class CodeSystemComparison extends CanonicalResourceComparison<CodeSystem> { 034 035 private StructuralMatch<PropertyComponent> properties = new StructuralMatch<PropertyComponent>(); 036 private StructuralMatch<CodeSystemFilterComponent> filters = new StructuralMatch<CodeSystemFilterComponent>(); 037 private StructuralMatch<ConceptDefinitionComponent> combined; 038 private Map<String, String> propMap = new HashMap<>(); // right to left; left retains it's name 039 public CodeSystemComparison(CodeSystem left, CodeSystem right) { 040 super(left, right); 041 combined = new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(); // base 042 } 043 044 public Map<String, String> getPropMap() { 045 return propMap; 046 } 047 048 public StructuralMatch<ConceptDefinitionComponent> getCombined() { 049 return combined; 050 } 051 052 public StructuralMatch<PropertyComponent> getProperties() { 053 return properties; 054 } 055 056 public StructuralMatch<CodeSystemFilterComponent> getFilters() { 057 return filters; 058 } 059 060 @Override 061 protected String abbreviation() { 062 return "cs"; 063 } 064 065 @Override 066 protected String summary() { 067 String res = "CodeSystem: "+left.present()+" vs "+right.present(); 068 String ch = changeSummary(); 069 if (ch != null) { 070 res = res + ". "+ch; 071 } 072 return res; 073 } 074 075 076 @Override 077 protected String fhirType() { 078 return "CodeSystem"; 079 } 080 081 @Override 082 protected void countMessages(MessageCounts cnts) { 083 super.countMessages(cnts); 084 combined.countMessages(cnts); 085 } 086 087 } 088 089 private CodeSystem right; 090 091 public CodeSystemComparer(ComparisonSession session) { 092 super(session); 093 } 094 095 public CodeSystemComparison compare(CodeSystem left, CodeSystem right) { 096 if (left == null) 097 throw new DefinitionException("No CodeSystem provided (left)"); 098 if (right == null) 099 throw new DefinitionException("No CodeSystem provided (right)"); 100 101 CodeSystemComparison res = new CodeSystemComparison(left, right); 102 session.identify(res); 103 CodeSystem cs = new CodeSystem(); 104 res.setUnion(cs); 105 session.identify(cs); 106 cs.setName("Union"+left.getName()+"And"+right.getName()); 107 cs.setTitle("Union of "+left.getTitle()+" And "+right.getTitle()); 108 cs.setStatus(left.getStatus()); 109 cs.setDate(new Date()); 110 for (PropertyComponent pL : left.getProperty()) { 111 cs.addProperty(pL.copy()); 112 } 113 for (PropertyComponent pR : left.getProperty()) { 114 PropertyComponent pL = findProperty(left, pR); 115 if (pL == null) { 116 String code = getUniqued(pR.getCode(), cs.getProperty()); 117 cs.addProperty(pR.copy().setCode(code)); 118 } else { 119 res.getPropMap().put(pR.getCode(), pL.getCode()); 120 } 121 } 122 123 CodeSystem cs1 = new CodeSystem(); 124 res.setIntersection(cs1); 125 session.identify(cs1); 126 cs1.setName("Intersection"+left.getName()+"And"+right.getName()); 127 cs1.setTitle("Intersection of "+left.getTitle()+" And "+right.getTitle()); 128 cs1.setStatus(left.getStatus()); 129 cs1.setDate(new Date()); 130 cs1.getProperty().addAll(cs.getProperty()); 131 132 133 List<String> chMetadata = new ArrayList<>(); 134 boolean ch = compareMetadata(left, right, res.getMetadata(), res, chMetadata, right); 135 if (comparePrimitives("versionNeeded", left.getVersionNeededElement(), right.getVersionNeededElement(), res.getMetadata(), IssueSeverity.INFORMATION, res)) { 136 ch = true; 137 chMetadata.add("versionNeeded"); 138 } 139 if (comparePrimitives("compositional", left.getCompositionalElement(), right.getCompositionalElement(), res.getMetadata(), IssueSeverity.WARNING, res)) { 140 ch = true; 141 chMetadata.add("compositional"); 142 } 143 res.updatedMetadataState(ch, chMetadata); 144 ch = false; 145 ch = comparePrimitivesWithTracking("caseSensitive", left.getCaseSensitiveElement(), right.getCaseSensitiveElement(), res.getMetadata(), IssueSeverity.ERROR, res, right) || ch; 146 ch = comparePrimitivesWithTracking("hierarchyMeaning", left.getHierarchyMeaningElement(), right.getHierarchyMeaningElement(), res.getMetadata(), IssueSeverity.ERROR, res, right) || ch; 147 ch = comparePrimitivesWithTracking("content", left.getContentElement(), right.getContentElement(), res.getMetadata(), IssueSeverity.WARNING, res, right); 148 149 ch = compareProperties(left.getProperty(), right.getProperty(), res.getProperties(), res.getUnion().getProperty(), res.getIntersection().getProperty(), res.getUnion(), res.getIntersection(), res, "CodeSystem.property", right) || ch; 150 ch = compareFilters(left.getFilter(), right.getFilter(), res.getFilters(), res.getUnion().getFilter(), res.getIntersection().getFilter(), res.getUnion(), res.getIntersection(), res, "CodeSystem.filter", right) || ch; 151 ch = compareConcepts(left.getConcept(), right.getConcept(), res.getCombined(), res.getUnion().getConcept(), res.getIntersection().getConcept(), res.getUnion(), res.getIntersection(), res, "CodeSystem.concept", right) || ch; 152 res.updateDefinitionsState(ch); 153 154 session.annotate(right, res); 155 return res; 156 } 157 158 private String getUniqued(String code, List<PropertyComponent> list) { 159 int i = 0; 160 while (true) { 161 boolean ok = true; 162 String res = code+(i == 0 ? "" : i); 163 for (PropertyComponent t : list) { 164 if (res.equals(t.getCode())) { 165 ok = false; 166 } 167 } 168 if (ok) { 169 return res; 170 } 171 } 172 } 173 174 private PropertyComponent findProperty(CodeSystem left, PropertyComponent p) { 175 for (PropertyComponent t : left.getProperty()) { 176 if (p.hasUri() && t.hasUri() && p.getUri().equals(t.getUri())) { 177 return t; 178 } else if (!p.hasUri() && !t.hasUri() && p.getCode().equals(t.getCode())) { 179 return t; 180 } 181 } 182 return null; 183 } 184 185 private boolean compareProperties(List<PropertyComponent> left, List<PropertyComponent> right, StructuralMatch<PropertyComponent> combined, 186 List<PropertyComponent> union, List<PropertyComponent> intersection, CodeSystem csU, CodeSystem csI, CodeSystemComparison res, String path, Base parent) { 187 boolean def = false; 188 List<PropertyComponent> matchR = new ArrayList<>(); 189 for (PropertyComponent l : left) { 190 PropertyComponent r = findInList(right, l); 191 if (r == null) { 192 union.add(l); 193 res.updateContentState(true); 194 combined.getChildren().add(new StructuralMatch<CodeSystem.PropertyComponent>(l, vmI(IssueSeverity.INFORMATION, "Removed this concept", path))); 195 session.markDeleted(parent, "concept", l); 196 } else { 197 matchR.add(r); 198 PropertyComponent cdM = merge(l, r, res); 199 PropertyComponent cdI = intersect(l, r, res); 200 union.add(cdM); 201 intersection.add(cdI); 202 StructuralMatch<PropertyComponent> sm = new StructuralMatch<CodeSystem.PropertyComponent>(l, r); 203 if (compare(sm.getMessages(), l, r, path+".where(code='"+l.getCode()+"')", res, parent)) { 204 def = true; 205 } 206 combined.getChildren().add(sm); 207 } 208 } 209 for (PropertyComponent r : right) { 210 if (!matchR.contains(r)) { 211 union.add(r); 212 res.updateContentState(true); 213 combined.getChildren().add(new StructuralMatch<CodeSystem.PropertyComponent>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r)); 214 session.markAdded(r); 215 } 216 } 217 return def; 218 } 219 220 221 private boolean compareFilters(List<CodeSystemFilterComponent> left, List<CodeSystemFilterComponent> right, StructuralMatch<CodeSystemFilterComponent> combined, 222 List<CodeSystemFilterComponent> union, List<CodeSystemFilterComponent> intersection, CodeSystem csU, CodeSystem csI, CodeSystemComparison res, String path, Base parent) { 223 boolean def = false; 224 List<CodeSystemFilterComponent> matchR = new ArrayList<>(); 225 for (CodeSystemFilterComponent l : left) { 226 CodeSystemFilterComponent r = findInList(right, l); 227 if (r == null) { 228 union.add(l); 229 res.updateContentState(true); 230 combined.getChildren().add(new StructuralMatch<CodeSystem.CodeSystemFilterComponent>(l, vmI(IssueSeverity.INFORMATION, "Removed this concept", path))); 231 session.markDeleted(parent, "concept", l); 232 } else { 233 matchR.add(r); 234 CodeSystemFilterComponent cdM = merge(l, r, res); 235 CodeSystemFilterComponent cdI = intersect(l, r, res); 236 union.add(cdM); 237 intersection.add(cdI); 238 StructuralMatch<CodeSystemFilterComponent> sm = new StructuralMatch<CodeSystem.CodeSystemFilterComponent>(l, r); 239 if (compare(sm.getMessages(), l, r, path+".where(code='"+l.getCode()+"')", res, parent)) { 240 def = true; 241 } 242 combined.getChildren().add(sm); 243 } 244 } 245 for (CodeSystemFilterComponent r : right) { 246 if (!matchR.contains(r)) { 247 union.add(r); 248 res.updateContentState(true); 249 combined.getChildren().add(new StructuralMatch<CodeSystem.CodeSystemFilterComponent>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r)); 250 session.markAdded(r); 251 } 252 } 253 return def; 254 } 255 256 257 private boolean compareConcepts(List<ConceptDefinitionComponent> left, List<ConceptDefinitionComponent> right, StructuralMatch<ConceptDefinitionComponent> combined, 258 List<ConceptDefinitionComponent> union, List<ConceptDefinitionComponent> intersection, CodeSystem csU, CodeSystem csI, CodeSystemComparison res, String path, Base parent) { 259 boolean def = false; 260 List<ConceptDefinitionComponent> matchR = new ArrayList<>(); 261 for (ConceptDefinitionComponent l : left) { 262 ConceptDefinitionComponent r = findInList(right, l); 263 if (r == null) { 264 union.add(l); 265 res.updateContentState(true); 266 combined.getChildren().add(new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(l, vmI(IssueSeverity.INFORMATION, "Removed this concept", path))); 267 session.markDeleted(parent, "concept", l); 268 } else { 269 matchR.add(r); 270 ConceptDefinitionComponent cdM = merge(l, r, csU.getProperty(), res); 271 ConceptDefinitionComponent cdI = intersect(l, r, res); 272 union.add(cdM); 273 intersection.add(cdI); 274 StructuralMatch<ConceptDefinitionComponent> sm = new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(l, r); 275 if (compare(sm.getMessages(), l, r, path+".where(code='"+l.getCode()+"')", res, parent)) { 276 def = true; 277 } 278 combined.getChildren().add(sm); 279 if (compareConcepts(l.getConcept(), r.getConcept(), sm, cdM.getConcept(), cdI.getConcept(), csU, csI, res, path+".where(code='"+l.getCode()+"').concept", r)) { 280 def = true; 281 } 282 } 283 } 284 for (ConceptDefinitionComponent r : right) { 285 if (!matchR.contains(r)) { 286 union.add(r); 287 res.updateContentState(true); 288 combined.getChildren().add(new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r)); 289 session.markAdded(r); 290 } 291 } 292 return def; 293 } 294 295 private CodeSystemFilterComponent findInList(List<CodeSystemFilterComponent> list, CodeSystemFilterComponent item) { 296 for (CodeSystemFilterComponent t : list) { 297 if (t.getCode().equals(item.getCode())) { 298 return t; 299 } 300 } 301 return null; 302 } 303 304 305 private ConceptDefinitionComponent findInList(List<ConceptDefinitionComponent> list, ConceptDefinitionComponent item) { 306 for (ConceptDefinitionComponent t : list) { 307 if (t.getCode().equals(item.getCode())) { 308 return t; 309 } 310 } 311 return null; 312 } 313 314 315 private PropertyComponent findInList(List<PropertyComponent> list, PropertyComponent item) { 316 for (PropertyComponent t : list) { 317 if (t.getCode().equals(item.getCode())) { 318 return t; 319 } 320 } 321 return null; 322 } 323 324 private boolean compare(List<ValidationMessage> msgs, ConceptDefinitionComponent l, ConceptDefinitionComponent r, String path, CodeSystemComparison res, Base parent) { 325 boolean result = false; 326 result = compareStrings(path, msgs, l.getDisplay(), r.getDisplay(), "display", IssueSeverity.WARNING, res, parent, l.getDisplayElement(), r.getDisplayElement()) || result; 327 result = compareStrings(path, msgs, l.getDefinition(), r.getDefinition(), "definition", IssueSeverity.INFORMATION, res, parent, l.getDefinitionElement(), r.getDefinitionElement()) || result; 328 // todo: designations, properties 329 return result; 330 } 331 332 private boolean compare(List<ValidationMessage> msgs, PropertyComponent l, PropertyComponent r, String path, CodeSystemComparison res, Base parent) { 333 boolean result = false; 334 result = compareStrings(path, msgs, l.getUri(), r.getUri(), "uri", IssueSeverity.WARNING, res, parent, l.getUriElement(), r.getUriElement()) || result; 335 result = compareStrings(path, msgs, l.hasType() ? l.getType().toCode() : null, r.hasType() ? r.getType().toCode() : null, "type", IssueSeverity.ERROR, res, parent, l.getTypeElement(), r.getTypeElement()) || result; 336 result = compareStrings(path, msgs, l.getDescription(), r.getDescription(), "description", IssueSeverity.WARNING, res, parent, l.getDescriptionElement(), r.getDescriptionElement()) || result; 337 return result; 338 } 339 340 private boolean compare(List<ValidationMessage> msgs, CodeSystemFilterComponent l, CodeSystemFilterComponent r, String path, CodeSystemComparison res, Base parent) { 341 boolean result = false; 342 result = compareStrings(path, msgs, l.getDescription(), r.getDescription(), "description", IssueSeverity.WARNING, res, parent, l.getDescriptionElement(), r.getDescriptionElement()) || result; 343// todo: repeating 344// result = compareStrings(path, msgs, l.hasOperator() ? l.getOperator().toCode() : null, r.hasType() ? r.getType().toCode() : null, "type", IssueSeverity.ERROR, res, parent, l.getTypeElement(), r.getTypeElement()) || result; 345 result = compareStrings(path, msgs, l.getValue(), r.getValue(), "value", IssueSeverity.WARNING, res, parent, l.getValueElement(), r.getValueElement()) || result; 346 return result; 347 } 348 349 private boolean compareStrings(String path, List<ValidationMessage> msgs, String left, String right, String name, IssueSeverity level, CodeSystemComparison res, Base parent, Base l, Base r) { 350 if (!Utilities.noString(right)) { 351 if (Utilities.noString(left)) { 352 msgs.add(vmI(level, "Value for "+name+" added", path)); 353 session.markAdded(r); 354 return true; 355 } else if (!left.equals(right)) { 356 if (level != IssueSeverity.NULL) { 357 res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path+"."+name, "Changed value for "+name+": '"+left+"' vs '"+right+"'", level)); 358 } 359 msgs.add(vmI(level, name+" changed from left to right", path)); 360 session.markChanged(r, l); 361 return true; 362 } 363 } else if (!Utilities.noString(left)) { 364 msgs.add(vmI(level, "Value for "+name+" removed", path)); 365 session.markDeleted(parent, "concept", l); 366 return true; 367 } 368 return false; 369 } 370 371 private ConceptDefinitionComponent merge(ConceptDefinitionComponent l, ConceptDefinitionComponent r, List<PropertyComponent> destProps, CodeSystemComparison res) { 372 ConceptDefinitionComponent cd = l.copy(); 373 if (!l.hasDisplay() && r.hasDisplay()) { 374 cd.setDisplay(r.getDisplay()); 375 } 376 if (!l.hasDefinition() && r.hasDefinition()) { 377 cd.setDefinition(r.getDefinition()); 378 } 379 mergeProps(cd, l, r, destProps, res); 380 mergeDesignations(cd, l, r); 381 return cd; 382 } 383 384 private PropertyComponent merge(PropertyComponent l, PropertyComponent r, CodeSystemComparison res) { 385 PropertyComponent cd = l.copy(); 386 if (!l.hasDescription() && r.hasDescription()) { 387 cd.setDescription(r.getDescription()); 388 } 389 return cd; 390 } 391 392 393 private CodeSystemFilterComponent merge(CodeSystemFilterComponent l, CodeSystemFilterComponent r, CodeSystemComparison res) { 394 CodeSystemFilterComponent cd = l.copy(); 395 if (!l.hasDescription() && r.hasDescription()) { 396 cd.setDescription(r.getDescription()); 397 } 398 return cd; 399 } 400 401 private ConceptDefinitionComponent intersect(ConceptDefinitionComponent l, ConceptDefinitionComponent r, CodeSystemComparison res) { 402 ConceptDefinitionComponent cd = l.copy(); 403 if (l.hasDisplay() && !r.hasDisplay()) { 404 cd.setDisplay(null); 405 } 406 if (l.hasDefinition() && !r.hasDefinition()) { 407 cd.setDefinition(null); 408 } 409 intersectProps(cd, l, r, res); 410 // mergeDesignations(cd, l, r); 411 return cd; 412 } 413 414 private PropertyComponent intersect(PropertyComponent l, PropertyComponent r, CodeSystemComparison res) { 415 PropertyComponent cd = l.copy(); 416 if (l.hasDescription() && !r.hasDescription()) { 417 cd.setDescription(null); 418 } 419 return cd; 420 } 421 422 private CodeSystemFilterComponent intersect(CodeSystemFilterComponent l, CodeSystemFilterComponent r, CodeSystemComparison res) { 423 CodeSystemFilterComponent cd = l.copy(); 424 if (l.hasDescription() && !r.hasDescription()) { 425 cd.setDescription(null); 426 } 427 return cd; 428 } 429 430 private void mergeDesignations(ConceptDefinitionComponent cd, ConceptDefinitionComponent l, ConceptDefinitionComponent r) { 431 for (ConceptDefinitionDesignationComponent td : l.getDesignation()) { 432 if (hasDesignation(td, r.getDesignation())) { 433 cd.getDesignation().add(td); 434 } 435 } 436 for (ConceptDefinitionDesignationComponent td : r.getDesignation()) { 437 if (hasDesignation(td, l.getDesignation())) { 438 cd.getDesignation().add(td); 439 } 440 } 441 } 442 443 private boolean hasDesignation(ConceptDefinitionDesignationComponent td, List<ConceptDefinitionDesignationComponent> designation) { 444 for (ConceptDefinitionDesignationComponent t : designation) { 445 if (designationsMatch(td, t)) { 446 return true; 447 } 448 } 449 return false; 450 } 451 452 private boolean designationsMatch(ConceptDefinitionDesignationComponent l, ConceptDefinitionDesignationComponent r) { 453 if (l.hasUse() != r.hasUse()) { 454 return false; 455 } 456 if (l.hasLanguage() != r.hasLanguage()) { 457 return false; 458 } 459 if (l.hasValue() != r.hasValue()) { 460 return false; 461 } 462 if (l.hasUse()) { 463 if (l.getUse().equalsDeep(r.getUse())) { 464 return false; 465 } 466 } 467 if (l.hasLanguage()) { 468 if (l.getLanguageElement().equalsDeep(r.getLanguageElement())) { 469 return false; 470 } 471 } 472 if (l.hasValue()) { 473 if (l.getValueElement().equalsDeep(r.getValueElement())) { 474 return false; 475 } 476 } 477 return true; 478 } 479 480 private void mergeProps(ConceptDefinitionComponent cd, ConceptDefinitionComponent l, ConceptDefinitionComponent r, List<PropertyComponent> destProps, CodeSystemComparison res) { 481 List<ConceptPropertyComponent> matchR = new ArrayList<>(); 482 for (ConceptPropertyComponent lp : l.getProperty()) { 483 ConceptPropertyComponent rp = findRightProp(r.getProperty(), lp, res); 484 if (rp == null) { 485 cd.getProperty().add(lp); 486 } else { 487 matchR.add(rp); 488 cd.getProperty().add(lp); 489 if (lp.getValue().equalsDeep(rp.getValue())) { 490 cd.getProperty().add(rp.setCode(res.getPropMap().get(rp.getCode()))); 491 } 492 } 493 } 494 for (ConceptPropertyComponent rp : r.getProperty()) { 495 if (!matchR.contains(rp)) { 496 cd.getProperty().add(rp.setCode(res.getPropMap().get(rp.getCode()))); 497 } 498 } 499 } 500 501 private void intersectProps(ConceptDefinitionComponent cd, ConceptDefinitionComponent l, ConceptDefinitionComponent r, CodeSystemComparison res) { 502 for (ConceptPropertyComponent lp : l.getProperty()) { 503 ConceptPropertyComponent rp = findRightProp(r.getProperty(), lp, res); 504 if (rp != null) { 505 cd.getProperty().add(lp); 506 } 507 } 508 } 509 510 private ConceptPropertyComponent findRightProp(List<ConceptPropertyComponent> rightProperties, ConceptPropertyComponent lp, CodeSystemComparison res) { 511 for (ConceptPropertyComponent p : rightProperties) { 512 String rp = res.getPropMap().get(p.getCode()); 513 if (rp != null && rp.equals(lp.getCode())) { 514 return p; 515 } 516 } 517 return null; 518 } 519 520 public XhtmlNode renderConcepts(CodeSystemComparison comparison, String id, String prefix) throws FHIRException, IOException { 521 // columns: code, display (left|right), properties (left|right) 522 HierarchicalTableGenerator gen = new HierarchicalTableGenerator(Utilities.path("[tmp]", "compare"), false); 523 TableModel model = gen.new TableModel(id, true); 524 model.setAlternating(true); 525 model.getTitles().add(gen.new Title(null, null, "Code", "The code for the concept", null, 100)); 526 model.getTitles().add(gen.new Title(null, null, "Display", "The display for the concept", null, 200, 2)); 527 for (PropertyComponent p : comparison.getUnion().getProperty()) { 528 model.getTitles().add(gen.new Title(null, null, p.getCode(), p.getDescription(), null, 100, 2)); 529 } 530 model.getTitles().add(gen.new Title(null, null, "Comments", "Additional information about the comparison", null, 200)); 531 for (StructuralMatch<ConceptDefinitionComponent> t : comparison.getCombined().getChildren()) { 532 addRow(gen, model.getRows(), t, comparison); 533 } 534 return gen.generate(model, prefix, 0, null); 535 } 536 537 private void addRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<ConceptDefinitionComponent> t, CodeSystemComparison comparison) { 538 Row r = gen.new Row(); 539 rows.add(r); 540 r.getCells().add(gen.new Cell(null, null, t.either().getCode(), null, null)); 541 if (t.hasLeft() && t.hasRight()) { 542 if (t.getLeft().hasDisplay() && t.getRight().hasDisplay()) { 543 if (t.getLeft().getDisplay().equals(t.getRight().getDisplay())) { 544 r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).span(2)); 545 } else { 546 r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT)); 547 r.getCells().add(gen.new Cell(null, null, t.getRight().getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT)); 548 } 549 } else if (t.getLeft().hasDisplay()) { 550 r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null)); 551 r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT)); 552 } else if (t.getRight().hasDisplay()) { 553 r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT)); 554 r.getCells().add(gen.new Cell(null, null, t.getRight().getDisplay(), null, null)); 555 } else { 556 r.getCells().add(missingCell(gen).span(2)); 557 } 558 for (PropertyComponent p : comparison.getUnion().getProperty()) { 559 ConceptPropertyComponent lp = getProp(t.getLeft(), p, false, comparison); 560 ConceptPropertyComponent rp = getProp(t.getRight(), p, true, comparison); 561 562 if (lp != null && rp != null) { 563 if (lp.getValue().equals(rp.getValue())) { 564 r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).span(2)); 565 } else { 566 r.getCells().add(gen.new Cell(null, null, lp.getValue().toString(), null, null)); 567 r.getCells().add(gen.new Cell(null, null, rp.getValue().toString(), null, null)); 568 } 569 } else if (lp != null) { 570 r.getCells().add(gen.new Cell(null, null, lp.getValue().toString(), null, null)); 571 r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT)); 572 } else if (rp != null) { 573 r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT)); 574 r.getCells().add(gen.new Cell(null, null, rp.getValue().toString(), null, null)); 575 } else { 576 r.getCells().add(missingCell(gen).span(2)); 577 } 578 579 } 580 } else if (t.hasLeft()) { 581 r.setColor(COLOR_NO_ROW_RIGHT); 582 r.getCells().add(gen.new Cell(null, null, t.either().getDisplay(), null, null)); 583 r.getCells().add(missingCell(gen)); 584 for (PropertyComponent p : comparison.getUnion().getProperty()) { 585 r.getCells().add(propertyCell(gen, t.getLeft(), p, false, comparison)); 586 r.getCells().add(missingCell(gen)); 587 } 588 } else { 589 r.setColor(COLOR_NO_ROW_LEFT); 590 r.getCells().add(missingCell(gen)); 591 r.getCells().add(gen.new Cell(null, null, t.either().getDisplay(), null, null)); 592 for (PropertyComponent p : comparison.getUnion().getProperty()) { 593 r.getCells().add(missingCell(gen)); 594 r.getCells().add(propertyCell(gen, t.getLeft(), p, true, comparison)); 595 } 596 } 597 r.getCells().add(cellForMessages(gen, t.getMessages())); 598 } 599 600 private Cell propertyCell(HierarchicalTableGenerator gen, ConceptDefinitionComponent cd, PropertyComponent p, boolean right, CodeSystemComparison comp) { 601 ConceptPropertyComponent cp = getProp(cd, p, right, comp); 602 if (cp == null) { 603 return missingCell(gen, right ? COLOR_NO_CELL_RIGHT : COLOR_NO_CELL_LEFT); 604 } else { 605 return gen.new Cell(null, null, cp.getValue().toString(), null, null); 606 } 607 } 608 609 public ConceptPropertyComponent getProp(ConceptDefinitionComponent cd, PropertyComponent p, boolean right, CodeSystemComparison comp) { 610 String c = p.getCode(); 611 if (right) { 612 c = comp.getPropMap().get(c); 613 } 614 ConceptPropertyComponent cp = null; 615 if (cd != null) { 616 for (ConceptPropertyComponent t : cd.getProperty()) { 617 if (t.hasCode() && t.getCode().equals(c)) { 618 cp = t; 619 } 620 } 621 } 622 return cp; 623 } 624 625 @Override 626 protected String fhirType() { 627 return "CodeSystem"; 628 } 629 630}