
001package org.hl7.fhir.r5.renderers; 002 003import java.io.IOException; 004import java.io.UnsupportedEncodingException; 005import java.util.ArrayList; 006import java.util.Collections; 007import java.util.Comparator; 008import java.util.HashMap; 009import java.util.HashSet; 010import java.util.List; 011import java.util.Map; 012 013import org.hl7.fhir.exceptions.DefinitionException; 014import org.hl7.fhir.exceptions.FHIRException; 015import org.hl7.fhir.exceptions.FHIRFormatError; 016import org.hl7.fhir.r5.conformance.profile.ProfileUtilities; 017import org.hl7.fhir.r5.model.CodeSystem; 018import org.hl7.fhir.r5.model.Coding; 019import org.hl7.fhir.r5.model.ConceptMap; 020import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent; 021import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupUnmappedMode; 022import org.hl7.fhir.r5.model.ConceptMap.MappingPropertyComponent; 023import org.hl7.fhir.r5.model.ConceptMap.OtherElementComponent; 024import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent; 025import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent; 026import org.hl7.fhir.r5.model.ContactDetail; 027import org.hl7.fhir.r5.model.ContactPoint; 028import org.hl7.fhir.r5.model.ElementDefinition; 029import org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship; 030import org.hl7.fhir.r5.model.Resource; 031import org.hl7.fhir.r5.model.StructureDefinition; 032import org.hl7.fhir.r5.renderers.utils.RenderingContext; 033import org.hl7.fhir.r5.renderers.utils.ResourceWrapper; 034import org.hl7.fhir.r5.utils.EOperationOutcome; 035import org.hl7.fhir.r5.utils.ToolingExtensions; 036import org.hl7.fhir.utilities.CommaSeparatedStringBuilder; 037import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 038import org.hl7.fhir.utilities.Utilities; 039import org.hl7.fhir.utilities.xhtml.NodeType; 040import org.hl7.fhir.utilities.xhtml.XhtmlNode; 041 042@MarkedToMoveToAdjunctPackage 043public class ConceptMapRenderer extends TerminologyRenderer { 044 045 046 public ConceptMapRenderer(RenderingContext context) { 047 super(context); 048 } 049 050 @Override 051 public void buildNarrative(RenderingStatus status, XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { 052 if (r.isDirect()) { 053 renderResourceTechDetails(r, x); 054 genSummaryTable(status, x, (ConceptMap) r.getBase()); 055 render(status, r, x, (ConceptMap) r.getBase(), false); 056 } else { 057 // the intention is to change this in the future 058 x.para().tx("ConceptMapRenderer only renders native resources directly"); 059 } 060 } 061 062 @Override 063 public String buildSummary(ResourceWrapper r) throws UnsupportedEncodingException, IOException { 064 return canonicalTitle(r); 065 } 066 067 068 069 public static class CollateralDefinition { 070 private Resource resource; 071 private String label; 072 public CollateralDefinition(Resource resource, String label) { 073 super(); 074 this.resource = resource; 075 this.label = label; 076 } 077 public Resource getResource() { 078 return resource; 079 } 080 public String getLabel() { 081 return label; 082 } 083 } 084 085 public enum RenderMultiRowSortPolicy { 086 UNSORTED, FIRST_COL, LAST_COL 087 } 088 089 public interface IMultiMapRendererAdvisor { 090 public RenderMultiRowSortPolicy sortPolicy(Object rmmContext); 091 public List<Coding> getMembers(Object rmmContext, String uri); 092 public boolean describeMap(Object rmmContext, ConceptMap map, XhtmlNode x); 093 public boolean hasCollateral(Object rmmContext); 094 public List<CollateralDefinition> getCollateral(Object rmmContext, String uri); // URI identifies which column the collateral is for 095 public String getLink(Object rmmContext, String system, String code); 096 public boolean makeMapLinks(); 097 } 098 099 public static class MultipleMappingRowSorter implements Comparator<MultipleMappingRow> { 100 private boolean first; 101 102 protected MultipleMappingRowSorter(boolean first) { 103 super(); 104 this.first = first; 105 } 106 107 @Override 108 public int compare(MultipleMappingRow o1, MultipleMappingRow o2) { 109 String s1 = first ? o1.firstCode() : o1.lastCode(); 110 String s2 = first ? o2.firstCode() : o2.lastCode(); 111 return s1.compareTo(s2); 112 } 113 } 114 115 public static class Cell { 116 private String system; 117 private String code; 118 private String display; 119 private String relationship; 120 private String relComment; 121 public boolean renderedRel; 122 public boolean renderedCode; 123 private Cell clone; 124 125 protected Cell() { 126 super(); 127 } 128 129 public Cell(String system, String code, String display) { 130 this.system = system; 131 this.code = code; 132 this.display = display; 133 } 134 135 public Cell(String system, String code, String relationship, String comment) { 136 this.system = system; 137 this.code = code; 138 this.relationship = relationship; 139 this.relComment = comment; 140 } 141 142 public boolean matches(String system, String code) { 143 return (system != null && system.equals(this.system)) && (code != null && code.equals(this.code)); 144 } 145 146 public String present() { 147 if (system == null) { 148 return code; 149 } else { 150 return code; //+(clone == null ? "" : " (@"+clone.code+")"); 151 } 152 } 153 154 public Cell copy(boolean clone) { 155 Cell res = new Cell(); 156 res.system = system; 157 res.code = code; 158 res.display = display; 159 res.relationship = relationship; 160 res.relComment = relComment; 161 res.renderedRel = renderedRel; 162 res.renderedCode = renderedCode; 163 if (clone) { 164 res.clone = this; 165 } 166 return res; 167 } 168 169 @Override 170 public String toString() { 171 return relationship+" "+system + "#" + code + " \"" + display + "\""; 172 } 173 174 } 175 176 177 public static class MultipleMappingRowItem { 178 List<Cell> cells = new ArrayList<>(); 179 180 @Override 181 public String toString() { 182 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 183 for (Cell cell : cells) { 184 if (cell.relationship != null) { 185 b.append(cell.relationship+cell.code); 186 } else { 187 b.append(cell.code); 188 } 189 } 190 return b.toString(); 191 } 192 } 193 194 public static class MultipleMappingRow { 195 private List<MultipleMappingRowItem> rowSets = new ArrayList<>(); 196 private MultipleMappingRow stickySource; 197 198 public MultipleMappingRow(int i, String system, String code, String display) { 199 MultipleMappingRowItem row = new MultipleMappingRowItem(); 200 rowSets.add(row); 201 for (int c = 0; c < i; c++) { 202 row.cells.add(new Cell()); // blank cell spaces 203 } 204 row.cells.add(new Cell(system, code, display)); 205 } 206 207 208 public MultipleMappingRow(MultipleMappingRow stickySource) { 209 this.stickySource = stickySource; 210 } 211 212 @Override 213 public String toString() { 214 CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder(); 215 for (MultipleMappingRowItem rowSet : rowSets) { 216 b.append(""+rowSet.cells.size()); 217 } 218 CommaSeparatedStringBuilder b2 = new CommaSeparatedStringBuilder(";"); 219 for (MultipleMappingRowItem rowSet : rowSets) { 220 b2.append(rowSet.toString()); 221 } 222 return ""+rowSets.size()+" ["+b.toString()+"] ("+b2.toString()+")"; 223 } 224 225 226 public String lastCode() { 227 MultipleMappingRowItem first = rowSets.get(0); 228 for (int i = first.cells.size()-1; i >= 0; i--) { 229 if (first.cells.get(i).code != null) { 230 return first.cells.get(i).code; 231 } 232 } 233 return ""; 234 } 235 236 public String firstCode() { 237 MultipleMappingRowItem first = rowSets.get(0); 238 for (int i = 0; i < first.cells.size(); i++) { 239 if (first.cells.get(i).code != null) { 240 return first.cells.get(i).code; 241 } 242 } 243 return ""; 244 } 245 246 public void addSource(MultipleMappingRow sourceRow, List<MultipleMappingRow> rowList, ConceptMapRelationship relationship, String comment) { 247 // we already have a row, and we're going to collapse the rows on sourceRow into here, and add a matching terminus 248 assert sourceRow.rowSets.get(0).cells.size() == rowSets.get(0).cells.size()-1; 249 rowList.remove(sourceRow); 250 Cell template = rowSets.get(0).cells.get(rowSets.get(0).cells.size()-1); 251 for (MultipleMappingRowItem row : sourceRow.rowSets) { 252 row.cells.add(new Cell(template.system, template.code, relationship.getSymbol(), comment)); 253 } 254 rowSets.addAll(sourceRow.rowSets); 255 } 256 257 public void addTerminus() { 258 for (MultipleMappingRowItem row : rowSets) { 259 row.cells.add(new Cell(null, null, "X", null)); 260 } 261 } 262 263 public void addTarget(String system, String code, ConceptMapRelationship relationship, String comment, List<MultipleMappingRow> sets, int colCount) { 264 if (rowSets.get(0).cells.size() == colCount+1) { // if it's already has a target for this col then we have to clone (and split) the rows 265 for (MultipleMappingRowItem row : rowSets) { 266 row.cells.add(new Cell(system, code, relationship.getSymbol(), comment)); 267 } 268 } else { 269 MultipleMappingRow nrow = new MultipleMappingRow(this); 270 for (MultipleMappingRowItem row : rowSets) { 271 MultipleMappingRowItem n = new MultipleMappingRowItem(); 272 for (int i = 0; i < row.cells.size()-1; i++) { // note to skip the last 273 n.cells.add(row.cells.get(i).copy(true)); 274 } 275 n.cells.add(new Cell(system, code, relationship.getSymbol(), comment)); 276 nrow.rowSets.add(n); 277 } 278 sets.add(sets.indexOf(this), nrow); 279 } 280 } 281 282 public String lastSystem() { 283 MultipleMappingRowItem first = rowSets.get(0); 284 for (int i = first.cells.size()-1; i >= 0; i--) { 285 if (first.cells.get(i).system != null) { 286 return first.cells.get(i).system; 287 } 288 } 289 return ""; 290 } 291 292 public void addCopy(String system) { 293 for (MultipleMappingRowItem row : rowSets) { 294 row.cells.add(new Cell(system, lastCode(), "=", null)); 295 } 296 } 297 298 299 public boolean alreadyHasMappings(int i) { 300 for (MultipleMappingRowItem row : rowSets) { 301 if (row.cells.size() > i+1) { 302 return true; 303 } 304 } 305 return false; 306 } 307 308 309 public Cell getLastSource(int i) { 310 for (MultipleMappingRowItem row : rowSets) { 311 return row.cells.get(i+1); 312 } 313 throw new Error("Should not get here"); // return null 314 } 315 316 317 public void cloneSource(int i, Cell cell) { 318 MultipleMappingRowItem row = new MultipleMappingRowItem(); 319 rowSets.add(row); 320 for (int c = 0; c < i-1; c++) { 321 row.cells.add(new Cell()); // blank cell spaces 322 } 323 row.cells.add(cell.copy(true)); 324 row.cells.add(rowSets.get(0).cells.get(rowSets.get(0).cells.size()-1).copy(false)); 325 } 326 } 327 328 329 330 public void render(RenderingStatus status, ResourceWrapper res, XhtmlNode x, ConceptMap cm, boolean header) throws FHIRFormatError, DefinitionException, IOException { 331 332 if (context.isShowSummaryTable()) { 333 XhtmlNode h = x.h2(); 334 h.addText(cm.hasTitle() ? cm.getTitle() : cm.getName()); 335 addMarkdown(x, cm.getDescription()); 336 if (cm.hasCopyright()) 337 generateCopyright(x, res); 338 } 339 340 XhtmlNode p = x.para(); 341 p.tx(context.formatPhrase(RenderingContext.CONC_MAP_FROM) + " "); 342 if (cm.hasSourceScope()) 343 AddVsRef(cm.getSourceScope().primitiveValue(), p, cm); 344 else 345 p.tx(context.formatPhrase(RenderingContext.CONC_MAP_NOT_SPEC)); 346 p.tx(" "+ (context.formatPhrase(RenderingContext.CONC_MAP_TO) + " ")); 347 if (cm.hasTargetScope()) 348 AddVsRef(cm.getTargetScope().primitiveValue(), p, cm); 349 else 350 p.tx(context.formatPhrase(RenderingContext.CONC_MAP_NOT_SPEC)); 351 352 x.br(); 353 int gc = 0; 354 355 CodeSystem cs = getContext().getWorker().fetchCodeSystem("http://hl7.org/fhir/concept-map-relationship"); 356 if (cs == null) 357 cs = getContext().getWorker().fetchCodeSystem("http://hl7.org/fhir/concept-map-equivalence"); 358 String eqpath = cs == null ? null : cs.getWebPath(); 359 360 for (ConceptMapGroupComponent grp : cm.getGroup()) { 361 boolean hasComment = false; 362 boolean hasProperties = false; 363 boolean ok = true; 364 Map<String, HashSet<String>> props = new HashMap<String, HashSet<String>>(); 365 Map<String, HashSet<String>> sources = new HashMap<String, HashSet<String>>(); 366 Map<String, HashSet<String>> targets = new HashMap<String, HashSet<String>>(); 367 sources.put("code", new HashSet<String>()); 368 targets.put("code", new HashSet<String>()); 369 sources.get("code").add(grp.getSource()); 370 targets.get("code").add(grp.getTarget()); 371 for (SourceElementComponent ccl : grp.getElement()) { 372 ok = ok && (ccl.getNoMap() || (ccl.getTarget().size() == 1 && ccl.getTarget().get(0).getDependsOn().isEmpty() && ccl.getTarget().get(0).getProduct().isEmpty())); 373 if (ccl.hasExtension(ToolingExtensions.EXT_CM_NOMAP_COMMENT)) { 374 hasComment = true; 375 } 376 for (TargetElementComponent ccm : ccl.getTarget()) { 377 hasComment = hasComment || !Utilities.noString(ccm.getComment()); 378 for (MappingPropertyComponent pp : ccm.getProperty()) { 379 if (!props.containsKey(pp.getCode())) 380 props.put(pp.getCode(), new HashSet<String>()); 381 } 382 for (OtherElementComponent d : ccm.getDependsOn()) { 383 if (!sources.containsKey(d.getAttribute())) 384 sources.put(d.getAttribute(), new HashSet<String>()); 385 } 386 for (OtherElementComponent d : ccm.getProduct()) { 387 if (!targets.containsKey(d.getAttribute())) 388 targets.put(d.getAttribute(), new HashSet<String>()); 389 } 390 } 391 } 392 if (props.size() > 0) { 393 hasProperties = true; 394 } 395 gc++; 396 if (gc > 1) { 397 x.hr(); 398 } 399 StructureDefinition sdSrc = findSourceStructure(grp.getSource()); 400 StructureDefinition sdTgt = findSourceStructure(grp.getTarget()); 401 if (sdSrc != null && sdTgt != null) { 402 renderModelMap(sdSrc, sdTgt, status, res, x, gc, eqpath, grp, hasComment, hasProperties, ok, props, sources, targets); 403 } else { 404 renderCodeSystemMap(status, res, x, gc, eqpath, grp, hasComment, hasProperties, ok, props, sources, targets); 405 } 406 } 407 } 408 409 private StructureDefinition findSourceStructure(String source) { 410 if (source == null) { 411 return null; 412 } 413 String url = ProfileUtilities.getUrlFromCSUrl(source); 414 StructureDefinition sd = context.getContext().fetchResource(StructureDefinition.class, url); 415 if (sd != null) { 416 return sd; 417 } 418 for (StructureDefinition t : context.getContextUtilities().allStructures()) { 419 String u = ProfileUtilities.getCSUrl(t); 420 if (source.equals(u)) { 421 return t; 422 } 423 } 424 return null; 425 } 426 427 private void renderModelMap(StructureDefinition sdSrc, StructureDefinition sdTgt, RenderingStatus status, ResourceWrapper res, XhtmlNode x, int gc, String eqpath, 428 ConceptMapGroupComponent grp, boolean hasComment, boolean hasProperties, boolean ok, 429 Map<String, HashSet<String>> props, Map<String, HashSet<String>> sources, Map<String, HashSet<String>> targets) 430 throws UnsupportedEncodingException, IOException { 431 432 XhtmlNode pp = x.para(); 433 pp.b().tx(context.formatPhrase(RenderingContext.CONC_MAP_GRP, gc) + " "); 434 pp.tx(context.formatPhrase(RenderingContext.CONC_MAP_FROM) + " "); 435 pp.ah(sdSrc.getWebPath()).tx(sdSrc.present(context.getLocale().toLanguageTag())); 436 pp.tx(" to "); 437 pp.ah(sdTgt.getWebPath()).tx(sdTgt.present(context.getLocale().toLanguageTag())); 438 439 XhtmlNode tbl = x.table( "grid", false); 440 XhtmlNode tr = tbl.tr(); 441 tr.td().b().tx(context.formatPhrase(RenderingContext.CONC_MAP_SOURCE)); 442 tr.td().b().tx(context.formatPhrase(RenderingContext.CONC_MAP_SOURCE_CARD)); 443 tr.td().b().tx(context.formatPhrase(RenderingContext.CONC_MAP_SOURCE_TYPE)); 444 tr.td().b().tx(context.formatPhrase(RenderingContext.CONC_MAP_REL)); 445 tr.td().b().tx(context.formatPhrase(RenderingContext.CONC_MAP_TRGT)); 446 tr.td().b().tx(context.formatPhrase(RenderingContext.CONC_MAP_TRGT_CARD)); 447 tr.td().b().tx(context.formatPhrase(RenderingContext.CONC_MAP_TRGT_TYPE)); 448 if (hasComment) 449 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_COMMENT)); 450 for (SourceElementComponent ccl : grp.getElement()) { 451 tr = tbl.tr(); 452 ElementDefinition edSrc = sdSrc.getSnapshot().getElementById(ccl.getCode()); 453 if (edSrc == null) { 454 tr.td().colspan(3).addText(ccl.getCode()); 455 } else { 456 tr.td().ah(sdSrc.getWebPath()+"#"+ccl.getCode()).tx(ccl.getCode()); 457 tr.td().tx(""+edSrc.getMin()+".."+edSrc.getMax()); 458 tr.td().tx("todo"); 459 } 460 461 if (ccl.getNoMap()) { 462 if (!hasComment) { 463 tr.td().colspan("5").style("background-color: #efefef").tx("(not mapped)"); 464 } else if (ccl.hasExtension(ToolingExtensions.EXT_CM_NOMAP_COMMENT)) { 465 tr.td().colspan("5").style("background-color: #efefef").tx("(not mapped)"); 466 tr.td().style("background-color: #efefef").tx(ccl.getExtensionString(ToolingExtensions.EXT_CM_NOMAP_COMMENT)); 467 } else { 468 tr.td().colspan("4").style("background-color: #efefef").tx("(not mapped)"); 469 } 470 } else { 471 boolean first = true; 472 for (TargetElementComponent ccm : ccl.getTarget()) { 473 if (first) { 474 first = false; 475 } else { 476 tr = tbl.tr(); 477 tr.td().colspan(3).style("opacity: 0.5").addText("\""); 478 } 479 if (!ccm.hasRelationship()) { 480 tr.td().tx(":"+"("+ConceptMapRelationship.EQUIVALENT.toCode()+")"); 481 } else { 482 if (ccm.hasExtension(ToolingExtensions.EXT_OLD_CONCEPTMAP_EQUIVALENCE)) { 483 String code = ToolingExtensions.readStringExtension(ccm, ToolingExtensions.EXT_OLD_CONCEPTMAP_EQUIVALENCE); 484 tr.td().ah(context.prefixLocalHref(eqpath+"#"+code), code).tx(presentEquivalenceCode(code)); 485 } else { 486 tr.td().ah(context.prefixLocalHref(eqpath+"#"+ccm.getRelationship().toCode()), ccm.getRelationship().toCode()).tx(presentRelationshipCode(ccm.getRelationship().toCode())); 487 } 488 } 489 ElementDefinition edTgt = sdTgt.getSnapshot().getElementById(ccm.getCode()); 490 if (edTgt == null) { 491 tr.td().colspan(3).addText(ccm.getCode()); 492 } else { 493 tr.td().ah(sdTgt.getWebPath()+"#"+ccm.getCode()).tx(ccm.getCode()); 494 tr.td().tx(""+edTgt.getMin()+".."+edTgt.getMax()); 495 tr.td().tx("todo"); 496 } 497 498 if (hasComment) 499 tr.td().addText(ccm.getComment()); 500 } 501 } 502 } 503 } 504 505 private void renderCodeSystemMap(RenderingStatus status, ResourceWrapper res, XhtmlNode x, int gc, String eqpath, 506 ConceptMapGroupComponent grp, boolean hasComment, boolean hasProperties, boolean ok, 507 Map<String, HashSet<String>> props, Map<String, HashSet<String>> sources, Map<String, HashSet<String>> targets) 508 throws UnsupportedEncodingException, IOException { 509 XhtmlNode pp = x.para(); 510 pp.b().tx(context.formatPhrase(RenderingContext.CONC_MAP_GRP, gc) + " "); 511 pp.tx(context.formatPhrase(RenderingContext.CONC_MAP_FROM) + " "); 512 if (grp.hasSource()) { 513 renderCanonical(status, res, pp, CodeSystem.class, grp.getSourceElement()); 514 } else { 515 pp.code(context.formatPhrase(RenderingContext.CONC_MAP_CODE_SYS_UNSPEC)); 516 } 517 pp.tx(" to "); 518 if (grp.hasTarget()) { 519 renderCanonical(status, res, pp, CodeSystem.class, grp.getTargetElement()); 520 } else { 521 pp.code(context.formatPhrase(RenderingContext.CONC_MAP_CODE_SYS_UNSPEC)); 522 } 523 524 String display; 525 if (ok) { 526 // simple 527 XhtmlNode tbl = x.table( "grid", false); 528 XhtmlNode tr = tbl.tr(); 529 tr.td().b().tx(context.formatPhrase(RenderingContext.CONC_MAP_SOURCE)); 530 tr.td().b().tx(context.formatPhrase(RenderingContext.CONC_MAP_REL)); 531 tr.td().b().tx(context.formatPhrase(RenderingContext.CONC_MAP_TRGT)); 532 if (hasComment) 533 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_COMMENT)); 534 for (SourceElementComponent ccl : grp.getElement()) { 535 tr = tbl.tr(); 536 XhtmlNode td = tr.td(); 537 td.addText(ccl.getCode()); 538 display = ccl.hasDisplay() ? ccl.getDisplay() : getDisplayForConcept(grp.getSource(), ccl.getCode()); 539 if (display != null && !isSameCodeAndDisplay(ccl.getCode(), display)) 540 td.tx(" ("+display+")"); 541 if (ccl.getNoMap()) { 542 if (!hasComment) { 543 tr.td().colspan("2").style("background-color: #efefef").tx("(not mapped)"); 544 } else if (ccl.hasExtension(ToolingExtensions.EXT_CM_NOMAP_COMMENT)) { 545 tr.td().colspan("2").style("background-color: #efefef").tx("(not mapped)"); 546 tr.td().style("background-color: #efefef").tx(ccl.getExtensionString(ToolingExtensions.EXT_CM_NOMAP_COMMENT)); 547 } else { 548 tr.td().colspan("3").style("background-color: #efefef").tx("(not mapped)"); 549 } 550 } else { 551 boolean first = true; 552 for (TargetElementComponent ccm : ccl.getTarget()) { 553 if (first) { 554 first = false; 555 } else { 556 tr = tbl.tr(); 557 tr.td().style("opacity: 0.5").tx("\""); 558 } 559 if (!ccm.hasRelationship()) 560 tr.td().tx(":"+"("+ConceptMapRelationship.EQUIVALENT.toCode()+")"); 561 else { 562 if (ccm.hasExtension(ToolingExtensions.EXT_OLD_CONCEPTMAP_EQUIVALENCE)) { 563 String code = ToolingExtensions.readStringExtension(ccm, ToolingExtensions.EXT_OLD_CONCEPTMAP_EQUIVALENCE); 564 tr.td().ah(context.prefixLocalHref(eqpath+"#"+code), code).tx(presentEquivalenceCode(code)); 565 } else { 566 tr.td().ah(context.prefixLocalHref(eqpath+"#"+ccm.getRelationship().toCode()), ccm.getRelationship().toCode()).tx(presentRelationshipCode(ccm.getRelationship().toCode())); 567 } 568 } 569 td = tr.td(); 570 td.addText(ccm.getCode()); 571 display = ccm.hasDisplay() ? ccm.getDisplay() : getDisplayForConcept(grp.getTarget(), ccm.getCode()); 572 if (display != null && !isSameCodeAndDisplay(ccm.getCode(), display)) 573 td.tx(" ("+display+")"); 574 if (hasComment) 575 tr.td().addText(ccm.getComment()); 576 } 577 } 578 addUnmapped(tbl, grp); 579 } 580 } else { 581 boolean hasRelationships = false; 582 for (int si = 0; si < grp.getElement().size(); si++) { 583 SourceElementComponent ccl = grp.getElement().get(si); 584 for (int ti = 0; ti < ccl.getTarget().size(); ti++) { 585 TargetElementComponent ccm = ccl.getTarget().get(ti); 586 if (ccm.hasRelationship()) { 587 hasRelationships = true; 588 } 589 } 590 } 591 592 XhtmlNode tbl = x.table("grid", false); 593 XhtmlNode tr = tbl.tr(); 594 XhtmlNode td; 595 tr.td().colspan(Integer.toString(1+sources.size())).b().tx(context.formatPhrase(RenderingContext.CONC_MAP_SRC_DET)); 596 if (hasRelationships) { 597 tr.td().b().tx(context.formatPhrase(RenderingContext.CONC_MAP_REL)); 598 } 599 tr.td().colspan(Integer.toString(1+targets.size())).b().tx(context.formatPhrase(RenderingContext.CONC_MAP_TRGT_DET)); 600 if (hasComment) { 601 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_COMMENT)); 602 } 603 if (hasProperties) { 604 tr.td().colspan(Integer.toString(1+targets.size())).b().tx(context.formatPhrase(RenderingContext.GENERAL_PROPS)); 605 } 606 tr = tbl.tr(); 607 if (sources.get("code").size() == 1) { 608 String url = sources.get("code").iterator().next(); 609 renderCSDetailsLink(tr, url, true); 610 } else 611 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 612 for (String s : sources.keySet()) { 613 if (s != null && !s.equals("code")) { 614 if (sources.get(s).size() == 1) { 615 String url = sources.get(s).iterator().next(); 616 renderCSDetailsLink(tr, url, false); 617 } else 618 tr.td().b().addText(getDescForConcept(s)); 619 } 620 } 621 if (hasRelationships) { 622 tr.td(); 623 } 624 if (targets.get("code").size() == 1) { 625 String url = targets.get("code").iterator().next(); 626 renderCSDetailsLink(tr, url, true); 627 } else 628 tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE)); 629 for (String s : targets.keySet()) { 630 if (s != null && !s.equals("code")) { 631 if (targets.get(s).size() == 1) { 632 String url = targets.get(s).iterator().next(); 633 renderCSDetailsLink(tr, url, false); 634 } else 635 tr.td().b().addText(getDescForConcept(s)); 636 } 637 } 638 if (hasComment) { 639 tr.td(); 640 } 641 if (hasProperties) { 642 for (String s : props.keySet()) { 643 if (s != null) { 644 if (props.get(s).size() == 1) { 645 String url = props.get(s).iterator().next(); 646 renderCSDetailsLink(tr.td(), url, false); 647 } else 648 tr.td().b().addText(getDescForConcept(s)); 649 } 650 } 651 } 652 653 for (int si = 0; si < grp.getElement().size(); si++) { 654 SourceElementComponent ccl = grp.getElement().get(si); 655 boolean slast = si == grp.getElement().size()-1; 656 boolean first = true; 657 if (ccl.hasNoMap() && ccl.getNoMap()) { 658 tr = tbl.tr(); 659 td = tr.td().style("border-right-width: 0px"); 660 if (!first) 661 td.style("border-top-style: none"); 662 else 663 td.style("border-bottom-style: none"); 664 if (sources.get("code").size() == 1) 665 td.addText(ccl.getCode()); 666 else 667 td.addText(grp.getSource()+" / "+ccl.getCode()); 668 display = ccl.hasDisplay() ? ccl.getDisplay() : getDisplayForConcept(grp.getSource(), ccl.getCode()); 669 tr.td().style("border-left-width: 0px").tx(display == null ? "" : display); 670 if (ccl.hasExtension(ToolingExtensions.EXT_CM_NOMAP_COMMENT)) { 671 tr.td().colspan("3").style("background-color: #efefef").tx("(not mapped)"); 672 tr.td().style("background-color: #efefef").tx(ccl.getExtensionString(ToolingExtensions.EXT_CM_NOMAP_COMMENT)); 673 } else { 674 tr.td().colspan("4").style("background-color: #efefef").tx("(not mapped)"); 675 } 676 } else { 677 for (int ti = 0; ti < ccl.getTarget().size(); ti++) { 678 TargetElementComponent ccm = ccl.getTarget().get(ti); 679 boolean last = ti == ccl.getTarget().size()-1; 680 tr = tbl.tr(); 681 td = tr.td().style("border-right-width: 0px"); 682 if (!first && !last) 683 td.style("border-top-style: none; border-bottom-style: none"); 684 else if (!first) 685 td.style("border-top-style: none"); 686 else if (!last) 687 td.style("border-bottom-style: none"); 688 if (first) { 689 if (sources.get("code").size() == 1) 690 td.addText(ccl.getCode()); 691 else 692 td.addText(grp.getSource()+" / "+ccl.getCode()); 693 display = ccl.hasDisplay() ? ccl.getDisplay() : getDisplayForConcept(grp.getSource(), ccl.getCode()); 694 td = tr.td(); 695 if (!last) 696 td.style("border-left-width: 0px; border-bottom-style: none"); 697 else 698 td.style("border-left-width: 0px"); 699 td.tx(display == null ? "" : display); 700 } else { 701 td = tr.td(); // for display 702 if (!last) 703 td.style("border-left-width: 0px; border-top-style: none; border-bottom-style: none"); 704 else 705 td.style("border-top-style: none; border-left-width: 0px"); 706 } 707 for (String s : sources.keySet()) { 708 if (s != null && !s.equals("code")) { 709 td = tr.td(); 710 td.addText(getValue(ccm.getDependsOn(), s, sources.get(s).size() != 1)); 711 display = getDisplay(ccm.getDependsOn(), s); 712 if (display != null) 713 td.tx(" ("+display+")"); 714 } 715 } 716 first = false; 717 if (hasRelationships) { 718 if (!ccm.hasRelationship()) 719 tr.td(); 720 else { 721 if (ccm.hasExtension(ToolingExtensions.EXT_OLD_CONCEPTMAP_EQUIVALENCE)) { 722 String code = ToolingExtensions.readStringExtension(ccm, ToolingExtensions.EXT_OLD_CONCEPTMAP_EQUIVALENCE); 723 tr.td().ah(context.prefixLocalHref(eqpath+"#"+code), code).tx(presentEquivalenceCode(code)); 724 } else { 725 tr.td().ah(context.prefixLocalHref(eqpath+"#"+ccm.getRelationship().toCode()), ccm.getRelationship().toCode()).tx(presentRelationshipCode(ccm.getRelationship().toCode())); 726 } 727 } 728 } 729 td = tr.td().style("border-right-width: 0px"); 730 if (targets.get("code").size() == 1) 731 td.addText(ccm.getCode()); 732 else 733 td.addText(grp.getTarget()+" / "+ccm.getCode()); 734 display = ccm.hasDisplay() ? ccm.getDisplay() : getDisplayForConcept(grp.getSource(), ccm.getCode()); 735 tr.td().style("border-left-width: 0px").tx(display == null ? "" : display); 736 737 for (String s : targets.keySet()) { 738 if (s != null && !s.equals("code")) { 739 td = tr.td(); 740 td.addText(getValue(ccm.getProduct(), s, targets.get(s).size() != 1)); 741 display = getDisplay(ccm.getProduct(), s); 742 if (display != null) 743 td.tx(" ("+display+")"); 744 } 745 } 746 if (hasComment) 747 tr.td().addText(ccm.getComment()); 748 749 for (String s : props.keySet()) { 750 if (s != null) { 751 td = tr.td(); 752 td.addText(getValue(ccm.getProperty(), s)); 753 } 754 } 755 } 756 } 757 addUnmapped(tbl, grp); 758 } 759 } 760 } 761 762 public void describe(XhtmlNode x, ConceptMap cm) { 763 x.tx(display(cm)); 764 } 765 766 public String display(ConceptMap cm) { 767 return cm.present(); 768 } 769 770 private boolean isSameCodeAndDisplay(String code, String display) { 771 String c = code.replace(" ", "").replace("-", "").toLowerCase(); 772 String d = display.replace(" ", "").replace("-", "").toLowerCase(); 773 return c.equals(d); 774 } 775 776 777 private String presentRelationshipCode(String code) { 778 if ("related-to".equals(code)) { 779 return "is related to"; 780 } else if ("equivalent".equals(code)) { 781 return "is equivalent to"; 782 } else if ("source-is-narrower-than-target".equals(code)) { 783 return "is narrower than"; 784 } else if ("source-is-broader-than-target".equals(code)) { 785 return "is broader than"; 786 } else if ("not-related-to".equals(code)) { 787 return "is not related to"; 788 } else { 789 return code; 790 } 791 } 792 793 private String presentEquivalenceCode(String code) { 794 if ("relatedto".equals(code)) { 795 return "is related to"; 796 } else if ("equivalent".equals(code)) { 797 return "is equivalent to"; 798 } else if ("equal".equals(code)) { 799 return "is equal to"; 800 } else if ("wider".equals(code)) { 801 return "maps to wider concept"; 802 } else if ("subsumes".equals(code)) { 803 return "is subsumed by"; 804 } else if ("source-is-broader-than-target".equals(code)) { 805 return "maps to narrower concept"; 806 } else if ("specializes".equals(code)) { 807 return "has specialization"; 808 } else if ("inexact".equals(code)) { 809 return "maps loosely to"; 810 } else if ("unmatched".equals(code)) { 811 return "has no match"; 812 } else if ("disjoint".equals(code)) { 813 return "is not related to"; 814 } else { 815 return code; 816 } 817 } 818 819 public void renderCSDetailsLink(XhtmlNode tr, String url, boolean span2) { 820 CodeSystem cs; 821 XhtmlNode td; 822 cs = getContext().getWorker().fetchCodeSystem(url); 823 td = tr.td(); 824 if (span2) { 825 td.colspan("2"); 826 } 827 td.b().tx(context.formatPhrase(RenderingContext.CONC_MAP_CODES)); 828 td.tx(" " + (context.formatPhrase(RenderingContext.CONC_MAP_FRM) + " ")); 829 if (cs == null) 830 td.tx(url); 831 else 832 td.ah(context.prefixLocalHref(context.fixReference(cs.getWebPath()))).attribute("title", url).tx(cs.present()); 833 } 834 835 private void addUnmapped(XhtmlNode tbl, ConceptMapGroupComponent grp) { 836 if (grp.hasUnmapped()) { 837// throw new Error("not done yet"); 838 } 839 840 } 841 842 private String getDescForConcept(String s) { 843 if (s.startsWith("http://hl7.org/fhir/v2/element/")) 844 return "v2 "+s.substring("http://hl7.org/fhir/v2/element/".length()); 845 return s; 846 } 847 848 849 private String getValue(List<MappingPropertyComponent> list, String s) { 850 StringBuilder b = new StringBuilder(); 851 for (MappingPropertyComponent m : list) { 852 if (s.equals(m.getCode())) { 853 b.append(displayBase(m.getValue())); 854 } 855 } 856 return b.toString(); 857 } 858 859 private String getValue(List<OtherElementComponent> list, String s, boolean withSystem) { 860 for (OtherElementComponent c : list) { 861 if (s.equals(c.getAttribute())) 862 if (withSystem) 863 return /*c.getSystem()+" / "+*/c.getValue().primitiveValue(); 864 else 865 return c.getValue().primitiveValue(); 866 } 867 return null; 868 } 869 870 private String getDisplay(List<OtherElementComponent> list, String s) { 871 for (OtherElementComponent c : list) { 872 if (s.equals(c.getAttribute())) { 873 // return getDisplayForConcept(systemFromCanonical(c.getSystem()), versionFromCanonical(c.getSystem()), c.getValue()); 874 } 875 } 876 return null; 877 } 878 879 public static XhtmlNode renderMultipleMaps(String start, List<ConceptMap> maps, IMultiMapRendererAdvisor advisor, Object rmmContext) { 880 // 1+1 column for each provided map 881 List<MultipleMappingRow> rowSets = new ArrayList<>(); 882 for (int i = 0; i < maps.size(); i++) { 883 populateRows(rowSets, maps.get(i), i, advisor, rmmContext); 884 } 885 collateRows(rowSets); 886 if (advisor.sortPolicy(rmmContext) != RenderMultiRowSortPolicy.UNSORTED) { 887 Collections.sort(rowSets, new MultipleMappingRowSorter(advisor.sortPolicy(rmmContext) == RenderMultiRowSortPolicy.FIRST_COL)); 888 } 889 XhtmlNode div = new XhtmlNode(NodeType.Element, "div"); 890 XhtmlNode tbl = div.table("none", false).style("text-align: var(--ig-left,left); border-spacing: 0; padding: 5px"); 891 XhtmlNode tr = tbl.tr(); 892 styleCell(tr.td(), false, true, 5).b().tx(start); 893 for (ConceptMap map : maps) { 894 XhtmlNode td = styleCell(tr.td(), false, true, 5).colspan(2); 895 if (!advisor.describeMap(rmmContext, map, td)) { 896 if (map.hasWebPath() && advisor.makeMapLinks()) { 897 td.b().ah(map.getWebPath(), map.getVersionedUrl()).tx(map.present()); 898 } else { 899 td.b().tx(map.present()); 900 } 901 } 902 } 903 if (advisor.hasCollateral(rmmContext)) { 904 tr = tbl.tr(); 905 renderLinks(styleCell(tr.td(), false, true, 5), advisor.getCollateral(rmmContext, null)); 906 for (ConceptMap map : maps) { 907 renderLinks(styleCell(tr.td(), false, true, 5).colspan(2), advisor.getCollateral(rmmContext, map.getUrl())); 908 } 909 } 910 for (MultipleMappingRow row : rowSets) { 911 renderMultiRow(tbl, row, maps, advisor, rmmContext); 912 } 913 return div; 914 } 915 916 private static void renderLinks(XhtmlNode td, List<CollateralDefinition> collateral) { 917 if (collateral.size() > 0) { 918 td.tx( "Links:"); 919 td.tx(" "); 920 boolean first = true; 921 for (CollateralDefinition c : collateral) { 922 if (first) first = false; else td.tx(", "); 923 td.ah(c.getResource().getWebPath()).tx(c.getLabel()); 924 } 925 } 926 } 927 928 private static void collateRows(List<MultipleMappingRow> rowSets) { 929 List<MultipleMappingRow> toDelete = new ArrayList<ConceptMapRenderer.MultipleMappingRow>(); 930 for (MultipleMappingRow rowSet : rowSets) { 931 MultipleMappingRow tgt = rowSet.stickySource; 932 while (toDelete.contains(tgt)) { 933 tgt = tgt.stickySource; 934 } 935 if (tgt != null && rowSets.contains(tgt)) { 936 tgt.rowSets.addAll(rowSet.rowSets); 937 toDelete.add(rowSet); 938 } 939 } 940 rowSets.removeAll(toDelete); 941 } 942 943 private static void renderMultiRow(XhtmlNode tbl, MultipleMappingRow rows, List<ConceptMap> maps, IMultiMapRendererAdvisor advisor, Object rmmContext) { 944 int rowCounter = 0; 945 for (MultipleMappingRowItem row : rows.rowSets) { 946 XhtmlNode tr = tbl.tr(); 947 boolean first = true; 948 int cellCounter = 0; 949 Cell last = null; 950 for (Cell cell : row.cells) { 951 if (first) { 952 if (!cell.renderedCode) { 953 int c = 1; 954 for (int i = rowCounter + 1; i < rows.rowSets.size(); i++) { 955 if (cell.code != null && rows.rowSets.get(i).cells.size() > cellCounter && cell.code.equals(rows.rowSets.get(i).cells.get(cellCounter).code)) { 956 rows.rowSets.get(i).cells.get(cellCounter).renderedCode = true; 957 c++; 958 } else { 959 break; 960 } 961 } 962 if (cell.code == null) { 963 styleCell(tr.td(), rowCounter == 0, true, 5).rowspan(c).style("background-color: #eeeeee"); 964 } else { 965 String link = advisor.getLink(rmmContext, cell.system, cell.code); 966 XhtmlNode x = null; 967 if (link != null) { 968 x = styleCell(tr.td(), rowCounter == 0, true, 5).attributeNN("title", cell.display).rowspan(c).ah(link); 969 } else { 970 x = styleCell(tr.td(), rowCounter == 0, true, 5).attributeNN("title", cell.display).rowspan(c); 971 } 972// if (cell.clone != null) { 973// x.style("color: grey"); 974// } 975 x.tx(cell.present()); 976 } 977 } 978 first = false; 979 } else { 980 if (!cell.renderedRel) { 981 int c = 1; 982 for (int i = rowCounter + 1; i < rows.rowSets.size(); i++) { 983 if ((cell.relationship != null && rows.rowSets.get(i).cells.size() > cellCounter && cell.relationship.equals(rows.rowSets.get(i).cells.get(cellCounter).relationship)) && 984 (cell.code != null && cell.code.equals(rows.rowSets.get(i).cells.get(cellCounter).code)) && 985 (last.code != null && cell.code.equals(rows.rowSets.get(i).cells.get(cellCounter-1).code))) { 986 rows.rowSets.get(i).cells.get(cellCounter).renderedRel = true; 987 c++; 988 } else { 989 break; 990 } 991 } 992 if (last.code == null || cell.code == null) { 993 styleCell(tr.td(), rowCounter == 0, true, 5).style("background-color: #eeeeee"); 994 } else if (cell.relationship != null) { 995 styleCell(tr.tdW(16), rowCounter == 0, true, 0).attributeNN("title", cell.relComment).rowspan(c).style("background-color: LightGrey; text-align: center; vertical-align: middle; color: white").tx(cell.relationship); 996 } else { 997 styleCell(tr.tdW(16), rowCounter == 0, false, 0).rowspan(c); 998 } 999 } 1000 if (!cell.renderedCode) { 1001 int c = 1; 1002 for (int i = rowCounter + 1; i < rows.rowSets.size(); i++) { 1003 if (cell.code != null && rows.rowSets.get(i).cells.size() > cellCounter && cell.code.equals(rows.rowSets.get(i).cells.get(cellCounter).code)) { 1004 rows.rowSets.get(i).cells.get(cellCounter).renderedCode = true; 1005 c++; 1006 } else { 1007 break; 1008 } 1009 } 1010 if (cell.code == null) { 1011 styleCell(tr.td(), rowCounter == 0, true, 5).rowspan(c).style("background-color: #eeeeee"); 1012 } else { 1013 String link = advisor.getLink(rmmContext, cell.system, cell.code); 1014 XhtmlNode x = null; 1015 if (link != null) { 1016 x = styleCell(tr.td(), rowCounter == 0, true, 5).attributeNN("title", cell.display).rowspan(c).ah(link); 1017 } else { 1018 x = styleCell(tr.td(), rowCounter == 0, true, 5).attributeNN("title", cell.display).rowspan(c); 1019 } 1020// if (cell.clone != null) { 1021// x.style("color: grey"); 1022// } 1023 x.tx(cell.present()); 1024 } 1025 } 1026 } 1027 last = cell; 1028 cellCounter++; 1029 } 1030 rowCounter++; 1031 } 1032 } 1033 1034 private static XhtmlNode styleCell(XhtmlNode td, boolean firstrow, boolean sides, int padding) { 1035 if (firstrow) { 1036 td.style("vertical-align: middle; border-top: 1px solid black; padding: "+padding+"px"); 1037 } else { 1038 td.style("vertical-align: middle; border-top: 1px solid LightGrey; padding: "+padding+"px"); 1039 } 1040 if (sides) { 1041 td.style("border-left: 1px solid LightGrey; border-right: 2px solid LightGrey"); 1042 } 1043 return td; 1044 } 1045 1046 private static void populateRows(List<MultipleMappingRow> rowSets, ConceptMap map, int i, IMultiMapRendererAdvisor advisor, Object rmmContext) { 1047 // if we can resolve the value set, we create entries for it 1048 if (map.hasSourceScope()) { 1049 List<Coding> codings = advisor.getMembers(rmmContext, map.getSourceScope().primitiveValue()); 1050 if (codings != null) { 1051 for (Coding c : codings) { 1052 MultipleMappingRow row = i == 0 ? null : findExistingRowBySource(rowSets, c.getSystem(), c.getCode(), i); 1053 if (row == null) { 1054 row = new MultipleMappingRow(i, c.getSystem(), c.getCode(), c.getDisplay()); 1055 rowSets.add(row); 1056 } 1057 1058 } 1059 } 1060 } 1061 1062 for (ConceptMapGroupComponent grp : map.getGroup()) { 1063 for (SourceElementComponent src : grp.getElement()) { 1064 MultipleMappingRow row = findExistingRowBySource(rowSets, grp.getSource(), src.getCode(), i); 1065 if (row == null) { 1066 row = new MultipleMappingRow(i, grp.getSource(), src.getCode(), src.getDisplay()); 1067 rowSets.add(row); 1068 } 1069 if (src.getNoMap()) { 1070 row.addTerminus(); 1071 } else { 1072 List<TargetElementComponent> todo = new ArrayList<>(); 1073 for (TargetElementComponent tgt : src.getTarget()) { 1074 MultipleMappingRow trow = findExistingRowByTarget(rowSets, grp.getTarget(), tgt.getCode(), i); 1075 if (trow == null) { 1076 row.addTarget(grp.getTarget(), tgt.getCode(), tgt.getRelationship(), tgt.getComment(), rowSets, i); 1077 } else { 1078 todo.add(tgt); 1079 } 1080 } 1081 // we've already got a mapping to these targets. So we gather them under the one mapping - but do this after the others are done 1082 for (TargetElementComponent t : todo) { 1083 MultipleMappingRow trow = findExistingRowByTarget(rowSets, grp.getTarget(), t.getCode(), i); 1084 if (row.alreadyHasMappings(i)) { 1085 // src is already mapped, and so is target, and now we need to map src to target too 1086 // we have to clone src, but we only clone the last 1087 trow.cloneSource(i, row.getLastSource(i)); 1088 } else { 1089 trow.addSource(row, rowSets, t.getRelationship(), t.getComment()); 1090 } 1091 } 1092 } 1093 } 1094 boolean copy = grp.hasUnmapped() && grp.getUnmapped().getMode() == ConceptMapGroupUnmappedMode.USESOURCECODE; 1095 if (copy) { 1096 for (MultipleMappingRow row : rowSets) { 1097 if (row.rowSets.get(0).cells.size() == i && row.lastSystem().equals(grp.getSource())) { 1098 row.addCopy(grp.getTarget()); 1099 } 1100 } 1101 } 1102 } 1103 for (MultipleMappingRow row : rowSets) { 1104 if (row.rowSets.get(0).cells.size() == i) { 1105 row.addTerminus(); 1106 } 1107 } 1108 if (map.hasTargetScope()) { 1109 List<Coding> codings = advisor.getMembers(rmmContext, map.getTargetScope().primitiveValue()); 1110 if (codings != null) { 1111 for (Coding c : codings) { 1112 MultipleMappingRow row = findExistingRowByTarget(rowSets, c.getSystem(), c.getCode(), i); 1113 if (row == null) { 1114 row = new MultipleMappingRow(i+1, c.getSystem(), c.getCode(), c.getDisplay()); 1115 rowSets.add(row); 1116 } else { 1117 for (MultipleMappingRowItem cells : row.rowSets) { 1118 Cell last = cells.cells.get(cells.cells.size() -1); 1119 if (last.system != null && last.system.equals(c.getSystem()) && last.code.equals(c.getCode()) && last.display == null) { 1120 last.display = c.getDisplay(); 1121 } 1122 } 1123 } 1124 } 1125 } 1126 } 1127 1128 } 1129 1130 private static MultipleMappingRow findExistingRowByTarget(List<MultipleMappingRow> rows, String system, String code, int i) { 1131 for (MultipleMappingRow row : rows) { 1132 for (MultipleMappingRowItem cells : row.rowSets) { 1133 if (cells.cells.size() > i + 1 && cells.cells.get(i+1).matches(system, code)) { 1134 return row; 1135 } 1136 } 1137 } 1138 return null; 1139 } 1140 1141 private static MultipleMappingRow findExistingRowBySource(List<MultipleMappingRow> rows, String system, String code, int i) { 1142 for (MultipleMappingRow row : rows) { 1143 for (MultipleMappingRowItem cells : row.rowSets) { 1144 if (cells.cells.size() > i && cells.cells.get(i).matches(system, code)) { 1145 return row; 1146 } 1147 } 1148 } 1149 return null; 1150 } 1151}