
001package org.hl7.fhir.r5.renderers; 002 003import java.io.IOException; 004import java.util.ArrayList; 005import java.util.HashMap; 006import java.util.List; 007 008import org.hl7.fhir.exceptions.DefinitionException; 009import org.hl7.fhir.exceptions.FHIRFormatError; 010import org.hl7.fhir.r5.conformance.profile.BindingResolution; 011import org.hl7.fhir.r5.conformance.profile.ProfileKnowledgeProvider; 012import org.hl7.fhir.r5.model.*; 013import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingAdditionalComponent; 014import org.hl7.fhir.r5.renderers.CodeResolver.CodeResolution; 015import org.hl7.fhir.r5.renderers.Renderer.RenderingStatus; 016import org.hl7.fhir.r5.renderers.utils.RenderingContext; 017import org.hl7.fhir.r5.utils.UserDataNames; 018import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 019import org.hl7.fhir.utilities.Utilities; 020import org.hl7.fhir.utilities.VersionUtilities; 021import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 022import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell; 023import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece; 024import org.hl7.fhir.utilities.xhtml.NodeType; 025import org.hl7.fhir.utilities.xhtml.XhtmlComposer; 026import org.hl7.fhir.utilities.xhtml.XhtmlNode; 027import org.hl7.fhir.utilities.xhtml.XhtmlNodeList; 028 029@MarkedToMoveToAdjunctPackage 030public class AdditionalBindingsRenderer { 031 public class AdditionalBindingDetail { 032 private String purpose; 033 private String valueSet; 034 private String doco; 035 private String docoShort; 036 private List<UsageContext> usages = new ArrayList<UsageContext>(); 037 private boolean any = false; 038 private boolean isUnchanged = false; 039 private boolean matched = false; 040 private boolean removed = false; 041// private ValueSet vs; 042 043 private AdditionalBindingDetail compare; 044 private int count = 1; 045 private String getKey() { 046 // Todo: Consider extending this with content from usageContext if purpose isn't sufficiently differentiating 047 return purpose + Integer.toString(count); 048 } 049 private void incrementCount() { 050 count++; 051 } 052 private void setCompare(AdditionalBindingDetail match) { 053 compare = match; 054 match.matched = true; 055 } 056 private boolean alreadyMatched() { 057 return matched; 058 } 059 public String getDoco(boolean full) { 060 return full ? doco : docoShort; 061 } 062 public boolean unchanged() { 063 if (!isUnchanged) 064 return false; 065 if (compare==null) 066 return true; 067 isUnchanged = true; 068 isUnchanged = isUnchanged && ((purpose==null && compare.purpose==null) || purpose.equals(compare.purpose)); 069 isUnchanged = isUnchanged && ((valueSet==null && compare.valueSet==null) || valueSet.equals(compare.valueSet)); 070 isUnchanged = isUnchanged && ((doco==null && compare.doco==null) || doco.equals(compare.doco)); 071 isUnchanged = isUnchanged && ((docoShort==null && compare.docoShort==null) || docoShort.equals(compare.docoShort)); 072 isUnchanged = isUnchanged && ((usages==null && compare.usages==null) || usages.equals(compare.usages)); 073 return isUnchanged; 074 } 075 } 076 077 private static String STYLE_UNCHANGED = "opacity: 0.5;"; 078 private static String STYLE_REMOVED = STYLE_UNCHANGED + "text-decoration: line-through;"; 079 080 private List<AdditionalBindingDetail> bindings = new ArrayList<>(); 081 private ProfileKnowledgeProvider pkp; 082 private String corePath; 083 private StructureDefinition profile; 084 private String path; 085 private RenderingContext context; 086 private IMarkdownProcessor md; 087 private CodeResolver cr; 088 089 public AdditionalBindingsRenderer(ProfileKnowledgeProvider pkp, String corePath, StructureDefinition profile, String path, RenderingContext context, IMarkdownProcessor md, CodeResolver cr) { 090 this.pkp = pkp; 091 this.corePath = corePath; 092 this.profile = profile; 093 this.path = path; 094 this.context = context; 095 this.md = md; 096 this.cr = cr; 097 } 098 099 public void seeMaxBinding(Extension ext) { 100 seeMaxBinding(ext, null, false); 101 } 102 103 public void seeMaxBinding(Extension ext, Extension compExt, boolean compare) { 104 seeBinding(ext, compExt, compare, "maximum"); 105 } 106 107 protected void seeBinding(Extension ext, Extension compExt, boolean compare, String label) { 108 AdditionalBindingDetail abr = new AdditionalBindingDetail(); 109 abr.purpose = label; 110 abr.valueSet = ext.getValue().primitiveValue(); 111 if (compare) { 112 abr.isUnchanged = compExt!=null && ext.getValue().primitiveValue().equals(compExt.getValue().primitiveValue()); 113 114 abr.compare = new AdditionalBindingDetail(); 115 abr.compare.valueSet = compExt==null ? null : compExt.getValue().primitiveValue(); 116 } else { 117 abr.isUnchanged = ext.hasUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS); 118 } 119 bindings.add(abr); 120 } 121 122 public void seeMinBinding(Extension ext) { 123 seeMinBinding(ext, null, false); 124 } 125 126 public void seeMinBinding(Extension ext, Extension compExt, boolean compare) { 127 seeBinding(ext, compExt, compare, "minimum"); 128 } 129 130 public void seeAdditionalBindings(List<Extension> list) { 131 seeAdditionalBindings(list, null, false); 132 } 133 134 public void seeAdditionalBindings(List<Extension> list, List<Extension> compList, boolean compare) { 135 HashMap<String, AdditionalBindingDetail> compBindings = new HashMap<String, AdditionalBindingDetail>(); 136 if (compare && compList!=null) { 137 for (Extension ext : compList) { 138 AdditionalBindingDetail abr = additionalBinding(ext); 139 if (compBindings.containsKey(abr.getKey())) { 140 abr.incrementCount(); 141 } 142 compBindings.put(abr.getKey(), abr); 143 } 144 } 145 146 for (Extension ext : list) { 147 AdditionalBindingDetail abr = additionalBinding(ext); 148 if (compare && compList!=null) { 149 AdditionalBindingDetail match = null; 150 do { 151 match = compBindings.get(abr.getKey()); 152 if (abr.alreadyMatched()) 153 abr.incrementCount(); 154 } while (match!=null && abr.alreadyMatched()); 155 if (match!=null) 156 abr.setCompare(match); 157 bindings.add(abr); 158 if (abr.compare!=null) 159 compBindings.remove(abr.compare.getKey()); 160 } else 161 bindings.add(abr); 162 } 163 for (AdditionalBindingDetail b: compBindings.values()) { 164 b.removed = true; 165 bindings.add(b); 166 } 167 } 168 169 protected AdditionalBindingDetail additionalBinding(Extension ext) { 170 AdditionalBindingDetail abr = new AdditionalBindingDetail(); 171 abr.purpose = ext.getExtensionString("purpose"); 172 abr.valueSet = ext.getExtensionString("valueSet"); 173 abr.doco = ext.getExtensionString("documentation"); 174 abr.docoShort = ext.getExtensionString("shortDoco"); 175 for (Extension x : ext.getExtensionsByUrl("usage")) { 176 if (x.hasValueUsageContext()) { 177 abr.usages.add(x.getValueUsageContext()); 178 } 179 } 180 abr.any = "any".equals(ext.getExtensionString("scope")); 181 abr.isUnchanged = ext.hasUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS); 182 return abr; 183 } 184 185 protected AdditionalBindingDetail additionalBinding(ElementDefinitionBindingAdditionalComponent ab) { 186 AdditionalBindingDetail abr = new AdditionalBindingDetail(); 187 abr.purpose = ab.getPurpose().toCode(); 188 abr.valueSet = ab.getValueSet(); 189 abr.doco = ab.getDocumentation(); 190 abr.docoShort = ab.getShortDoco(); 191 abr.usages.addAll(ab.getUsage()); 192 abr.any = ab.getAny(); 193 abr.isUnchanged = ab.hasUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS); 194 return abr; 195 } 196 197 public String render() throws IOException { 198 if (bindings.isEmpty()) { 199 return ""; 200 } else { 201 XhtmlNode tbl = new XhtmlNode(NodeType.Element, "table"); 202 tbl.attribute("class", "grid"); 203 render(tbl.getChildNodes(), true); 204 return new XhtmlComposer(false).compose(tbl); 205 } 206 } 207 208 public void render(HierarchicalTableGenerator gen, Cell c) throws FHIRFormatError, DefinitionException, IOException { 209 if (bindings.isEmpty()) { 210 return; 211 } else { 212 Piece piece = gen.new Piece("binding", "table").setClass("grid"); 213 c.getPieces().add(piece); 214 render(piece.getChildren(), false); 215 } 216 } 217 218 public void render(List<XhtmlNode> children, boolean fullDoco) throws FHIRFormatError, DefinitionException, IOException { 219 boolean doco = false; 220 boolean usage = false; 221 boolean any = false; 222 for (AdditionalBindingDetail binding : bindings) { 223 doco = doco || binding.getDoco(fullDoco)!=null || (binding.compare!=null && binding.compare.getDoco(fullDoco)!=null); 224 usage = usage || !binding.usages.isEmpty() || (binding.compare!=null && !binding.compare.usages.isEmpty()); 225 any = any || binding.any || (binding.compare!=null && binding.compare.any); 226 } 227 228 XhtmlNode tr = new XhtmlNode(NodeType.Element, "tr"); 229 children.add(tr); 230 tr.td().style("font-size: 11px").b().tx(context.formatPhrase(RenderingContext.ADD_BIND_ADD_BIND)); 231 tr.td().style("font-size: 11px").tx(context.formatPhrase(RenderingContext.GENERAL_PURPOSE)); 232 if (usage) { 233 tr.td().style("font-size: 11px").tx(context.formatPhrase(RenderingContext.GENERAL_USAGE)); 234 } 235 if (any) { 236 tr.td().style("font-size: 11px").tx(context.formatPhrase(RenderingContext.ADD_BIND_ANY)); 237 } 238 if (doco) { 239 tr.td().style("font-size: 11px").tx(context.formatPhrase(RenderingContext.GENERAL_DOCUMENTATION)); 240 } 241 for (AdditionalBindingDetail binding : bindings) { 242 tr = new XhtmlNode(NodeType.Element, "tr"); 243 if (binding.unchanged()) { 244 tr.style(STYLE_REMOVED); 245 } else if (binding.removed) { 246 tr.style(STYLE_REMOVED); 247 } 248 children.add(tr); 249 BindingResolution br = pkp == null ? makeNullBr(binding) : pkp.resolveBinding(profile, binding.valueSet, path); 250 BindingResolution compBr = null; 251 if (binding.compare!=null && binding.compare.valueSet!=null) 252 compBr = pkp == null ? makeNullBr(binding.compare) : pkp.resolveBinding(profile, binding.compare.valueSet, path); 253 254 XhtmlNode valueset = tr.td().style("font-size: 11px"); 255 if (binding.compare!=null && binding.valueSet.equals(binding.compare.valueSet)) 256 valueset.style(STYLE_UNCHANGED); 257 if (br.url != null) { 258 XhtmlNode a = valueset.ah(context.prefixLocalHref(determineUrl(br.url)), br.uri); 259 a.tx(br.display); 260 if (br.external) { 261 a.tx(" "); 262 a.img("external.png", null); 263 } 264 } else { 265 valueset.span(null, binding.valueSet).tx(br.display); 266 } 267 if (binding.compare!=null && binding.compare.valueSet!=null && !binding.valueSet.equals(binding.compare.valueSet)) { 268 valueset.br(); 269 valueset = valueset.span(STYLE_REMOVED, null); 270 if (compBr.url != null) { 271 valueset.ah(context.prefixLocalHref(determineUrl(compBr.url)), binding.compare.valueSet).tx(compBr.display); 272 } else { 273 valueset.span(null, binding.compare.valueSet).tx(compBr.display); 274 } 275 } 276 277 XhtmlNode purpose = tr.td().style("font-size: 11px"); 278 if (binding.compare!=null && binding.purpose.equals(binding.compare.purpose)) 279 purpose.style("font-color: darkgray"); 280 renderPurpose(purpose, binding.purpose); 281 if (binding.compare!=null && binding.compare.purpose!=null && !binding.purpose.equals(binding.compare.purpose)) { 282 purpose.br(); 283 purpose = purpose.span(STYLE_UNCHANGED, null); 284 renderPurpose(purpose, binding.compare.purpose); 285 } 286 if (usage) { 287 if (!binding.usages.isEmpty()) { 288 XhtmlNode td = tr.td(); 289 for (UsageContext uc : binding.usages) { 290 td.sep(", "); 291 Coding c = uc.getCode(); 292 renderUsageCode(td, c); 293 td.tx(" = "); 294 if (uc.hasValueCodeableConcept() && !uc.getValueCodeableConcept().hasText() && uc.getValueCodeableConcept().getCoding().size() == 1) { 295 c = uc.getValueCodeableConcept().getCodingFirstRep(); 296 renderUsageCode(td, c); 297 } else if (uc.getValue() != null) { 298 new DataRenderer(context).renderBase(new RenderingStatus(), td, uc.getValue()); 299 } 300 } 301 } else { 302 tr.td(); 303 } 304 } 305 if (any) { 306 String newRepeat = binding.any ? context.formatPhrase(RenderingContext.ADD_BIND_ANY_REP) : context.formatPhrase(RenderingContext.ADD_BIND_ALL_REP); 307 String oldRepeat = binding.compare!=null && binding.compare.any ? context.formatPhrase(RenderingContext.ADD_BIND_ANY_REP) : context.formatPhrase(RenderingContext.ADD_BIND_ALL_REP); 308 compareString(tr.td().style("font-size: 11px"), newRepeat, oldRepeat); 309 } 310 if (doco) { 311 if (binding.doco != null) { 312 String d = fullDoco ? md.processMarkdown("Binding.description", binding.doco) : binding.docoShort; 313 String oldD = binding.compare==null ? null : fullDoco ? md.processMarkdown("Binding.description.compare", binding.compare.doco) : binding.compare.docoShort; 314 tr.td().style("font-size: 11px").innerHTML(compareHtml(d, oldD)); 315 } else { 316 tr.td().style("font-size: 11px"); 317 } 318 } 319 } 320 } 321 322 private void renderUsageCode(XhtmlNode td, Coding c) throws IOException { 323 boolean rendered = false; 324 if (!c.hasDisplay()) { 325 if (c.hasSystem() && c.getSystem().contains("/StructureDefinition/")) { 326 StructureDefinition sd = context.getContext().fetchResource(StructureDefinition.class, c.getSystem()); 327 if (sd != null && sd.hasName()) { 328 rendered = true; 329 td.ah(sd.getWebPath()).tx(sd.getName()); 330 td.tx("#"); 331 td.code().tx(c.getCode()); 332 } 333 } else { 334 CodeSystem cs = context.getContext().fetchCodeSystem(c.getSystem()); 335 if (cs != null && cs.hasName()) { 336 rendered = true; 337 td.ah(cs.getWebPath()).tx(cs.getName()); 338 td.tx("#"); 339 td.code().tx(c.getCode()); 340 } 341 } 342 } 343 if (!rendered) { 344 new DataRenderer(context).renderBase(new RenderingStatus(), td, c); 345 } 346 } 347 348 private XhtmlNode compareString(XhtmlNode node, String newS, String oldS) { 349 if (oldS==null) 350 return node.tx(newS); 351 if (newS.equals(oldS)) 352 return node.style(STYLE_UNCHANGED).tx(newS); 353 node.tx(newS); 354 node.br(); 355 return node.span(STYLE_REMOVED,null).tx(oldS); 356 } 357 358 private String compareHtml(String newS, String oldS) { 359 if (oldS==null) 360 return newS; 361 if (newS.equals(oldS)) 362 return "<span style=\"" + STYLE_UNCHANGED + "\">" + newS + "</span>"; 363 return newS + "<br/><span style=\"" + STYLE_REMOVED + "\">" + oldS + "</span>"; 364 } 365 366 private String determineUrl(String url) { 367 return Utilities.isAbsoluteUrl(url) || !pkp.prependLinks() ? url : corePath + url; 368 } 369 370 private void renderPurpose(XhtmlNode td, String purpose) { 371 boolean r5 = context == null || context.getWorker() == null ? false : VersionUtilities.isR5Plus(context.getWorker().getVersion()); 372 switch (purpose) { 373 case "maximum": 374 td.ah(r5 ? corePath+"valueset-additional-binding-purpose.html#additional-binding-purpose-maximum" : corePath+"extension-elementdefinition-maxvalueset.html", context.formatPhrase(RenderingContext.ADD_BIND_EXT_PREF)).tx(context.formatPhrase(RenderingContext.ADD_BIND_MAX)); 375 break; 376 case "minimum": 377 td.ah(r5 ? corePath+"valueset-additional-binding-purpose.html#additional-binding-purpose-minimum" : corePath+"extension-elementdefinition-minvalueset.html", context.formatPhrase(RenderingContext.GENERAL_BIND_MIN_ALLOW)).tx(context.formatPhrase(RenderingContext.ADD_BIND_MIN)); 378 break; 379 case "required" : 380 td.ah(r5 ? corePath+"valueset-additional-binding-purpose.html#additional-binding-purpose-required" : corePath+"terminologies.html#strength", context.formatPhrase(RenderingContext.ADD_BIND_VALID_REQ)).tx(context.formatPhrase(RenderingContext.ADD_BIND_REQ_BIND)); 381 break; 382 case "extensible" : 383 td.ah(r5 ? corePath+"valueset-additional-binding-purpose.html#additional-binding-purpose-extensible" : corePath+"terminologies.html#strength", context.formatPhrase(RenderingContext.ADD_BIND_VALID_EXT)).tx(context.formatPhrase(RenderingContext.ADD_BIND_EX_BIND)); 384 break; 385 case "preferred" : 386 td.ah(r5 ? corePath+"valueset-additional-binding-purpose.html#additional-binding-purpose-preferred" : corePath+"terminologies.html#strength", context.formatPhrase(RenderingContext.ADD_BIND_RECOM_VALUE_SET)).tx(context.formatPhrase(RenderingContext.ADD_BIND_PREF_BIND)); 387 break; 388 case "current" : 389 if (r5) { 390 td.ah(corePath+"valueset-additional-binding-purpose.html#additional-binding-purpose-current", context.formatPhrase(RenderingContext.ADD_BIND_NEW_REC)).tx(context.formatPhrase(RenderingContext.ADD_BIND_CURR_BIND)); 391 } else { 392 td.span(null, context.formatPhrase(RenderingContext.ADD_BIND_NEW_REC)).tx(context.formatPhrase(RenderingContext.ADD_BIND_CURR_BIND)); 393 } 394 break; 395 case "ui" : 396 if (r5) { 397 td.ah(corePath+"valueset-additional-binding-purpose.html#additional-binding-purpose-ui", context.formatPhrase(RenderingContext.ADD_BIND_GIVEN_CONT)).tx(context.formatPhrase(RenderingContext.ADD_BIND_UI_BIND)); 398 } else { 399 td.span(null, context.formatPhrase(RenderingContext.ADD_BIND_GIVEN_CONT)).tx(context.formatPhrase(RenderingContext.ADD_BIND_UI)); 400 } 401 break; 402 case "starter" : 403 if (r5) { 404 td.ah(corePath+"valueset-additional-binding-purpose.html#additional-binding-purpose-starter", context.formatPhrase(RenderingContext.ADD_BIND_DESIG_SYS)).tx(context.formatPhrase(RenderingContext.GENERAL_STARTER)); 405 } else { 406 td.span(null, context.formatPhrase(RenderingContext.ADD_BIND_DESIG_SYS)).tx(context.formatPhrase(RenderingContext.GENERAL_STARTER)); 407 } 408 break; 409 case "component" : 410 if (r5) { 411 td.ah(corePath+"valueset-additional-binding-purpose.html#additional-binding-purpose-component", context.formatPhrase(RenderingContext.ADD_BIND_VALUE_COMP)).tx(context.formatPhrase(RenderingContext.GENERAL_COMPONENT)); 412 } else { 413 td.span(null, context.formatPhrase(RenderingContext.ADD_BIND_VALUE_COMP)).tx(context.formatPhrase(RenderingContext.GENERAL_COMPONENT)); 414 } 415 break; 416 default: 417 td.span(null, context.formatPhrase(RenderingContext.ADD_BIND_UNKNOWN_PUR)).tx(purpose); 418 } 419 } 420 421 private BindingResolution makeNullBr(AdditionalBindingDetail binding) { 422 BindingResolution br = new BindingResolution(); 423 br.url = "http://none.none/none"; 424 br.display = "todo"; 425 return br; 426 } 427 428 public boolean hasBindings() { 429 return !bindings.isEmpty(); 430 } 431 432 public void render(XhtmlNodeList children, List<ElementDefinitionBindingAdditionalComponent> list) { 433 if (list.size() == 1) { 434 render(children, list.get(0)); 435 } else { 436 XhtmlNode ul = children.ul(); 437 for (ElementDefinitionBindingAdditionalComponent b : list) { 438 render(ul.li().getChildNodes(), b); 439 } 440 } 441 } 442 443 private void render(XhtmlNodeList children, ElementDefinitionBindingAdditionalComponent b) { 444 if (b.getValueSet() == null) { 445 return; // what should happen? 446 } 447 BindingResolution br = pkp.resolveBinding(profile, b.getValueSet(), corePath); 448 XhtmlNode a = children.ahOrCode(br.url == null ? null : Utilities.isAbsoluteUrl(br.url) || !context.getPkp().prependLinks() ? br.url : corePath+br.url, b.hasDocumentation() ? b.getDocumentation() : br.uri); 449 if (b.hasDocumentation()) { 450 a.attribute("title", b.getDocumentation()); 451 } 452 a.tx(br.display); 453 454 if (b.hasShortDoco()) { 455 children.tx(": "); 456 children.tx(b.getShortDoco()); 457 } 458 if (b.getAny() || b.hasUsage()) { 459 children.tx(" ("); 460 boolean ffirst = !b.getAny(); 461 if (b.getAny()) { 462 children.tx(context.formatPhrase(RenderingContext.ADD_BIND_ANY_REP)); 463 } 464 for (UsageContext uc : b.getUsage()) { 465 if (ffirst) ffirst = false; else children.tx(","); 466 if (!uc.getCode().is("http://terminology.hl7.org/CodeSystem/usage-context-type", "jurisdiction")) { 467 children.tx(displayForUsage(uc.getCode())); 468 children.tx("="); 469 } 470 CodeResolution ccr = cr.resolveCode(uc.getValueCodeableConcept()); 471 children.ah(context.prefixLocalHref(ccr.getLink()), ccr.getHint()).tx(ccr.getDisplay()); 472 } 473 children.tx(")"); 474 } 475 } 476 477 478 private String displayForUsage(Coding c) { 479 if (c.hasDisplay()) { 480 return c.getDisplay(); 481 } 482 if ("http://terminology.hl7.org/CodeSystem/usage-context-type".equals(c.getSystem())) { 483 return c.getCode(); 484 } 485 return c.getCode(); 486 } 487 488 public void seeAdditionalBinding(String purpose, String doco, ValueSet valueSet) { 489 AdditionalBindingDetail abr = new AdditionalBindingDetail(); 490 abr.purpose = purpose; 491 abr.valueSet = valueSet.getUrl(); 492 bindings.add(abr); 493 } 494 495 public void seeAdditionalBinding(String purpose, String doco, String ref) { 496 AdditionalBindingDetail abr = new AdditionalBindingDetail(); 497 abr.purpose = purpose; 498 abr.valueSet = ref; 499 bindings.add(abr); 500 501 } 502 503 public void seeAdditionalBindings(ElementDefinition definition, ElementDefinition compDef, boolean compare) { 504 HashMap<String, AdditionalBindingDetail> compBindings = new HashMap<String, AdditionalBindingDetail>(); 505 if (compare && compDef.getBinding().getAdditional() != null) { 506 for (ElementDefinitionBindingAdditionalComponent ab : compDef.getBinding().getAdditional()) { 507 AdditionalBindingDetail abr = additionalBinding(ab); 508 if (compBindings.containsKey(abr.getKey())) { 509 abr.incrementCount(); 510 } 511 compBindings.put(abr.getKey(), abr); 512 } 513 } 514 515 for (ElementDefinitionBindingAdditionalComponent ab : definition.getBinding().getAdditional()) { 516 AdditionalBindingDetail abr = additionalBinding(ab); 517 if (compare && compDef != null) { 518 AdditionalBindingDetail match = null; 519 do { 520 match = compBindings.get(abr.getKey()); 521 if (abr.alreadyMatched()) 522 abr.incrementCount(); 523 } while (match!=null && abr.alreadyMatched()); 524 if (match!=null) 525 abr.setCompare(match); 526 bindings.add(abr); 527 if (abr.compare!=null) 528 compBindings.remove(abr.compare.getKey()); 529 } else 530 bindings.add(abr); 531 } 532 for (AdditionalBindingDetail b: compBindings.values()) { 533 b.removed = true; 534 bindings.add(b); 535 } 536 537 } 538 539}