org.hl7.fhir.r5.renderers.ObligationsRenderer Maven / Gradle / Ivy
package org.hl7.fhir.r5.renderers;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.conformance.profile.ProfileUtilities;
import org.hl7.fhir.r5.model.ActorDefinition;
import org.hl7.fhir.r5.model.CanonicalType;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.UsageContext;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.renderers.CodeResolver.CodeResolution;
import org.hl7.fhir.r5.renderers.utils.RenderingContext;
import org.hl7.fhir.r5.renderers.utils.ResourceWrapper;
import org.hl7.fhir.r5.utils.UserDataNames;
import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.hl7.fhir.utilities.xhtml.XhtmlNodeList;
@MarkedToMoveToAdjunctPackage
public class ObligationsRenderer extends Renderer {
public static class ObligationDetail {
private List codes = new ArrayList<>();
private List elementIds = new ArrayList<>();
private List actors = new ArrayList<>();
private String doco;
private String docoShort;
private String filter;
private String filterDoco;
private List usage = new ArrayList<>();
private boolean isUnchanged = false;
private boolean matched = false;
private boolean removed = false;
private ValueSet vs;
private ObligationDetail compare;
private int count = 1;
public ObligationDetail(Extension ext) {
for (Extension e: ext.getExtensionsByUrl("code")) {
codes.add(e.getValueStringType().toString());
}
for (Extension e: ext.getExtensionsByUrl("actor")) {
actors.add(e.getValueCanonicalType());
}
this.doco = ext.getExtensionString("documentation");
this.docoShort = ext.getExtensionString("shortDoco");
this.filter = ext.getExtensionString("filter");
this.filterDoco = ext.getExtensionString("filterDocumentation");
if (this.filterDoco == null) {
this.filterDoco = ext.getExtensionString("filter-desc");
}
for (Extension usage : ext.getExtensionsByUrl("usage")) {
this.usage.add(usage.getValueUsageContext());
}
for (Extension eid : ext.getExtensionsByUrl("elementId")) {
this.elementIds.add(eid.getValue().primitiveValue());
}
this.isUnchanged = ext.hasUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS);
}
private String getKey() {
// Todo: Consider extending this with content from usageContext if purpose isn't sufficiently differentiating
return String.join(",", codes) + Integer.toString(count);
}
private void incrementCount() {
count++;
}
private void setCompare(ObligationDetail match) {
compare = match;
match.matched = true;
}
private boolean alreadyMatched() {
return matched;
}
public String getDoco(boolean full) {
return full ? doco : docoShort;
}
public String getCodes() {
return String.join(",", codes);
}
public List getCodeList() {
return new ArrayList(codes);
}
public boolean unchanged() {
if (!isUnchanged)
return false;
if (compare==null)
return true;
isUnchanged = true;
isUnchanged = isUnchanged && ((codes.isEmpty() && compare.codes.isEmpty()) || codes.equals(compare.codes));
isUnchanged = elementIds.equals(compare.elementIds);
isUnchanged = isUnchanged && ((actors.isEmpty() && compare.actors.isEmpty()) || actors.equals(compare.actors));
isUnchanged = isUnchanged && ((doco==null && compare.doco==null) || doco.equals(compare.doco));
isUnchanged = isUnchanged && ((docoShort==null && compare.docoShort==null) || docoShort.equals(compare.docoShort));
isUnchanged = isUnchanged && ((filter==null && compare.filter==null) || filter.equals(compare.filter));
isUnchanged = isUnchanged && ((filterDoco==null && compare.filterDoco==null) || filterDoco.equals(compare.filterDoco));
isUnchanged = isUnchanged && ((usage==null && compare.usage==null) || usage.equals(compare.usage));
return isUnchanged;
}
public boolean hasFilter() {
return filter != null;
}
public boolean hasUsage() {
return !usage.isEmpty();
}
public String getFilterDesc() {
return filterDoco;
}
public String getFilter() {
return filter;
}
public List getUsage() {
return usage;
}
public boolean hasActors() {
return !actors.isEmpty();
}
public boolean hasActor(String id) {
for (CanonicalType actor: actors) {
if (actor.getValue().equals(id))
return true;
}
return false;
}
}
private static String STYLE_UNCHANGED = "opacity: 0.5;";
private static String STYLE_REMOVED = STYLE_UNCHANGED + "text-decoration: line-through;";
private List obligations = new ArrayList<>();
private String corePath;
private StructureDefinition profile;
private String path;
private RenderingContext context;
private IMarkdownProcessor md;
private CodeResolver cr;
public ObligationsRenderer(String corePath, StructureDefinition profile, String path, RenderingContext context, IMarkdownProcessor md, CodeResolver cr) {
super(context);
this.corePath = corePath;
this.profile = profile;
this.path = path;
this.context = context;
this.md = md;
this.cr = cr;
}
public void seeObligations(ElementDefinition element, String id) {
seeObligations(element.getExtension(), null, false, id);
}
public void seeObligations(List list) {
seeObligations(list, null, false, "$all");
}
public void seeRootObligations(String eid, List list) {
seeRootObligations(eid, list, null, false, "$all");
}
public void seeObligations(List list, List compList, boolean compare, String id) {
HashMap compBindings = new HashMap();
if (compare && compList!=null) {
for (Extension ext : compList) {
ObligationDetail abr = obligationDetail(ext);
if (compBindings.containsKey(abr.getKey())) {
abr.incrementCount();
}
compBindings.put(abr.getKey(), abr);
}
}
for (Extension ext : list) {
ObligationDetail obd = obligationDetail(ext);
if ("$all".equals(id) || (obd.hasActor(id))) {
if (compare && compList!=null) {
ObligationDetail match = null;
do {
match = compBindings.get(obd.getKey());
if (obd.alreadyMatched())
obd.incrementCount();
} while (match!=null && obd.alreadyMatched());
if (match!=null)
obd.setCompare(match);
obligations.add(obd);
if (obd.compare!=null)
compBindings.remove(obd.compare.getKey());
} else {
obligations.add(obd);
}
}
}
for (ObligationDetail b: compBindings.values()) {
b.removed = true;
obligations.add(b);
}
}
public void seeRootObligations(String eid, List list, List compList, boolean compare, String id) {
HashMap compBindings = new HashMap();
if (compare && compList!=null) {
for (Extension ext : compList) {
if (forElement(eid, ext)) {
ObligationDetail abr = obligationDetail(ext);
if (compBindings.containsKey(abr.getKey())) {
abr.incrementCount();
}
compBindings.put(abr.getKey(), abr);
}
}
}
for (Extension ext : list) {
if (forElement(eid, ext)) {
ObligationDetail obd = obligationDetail(ext);
obd.elementIds.clear();
if ("$all".equals(id) || (obd.hasActor(id))) {
if (compare && compList!=null) {
ObligationDetail match = null;
do {
match = compBindings.get(obd.getKey());
if (obd.alreadyMatched())
obd.incrementCount();
} while (match!=null && obd.alreadyMatched());
if (match!=null)
obd.setCompare(match);
obligations.add(obd);
if (obd.compare!=null)
compBindings.remove(obd.compare.getKey());
} else {
obligations.add(obd);
}
}
}
}
for (ObligationDetail b: compBindings.values()) {
b.removed = true;
obligations.add(b);
}
}
private boolean forElement(String eid, Extension ext) {
for (Extension exid : ext.getExtensionsByUrl("elementId")) {
if (eid.equals(exid.getValue().primitiveValue())) {
return true;
}
}
return false;
}
protected ObligationDetail obligationDetail(Extension ext) {
ObligationDetail abr = new ObligationDetail(ext);
return abr;
}
public String render(RenderingStatus status, ResourceWrapper res, String defPath, String anchorPrefix, List inScopeElements) throws IOException {
if (obligations.isEmpty()) {
return "";
} else {
XhtmlNode tbl = new XhtmlNode(NodeType.Element, "table");
tbl.attribute("class", "grid");
renderTable(status, res, tbl.getChildNodes(), true, defPath, anchorPrefix, inScopeElements);
return new XhtmlComposer(false).compose(tbl);
}
}
public void renderTable(RenderingStatus status, ResourceWrapper res, HierarchicalTableGenerator gen, Cell c, List inScopeElements) throws FHIRFormatError, DefinitionException, IOException {
if (obligations.isEmpty()) {
return;
} else {
Piece piece = gen.new Piece("table").attr("class", "grid");
c.getPieces().add(piece);
renderTable(status, res, piece.getChildren(), false, gen.getDefPath(), gen.getUniqueLocalPrefix(), inScopeElements);
}
}
public void renderList(HierarchicalTableGenerator gen, Cell c) throws FHIRFormatError, DefinitionException, IOException {
if (obligations.size() > 0) {
Piece p = gen.new Piece(null);
c.addPiece(p);
if (obligations.size() == 1) {
renderObligationLI(p.getChildren(), obligations.get(0));
} else {
XhtmlNode ul = p.getChildren().ul();
for (ObligationDetail ob : obligations) {
renderObligationLI(ul.li().getChildNodes(), ob);
}
}
}
}
private void renderObligationLI(XhtmlNodeList children, ObligationDetail ob) throws IOException {
renderCodes(children, ob.getCodeList());
if (ob.hasFilter() || ob.hasUsage() || !ob.elementIds.isEmpty()) {
children.tx(" (");
boolean ffirst = !ob.hasFilter();
boolean firstEid = true;
for (String eid: ob.elementIds) {
if (firstEid) {
children.span().i().tx("Elements: ");
firstEid = false;
} else
children.tx(", ");
String trimmedElement = eid.substring(eid.indexOf(".")+ 1);
children.tx(trimmedElement);
}
if (ob.hasFilter()) {
children.span(null, ob.getFilterDesc()).code().tx(ob.getFilter());
}
for (UsageContext uc : ob.getUsage()) {
if (ffirst) ffirst = false; else children.tx(",");
if (!uc.getCode().is("http://terminology.hl7.org/CodeSystem/usage-context-type", "jurisdiction")) {
children.tx(displayForUsage(uc.getCode()));
children.tx("=");
}
CodeResolution ccr = this.cr.resolveCode(uc.getValueCodeableConcept());
children.ah(context.prefixLocalHref(ccr.getLink()), ccr.getHint()).tx(ccr.getDisplay());
}
children.tx(")");
}
// usage
// filter
// process
}
public void renderTable(RenderingStatus status, ResourceWrapper res, List children, boolean fullDoco, String defPath, String anchorPrefix, List inScopeElements) throws FHIRFormatError, DefinitionException, IOException {
boolean doco = false;
boolean usage = false;
boolean actor = false;
boolean filter = false;
boolean elementId = false;
for (ObligationDetail binding : obligations) {
actor = actor || !binding.actors.isEmpty() || (binding.compare!=null && !binding.compare.actors.isEmpty());
doco = doco || binding.getDoco(fullDoco)!=null || (binding.compare!=null && binding.compare.getDoco(fullDoco)!=null);
usage = usage || !binding.usage.isEmpty() || (binding.compare!=null && !binding.compare.usage.isEmpty());
filter = filter || binding.filter != null || (binding.compare!=null && binding.compare.filter!=null);
elementId = elementId || !binding.elementIds.isEmpty() || (binding.compare!=null && !binding.compare.elementIds.isEmpty());
}
List inScopePaths = new ArrayList<>();
for (ElementDefinition e: inScopeElements) {
inScopePaths.add(e.getPath());
}
XhtmlNode tr = new XhtmlNode(NodeType.Element, "tr");
children.add(tr);
tr.td().style("font-size: 11px").b().tx(context.formatPhrase(RenderingContext.GENERAL_OBLIG));
if (actor) {
tr.td().style("font-size: 11px").b().tx(context.formatPhrase(RenderingContext.OBLIG_ACT));
}
if (elementId) {
tr.td().style("font-size: 11px").b().tx(context.formatPhrase(RenderingContext.OBLIG_ELE));
}
if (usage) {
tr.td().style("font-size: 11px").b().tx(context.formatPhrase(RenderingContext.GENERAL_USAGE));
}
if (doco) {
tr.td().style("font-size: 11px").b().tx(context.formatPhrase(RenderingContext.GENERAL_DOCUMENTATION));
}
if (filter) {
tr.td().style("font-size: 11px").b().tx(context.formatPhrase(RenderingContext.GENERAL_FILTER));
}
for (ObligationDetail ob : obligations) {
tr = new XhtmlNode(NodeType.Element, "tr");
if (ob.unchanged()) {
tr.style(STYLE_REMOVED);
} else if (ob.removed) {
tr.style(STYLE_REMOVED);
}
children.add(tr);
XhtmlNode code = tr.td().style("font-size: 11px");
if (ob.compare!=null && ob.getCodes().equals(ob.compare.getCodes()))
code.style("font-color: darkgray");
renderCodes(code.getChildNodes(), ob.getCodeList());
if (ob.compare!=null && !ob.compare.getCodeList().isEmpty() && !ob.getCodes().equals(ob.compare.getCodes())) {
code.br();
code = code.span(STYLE_UNCHANGED, null);
renderCodes(code.getChildNodes(), ob.compare.getCodeList());
}
XhtmlNode actorId = tr.td().style("font-size: 11px");
if (!ob.actors.isEmpty() || ob.compare.actors.isEmpty()) {
boolean firstActor = true;
for (CanonicalType anActor : ob.actors) {
ActorDefinition ad = context.getContext().fetchResource(ActorDefinition.class, anActor.getCanonical());
boolean existingActor = ob.compare != null && ob.compare.actors.contains(anActor);
if (!firstActor) {
actorId.br();
firstActor = false;
}
if (!existingActor)
actorId.style(STYLE_UNCHANGED);
if (ad == null) {
actorId.addText(anActor.getCanonical());
} else {
actorId.ah(ad.getWebPath()).tx(ad.present());
}
}
if (ob.compare != null) {
for (CanonicalType compActor : ob.compare.actors) {
if (!ob.actors.contains(compActor)) {
ActorDefinition compAd = context.getContext().fetchResource(ActorDefinition.class, compActor.toString());
if (!firstActor) {
actorId.br();
firstActor = true;
}
actorId = actorId.span(STYLE_REMOVED, null);
if (compAd == null) {
actorId.ah(context.prefixLocalHref(compActor.toString()), compActor.toString()).tx(compActor.toString());
} else if (compAd.hasWebPath()) {
actorId.ah(context.prefixLocalHref(compAd.getWebPath()), compActor.toString()).tx(compAd.present());
} else {
actorId.span(null, compActor.toString()).tx(compAd.present());
}
}
}
}
}
if (elementId) {
XhtmlNode elementIds = tr.td().style("font-size: 11px");
if (ob.compare!=null && ob.elementIds.equals(ob.compare.elementIds))
elementIds.style(STYLE_UNCHANGED);
for (String eid : ob.elementIds) {
elementIds.sep(", ");
ElementDefinition ed = profile.getSnapshot().getElementById(eid);
if (ed != null) {
boolean inScope = inScopePaths.contains(ed.getPath());
String name = eid.substring(eid.indexOf(".") + 1);
if (ed != null && inScope) {
String link = defPath + "#" + anchorPrefix + eid;
elementIds.ah(context.prefixLocalHref(link)).tx(name);
} else {
elementIds.code().tx(name);
}
}
}
if (ob.compare!=null && !ob.compare.elementIds.isEmpty()) {
for (String eid : ob.compare.elementIds) {
if (!ob.elementIds.contains(eid)) {
elementIds.sep(", ");
elementIds.span(STYLE_REMOVED, null).code().tx(eid);
}
}
}
}
if (usage) {
if (ob.usage != null) {
boolean first = true;
XhtmlNode td = tr.td();
for (UsageContext u : ob.usage) {
if (first) first = false; else td.tx(", ");
new DataRenderer(context).renderDataType(status, td, wrapWC(res, u));
}
} else {
tr.td();
}
}
if (doco) {
if (ob.doco != null) {
String d = fullDoco ? md.processMarkdown("Obligation.documentation", ob.doco) : ob.docoShort;
String oldD = ob.compare==null ? null : fullDoco ? md.processMarkdown("Binding.description.compare", ob.compare.doco) : ob.compare.docoShort;
tr.td().style("font-size: 11px").innerHTML(compareHtml(d, oldD));
} else {
tr.td().style("font-size: 11px");
}
}
if (filter) {
if (ob.filter != null) {
String d = ""+ob.filter+"" + (fullDoco ? md.processMarkdown("Binding.description", ob.filterDoco) : "");
String oldD = ob.compare==null ? null : ""+ob.compare.filter+"" + (fullDoco ? md.processMarkdown("Binding.description", ob.compare.filterDoco) : "");
tr.td().style("font-size: 11px").innerHTML(compareHtml(d, oldD));
} else {
tr.td().style("font-size: 11px");
}
}
}
}
private XhtmlNode compareString(XhtmlNode node, String newS, String oldS) {
if (oldS==null)
return node.tx(newS);
if (newS.equals(oldS))
return node.style(STYLE_UNCHANGED).tx(newS);
node.tx(newS);
node.br();
return node.span(STYLE_REMOVED,null).tx(oldS);
}
private String compareHtml(String newS, String oldS) {
if (oldS==null)
return newS;
if (newS.equals(oldS))
return "" + newS + "";
return newS + "
" + oldS + "";
}
private void renderCodes(XhtmlNodeList children, List codes) {
if (!codes.isEmpty()) {
boolean first = true;
for (String code : codes) {
if (first) first = false; else children.tx(" & ");
int i = code.indexOf(":");
if (i > -1) {
String c = code.substring(0, i);
code = code.substring(i+1);
children.b().tx(c.toUpperCase());
children.tx(":");
}
CodeResolution cr = this.cr.resolveCode("http://hl7.org/fhir/tools/CodeSystem/obligation", code);
code = code.replace("will-", "").replace("can-", "");
if (cr.getLink() != null) {
children.ah(context.prefixLocalHref(cr.getLink()), cr.getHint()).tx(code);
} else {
children.span(null, cr.getHint()).tx(code);
}
}
} else {
children.span(null, "No Obligation Code?").tx("??");
}
}
public boolean hasObligations() {
return !obligations.isEmpty();
}
private String displayForUsage(Coding c) {
if (c.hasDisplay()) {
return c.getDisplay();
}
if ("http://terminology.hl7.org/CodeSystem/usage-context-type".equals(c.getSystem())) {
return c.getCode();
}
return c.getCode();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy