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