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