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

org.hl7.fhir.r5.renderers.ConceptMapRenderer Maven / Gradle / Ivy

package org.hl7.fhir.r5.renderers;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.Coding;
import org.hl7.fhir.r5.model.ConceptMap;
import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupComponent;
import org.hl7.fhir.r5.model.ConceptMap.ConceptMapGroupUnmappedMode;
import org.hl7.fhir.r5.model.ConceptMap.MappingPropertyComponent;
import org.hl7.fhir.r5.model.ConceptMap.OtherElementComponent;
import org.hl7.fhir.r5.model.ConceptMap.SourceElementComponent;
import org.hl7.fhir.r5.model.ConceptMap.TargetElementComponent;
import org.hl7.fhir.r5.model.ContactDetail;
import org.hl7.fhir.r5.model.ContactPoint;
import org.hl7.fhir.r5.model.Enumerations.ConceptMapRelationship;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.renderers.utils.RenderingContext;
import org.hl7.fhir.r5.renderers.utils.ResourceWrapper;
import org.hl7.fhir.r5.utils.EOperationOutcome;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;

@MarkedToMoveToAdjunctPackage
public class ConceptMapRenderer extends TerminologyRenderer {


  public ConceptMapRenderer(RenderingContext context) { 
    super(context); 
  } 
 
