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