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