  @Override
  public void buildNarrative(RenderingStatus status, XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome {
    if (r.isDirect()) {
      renderResourceTechDetails(r, x);
      genSummaryTable(status, x, (ConceptMap) r.getBase());
      render(status, r, x, (ConceptMap) r.getBase(), false);      
    } else {
      // the intention is to change this in the future
      x.para().tx("ConceptMapRenderer only renders native resources directly");
    }
  }
  
  @Override
  public String buildSummary(ResourceWrapper r) throws UnsupportedEncodingException, IOException {
    return canonicalTitle(r);
  }

  
  
  public static class CollateralDefinition {
    private Resource resource;
    private String label;
    public CollateralDefinition(Resource resource, String label) {
      super();
      this.resource = resource;
      this.label = label;
    }
    public Resource getResource() {
      return resource;
    }
    public String getLabel() {
      return label;
    }
  }

  public enum RenderMultiRowSortPolicy {
    UNSORTED, FIRST_COL, LAST_COL
  }

  public interface IMultiMapRendererAdvisor {
    public RenderMultiRowSortPolicy sortPolicy(Object rmmContext);
    public List getMembers(Object rmmContext, String uri);
    public boolean describeMap(Object rmmContext, ConceptMap map, XhtmlNode x);
    public boolean hasCollateral(Object rmmContext);
    public List getCollateral(Object rmmContext, String uri); // URI identifies which column the collateral is for
    public String getLink(Object rmmContext, String system, String code);
    public boolean makeMapLinks();
  }
  
  public static class MultipleMappingRowSorter implements Comparator {

    private boolean first;
    
    protected MultipleMappingRowSorter(boolean first) {
      super();
      this.first = first;
    }

    @Override
    public int compare(MultipleMappingRow o1, MultipleMappingRow o2) {
      String s1 = first ? o1.firstCode() : o1.lastCode();
      String s2 = first ? o2.firstCode() : o2.lastCode();
      return s1.compareTo(s2);
    }
  }

  public static class Cell {

    private String system;
    private String code;
    private String display;
    private String relationship;
    private String relComment;
    public boolean renderedRel;
    public boolean renderedCode;
    private Cell clone;
    
    protected Cell() {
      super();
    }

    public Cell(String system, String code, String display) {
      this.system = system;
      this.code = code;
      this.display = display;
    }

    public Cell(String system, String code, String relationship, String comment) {
      this.system = system;
      this.code = code;
      this.relationship = relationship;
      this.relComment = comment;
    }

    public boolean matches(String system, String code) {
      return (system != null && system.equals(this.system)) && (code != null && code.equals(this.code));
    }

    public String present() {
      if (system == null) {
        return code;
      } else {
        return code; //+(clone == null ? "" : " (@"+clone.code+")");
      }
    }

    public Cell copy(boolean clone) {
      Cell res = new Cell();
      res.system = system;
      res.code = code;
      res.display = display;
      res.relationship = relationship;
      res.relComment = relComment;
      res.renderedRel = renderedRel;
      res.renderedCode = renderedCode;
      if (clone) {
        res.clone = this;
      }
      return res;
    }

    @Override
    public String toString() {
      return relationship+" "+system + "#" + code + " \"" + display + "\"";
    }
    
  }
  

  public static class MultipleMappingRowItem {
    List cells = new ArrayList<>();

    @Override
    public String toString() {
      CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
      for (Cell cell : cells) {
        if (cell.relationship != null) {
          b.append(cell.relationship+cell.code);
        } else {
          b.append(cell.code);
        }
      }
      return b.toString();
    }
  }
  
  public static class MultipleMappingRow {
    private List rowSets = new ArrayList<>();
    private MultipleMappingRow stickySource;

    public MultipleMappingRow(int i, String system, String code, String display) {
      MultipleMappingRowItem row = new MultipleMappingRowItem();
      rowSets.add(row);
      for (int c = 0; c < i; c++) {
        row.cells.add(new Cell()); // blank cell spaces
      }
      row.cells.add(new Cell(system, code, display));
    }


    public MultipleMappingRow(MultipleMappingRow stickySource) {
      this.stickySource = stickySource;
    }

    @Override
    public String toString() {
      CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
      for (MultipleMappingRowItem rowSet : rowSets) {
        b.append(""+rowSet.cells.size());
      }
      CommaSeparatedStringBuilder b2 = new CommaSeparatedStringBuilder(";");
      for (MultipleMappingRowItem rowSet : rowSets) {
        b2.append(rowSet.toString());
      }
      return ""+rowSets.size()+" ["+b.toString()+"] ("+b2.toString()+")";
    }


    public String lastCode() {
      MultipleMappingRowItem first = rowSets.get(0);
      for (int i = first.cells.size()-1; i >= 0; i--) {
        if (first.cells.get(i).code != null) {
          return first.cells.get(i).code;
        }
      }
      return "";
    }

    public String firstCode() {
      MultipleMappingRowItem first = rowSets.get(0);
      for (int i = 0; i < first.cells.size(); i++) {
        if (first.cells.get(i).code != null) {
          return first.cells.get(i).code;
        }
      }
      return "";
    }

    public void addSource(MultipleMappingRow sourceRow, List rowList, ConceptMapRelationship relationship, String comment) {
      // we already have a row, and we're going to collapse the rows on sourceRow into here, and add a matching terminus 
      assert sourceRow.rowSets.get(0).cells.size() == rowSets.get(0).cells.size()-1;
      rowList.remove(sourceRow);
      Cell template = rowSets.get(0).cells.get(rowSets.get(0).cells.size()-1);
      for (MultipleMappingRowItem row : sourceRow.rowSets) {
        row.cells.add(new Cell(template.system, template.code, relationship.getSymbol(), comment));
      }
      rowSets.addAll(sourceRow.rowSets);
    }

    public void addTerminus() {
      for (MultipleMappingRowItem row : rowSets) {
        row.cells.add(new Cell(null, null, "X", null));
      }
    }

    public void addTarget(String system, String code, ConceptMapRelationship relationship, String comment, List sets, int colCount) {
      if (rowSets.get(0).cells.size() == colCount+1) { // if it's already has a target for this col then we have to clone (and split) the rows
        for (MultipleMappingRowItem row : rowSets) {
          row.cells.add(new Cell(system, code, relationship.getSymbol(), comment));
        }
      } else {
        MultipleMappingRow nrow = new MultipleMappingRow(this);
        for (MultipleMappingRowItem row : rowSets) {
          MultipleMappingRowItem n = new MultipleMappingRowItem();
          for (int i = 0; i < row.cells.size()-1; i++) { // note to skip the last
            n.cells.add(row.cells.get(i).copy(true));
          }
          n.cells.add(new Cell(system, code, relationship.getSymbol(), comment));
          nrow.rowSets.add(n);
        }
        sets.add(sets.indexOf(this), nrow);
      }
    }

    public String lastSystem() {
      MultipleMappingRowItem first = rowSets.get(0);
      for (int i = first.cells.size()-1; i >= 0; i--) {
        if (first.cells.get(i).system != null) {
          return first.cells.get(i).system;
        }
      }
      return "";
    }

    public void addCopy(String system) {
      for (MultipleMappingRowItem row : rowSets) {
        row.cells.add(new Cell(system, lastCode(), "=", null));
      }
    }


    public boolean alreadyHasMappings(int i) {
      for (MultipleMappingRowItem row : rowSets) {
        if (row.cells.size() > i+1) {
          return true;
        }
      }
      return false;
    }


    public Cell getLastSource(int i) {
      for (MultipleMappingRowItem row : rowSets) {
        return row.cells.get(i+1);
      }
      throw new Error("Should not get here");   // return null
    }


    public void cloneSource(int i, Cell cell) {
      MultipleMappingRowItem row = new MultipleMappingRowItem();
      rowSets.add(row);
      for (int c = 0; c < i-1; c++) {
        row.cells.add(new Cell()); // blank cell spaces
      }
      row.cells.add(cell.copy(true));
      row.cells.add(rowSets.get(0).cells.get(rowSets.get(0).cells.size()-1).copy(false));      
    }
  }

  

  public void render(RenderingStatus status, ResourceWrapper res, XhtmlNode x, ConceptMap cm, boolean header) throws FHIRFormatError, DefinitionException, IOException {

    if (context.isShowSummaryTable()) {
      XhtmlNode h = x.h2();
      h.addText(cm.hasTitle() ? cm.getTitle() : cm.getName());
      addMarkdown(x, cm.getDescription());
      if (cm.hasCopyright())
        generateCopyright(x, res);
    }
    
    XhtmlNode p = x.para();
    p.tx(context.formatPhrase(RenderingContext.CONC_MAP_FROM) + " ");
    if (cm.hasSourceScope())
      AddVsRef(cm.getSourceScope().primitiveValue(), p, cm);
    else
      p.tx(context.formatPhrase(RenderingContext.CONC_MAP_NOT_SPEC));
    p.tx(" "+ (context.formatPhrase(RenderingContext.CONC_MAP_TO) + " "));
    if (cm.hasTargetScope())
      AddVsRef(cm.getTargetScope().primitiveValue(), p, cm);
    else 
      p.tx(context.formatPhrase(RenderingContext.CONC_MAP_NOT_SPEC));

    x.br();
    int gc = 0;
    
    CodeSystem cs = getContext().getWorker().fetchCodeSystem("http://hl7.org/fhir/concept-map-relationship");
    if (cs == null)
      cs = getContext().getWorker().fetchCodeSystem("http://hl7.org/fhir/concept-map-equivalence");
    String eqpath = cs == null ? null : cs.getWebPath();

    for (ConceptMapGroupComponent grp : cm.getGroup()) {
      String src = grp.getSource();
      boolean comment = false;
      boolean ok = true;
      Map> props = new HashMap>();
      Map> sources = new HashMap>();
      Map> targets = new HashMap>();
      sources.put("code", new HashSet());
      targets.put("code", new HashSet());
      sources.get("code").add(grp.getSource());
      targets.get("code").add(grp.getTarget());
      for (SourceElementComponent ccl : grp.getElement()) {
        ok = ok && (ccl.getNoMap() || (ccl.getTarget().size() == 1 && ccl.getTarget().get(0).getDependsOn().isEmpty() && ccl.getTarget().get(0).getProduct().isEmpty()));
        for (TargetElementComponent ccm : ccl.getTarget()) {
          comment = comment || !Utilities.noString(ccm.getComment());
          for (MappingPropertyComponent pp : ccm.getProperty()) {
            if (!props.containsKey(pp.getCode()))
              props.put(pp.getCode(), new HashSet());            
          }
          for (OtherElementComponent d : ccm.getDependsOn()) {
            if (!sources.containsKey(d.getAttribute()))
              sources.put(d.getAttribute(), new HashSet());
          }
          for (OtherElementComponent d : ccm.getProduct()) {
            if (!targets.containsKey(d.getAttribute()))
              targets.put(d.getAttribute(), new HashSet());
          }
        }
      }

      gc++;
      if (gc > 1) {
        x.hr();
      }
      XhtmlNode pp = x.para();
      pp.b().tx(context.formatPhrase(RenderingContext.CONC_MAP_GRP, gc) + " ");
      pp.tx(context.formatPhrase(RenderingContext.CONC_MAP_FROM) + " ");
      if (grp.hasSource()) {
        renderCanonical(status, res, pp, CodeSystem.class, grp.getSourceElement());
      } else {
        pp.code(context.formatPhrase(RenderingContext.CONC_MAP_CODE_SYS_UNSPEC));
      }
      pp.tx(" to ");
      if (grp.hasTarget()) {
        renderCanonical(status, res, pp, CodeSystem.class, grp.getTargetElement());
      } else {
        pp.code(context.formatPhrase(RenderingContext.CONC_MAP_CODE_SYS_UNSPEC));
      }
      
      String display;
      if (ok) {
        // simple
        XhtmlNode tbl = x.table( "grid", false);
        XhtmlNode tr = tbl.tr();
        tr.td().b().tx(context.formatPhrase(RenderingContext.CONC_MAP_SOURCE));
        tr.td().b().tx(context.formatPhrase(RenderingContext.CONC_MAP_REL));
        tr.td().b().tx(context.formatPhrase(RenderingContext.CONC_MAP_TRGT));
        if (comment)
          tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_COMMENT));
        for (SourceElementComponent ccl : grp.getElement()) {
          tr = tbl.tr();
          XhtmlNode td = tr.td();
          td.addText(ccl.getCode());
          display = ccl.hasDisplay() ? ccl.getDisplay() : getDisplayForConcept(grp.getSource(), ccl.getCode());
          if (display != null && !isSameCodeAndDisplay(ccl.getCode(), display))
            td.tx(" ("+display+")");
          if (ccl.getNoMap()) {
            tr.td().colspan(comment ? "3" : "2").style("background-color: #efefef").tx("(not mapped)");
          } else {
            TargetElementComponent ccm = ccl.getTarget().get(0);
            if (!ccm.hasRelationship())
              tr.td().tx(":"+"("+ConceptMapRelationship.EQUIVALENT.toCode()+")");
            else {
              if (ccm.hasExtension(ToolingExtensions.EXT_OLD_CONCEPTMAP_EQUIVALENCE)) {
                String code = ToolingExtensions.readStringExtension(ccm, ToolingExtensions.EXT_OLD_CONCEPTMAP_EQUIVALENCE);
                tr.td().ah(context.prefixLocalHref(eqpath+"#"+code), code).tx(presentEquivalenceCode(code));                
              } else {
                tr.td().ah(context.prefixLocalHref(eqpath+"#"+ccm.getRelationship().toCode()), ccm.getRelationship().toCode()).tx(presentRelationshipCode(ccm.getRelationship().toCode()));
              }
            }
            td = tr.td();
            td.addText(ccm.getCode());
            display = ccm.hasDisplay() ? ccm.getDisplay() : getDisplayForConcept(grp.getTarget(), ccm.getCode());
            if (display != null && !isSameCodeAndDisplay(ccm.getCode(), display))
              td.tx(" ("+display+")");
            if (comment)
              tr.td().addText(ccm.getComment());
          }
          addUnmapped(tbl, grp);
        }      
      } else {
        boolean hasRelationships = false;
        for (int si = 0; si < grp.getElement().size(); si++) {
          SourceElementComponent ccl = grp.getElement().get(si);
          for (int ti = 0; ti < ccl.getTarget().size(); ti++) {
            TargetElementComponent ccm = ccl.getTarget().get(ti);
            if (ccm.hasRelationship()) {
              hasRelationships = true;
            }  
          }
        }
        
        XhtmlNode tbl = x.table("grid", false);
        XhtmlNode tr = tbl.tr();
        XhtmlNode td;
        tr.td().colspan(Integer.toString(1+sources.size())).b().tx(context.formatPhrase(RenderingContext.CONC_MAP_SRC_DET));
        if (hasRelationships) {
          tr.td().b().tx(context.formatPhrase(RenderingContext.CONC_MAP_REL));
        }
        tr.td().colspan(Integer.toString(1+targets.size())).b().tx(context.formatPhrase(RenderingContext.CONC_MAP_TRGT_DET));
        if (comment) {
          tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_COMMENT));
        }
        tr.td().colspan(Integer.toString(1+targets.size())).b().tx(context.formatPhrase(RenderingContext.GENERAL_PROPS));
        tr = tbl.tr();
        if (sources.get("code").size() == 1) {
          String url = sources.get("code").iterator().next();
          renderCSDetailsLink(tr, url, true);           
        } else
          tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE));
        for (String s : sources.keySet()) {
          if (s != null && !s.equals("code")) {
            if (sources.get(s).size() == 1) {
              String url = sources.get(s).iterator().next();
              renderCSDetailsLink(tr, url, false);           
            } else
              tr.td().b().addText(getDescForConcept(s));
          }
        }
        if (hasRelationships) {
          tr.td();
        }
        if (targets.get("code").size() == 1) {
          String url = targets.get("code").iterator().next();
          renderCSDetailsLink(tr, url, true);           
        } else
          tr.td().b().tx(context.formatPhrase(RenderingContext.GENERAL_CODE));
        for (String s : targets.keySet()) {
          if (s != null && !s.equals("code")) {
            if (targets.get(s).size() == 1) {
              String url = targets.get(s).iterator().next();
              renderCSDetailsLink(tr, url, false);           
            } else
              tr.td().b().addText(getDescForConcept(s));
          }
        }
        if (comment) {
          tr.td();
        }
        for (String s : props.keySet()) {
          if (s != null) {
            if (props.get(s).size() == 1) {
              String url = props.get(s).iterator().next();
              renderCSDetailsLink(tr, url, false);           
            } else
              tr.td().b().addText(getDescForConcept(s));
          }
        }

        for (int si = 0; si < grp.getElement().size(); si++) {
          SourceElementComponent ccl = grp.getElement().get(si);
          boolean slast = si == grp.getElement().size()-1;
          boolean first = true;
          if (ccl.hasNoMap() && ccl.getNoMap()) {
            tr = tbl.tr();
            td = tr.td().style("border-right-width: 0px");
            if (!first)
              td.style("border-top-style: none");
            else 
              td.style("border-bottom-style: none");
            if (sources.get("code").size() == 1)
              td.addText(ccl.getCode());
            else
              td.addText(grp.getSource()+" / "+ccl.getCode());
            display = ccl.hasDisplay() ? ccl.getDisplay() : getDisplayForConcept(grp.getSource(), ccl.getCode());
            tr.td().style("border-left-width: 0px").tx(display == null ? "" : display);
            tr.td().colspan("4").style("background-color: #efefef").tx("(not mapped)");

          } else {
            for (int ti = 0; ti < ccl.getTarget().size(); ti++) {
              TargetElementComponent ccm = ccl.getTarget().get(ti);
              boolean last = ti == ccl.getTarget().size()-1;
              tr = tbl.tr();
              td = tr.td().style("border-right-width: 0px");
              if (!first && !last)
                td.style("border-top-style: none; border-bottom-style: none");
              else if (!first)
                td.style("border-top-style: none");
              else if (!last)
                td.style("border-bottom-style: none");
              if (first) {
                if (sources.get("code").size() == 1)
                  td.addText(ccl.getCode());
                else
                  td.addText(grp.getSource()+" / "+ccl.getCode());
                display = ccl.hasDisplay() ? ccl.getDisplay() : getDisplayForConcept(grp.getSource(), ccl.getCode());
                td = tr.td();
                if (!last)
                  td.style("border-left-width: 0px; border-bottom-style: none");
                else
                  td.style("border-left-width: 0px");
                td.tx(display == null ? "" : display);
              } else {
                td = tr.td(); // for display
                if (!last)
                  td.style("border-left-width: 0px; border-top-style: none; border-bottom-style: none");
                else
                  td.style("border-top-style: none; border-left-width: 0px");
              }
              for (String s : sources.keySet()) {
                if (s != null && !s.equals("code")) {
                  td = tr.td();
                  if (first) {
                    td.addText(getValue(ccm.getDependsOn(), s, sources.get(s).size() != 1));
                    display = getDisplay(ccm.getDependsOn(), s);
                    if (display != null)
                      td.tx(" ("+display+")");
                  }
                }
              }
              first = false;
              if (hasRelationships) {
                if (!ccm.hasRelationship())
                  tr.td();
                else {
                  if (ccm.hasExtension(ToolingExtensions.EXT_OLD_CONCEPTMAP_EQUIVALENCE)) {
                    String code = ToolingExtensions.readStringExtension(ccm, ToolingExtensions.EXT_OLD_CONCEPTMAP_EQUIVALENCE);
                    tr.td().ah(context.prefixLocalHref(eqpath+"#"+code), code).tx(presentEquivalenceCode(code));                
                  } else {
                    tr.td().ah(context.prefixLocalHref(eqpath+"#"+ccm.getRelationship().toCode()), ccm.getRelationship().toCode()).tx(presentRelationshipCode(ccm.getRelationship().toCode()));
                  }
                }
              }
              td = tr.td().style("border-right-width: 0px");
              if (targets.get("code").size() == 1)
                td.addText(ccm.getCode());
              else
                td.addText(grp.getTarget()+" / "+ccm.getCode());
              display = ccm.hasDisplay() ? ccm.getDisplay() : getDisplayForConcept(grp.getSource(), ccm.getCode());
              tr.td().style("border-left-width: 0px").tx(display == null ? "" : display);

              for (String s : targets.keySet()) {
                if (s != null && !s.equals("code")) {
                  td = tr.td();
                  td.addText(getValue(ccm.getProduct(), s, targets.get(s).size() != 1));
                  display = getDisplay(ccm.getProduct(), s);
                  if (display != null)
                    td.tx(" ("+display+")");
                }
              }
              if (comment)
                tr.td().addText(ccm.getComment());

              for (String s : props.keySet()) {
                if (s != null) {
                  td = tr.td();
                  td.addText(getValue(ccm.getProperty(), s));
                }
              }
            }
          }
          addUnmapped(tbl, grp);
        }
      }
    }
  }

  public void describe(XhtmlNode x, ConceptMap cm) {
    x.tx(display(cm));
  }

  public String display(ConceptMap cm) {
    return cm.present();
  }

  private boolean isSameCodeAndDisplay(String code, String display) {
    String c = code.replace(" ", "").replace("-", "").toLowerCase();
    String d = display.replace(" ", "").replace("-", "").toLowerCase();
    return c.equals(d);
  }


  private String presentRelationshipCode(String code) {
    if ("related-to".equals(code)) {
      return "is related to";
    } else if ("equivalent".equals(code)) {
      return "is equivalent to";
    } else if ("source-is-narrower-than-target".equals(code)) {
      return "is narrower than";
    } else if ("source-is-broader-than-target".equals(code)) {
      return "is broader than";
    } else if ("not-related-to".equals(code)) {
      return "is not related to";
    } else {
      return code;
    }
  }

  private String presentEquivalenceCode(String code) {
    if ("relatedto".equals(code)) {
      return "is related to";
    } else if ("equivalent".equals(code)) {
      return "is equivalent to";
    } else if ("equal".equals(code)) {
      return "is equal to";
    } else if ("wider".equals(code)) {
      return "maps to wider concept";
    } else if ("subsumes".equals(code)) {
      return "is subsumed by";
    } else if ("source-is-broader-than-target".equals(code)) {
      return "maps to narrower concept";
    } else if ("specializes".equals(code)) {
      return "has specialization";
    } else if ("inexact".equals(code)) {
      return "maps loosely to";
    } else if ("unmatched".equals(code)) {
      return "has no match";
    } else if ("disjoint".equals(code)) {
      return "is not related to";
    } else {
      return code;
    }
  }

  public void renderCSDetailsLink(XhtmlNode tr, String url, boolean span2) {
    CodeSystem cs;
    XhtmlNode td;
    cs = getContext().getWorker().fetchCodeSystem(url);
    td = tr.td();
    if (span2) {
      td.colspan("2");
    }
    td.b().tx(context.formatPhrase(RenderingContext.CONC_MAP_CODES));
    td.tx(" " + (context.formatPhrase(RenderingContext.CONC_MAP_FRM) + " "));
    if (cs == null)
      td.tx(url);
    else
      td.ah(context.prefixLocalHref(context.fixReference(cs.getWebPath()))).attribute("title", url).tx(cs.present());
  }

  private void addUnmapped(XhtmlNode tbl, ConceptMapGroupComponent grp) {
    if (grp.hasUnmapped()) {
//      throw new Error("not done yet");
    }
    
  }

  private String getDescForConcept(String s) {
    if (s.startsWith("http://hl7.org/fhir/v2/element/"))
        return "v2 "+s.substring("http://hl7.org/fhir/v2/element/".length());
    return s;
  }


  private String getValue(List list, String s) {
    return "todo";
  }

  private String getValue(List list, String s, boolean withSystem) {
    for (OtherElementComponent c : list) {
      if (s.equals(c.getAttribute()))
        if (withSystem)
          return /*c.getSystem()+" / "+*/c.getValue().primitiveValue();
        else
          return c.getValue().primitiveValue();
    }
    return null;
  }

  private String getDisplay(List list, String s) {
    for (OtherElementComponent c : list) {
      if (s.equals(c.getAttribute())) {
        // return getDisplayForConcept(systemFromCanonical(c.getSystem()), versionFromCanonical(c.getSystem()), c.getValue());
      }
    }
    return null;
  }

  public static XhtmlNode renderMultipleMaps(String start, List maps, IMultiMapRendererAdvisor advisor, Object rmmContext) {
    // 1+1 column for each provided map
    List rowSets = new ArrayList<>();
    for (int i = 0; i < maps.size(); i++) {
      populateRows(rowSets, maps.get(i), i, advisor, rmmContext);
    }
    collateRows(rowSets);
    if (advisor.sortPolicy(rmmContext) != RenderMultiRowSortPolicy.UNSORTED) {
      Collections.sort(rowSets, new MultipleMappingRowSorter(advisor.sortPolicy(rmmContext) == RenderMultiRowSortPolicy.FIRST_COL));
    }
    XhtmlNode div = new XhtmlNode(NodeType.Element, "div");
    XhtmlNode tbl = div.table("none", false).style("text-align: left; border-spacing: 0; padding: 5px");
    XhtmlNode tr = tbl.tr();
    styleCell(tr.td(), false, true, 5).b().tx(start);
    for (ConceptMap map : maps) {
      XhtmlNode td = styleCell(tr.td(), false, true, 5).colspan(2);
      if (!advisor.describeMap(rmmContext, map, td)) {
        if (map.hasWebPath() && advisor.makeMapLinks()) {
          td.b().ah(map.getWebPath(), map.getVersionedUrl()).tx(map.present());
        } else {
          td.b().tx(map.present());
        }
      }
    }
    if (advisor.hasCollateral(rmmContext)) {
      tr = tbl.tr();
      renderLinks(styleCell(tr.td(), false, true, 5), advisor.getCollateral(rmmContext, null));
      for (ConceptMap map : maps) {
        renderLinks(styleCell(tr.td(), false, true, 5).colspan(2), advisor.getCollateral(rmmContext, map.getUrl()));      
      }
    }
    for (MultipleMappingRow row : rowSets) {
      renderMultiRow(tbl, row, maps, advisor, rmmContext);
    }
    return div;
  }

  private static void renderLinks(XhtmlNode td, List collateral) {
    if (collateral.size() > 0) {
      td.tx( "Links:");
      td.tx(" ");
      boolean first = true;
      for (CollateralDefinition c : collateral) {
        if (first) first = false; else td.tx(", ");
        td.ah(c.getResource().getWebPath()).tx(c.getLabel());
      }
    }
  }

  private static void collateRows(List rowSets) {
    List toDelete = new ArrayList();
    for (MultipleMappingRow rowSet : rowSets) {
      MultipleMappingRow tgt = rowSet.stickySource;
      while (toDelete.contains(tgt)) {
        tgt = tgt.stickySource;
      }
      if (tgt != null && rowSets.contains(tgt)) {
        tgt.rowSets.addAll(rowSet.rowSets);
        toDelete.add(rowSet);
      }
    }
    rowSets.removeAll(toDelete);    
  }

  private static void renderMultiRow(XhtmlNode tbl, MultipleMappingRow rows, List maps, IMultiMapRendererAdvisor advisor, Object rmmContext) {
    int rowCounter = 0;
    for (MultipleMappingRowItem row : rows.rowSets) {
      XhtmlNode tr = tbl.tr();
      boolean first = true;
      int cellCounter = 0;
      Cell last = null;
      for (Cell cell : row.cells) {
        if (first) {     
          if (!cell.renderedCode) { 
            int c = 1;
            for (int i = rowCounter + 1; i < rows.rowSets.size(); i++) {
              if (cell.code != null && rows.rowSets.get(i).cells.size() > cellCounter && cell.code.equals(rows.rowSets.get(i).cells.get(cellCounter).code)) {
                rows.rowSets.get(i).cells.get(cellCounter).renderedCode = true;
                c++;
              } else {
                break;
              }
            }  
            if (cell.code == null) {
              styleCell(tr.td(), rowCounter == 0, true, 5).rowspan(c).style("background-color: #eeeeee");
            } else {
              String link = advisor.getLink(rmmContext, cell.system, cell.code);
              XhtmlNode x = null;
              if (link != null) {
                x = styleCell(tr.td(), rowCounter == 0, true, 5).attributeNN("title", cell.display).rowspan(c).ah(link);
              } else {
                x = styleCell(tr.td(), rowCounter == 0, true, 5).attributeNN("title", cell.display).rowspan(c);
              }
//              if (cell.clone != null) {
//                x.style("color: grey");
//              }
              x.tx(cell.present());
            }
          }
          first = false;
        } else {
          if (!cell.renderedRel) { 
            int c = 1;
            for (int i = rowCounter + 1; i < rows.rowSets.size(); i++) {
              if ((cell.relationship != null && rows.rowSets.get(i).cells.size() > cellCounter && cell.relationship.equals(rows.rowSets.get(i).cells.get(cellCounter).relationship)) && 
                  (cell.code != null && cell.code.equals(rows.rowSets.get(i).cells.get(cellCounter).code)) && 
                  (last.code != null && cell.code.equals(rows.rowSets.get(i).cells.get(cellCounter-1).code))) {
                rows.rowSets.get(i).cells.get(cellCounter).renderedRel = true;
                c++;
              } else {
                break;
              }
            }
            if (last.code == null || cell.code == null) {
              styleCell(tr.td(), rowCounter == 0, true, 5).style("background-color: #eeeeee");
            } else if (cell.relationship != null) {
              styleCell(tr.tdW(16), rowCounter == 0, true, 0).attributeNN("title", cell.relComment).rowspan(c).style("background-color: LightGrey; text-align: center; vertical-align: middle; color: white").tx(cell.relationship);
            } else {
              styleCell(tr.tdW(16), rowCounter == 0, false, 0).rowspan(c);
            }
          }
          if (!cell.renderedCode) { 
            int c = 1;
            for (int i = rowCounter + 1; i < rows.rowSets.size(); i++) {
              if (cell.code != null && rows.rowSets.get(i).cells.size() > cellCounter && cell.code.equals(rows.rowSets.get(i).cells.get(cellCounter).code)) {
                rows.rowSets.get(i).cells.get(cellCounter).renderedCode = true;
                c++;
              } else {
                break;
              }
            }
            if (cell.code == null) {
              styleCell(tr.td(), rowCounter == 0, true, 5).rowspan(c).style("background-color: #eeeeee");
            } else {
              String link = advisor.getLink(rmmContext, cell.system, cell.code);
              XhtmlNode x = null;
              if (link != null) {
                x = styleCell(tr.td(), rowCounter == 0, true, 5).attributeNN("title", cell.display).rowspan(c).ah(link);
              } else {
                x = styleCell(tr.td(), rowCounter == 0, true, 5).attributeNN("title", cell.display).rowspan(c);                
              }
//              if (cell.clone != null) {
//                x.style("color: grey");
//              }
              x.tx(cell.present());
            }
          }
        }
        last = cell;
        cellCounter++;
      }
      rowCounter++;
    }    
  }

  private static XhtmlNode styleCell(XhtmlNode td, boolean firstrow, boolean sides, int padding) {
    if (firstrow) {
      td.style("vertical-align: middle; border-top: 1px solid black; padding: "+padding+"px");
    } else {
      td.style("vertical-align: middle; border-top: 1px solid LightGrey; padding: "+padding+"px");
    }
    if (sides) {
      td.style("border-left: 1px solid LightGrey; border-right: 2px solid LightGrey");
    }
    return td;
  }

  private static void populateRows(List rowSets, ConceptMap map, int i, IMultiMapRendererAdvisor advisor, Object rmmContext) {
    // if we can resolve the value set, we create entries for it
    if (map.hasSourceScope()) {
      List codings = advisor.getMembers(rmmContext, map.getSourceScope().primitiveValue());
      if (codings != null) {
        for (Coding c : codings) {
          MultipleMappingRow row = i == 0 ? null : findExistingRowBySource(rowSets, c.getSystem(), c.getCode(), i);
          if (row == null) {
            row = new MultipleMappingRow(i, c.getSystem(), c.getCode(), c.getDisplay());
            rowSets.add(row);
          } 
          
        }
      }
    }  
    
    for (ConceptMapGroupComponent grp : map.getGroup()) {
      for (SourceElementComponent src : grp.getElement()) {
        MultipleMappingRow row = findExistingRowBySource(rowSets, grp.getSource(), src.getCode(), i);
        if (row == null) {
          row = new MultipleMappingRow(i, grp.getSource(), src.getCode(), src.getDisplay());
          rowSets.add(row);
        } 
        if (src.getNoMap()) {
          row.addTerminus();
        } else {
          List todo = new ArrayList<>();
          for (TargetElementComponent tgt : src.getTarget()) {
            MultipleMappingRow trow = findExistingRowByTarget(rowSets, grp.getTarget(), tgt.getCode(), i);
            if (trow == null) {
              row.addTarget(grp.getTarget(), tgt.getCode(), tgt.getRelationship(), tgt.getComment(), rowSets, i);
            } else {
              todo.add(tgt);
            }
          }
          // we've already got a mapping to these targets. So we gather them under the one mapping - but do this after the others are done
          for (TargetElementComponent t : todo) {
            MultipleMappingRow trow = findExistingRowByTarget(rowSets, grp.getTarget(), t.getCode(), i);     
            if (row.alreadyHasMappings(i)) {
              // src is already mapped, and so is target, and now we need to map src to target too
              // we have to clone src, but we only clone the last
              trow.cloneSource(i, row.getLastSource(i));
            } else {
              trow.addSource(row, rowSets, t.getRelationship(), t.getComment());
            }
          }
        }
      }
      boolean copy = grp.hasUnmapped() && grp.getUnmapped().getMode() == ConceptMapGroupUnmappedMode.USESOURCECODE;
      if (copy) {
        for (MultipleMappingRow row : rowSets) {
          if (row.rowSets.get(0).cells.size() == i && row.lastSystem().equals(grp.getSource())) {
            row.addCopy(grp.getTarget());
          }
        }
      }
    } 
    for (MultipleMappingRow row : rowSets) {
      if (row.rowSets.get(0).cells.size() == i) {
        row.addTerminus();
      }
    }
    if (map.hasTargetScope()) {
      List codings = advisor.getMembers(rmmContext, map.getTargetScope().primitiveValue());
      if (codings != null) {
        for (Coding c : codings) {
          MultipleMappingRow row = findExistingRowByTarget(rowSets, c.getSystem(), c.getCode(), i);
          if (row == null) {
            row = new MultipleMappingRow(i+1, c.getSystem(), c.getCode(), c.getDisplay());
            rowSets.add(row);
          } else {
            for (MultipleMappingRowItem cells : row.rowSets) {
              Cell last = cells.cells.get(cells.cells.size() -1);
              if (last.system != null && last.system.equals(c.getSystem()) && last.code.equals(c.getCode()) && last.display == null) {
                last.display = c.getDisplay();
              }
            }
          }
        }
      }
    }

  }

  private static MultipleMappingRow findExistingRowByTarget(List rows, String system, String code, int i) {
    for (MultipleMappingRow row : rows) {
      for (MultipleMappingRowItem cells : row.rowSets) {
        if (cells.cells.size() > i + 1 && cells.cells.get(i+1).matches(system, code)) {
          return row;
        }
      }
    }
    return null;
  }

  private static MultipleMappingRow findExistingRowBySource(List rows, String system, String code, int i) {
    for (MultipleMappingRow row : rows) {
      for (MultipleMappingRowItem cells : row.rowSets) {
        if (cells.cells.size() > i && cells.cells.get(i).matches(system, code)) {
          return row;
        }
      }
    }
    return null;
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy