All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.hl7.fhir.r5.renderers.AdditionalBindingsRenderer 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.BindingResolution;
import org.hl7.fhir.r5.conformance.profile.ProfileKnowledgeProvider;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingAdditionalComponent;
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.Renderer.RenderingStatus;
import org.hl7.fhir.r5.renderers.utils.RenderingContext;
import org.hl7.fhir.r5.utils.UserDataNames;
import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
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 AdditionalBindingsRenderer {
  public class AdditionalBindingDetail {
    private String purpose;
    private String valueSet;
    private String doco;
    private String docoShort;
    private List usages = new ArrayList();
    private boolean any = false;
    private boolean isUnchanged = false;
    private boolean matched = false;
    private boolean removed = false;
//    private ValueSet vs;
    
    private AdditionalBindingDetail compare;
    private int count = 1;
    private String getKey() {
      // Todo: Consider extending this with content from usageContext if purpose isn't sufficiently differentiating
      return purpose + Integer.toString(count);
    }
    private void incrementCount() {
      count++;
    }
    private void setCompare(AdditionalBindingDetail match) {
      compare = match;
      match.matched = true;
    }
    private boolean alreadyMatched() {
      return matched;
    }
    public String getDoco(boolean full) {
      return full ? doco : docoShort;
    }
    public boolean unchanged() {
      if (!isUnchanged)
        return false;
      if (compare==null)
        return true;
      isUnchanged = true;
      isUnchanged = isUnchanged && ((purpose==null && compare.purpose==null) || purpose.equals(compare.purpose));
      isUnchanged = isUnchanged && ((valueSet==null && compare.valueSet==null) || valueSet.equals(compare.valueSet));
      isUnchanged = isUnchanged && ((doco==null && compare.doco==null) || doco.equals(compare.doco));
      isUnchanged = isUnchanged && ((docoShort==null && compare.docoShort==null) || docoShort.equals(compare.docoShort));
      isUnchanged = isUnchanged && ((usages==null && compare.usages==null) || usages.equals(compare.usages));
      return isUnchanged;
    }
  }

  private static String STYLE_UNCHANGED = "opacity: 0.5;";
  private static String STYLE_REMOVED = STYLE_UNCHANGED + "text-decoration: line-through;";

  private List bindings = new ArrayList<>();
  private ProfileKnowledgeProvider pkp;
  private String corePath;
  private StructureDefinition profile;
  private String path;
  private RenderingContext context;
  private IMarkdownProcessor md;
  private CodeResolver cr;

  public AdditionalBindingsRenderer(ProfileKnowledgeProvider pkp, String corePath, StructureDefinition profile, String path, RenderingContext context, IMarkdownProcessor md, CodeResolver cr) {
    this.pkp = pkp;
    this.corePath = corePath;
    this.profile = profile;
    this.path = path;
    this.context = context;
    this.md = md;
    this.cr = cr;
  }

  public void seeMaxBinding(Extension ext) {
    seeMaxBinding(ext, null, false);
  }

  public void seeMaxBinding(Extension ext, Extension compExt, boolean compare) {
    seeBinding(ext, compExt, compare, "maximum");
  }

  protected void seeBinding(Extension ext, Extension compExt, boolean compare, String label) {
    AdditionalBindingDetail abr = new AdditionalBindingDetail();
    abr.purpose =  label;
    abr.valueSet =  ext.getValue().primitiveValue();
    if (compare) {
      abr.isUnchanged = compExt!=null && ext.getValue().primitiveValue().equals(compExt.getValue().primitiveValue());

      abr.compare = new AdditionalBindingDetail();
      abr.compare.valueSet = compExt==null ? null : compExt.getValue().primitiveValue();
    } else {
      abr.isUnchanged = ext.hasUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS);
    }
    bindings.add(abr);
  }

  public void seeMinBinding(Extension ext) {
    seeMinBinding(ext, null, false);
  }

  public void seeMinBinding(Extension ext, Extension compExt, boolean compare) {
    seeBinding(ext, compExt, compare, "minimum");
  }

  public void seeAdditionalBindings(List list) {
    seeAdditionalBindings(list, null, false);
  }

  public void seeAdditionalBindings(List list, List compList, boolean compare) {
    HashMap compBindings = new HashMap();
    if (compare && compList!=null) {
      for (Extension ext : compList) {
        AdditionalBindingDetail abr = additionalBinding(ext);
        if (compBindings.containsKey(abr.getKey())) {
          abr.incrementCount();
        }
        compBindings.put(abr.getKey(), abr);
      }
    }

    for (Extension ext : list) {
      AdditionalBindingDetail abr = additionalBinding(ext);
      if (compare && compList!=null) {
        AdditionalBindingDetail match = null;
        do {
          match = compBindings.get(abr.getKey());
          if (abr.alreadyMatched())
            abr.incrementCount();
        } while (match!=null && abr.alreadyMatched());
        if (match!=null)
          abr.setCompare(match);
        bindings.add(abr);
        if (abr.compare!=null)
          compBindings.remove(abr.compare.getKey());
      } else
        bindings.add(abr);
    }
    for (AdditionalBindingDetail b: compBindings.values()) {
      b.removed = true;
      bindings.add(b);
    }
  }

  protected AdditionalBindingDetail additionalBinding(Extension ext) {
    AdditionalBindingDetail abr = new AdditionalBindingDetail();
    abr.purpose =  ext.getExtensionString("purpose");
    abr.valueSet =  ext.getExtensionString("valueSet");
    abr.doco =  ext.getExtensionString("documentation");
    abr.docoShort =  ext.getExtensionString("shortDoco");
    for (Extension x : ext.getExtensionsByUrl("usage")) {
      if (x.hasValueUsageContext()) {
        abr.usages.add(x.getValueUsageContext());
      }
    }
    abr.any = "any".equals(ext.getExtensionString("scope"));
    abr.isUnchanged = ext.hasUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS);
    return abr;
  }

  protected AdditionalBindingDetail additionalBinding(ElementDefinitionBindingAdditionalComponent ab) {
    AdditionalBindingDetail abr = new AdditionalBindingDetail();
    abr.purpose =  ab.getPurpose().toCode();
    abr.valueSet =  ab.getValueSet();
    abr.doco =  ab.getDocumentation();
    abr.docoShort =  ab.getShortDoco();
    abr.usages.addAll(ab.getUsage());
    abr.any = ab.getAny();
    abr.isUnchanged = ab.hasUserData(UserDataNames.SNAPSHOT_DERIVATION_EQUALS);
    return abr;
  }

  public String render() throws IOException {
    if (bindings.isEmpty()) {
      return "";
    } else {
      XhtmlNode tbl = new XhtmlNode(NodeType.Element, "table");
      tbl.attribute("class", "grid");
      render(tbl.getChildNodes(), true);
      return new XhtmlComposer(false).compose(tbl);
    }
  }

  public void render(HierarchicalTableGenerator gen, Cell c) throws FHIRFormatError, DefinitionException, IOException {
    if (bindings.isEmpty()) {
      return;
    } else {
      Piece piece = gen.new Piece("table").attr("class", "grid");
      c.getPieces().add(piece);
      render(piece.getChildren(), false);
    }
  }
  
  public void render(List children, boolean fullDoco) throws FHIRFormatError, DefinitionException, IOException {
    boolean doco = false;
    boolean usage = false;
    boolean any = false;
    for (AdditionalBindingDetail binding : bindings) {
      doco = doco || binding.getDoco(fullDoco)!=null  || (binding.compare!=null && binding.compare.getDoco(fullDoco)!=null);
      usage = usage || !binding.usages.isEmpty() || (binding.compare!=null && !binding.compare.usages.isEmpty());
      any = any || binding.any || (binding.compare!=null && binding.compare.any);
    }

    XhtmlNode tr = new XhtmlNode(NodeType.Element, "tr");
    children.add(tr);
    tr.td().style("font-size: 11px").b().tx(context.formatPhrase(RenderingContext.ADD_BIND_ADD_BIND));
    tr.td().style("font-size: 11px").tx(context.formatPhrase(RenderingContext.GENERAL_PURPOSE));
    if (usage) {
      tr.td().style("font-size: 11px").tx(context.formatPhrase(RenderingContext.GENERAL_USAGE));
    }
    if (any) {
      tr.td().style("font-size: 11px").tx(context.formatPhrase(RenderingContext.ADD_BIND_ANY));
    }
    if (doco) {
      tr.td().style("font-size: 11px").tx(context.formatPhrase(RenderingContext.GENERAL_DOCUMENTATION));
    }
    for (AdditionalBindingDetail binding : bindings) {
      tr =  new XhtmlNode(NodeType.Element, "tr");
      if (binding.unchanged()) {
        tr.style(STYLE_REMOVED);
      } else if (binding.removed) {
        tr.style(STYLE_REMOVED);
      }
      children.add(tr);
      BindingResolution br = pkp == null ? makeNullBr(binding) : pkp.resolveBinding(profile, binding.valueSet, path);
      BindingResolution compBr = null;
      if (binding.compare!=null  && binding.compare.valueSet!=null)
        compBr = pkp == null ? makeNullBr(binding.compare) : pkp.resolveBinding(profile, binding.compare.valueSet, path);

      XhtmlNode valueset = tr.td().style("font-size: 11px");
      if (binding.compare!=null && binding.valueSet.equals(binding.compare.valueSet))
        valueset.style(STYLE_UNCHANGED);
      if (br.url != null) {
        XhtmlNode a = valueset.ah(context.prefixLocalHref(determineUrl(br.url)), br.uri);
        a.tx(br.display);
        if (br.external) {
          a.tx(" ");
          a.img("external.png", null);
        }
      } else {
        valueset.span(null, binding.valueSet).tx(br.display);
      }
      if (binding.compare!=null && binding.compare.valueSet!=null && !binding.valueSet.equals(binding.compare.valueSet)) {
        valueset.br();
        valueset = valueset.span(STYLE_REMOVED, null);
        if (compBr.url != null) {
          valueset.ah(context.prefixLocalHref(determineUrl(compBr.url)), binding.compare.valueSet).tx(compBr.display);
        } else {
          valueset.span(null, binding.compare.valueSet).tx(compBr.display);
        }
      }

      XhtmlNode purpose = tr.td().style("font-size: 11px");
      if (binding.compare!=null && binding.purpose.equals(binding.compare.purpose))
        purpose.style("font-color: darkgray");
      renderPurpose(purpose, binding.purpose);
      if (binding.compare!=null && binding.compare.purpose!=null && !binding.purpose.equals(binding.compare.purpose)) {
        purpose.br();
        purpose = purpose.span(STYLE_UNCHANGED, null);
        renderPurpose(purpose, binding.compare.purpose);
      }
      if (usage) {
        if (!binding.usages.isEmpty()) {
          XhtmlNode td = tr.td();
          for (UsageContext uc : binding.usages) {
            td.sep(", ");
            new DataRenderer(context).renderBase(new RenderingStatus(), td, uc);
          }
        } else {
          tr.td();          
        }
      }
      if (any) {
        String newRepeat = binding.any ? context.formatPhrase(RenderingContext.ADD_BIND_ANY_REP) : context.formatPhrase(RenderingContext.ADD_BIND_ALL_REP);
        String oldRepeat = binding.compare!=null && binding.compare.any ? context.formatPhrase(RenderingContext.ADD_BIND_ANY_REP) : context.formatPhrase(RenderingContext.ADD_BIND_ALL_REP);
        compareString(tr.td().style("font-size: 11px"), newRepeat, oldRepeat);
      }
      if (doco) {
        if (binding.doco != null) {
          String d = fullDoco ? md.processMarkdown("Binding.description", binding.doco) : binding.docoShort;
          String oldD = binding.compare==null ? null : fullDoco ? md.processMarkdown("Binding.description.compare", binding.compare.doco) : binding.compare.docoShort;
          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 String determineUrl(String url) { return Utilities.isAbsoluteUrl(url) || !pkp.prependLinks() ? url : corePath + url; } private void renderPurpose(XhtmlNode td, String purpose) { boolean r5 = context == null || context.getWorker() == null ? false : VersionUtilities.isR5Plus(context.getWorker().getVersion()); switch (purpose) { case "maximum": 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)); break; case "minimum": 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)); break; case "required" : 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)); break; case "extensible" : 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)); break; case "preferred" : 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)); break; case "current" : if (r5) { 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)); } else { td.span(null, context.formatPhrase(RenderingContext.ADD_BIND_NEW_REC)).tx(context.formatPhrase(RenderingContext.ADD_BIND_CURR_BIND)); } break; case "ui" : if (r5) { 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)); } else { td.span(null, context.formatPhrase(RenderingContext.ADD_BIND_GIVEN_CONT)).tx(context.formatPhrase(RenderingContext.ADD_BIND_UI)); } break; case "starter" : if (r5) { 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)); } else { td.span(null, context.formatPhrase(RenderingContext.ADD_BIND_DESIG_SYS)).tx(context.formatPhrase(RenderingContext.GENERAL_STARTER)); } break; case "component" : if (r5) { 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)); } else { td.span(null, context.formatPhrase(RenderingContext.ADD_BIND_VALUE_COMP)).tx(context.formatPhrase(RenderingContext.GENERAL_COMPONENT)); } break; default: td.span(null, context.formatPhrase(RenderingContext.ADD_BIND_UNKNOWN_PUR)).tx(purpose); } } private BindingResolution makeNullBr(AdditionalBindingDetail binding) { BindingResolution br = new BindingResolution(); br.url = "http://none.none/none"; br.display = "todo"; return br; } public boolean hasBindings() { return !bindings.isEmpty(); } public void render(XhtmlNodeList children, List list) { if (list.size() == 1) { render(children, list.get(0)); } else { XhtmlNode ul = children.ul(); for (ElementDefinitionBindingAdditionalComponent b : list) { render(ul.li().getChildNodes(), b); } } } private void render(XhtmlNodeList children, ElementDefinitionBindingAdditionalComponent b) { if (b.getValueSet() == null) { return; // what should happen? } BindingResolution br = pkp.resolveBinding(profile, b.getValueSet(), corePath); 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); if (b.hasDocumentation()) { a.attribute("title", b.getDocumentation()); } a.tx(br.display); if (b.hasShortDoco()) { children.tx(": "); children.tx(b.getShortDoco()); } if (b.getAny() || b.hasUsage()) { children.tx(" ("); boolean ffirst = !b.getAny(); if (b.getAny()) { children.tx(context.formatPhrase(RenderingContext.ADD_BIND_ANY_REP)); } for (UsageContext uc : b.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 = cr.resolveCode(uc.getValueCodeableConcept()); children.ah(context.prefixLocalHref(ccr.getLink()), ccr.getHint()).tx(ccr.getDisplay()); } children.tx(")"); } } 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(); } public void seeAdditionalBinding(String purpose, String doco, ValueSet valueSet) { AdditionalBindingDetail abr = new AdditionalBindingDetail(); abr.purpose = purpose; abr.valueSet = valueSet.getUrl(); bindings.add(abr); } public void seeAdditionalBinding(String purpose, String doco, String ref) { AdditionalBindingDetail abr = new AdditionalBindingDetail(); abr.purpose = purpose; abr.valueSet = ref; bindings.add(abr); } public void seeAdditionalBindings(ElementDefinition definition, ElementDefinition compDef, boolean compare) { HashMap compBindings = new HashMap(); if (compare && compDef.getBinding().getAdditional() != null) { for (ElementDefinitionBindingAdditionalComponent ab : compDef.getBinding().getAdditional()) { AdditionalBindingDetail abr = additionalBinding(ab); if (compBindings.containsKey(abr.getKey())) { abr.incrementCount(); } compBindings.put(abr.getKey(), abr); } } for (ElementDefinitionBindingAdditionalComponent ab : definition.getBinding().getAdditional()) { AdditionalBindingDetail abr = additionalBinding(ab); if (compare && compDef != null) { AdditionalBindingDetail match = null; do { match = compBindings.get(abr.getKey()); if (abr.alreadyMatched()) abr.incrementCount(); } while (match!=null && abr.alreadyMatched()); if (match!=null) abr.setCompare(match); bindings.add(abr); if (abr.compare!=null) compBindings.remove(abr.compare.getKey()); } else bindings.add(abr); } for (AdditionalBindingDetail b: compBindings.values()) { b.removed = true; bindings.add(b); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy