
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.extensions.ExtensionDefinitions; 011import org.hl7.fhir.r5.model.*; 012import org.hl7.fhir.r5.renderers.CodeResolver.CodeResolution; 013import org.hl7.fhir.r5.renderers.utils.RenderingContext; 014import org.hl7.fhir.r5.renderers.utils.ResourceWrapper; 015 016import org.hl7.fhir.r5.utils.UserDataNames; 017import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage; 018import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator; 019import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell; 020import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece; 021import org.hl7.fhir.utilities.xhtml.NodeType; 022import org.hl7.fhir.utilities.xhtml.XhtmlComposer; 023import org.hl7.fhir.utilities.xhtml.XhtmlNode; 024import org.hl7.fhir.utilities.xhtml.XhtmlNodeList; 025 026@MarkedToMoveToAdjunctPackage 027public class ObligationsRenderer extends Renderer { 028 public static class ObligationDetail { 029 private List<String> codes = new ArrayList<>(); 030 private List<String> elementIds = new ArrayList<>(); 031 private List<CanonicalType> actors = new ArrayList<>(); 032 private String doco; 033 private String docoShort; 034 private String filter; 035 private String filterDoco; 036 private List<UsageContext> usage = new ArrayList<>(); 037 private boolean isUnchanged = false; 038 private boolean matched = false; 039 private boolean removed = false; 040 private String source; 041// private ValueSet vs; 042 043 private ObligationDetail compare; 044 private int count = 1; 045 046 public ObligationDetail(Extension ext) { 047 for (Extension e: ext.getExtensionsByUrl("code")) { 048 codes.add(e.getValueStringType().toString()); 049 } 050 for (Extension e: ext.getExtensionsByUrl("actor")) { 051 actors.add(e.getValueCanonicalType()); 052 } 053 this.doco = ext.getExtensionString("documentation"); 054 this.docoShort = ext.getExtensionString("shortDoco"); 055 this.filter = ext.getExtensionString("filter"); 056 this.filterDoco = ext.getExtensionString("filterDocumentation"); 057 if (this.filterDoco == null) { 058 this.filterDoco = ext.getExtensionString("filter-desc"); 059 } 060 for (Extension usage : ext.getExtensionsByUrl("usage")) { 061 this.usage.add(usage.getValueUsageContext()); 062 } 063 for (Extension eid : ext.getExtensionsByUrl("elementId")) { 064 this.elementIds.add(eid.getValue().primitiveValue()); 065 } 066 this.isUnchanged = ext.hasUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS); 067 if (ext.hasExtension(ExtensionDefinitions.EXT_OBLIGATION_SOURCE, ExtensionDefinitions.EXT_OBLIGATION_SOURCE_SHORT)) { 068 this.source = ext.getExtensionString(ExtensionDefinitions.EXT_OBLIGATION_SOURCE, ExtensionDefinitions.EXT_OBLIGATION_SOURCE_SHORT); 069 } else if (ext.hasUserData(UserDataNames.SNAPSHOT_EXTENSION_SOURCE)) { 070 this.source = ((StructureDefinition) ext.getUserData(UserDataNames.SNAPSHOT_EXTENSION_SOURCE)).getVersionedUrl(); 071 } 072 } 073 074 private String getKey() { 075 // Todo: Consider extending this with content from usageContext if purpose isn't sufficiently differentiating 076 return String.join(",", codes) + Integer.toString(count); 077 } 078 079 private void incrementCount() { 080 count++; 081 } 082 private void setCompare(ObligationDetail match) { 083 compare = match; 084 match.matched = true; 085 } 086 private boolean alreadyMatched() { 087 return matched; 088 } 089 public String getDoco(boolean full) { 090 return full ? doco : docoShort; 091 } 092 public String getCodes() { 093 return String.join(",", codes); 094 } 095 public List<String> getCodeList() { 096 return new ArrayList<String>(codes); 097 } 098 public boolean unchanged() { 099 if (!isUnchanged) 100 return false; 101 if (compare==null) 102 return true; 103 isUnchanged = true; 104 isUnchanged = isUnchanged && ((codes.isEmpty() && compare.codes.isEmpty()) || codes.equals(compare.codes)); 105 isUnchanged = elementIds.equals(compare.elementIds); 106 isUnchanged = isUnchanged && ((actors.isEmpty() && compare.actors.isEmpty()) || actors.equals(compare.actors)); 107 isUnchanged = isUnchanged && ((doco==null && compare.doco==null) || doco.equals(compare.doco)); 108 isUnchanged = isUnchanged && ((docoShort==null && compare.docoShort==null) || docoShort.equals(compare.docoShort)); 109 isUnchanged = isUnchanged && ((filter==null && compare.filter==null) || filter.equals(compare.filter)); 110 isUnchanged = isUnchanged && ((filterDoco==null && compare.filterDoco==null) || filterDoco.equals(compare.filterDoco)); 111 isUnchanged = isUnchanged && ((usage==null && compare.usage==null) || usage.equals(compare.usage)); 112 return isUnchanged; 113 } 114 115 public boolean hasFilter() { 116 return filter != null; 117 } 118 119 public boolean hasUsage() { 120 return !usage.isEmpty(); 121 } 122 123 public String getFilterDesc() { 124 return filterDoco; 125 } 126 127 public String getFilter() { 128 return filter; 129 } 130 131 public List<UsageContext> getUsage() { 132 return usage; 133 } 134 135 public boolean hasActors() { 136 return !actors.isEmpty(); 137 } 138 139 public boolean hasActor(String id) { 140 for (CanonicalType actor: actors) { 141 if (actor.getValue().equals(id)) 142 return true; 143 } 144 return false; 145 } 146 } 147 148 private static String STYLE_UNCHANGED = "opacity: 0.5;"; 149 private static String STYLE_REMOVED = STYLE_UNCHANGED + "text-decoration: line-through;"; 150 151 private List<ObligationDetail> obligations = new ArrayList<>(); 152 private String corePath; 153 private StructureDefinition profile; 154 private String path; 155 private RenderingContext context; 156 private IMarkdownProcessor md; 157 private CodeResolver cr; 158 private boolean canDoNoList; 159 160 public ObligationsRenderer(String corePath, StructureDefinition profile, String path, RenderingContext context, IMarkdownProcessor md, CodeResolver cr, boolean canDoNoList) { 161 super(context); 162 this.corePath = corePath; 163 this.profile = profile; 164 this.path = path; 165 this.context = context; 166 this.md = md; 167 this.cr = cr; 168 this.canDoNoList = canDoNoList; 169 } 170 171 172 public void seeObligations(ElementDefinition element, String id) { 173 seeObligations(element.getExtension(), null, false, id); 174 } 175 176 public void seeObligations(List<Extension> list) { 177 seeObligations(list, null, false, "$all"); 178 } 179 180 public void seeRootObligations(String eid, List<Extension> list) { 181 seeRootObligations(eid, list, null, false, "$all"); 182 } 183 184 public void seeObligations(List<Extension> list, List<Extension> compList, boolean compare, String id) { 185 HashMap<String, ObligationDetail> compBindings = new HashMap<String, ObligationDetail>(); 186 if (compare && compList!=null) { 187 for (Extension ext : compList) { 188 ObligationDetail abr = obligationDetail(ext); 189 if (compBindings.containsKey(abr.getKey())) { 190 abr.incrementCount(); 191 } 192 compBindings.put(abr.getKey(), abr); 193 } 194 } 195 196 for (Extension ext : list) { 197 ObligationDetail obd = obligationDetail(ext); 198 if ("$all".equals(id) || (obd.hasActor(id))) { 199 if (compare && compList!=null) { 200 ObligationDetail match = null; 201 do { 202 match = compBindings.get(obd.getKey()); 203 if (obd.alreadyMatched()) 204 obd.incrementCount(); 205 } while (match!=null && obd.alreadyMatched()); 206 if (match!=null) 207 obd.setCompare(match); 208 addObligation(obd); 209 if (obd.compare!=null) 210 compBindings.remove(obd.compare.getKey()); 211 } else { 212 addObligation(obd); 213 } 214 } 215 } 216 for (ObligationDetail b: compBindings.values()) { 217 b.removed = true; 218 addObligation(b); 219 } 220 } 221 222 public void seeRootObligations(String eid, List<Extension> list, List<Extension> compList, boolean compare, String id) { 223 HashMap<String, ObligationDetail> compBindings = new HashMap<String, ObligationDetail>(); 224 if (compare && compList!=null) { 225 for (Extension ext : compList) { 226 if (forElement(eid, ext)) { 227 ObligationDetail abr = obligationDetail(ext); 228 if (compBindings.containsKey(abr.getKey())) { 229 abr.incrementCount(); 230 } 231 compBindings.put(abr.getKey(), abr); 232 } 233 } 234 } 235 236 for (Extension ext : list) { 237 if (forElement(eid, ext)) { 238 ObligationDetail obd = obligationDetail(ext); 239 obd.elementIds.clear(); 240 if ("$all".equals(id) || (obd.hasActor(id))) { 241 if (compare && compList!=null) { 242 ObligationDetail match = null; 243 do { 244 match = compBindings.get(obd.getKey()); 245 if (obd.alreadyMatched()) 246 obd.incrementCount(); 247 } while (match!=null && obd.alreadyMatched()); 248 if (match!=null) 249 obd.setCompare(match); 250 addObligation(obd); 251 if (obd.compare!=null) 252 compBindings.remove(obd.compare.getKey()); 253 } else { 254 addObligation(obd); 255 } 256 } 257 } 258 } 259 for (ObligationDetail b: compBindings.values()) { 260 b.removed = true; 261 addObligation(b); 262 } 263 } 264 265 266 private void addObligation(ObligationDetail obd) { 267 boolean add = context.getActorWhiteList().isEmpty(); 268 if (!add) { 269 for (CanonicalType a : obd.actors) { 270 ActorDefinition ad = context.getContext().fetchResource(ActorDefinition.class, a.getValue()); 271 add = add || (context.getActorWhiteList().contains(ad)); 272 } 273 } 274 if (add) { 275 obligations.add(obd); 276 } 277 } 278 279 280 private boolean forElement(String eid, Extension ext) { 281 282 for (Extension exid : ext.getExtensionsByUrl("elementId")) { 283 if (eid.equals(exid.getValue().primitiveValue())) { 284 return true; 285 } 286 } 287 return false; 288 } 289 290 291 protected ObligationDetail obligationDetail(Extension ext) { 292 ObligationDetail abr = new ObligationDetail(ext); 293 return abr; 294 } 295 296 public String render(RenderingStatus status, ResourceWrapper res, String defPath, String anchorPrefix, List<ElementDefinition> inScopeElements) throws IOException { 297 if (obligations.isEmpty()) { 298 return ""; 299 } else { 300 XhtmlNode tbl = new XhtmlNode(NodeType.Element, "table"); 301 tbl.attribute("class", "grid"); 302 renderTable(status, res, tbl.getChildNodes(), true, defPath, anchorPrefix, inScopeElements); 303 return new XhtmlComposer(false).compose(tbl); 304 } 305 } 306 307 public void renderTable(RenderingStatus status, ResourceWrapper res, HierarchicalTableGenerator gen, Cell c, List<ElementDefinition> inScopeElements) throws FHIRFormatError, DefinitionException, IOException { 308 if (obligations.isEmpty()) { 309 return; 310 } else { 311 Piece piece = gen.new Piece("obligation", "table").setClass("grid"); 312 c.getPieces().add(piece); 313 renderTable(status, res, piece.getChildren(), false, gen.getDefPath(), gen.getUniqueLocalPrefix(), inScopeElements); 314 } 315 } 316 317 public void renderList(HierarchicalTableGenerator gen, Cell c) throws FHIRFormatError, DefinitionException, IOException { 318 if (obligations.size() > 0) { 319 Piece p = gen.new Piece(null); 320 c.addPiece(p); 321 if (obligations.size() == 1 && canDoNoList) { 322 renderObligationLI(p.getChildren(), obligations.get(0)); 323 } else { 324 XhtmlNode ul = p.getChildren().ul(); 325 for (ObligationDetail ob : obligations) { 326 renderObligationLI(ul.li().getChildNodes(), ob); 327 } 328 } 329 } 330 } 331 332 private void renderObligationLI(XhtmlNodeList children, ObligationDetail ob) throws IOException { 333 renderCodes(children, ob.getCodeList()); 334 if (ob.hasFilter() || ob.hasUsage() || !ob.elementIds.isEmpty()) { 335 children.tx(" ("); 336 boolean ffirst = !ob.hasFilter(); 337 boolean firstEid = true; 338 339 for (String eid: ob.elementIds) { 340 if (firstEid) { 341 children.span().i().tx("Elements: "); 342 firstEid = false; 343 } else 344 children.tx(", "); 345 String trimmedElement = eid.substring(eid.indexOf(".")+ 1); 346 children.tx(trimmedElement); 347 } 348 if (ob.hasFilter()) { 349 children.span(null, ob.getFilterDesc()).code().tx(ob.getFilter()); 350 } 351 for (UsageContext uc : ob.getUsage()) { 352 if (ffirst) ffirst = false; else children.tx(","); 353 if (!uc.getCode().is("http://terminology.hl7.org/CodeSystem/usage-context-type", "jurisdiction")) { 354 children.tx(displayForUsage(uc.getCode())); 355 children.tx("="); 356 } 357 CodeResolution ccr = this.cr.resolveCode(uc.getValueCodeableConcept(), profile); 358 children.ah(context.prefixLocalHref(ccr.getLink()), ccr.getHint()).tx(ccr.getDisplay()); 359 } 360 children.tx(")"); 361 } 362 if (ob.source != null && !ob.source.equals(profile.getVersionedUrl())) { 363 children.tx(" "); 364 StructureDefinition sd = context.getContext().fetchResource(StructureDefinition.class, ob.source); 365 String link = sd != null ? sd.getWebPath() : ob.source; 366 String title = context.formatPhrase(RenderingContext.OBLIGATION_SOURCE, sd == null ? ob.source : sd.present()); 367 children.ah(link, title).attribute("data-no-external", "true").img("external.png", "source-link"); 368 } 369 // usage 370 // filter 371 // process 372 } 373 374 375 public void renderTable(RenderingStatus status, ResourceWrapper res, List<XhtmlNode> children, boolean fullDoco, String defPath, String anchorPrefix, List<ElementDefinition> inScopeElements) throws FHIRFormatError, DefinitionException, IOException { 376 boolean hasDoco = false; 377 boolean hasUsage = false; 378 boolean hasActor = false; 379 boolean hasFilter = false; 380 boolean hasElementId = false; 381 boolean hasSource = false; 382 for (ObligationDetail ob : obligations) { 383 hasActor = hasActor || !ob.actors.isEmpty() || (ob.compare!=null && !ob.compare.actors.isEmpty()); 384 hasDoco = hasDoco || ob.getDoco(fullDoco)!=null || (ob.compare!=null && ob.compare.getDoco(fullDoco)!=null); 385 hasUsage = hasUsage || !ob.usage.isEmpty() || (ob.compare!=null && !ob.compare.usage.isEmpty()); 386 hasFilter = hasFilter || ob.filter != null || (ob.compare!=null && ob.compare.filter!=null); 387 hasElementId = hasElementId || !ob.elementIds.isEmpty() || (ob.compare!=null && !ob.compare.elementIds.isEmpty()); 388 hasSource = hasSource || ((ob.source != null || (ob.compare!=null && ob.compare.source!=null)) && !ob.source.equals(profile.getVersionedUrl())); 389 } 390 391 List<String> inScopePaths = new ArrayList<>(); 392 for (ElementDefinition e: inScopeElements) { 393 inScopePaths.add(e.getPath()); 394 } 395 396 XhtmlNode tr = new XhtmlNode(NodeType.Element, "tr"); 397 children.add(tr); 398 tr.td().style("font-size: 11px").b().tx(context.formatPhrase(RenderingContext.GENERAL_OBLIG)); 399 if (hasActor) { 400 tr.td().style("font-size: 11px").b().tx(context.formatPhrase(RenderingContext.OBLIG_ACT)); 401 } 402 if (hasElementId) { 403 tr.td().style("font-size: 11px").b().tx(context.formatPhrase(RenderingContext.OBLIG_ELE)); 404 } 405 if (hasUsage) { 406 tr.td().style("font-size: 11px").b().tx(context.formatPhrase(RenderingContext.GENERAL_USAGE)); 407 } 408 if (hasDoco) { 409 tr.td().style("font-size: 11px").b().tx(context.formatPhrase(RenderingContext.GENERAL_DOCUMENTATION)); 410 } 411 if (hasFilter) { 412 tr.td().style("font-size: 11px").b().tx(context.formatPhrase(RenderingContext.GENERAL_FILTER)); 413 } 414 if (hasSource) { 415 tr.td().style("font-size: 11px").b().tx(context.formatPhrase(RenderingContext.GENERAL_SOURCE)); 416 } 417 for (ObligationDetail ob : obligations) { 418 tr = new XhtmlNode(NodeType.Element, "tr"); 419 if (ob.unchanged()) { 420 tr.style(STYLE_REMOVED); 421 } else if (ob.removed) { 422 tr.style(STYLE_REMOVED); 423 } 424 children.add(tr); 425 426 XhtmlNode code = tr.td().style("font-size: 11px"); 427 if (ob.compare!=null && ob.getCodes().equals(ob.compare.getCodes())) 428 code.style("font-color: darkgray"); 429 renderCodes(code.getChildNodes(), ob.getCodeList()); 430 if (ob.compare!=null && !ob.compare.getCodeList().isEmpty() && !ob.getCodes().equals(ob.compare.getCodes())) { 431 code.br(); 432 code = code.span(STYLE_UNCHANGED, null); 433 renderCodes(code.getChildNodes(), ob.compare.getCodeList()); 434 } 435 436 XhtmlNode actorId = tr.td().style("font-size: 11px"); 437 if (!ob.actors.isEmpty() || ob.compare == null || ob.compare.actors.isEmpty()) { 438 boolean firstActor = true; 439 for (CanonicalType anActor : ob.actors) { 440 ActorDefinition ad = context.getContext().fetchResource(ActorDefinition.class, anActor.getCanonical()); 441 boolean existingActor = ob.compare != null && ob.compare.actors.contains(anActor); 442 443 if (!firstActor) { 444 actorId.br(); 445 firstActor = false; 446 } 447 448 if (!existingActor) 449 actorId.style(STYLE_UNCHANGED); 450 if (ad == null) { 451 actorId.addText(anActor.getCanonical()); 452 } else { 453 actorId.ah(ad.getWebPath()).tx(ad.present()); 454 } 455 } 456 457 if (ob.compare != null) { 458 for (CanonicalType compActor : ob.compare.actors) { 459 if (!ob.actors.contains(compActor)) { 460 ActorDefinition compAd = context.getContext().fetchResource(ActorDefinition.class, compActor.toString()); 461 if (!firstActor) { 462 actorId.br(); 463 firstActor = true; 464 } 465 actorId = actorId.span(STYLE_REMOVED, null); 466 if (compAd == null) { 467 actorId.ah(context.prefixLocalHref(compActor.toString()), compActor.toString()).tx(compActor.toString()); 468 } else if (compAd.hasWebPath()) { 469 actorId.ah(context.prefixLocalHref(compAd.getWebPath()), compActor.toString()).tx(compAd.present()); 470 } else { 471 actorId.span(null, compActor.toString()).tx(compAd.present()); 472 } 473 } 474 } 475 } 476 } 477 478 479 if (hasElementId) { 480 XhtmlNode elementIds = tr.td().style("font-size: 11px"); 481 if (ob.compare!=null && ob.elementIds.equals(ob.compare.elementIds)) 482 elementIds.style(STYLE_UNCHANGED); 483 for (String eid : ob.elementIds) { 484 elementIds.sep(", "); 485 ElementDefinition ed = profile.getSnapshot().getElementById(eid); 486 if (ed != null) { 487 boolean inScope = inScopePaths.contains(ed.getPath()); 488 String name = eid.substring(eid.indexOf(".") + 1); 489 if (ed != null && inScope) { 490 String link = defPath + "#" + anchorPrefix + eid; 491 elementIds.ah(context.prefixLocalHref(link)).tx(name); 492 } else { 493 elementIds.code().tx(name); 494 } 495 } 496 } 497 498 if (ob.compare!=null && !ob.compare.elementIds.isEmpty()) { 499 for (String eid : ob.compare.elementIds) { 500 if (!ob.elementIds.contains(eid)) { 501 elementIds.sep(", "); 502 elementIds.span(STYLE_REMOVED, null).code().tx(eid); 503 } 504 } 505 } 506 } 507 if (hasUsage) { 508 if (ob.usage != null) { 509 boolean first = true; 510 XhtmlNode td = tr.td(); 511 for (UsageContext u : ob.usage) { 512 if (first) first = false; else td.tx(", "); 513 new DataRenderer(context).renderDataType(status, td, wrapWC(res, u)); 514 } 515 } else { 516 tr.td(); 517 } 518 } 519 if (hasDoco) { 520 if (ob.doco != null) { 521 String d = fullDoco ? md.processMarkdown("Obligation.documentation", ob.doco) : ob.docoShort; 522 String oldD = ob.compare==null ? null : fullDoco ? md.processMarkdown("Binding.description.compare", ob.compare.doco) : ob.compare.docoShort; 523 tr.td().style("font-size: 11px").innerHTML(compareHtml(d, oldD)); 524 } else { 525 tr.td().style("font-size: 11px"); 526 } 527 } 528 529 if (hasFilter) { 530 if (ob.filter != null) { 531 String d = "<code>"+ob.filter+"</code>" + (fullDoco ? md.processMarkdown("Binding.description", ob.filterDoco) : ""); 532 String oldD = ob.compare==null ? null : "<code>"+ob.compare.filter+"</code>" + (fullDoco ? md.processMarkdown("Binding.description", ob.compare.filterDoco) : ""); 533 tr.td().style("font-size: 11px").innerHTML(compareHtml(d, oldD)); 534 } else { 535 tr.td().style("font-size: 11px"); 536 } 537 } 538 if (hasSource) { 539 if (ob.source != null && !ob.source.equals(profile.getVersionedUrl())) { 540 StructureDefinition sd = context.getContext().fetchResource(StructureDefinition.class, ob.source); 541 var td = tr.td().style("font-size: 11px"); 542 td.tx("from "); 543 if (sd != null) { 544 td.ah(sd.getWebPath()).tx(sd.present()); 545 } else { 546 td.code().tx(ob.source); 547 } 548 } else { 549 tr.td().style("font-size: 11px"); 550 } 551 } 552 553 } 554 } 555 556 private XhtmlNode compareString(XhtmlNode node, String newS, String oldS) { 557 if (oldS==null) 558 return node.tx(newS); 559 if (newS.equals(oldS)) 560 return node.style(STYLE_UNCHANGED).tx(newS); 561 node.tx(newS); 562 node.br(); 563 return node.span(STYLE_REMOVED,null).tx(oldS); 564 } 565 566 private String compareHtml(String newS, String oldS) { 567 if (oldS==null) 568 return newS; 569 if (newS.equals(oldS)) 570 return "<span style=\"" + STYLE_UNCHANGED + "\">" + newS + "</span>"; 571 return newS + "<br/><span style=\"" + STYLE_REMOVED + "\">" + oldS + "</span>"; 572 } 573 574 private void renderCodes(XhtmlNodeList children, List<String> codes) { 575 576 if (!codes.isEmpty()) { 577 boolean first = true; 578 for (String code : codes) { 579 if (first) first = false; else children.tx(" & "); 580 int i = code.indexOf(":"); 581 if (i > -1) { 582 String c = code.substring(0, i); 583 code = code.substring(i+1); 584 children.b().tx(c.toUpperCase()); 585 children.tx(":"); 586 } 587 CodeResolution cr = this.cr.resolveCode("http://hl7.org/fhir/CodeSystem/obligation", code, profile); 588 if (cr == null) { 589 cr = this.cr.resolveCode("http://hl7.org/fhir/tools/CodeSystem/obligation", code, profile); 590 } 591 code = code.replace("will-", "").replace("can-", ""); 592 if (cr.getLink() != null) { 593 children.ah(context.prefixLocalHref(cr.getLink()), cr.getHint()).tx(code); 594 } else { 595 children.span(null, cr.getHint()).tx(code); 596 } 597 } 598 } else { 599 children.span(null, "No Obligation Code?").tx("??"); 600 } 601 } 602 603 public boolean hasObligations() { 604 return !obligations.isEmpty(); 605 } 606 607 private String displayForUsage(Coding c) { 608 if (c.hasDisplay()) { 609 return c.getDisplay(); 610 } 611 if ("http://terminology.hl7.org/CodeSystem/usage-context-type".equals(c.getSystem())) { 612 return c.getCode(); 613 } 614 return c.getCode(); 615 } 616 617}