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