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

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

package org.hl7.fhir.r5.renderers;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
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.Base;
import org.hl7.fhir.r5.model.CanonicalResource;
import org.hl7.fhir.r5.model.CanonicalType;
import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r5.model.Constants;
import org.hl7.fhir.r5.model.ContactDetail;
import org.hl7.fhir.r5.model.ContactPoint;
import org.hl7.fhir.r5.model.ContactPoint.ContactPointSystem;
import org.hl7.fhir.r5.model.DateTimeType;
import org.hl7.fhir.r5.model.Enumerations.PublicationStatus;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.Narrative.NarrativeStatus;
import org.hl7.fhir.r5.model.Reference;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.UriType;
import org.hl7.fhir.r5.renderers.utils.RenderingContext;
import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceReferenceKind;
import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceWithReference;
import org.hl7.fhir.r5.renderers.utils.ResourceWrapper;
import org.hl7.fhir.r5.renderers.utils.ResourceWrapper.ElementKind;
import org.hl7.fhir.r5.terminologies.CodeSystemUtilities;
import org.hl7.fhir.r5.utils.EOperationOutcome;
import org.hl7.fhir.r5.utils.ToolingExtensions;
import org.hl7.fhir.r5.utils.XVerExtensionManager;
import org.hl7.fhir.utilities.MarkedToMoveToAdjunctPackage;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;

@MarkedToMoveToAdjunctPackage
public abstract class ResourceRenderer extends DataRenderer {

  public enum RendererType {
    NATIVE, PROFILE, LIQUID

  }

  protected XVerExtensionManager xverManager;
  protected boolean multiLangMode;
  protected boolean inner;
  
  
  public ResourceRenderer(RenderingContext context) {
    super(context);
  }
  
  public boolean isMultiLangMode() {
    return multiLangMode;
  }

  public ResourceRenderer setMultiLangMode(boolean multiLangMode) {
    this.multiLangMode = multiLangMode;
    return this;
  }

  public boolean renderingUsesValidation() {
    return false;
  }
  
  public boolean isInner() {
    return inner;
  }

  public ResourceRenderer setInner(boolean inner) {
    this.inner = inner;
    return this;
  }

  /**
   * Just build the narrative that would go in the resource (per @renderResource()), but don't put it in the resource
   * @param dr
   * @return
   * @throws FHIRFormatError
   * @throws DefinitionException
   * @throws FHIRException
   * @throws IOException
   * @throws EOperationOutcome
   */
  public XhtmlNode buildNarrative(ResourceWrapper dr) throws FHIRFormatError, DefinitionException, FHIRException, IOException, EOperationOutcome {
    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
    buildNarrative(new RenderingStatus(), x, dr);
    return x;
  }
  
  /**
   * given a resource, update it's narrative with the best rendering available. 
   * 
   * ResourceWrapper is a facade to either a org.hl7.fhir.r5.model Resource, or
   * to a org.hl7.fhir.r5.elementModel (which might a resource of any version). 
   * 
   * Note that some resource renderers - only canonical ones - only render native
   * resources, and not element model ones. These may be migrated in the future 
   * (only reason not to is the sheer size of the task, though performance might 
   * be a factor)
   *  
   * @param r - the domain resource in question
   * 
   * @throws IOException
   * @throws EOperationOutcome 
   * @throws FHIRException 
   */
  public void renderResource(ResourceWrapper r) throws IOException, FHIRException, EOperationOutcome {  
    XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
    RenderingStatus status = new RenderingStatus();
    buildNarrative(status, x, r);
    String an = r.fhirType()+"_"+r.getId();
    if (context.isAddName()) {
      if (!hasAnchorName(x, an)) {
        injectAnchorName(x, an);
      }
    }
    inject(r, x, status.getExtensions() ? NarrativeStatus.EXTENSIONS :  NarrativeStatus.GENERATED);
  }

  public XhtmlNode checkNarrative(ResourceWrapper r) throws IOException, FHIRException, EOperationOutcome { 
    XhtmlNode x = r.getNarrative();
    String an = r.fhirType()+"_"+r.getId();
    if (context.isAddName()) {
      if (!hasAnchorName(x, an)) {
        injectAnchorName(x, an);
      }
    }
    return x;
  }

  private void injectAnchorName(XhtmlNode x, String an) {
    XhtmlNode ip = x;
    while (ip.hasChildren() && "div".equals(ip.getChildNodes().get(0).getName())) {
      ip = ip.getChildNodes().get(0);
    }
    ip.addTag(0, "a").setAttribute("name", an).tx(" ");    
  }

  protected boolean hasAnchorName(XhtmlNode x, String an) {
    if ("a".equals(x.getName()) && an.equals(x.getAttribute("name"))) {
      return true;
    }
    if (x.hasChildren()) {
      for (XhtmlNode c : x.getChildNodes()) {
        if (hasAnchorName(c, an)) {
          return true;
        }
      }
    }
    return false;
  }

  // these three are what the descendants of this class override
  public abstract void buildNarrative(RenderingStatus status, XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome;
  public abstract String buildSummary(ResourceWrapper r) throws UnsupportedEncodingException, IOException;
    
  public void buildSummary(RenderingStatus status, XhtmlNode x, ResourceWrapper r) throws UnsupportedEncodingException, IOException {
    x.tx(buildSummary(r));
  }

  public String canonicalTitle(ResourceWrapper r) {
    if (r.has("title")) {
      return r.primitiveValue("title");
    }
    if (r.has("name")) {
      return r.primitiveValue("name");
    }
    if (r.has("id")) {
      return r.primitiveValue("id");
    }
    return "?title?";
  }
  
  public void describe(XhtmlNode x, ResourceWrapper r) throws UnsupportedEncodingException, IOException {
    x.tx(displayDataType(r));
  }
  
  public void inject(ResourceWrapper r, XhtmlNode x, NarrativeStatus status) throws IOException {
    r.setNarrative(x, status.toCode(), multiLangMode, context.getLocale(), context.isPretty());
  }

  public void markLanguage(XhtmlNode x) {
    x.setAttribute("lang", context.getLocale().toLanguageTag());
    x.setAttribute("xml:lang", context.getLocale().toLanguageTag());
    x.addTag(0, "hr");
    x.addTag(0, "p").b().tx(context.getLocale().getDisplayName());
    x.addTag(0, "hr");
  }
  
  @Override
  protected void renderCanonical(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException {
    renderCanonical(status, x, Resource.class, type);
  }

  public  void renderCanonical(RenderingStatus status, XhtmlNode x, Class class_, ResourceWrapper canonical) throws UnsupportedEncodingException, IOException {
    if (!renderPrimitiveWithNoValue(status, x, canonical)) {
      CanonicalResource target = (CanonicalResource) context.getWorker().fetchResource(class_, canonical.primitiveValue(), canonical.getResourceNative());
      if (target != null && target.hasWebPath()) {
        if (canonical.primitiveValue().contains("|")) {
          x.ah(context.prefixLocalHref(target.getWebPath())).tx(target.present()+ context.formatPhrase(RenderingContext.RES_REND_VER) +target.getVersion()+")");
        } else {
          x.ah(context.prefixLocalHref(target.getWebPath())).tx(target.present());
        }
        return;
      }
      // we can't resolve it as a canonical in the context. We'll try to do a local resolution instead
      ResourceWithReference rr = resolveReference(canonical);
      if (rr == null) {
        x.code(canonical.primitiveValue());
      } else if (rr.getResource() == null) {
        x.ah(context.prefixLocalHref(rr.getWebPath())).tx(canonical.primitiveValue());        
      } else {
        x.ah(context.prefixLocalHref(rr.getWebPath())).tx(RendererFactory.factory(rr.getResource(), context.forContained()).buildSummary(rr.getResource()));        
      }
    }
  }
  

  protected String displayCanonical(ResourceWrapper canonical) {
    if (canonical == null || !canonical.hasPrimitiveValue()) {
      return "";
    }
    String url = canonical.primitiveValue();
    Resource target = context.getWorker().fetchResource(Resource.class, url, canonical.getResourceNative());
    if (target == null || !(target instanceof CanonicalResource)) {
      return url;       
    } else {
      CanonicalResource cr = (CanonicalResource) target;
      return "->"+cr.present();
    }     
  }
  
  protected String displayReference(ResourceWrapper type) {
    if (type == null) {
      return "";
    }
    ResourceWrapper display = null;
    ResourceWrapper actual = null;
    ResourceWrapper id = null;
    if (type.fhirType().equals("CodeableReference")) {
      if (type.has("reference")) {
        type = type.child("reference");
      } else {
        return displayCodeableConcept(type.child("concept"));
      }
    }
    if (type.fhirType().equals("Reference")) {
      display = type.child("display");
      actual = type.child("reference");
      id = type.child("identifier");
    } else {
      actual = type;
    }
    if (actual != null && actual.hasPrimitiveValue()) {
      if ("#".equals(actual.primitiveValue())) {
        return "this resource";
      } else {
        ResourceWithReference rr = resolveReference(actual);
        if (rr == null) {
          String disp = display != null && display.hasPrimitiveValue() ? displayDataType(display) : actual.primitiveValue();
          return "->"+disp;
        } else {
          String disp;
          try {
            disp = display != null && display.hasPrimitiveValue() ? displayDataType(display) : RendererFactory.factory(rr.getResource(), context.forContained()).buildSummary(rr.getResource());
          } catch (IOException e) {
            disp = e.getMessage();
          }
          return "->"+disp;
        }
      }
    } else if (display != null) {
      return "->"+display;
    } else if (id != null) {
      return "id: "+displayIdentifier(id);
    } else {
      return "?ref?";
    }
  }

  
  /**
   * @param 
   * @param status
   * @param res
   * @param x
   * @param class_ - makes resolution faster, but can just be Resource.class
   * @param canonical
   * @throws UnsupportedEncodingException
   * @throws IOException
   */
  public  void renderCanonical(RenderingStatus status, ResourceWrapper res, XhtmlNode x, Class class_, CanonicalType canonical) throws UnsupportedEncodingException, IOException {
    if (canonical == null || !canonical.hasPrimitiveValue()) {
      return;
    }
    String url = canonical.asStringValue();
    Resource target = context.getWorker().fetchResource(Resource.class, url, res.getResourceNative());
    if (target == null || !(target instanceof CanonicalResource)) {
      x.code().tx(url);       
    } else {
      CanonicalResource cr = (CanonicalResource) target;
      if (!target.hasWebPath()) {
        if (url.contains("|")) {
          x.code().tx(cr.getUrl());
          x.tx(context.formatPhrase(RenderingContext.RES_REND_VER, cr.getVersion()));
          x.tx(" ("+cr.present()+")");
        } else {
          x.code().tx(url);
          x.tx(" ("+cr.present()+")");
        }
      } else {
        if (url.contains("|")) {
          x.ah(context.prefixLocalHref(target.getWebPath())).tx(cr.present()+ context.formatPhrase(RenderingContext.RES_REND_VER) +cr.getVersion()+")");
        } else {
          x.ah(context.prefixLocalHref(target.getWebPath())).tx(cr.present());
        }
      }
    }     
  }

  // todo: if (r.hasExtension(ToolingExtensions.EXT_TARGET_ID) || r.hasExtension(ToolingExtensions.EXT_TARGET_PATH)) {
  @Override
  public void renderReference(RenderingStatus status, XhtmlNode x, ResourceWrapper type) throws FHIRFormatError, DefinitionException, IOException {
    if (type == null) {
      return;
    }
    ResourceWrapper display = null;
    ResourceWrapper actual = null;
    ResourceWrapper id = null;
    if (type.fhirType().equals("CodeableReference")) {
      if (type.has("reference")) {
        type = type.child("reference");
      } else {
        renderCodeableConcept(status, x, type.child("concept"));
        return;
      }
    }
    if (type.fhirType().equals("Reference")) {
      display = type.child("display");
      actual = type.child("reference");
      id = type.child("identifier");
    } else {
      actual = type;
    }
    if (actual != null && actual.hasPrimitiveValue()) {
      ResourceWithReference rr = resolveReference(actual);
      if (rr == null) {
        String disp = display != null && display.hasPrimitiveValue() ? displayDataType(display) : actual.primitiveValue();
        if (Utilities.isAbsoluteUrlLinkable(actual.primitiveValue()) || (isLocalReference(actual.primitiveValue()) && !context.isUnknownLocalReferencesNotLinks())) {
          x.ah(context.prefixLocalHref(actual.primitiveValue())).tx(disp);
        } else {
          x.code().tx(disp);
        }
      } else if (rr.getResource() == null) {
        String disp = display != null && display.hasPrimitiveValue() ? displayDataType(display) : rr.getUrlReference();
        x.ah(context.prefixLocalHref(rr.getWebPath())).tx(disp);
      } else if (rr.getResource() != null) {
        String disp = display != null && display.hasPrimitiveValue() ? displayDataType(display) : RendererFactory.factory(rr.getResource(), context.forContained()).buildSummary(rr.getResource());
        x.ah(context.prefixLocalHref(rr.getWebPath())).tx(disp);
      } else {
        String disp = display != null && display.hasPrimitiveValue() ? displayDataType(display) : "?rref2?";
        x.ah(context.prefixLocalHref(rr.getWebPath())).tx(disp);
      }
    } else if (display != null && id != null) {
      renderDataType(status, x, display);
      x.tx(" (Identifier: ");
      renderIdentifier(status, x, id);
      x.tx(")");
    } else if (display != null) {
      renderDataType(status, x, display);
    } else if (id != null) {
      x.tx("Identifier: ");
      renderIdentifier(status, x, id);
    } else {
      x.tx("?rref?");
    }
    checkRenderExtensions(status, x, type);
  }
  
 
  private boolean isLocalReference(String url) {
    if (url == null) {
      return false;
    }
    if (url.contains("/_history")) {
      url = url.substring(0, url.indexOf("/_hist"));
    }
    
    if (url.matches(Constants.LOCAL_REF_REGEX)) {
      return true;
    } else {
      return false;
    }
  }

  public void renderReference(ResourceWrapper res, HierarchicalTableGenerator gen, List pieces, Reference r, boolean allowLinks) throws UnsupportedEncodingException, IOException {
    if (r == null) { 
      pieces.add(gen.new Piece(null, "null!", null));
      return;
    }
    ResourceWithReference tr = null;
    String link = null;
    StringBuilder text = new StringBuilder();
    if (r.hasReferenceElement() && allowLinks) {
      tr = resolveReference(res, r.getReference(), true);

      if (!r.getReference().startsWith("#")) {
        if (tr != null && tr.getWebPath() != null) {
          link = tr.getWebPath();
        } else if (r.getReference().contains("?")) {
          text.append(context.formatPhrase(RenderingContext.RES_REND_COND_REF)+" ");
        } else {
          link = r.getReference();
        }
      } 
    }
    if (tr != null && tr.getWebPath() != null && tr.getWebPath().startsWith("#")) {
      text.append(context.formatPhrase(RenderingContext.RES_REND_SEE_ON_THIS_PAGE)+" ");
    }
    // what to display: if text is provided, then that. if the reference was resolved, then show the name, or the generated narrative
    String display = r.hasDisplayElement() ? r.getDisplay() : null;
    String name = tr != null && tr.getResource() != null ? getNameForResource(tr.getResource()) : null; 
    
    if (display == null && (tr == null || tr.getResource() == null)) {
      if (!Utilities.noString(r.getReference())) {
        text.append(r.getReference());
      } else if (r.hasIdentifier()) {
        text.append(displayIdentifier(wrapWC(res, r.getIdentifier())));
      } else {
        text.append("?r-ref?");        
      }
    } else if (context.isTechnicalMode()) {
      text.append(r.getReference());
      if (display != null) {
        text.append(": "+display);
      }
      if ((tr == null || (tr.getWebPath() != null && !tr.getWebPath().startsWith("#"))) && name != null) {
        text.append(" \""+name+"\"");
      }
      if (r.hasExtension(ToolingExtensions.EXT_TARGET_ID) || r.hasExtension(ToolingExtensions.EXT_TARGET_PATH)) {
        text.append("(");
        for (Extension ex : r.getExtensionsByUrl(ToolingExtensions.EXT_TARGET_ID)) {
          if (ex.hasValue()) {
            text.append(", ");
            text.append("#"+ex.getValue().primitiveValue());
          }
        }
        for (Extension ex : r.getExtensionsByUrl(ToolingExtensions.EXT_TARGET_PATH)) {
          if (ex.hasValue()) {
            text.append(", ");
            text.append("/#"+ex.getValue().primitiveValue());
          }
        }
        text.append(")");
      }  
    } else {
      if (display != null) {
        text.append(display);
      } else if (name != null) {
        text.append(name);
      } else {
        text.append(context.formatPhrase(RenderingContext.RES_REND_DESC));
      }
    }
    if (tr != null && tr.getWebPath() != null && tr.getWebPath().startsWith("#")) {
      text.append(")");
    }      
    pieces.add(gen.new Piece(link,text.toString(), null));
  }
  
  public void renderReference(ResourceWrapper res, HierarchicalTableGenerator gen, List pieces, ResourceWrapper r, boolean allowLinks) throws UnsupportedEncodingException, IOException {
    if (r == null) { 
      pieces.add(gen.new Piece(null, "null!", null));
      return;
    }
    ResourceWithReference trt = null;
    String link = null;
    StringBuilder text = new StringBuilder();
    if (r.has("reference") && allowLinks) {
      trt = resolveReference(res, r.primitiveValue("reference"), true);

      if (!r.primitiveValue("reference").startsWith("#")) {
        if (trt != null && trt.getWebPath() != null) {
          link = trt.getWebPath();
        } else if (r.primitiveValue("reference").contains("?")) {
          text.append(context.formatPhrase(RenderingContext.RES_REND_COND_REF)+" ");
        } else {
          link = r.primitiveValue("reference");
        }
      } 
    }
    if (trt != null && trt.getWebPath() != null && trt.getWebPath().startsWith("#")) {
      text.append(context.formatPhrase(RenderingContext.RES_REND_SEE_ON_THIS_PAGE)+" ");
    }
    // what to display: if text is provided, then that. if the reference was resolved, then show the name, or the generated narrative
    String display = r.has("display") ? r.primitiveValue("display") : null;
    String name = trt != null && trt.getResource() != null ? getNameForResource(trt.getResource()) : null; 
    
    if (display == null && (trt == null || trt.getResource() == null)) {
      if (!Utilities.noString(r.primitiveValue("reference"))) {
        text.append(r.primitiveValue("reference"));
      } else if (r.has("identifier")) {
        text.append(displayIdentifier(r.child("identifier")));
      } else {
        text.append("?r-ref2?");        
      }
    } else if (context.isTechnicalMode()) {
      text.append(r.primitiveValue("reference"));
      if (display != null) {
        text.append(": "+display);
      }
      if ((trt == null || (trt.getWebPath() != null && !trt.getWebPath().startsWith("#"))) && name != null) {
        text.append(" \""+name+"\"");
      }
      if (r.hasExtension(ToolingExtensions.EXT_TARGET_ID) || r.hasExtension(ToolingExtensions.EXT_TARGET_PATH)) {
        text.append("(");
        for (ResourceWrapper ex : r.extensions(ToolingExtensions.EXT_TARGET_ID)) {
          if (ex.has("value")) {
            text.append(", ");
            text.append("#"+ex.primitiveValue("value"));
          }
        }
        for (ResourceWrapper ex : r.extensions(ToolingExtensions.EXT_TARGET_PATH)) {
          if (ex.has("value")) {
            text.append(", ");
            text.append("#"+ex.primitiveValue("value"));
          }
        }
        text.append(")");
      }  
    } else {
      if (display != null) {
        text.append(display);
      } else if (name != null) {
        text.append(name);
      } else {
        text.append(context.formatPhrase(RenderingContext.RES_REND_DESC));
      }
    }
    if (trt != null && trt.getWebPath() != null && trt.getWebPath().startsWith("#")) {
      text.append(")");
    }      
    pieces.add(gen.new Piece(link,text.toString(), null));
  }
  
  protected String getNameForResource(ResourceWrapper resource) {
    ResourceWrapper name = resource.firstChild("name");
    if (name != null && !name.isEmpty()) {
      if (name.isPrimitive()) {
        return name.primitiveValue();          
      } else if (name.fhirType().equals("HumanName")) {
        String family = name.primitiveValue("family");
        String given = name.firstPrimitiveValue("given");
        return (family == null) ? given : given == null ? family : family+" "+given;
      } else {
        String n = name.primitiveValueMN("name", "text", "value");
        if (n != null) {            
          return n;            
        }
      }
    }
    String n = resource.primitiveValue("productName");
    if (n == null) {            
      throw new Error("What to render for 'name'? Type is "+resource.fhirType());
    } else {
      return n;            
    }
  }

  protected void renderUri(RenderingStatus status, ResourceWrapper resource, XhtmlNode x, UriType uri) throws FHIRFormatError, DefinitionException, IOException { 
    if (!renderPrimitiveWithNoValue(status, x, uri)) {
      String v = uri.primitiveValue();

      if (v.startsWith("mailto:")) { 
        x.ah(context.prefixLocalHref(v)).addText(v.substring(7)); 
      } else { 
        ResourceWithReference rr = resolveReference(resource, uri.primitiveValue(), false);
        if (rr != null) {
          x.ah(rr.getWebPath()).addText(RendererFactory.factory(rr.getResource(), context.forContained()).buildSummary(rr.getResource()));           
        } else {
          Resource r = context.getContext().fetchResource(Resource.class, v); 
          if (r != null && r.getWebPath() != null) { 
              x.ah(context.prefixLocalHref(r.getWebPath())).addText(RendererFactory.factory(r, context.forContained()).buildSummary(wrap(r)));           
          } else { 
            String url = context.getResolver() != null ? context.getResolver().resolveUri(context, v) : null; 
            if (url != null) {           
              x.ah(context.prefixLocalHref(url)).addText(v); 
            } else if (Utilities.isAbsoluteUrlLinkable(v) && !uri.fhirType().equals("id")) { 
              x.ah(context.prefixLocalHref(v)).addText(v); 
            } else { 
              x.addText(v); 
            } 
          } 
        }
      } 
    }
  } 
  
  @Override
  protected void renderUri(RenderingStatus status, XhtmlNode x, ResourceWrapper uri) throws FHIRFormatError, DefinitionException, IOException { 
    if (!renderPrimitiveWithNoValue(status, x, uri)) {
      String v = uri.primitiveValue();
      boolean local = false;

      if (context.getContextUtilities().isResource(v)) {
        v = "http://hl7.org/fhir/StructureDefinition/"+v;
        local = true;
      }
      if (v.startsWith("mailto:")) { 
        x.ah(v).addText(v.substring(7)); 
      } else { 
        String link = getLinkForCode(v, null, null);
        if (link != null) {  
          x.ah(context.prefixLocalHref(link)).addText(v);
        } else {
        ResourceWithReference rr = local ? resolveReference(uri.resource(), v, true) : resolveReference(uri);
        if (rr != null) {
          if (rr.getResource() == null) {
            x.ah(context.prefixLocalHref(rr.getWebPath())).addText(rr.getUrlReference());
          } else {
            x.ah(context.prefixLocalHref(rr.getWebPath())).addText(RendererFactory.factory(rr.getResource(), context.forContained()).buildSummary(rr.getResource()));
          }
        } else {
          Resource r = context.getContext().fetchResource(Resource.class, v); 
          if (r != null && r.getWebPath() != null) { 
              x.ah(context.prefixLocalHref(r.getWebPath())).addText(RendererFactory.factory(r, context.forContained()).buildSummary(wrap(r)));           
          } else if (r != null) { 
            x.ah(context.prefixLocalHref(v)).addText(RendererFactory.factory(r, context.forContained()).buildSummary(wrap(r)));           
          } else { 
            String url = context.getResolver() != null ? context.getResolver().resolveUri(context, v) : null; 
            if (url != null) {           
              x.ah(context.prefixLocalHref(url)).addText(v); 
            } else if (Utilities.isAbsoluteUrlLinkable(v) && !uri.fhirType().equals("id")) { 
              x.ah(context.prefixLocalHref(v)).addText(v); 
            } else { 
              x.addText(v); 
            } 
          } 
        }
      } 
      }
    }
  } 

  /**
   * Eventually this will be retired if and when there's no more direct renderers 
   * 
   * @param 
   */
  protected  T findCanonical(Class class_, UriType canonical, ResourceWrapper sourceOfReference) {
    return context.getContext().fetchResource(class_, canonical.asStringValue(), sourceOfReference.getResourceNative());
  }

  protected  T findCanonical(Class class_, String canonical, ResourceWrapper sourceOfReference) {
    return context.getContext().fetchResource(class_, canonical, sourceOfReference.getResourceNative());
  }


  private ResourceWithReference resolveContained(ResourceWrapper resource, String url) {
    ResourceWrapper container = findContainer(resource);
    if (container == null) {
      return null;
    } else if ("#".equals(url)) {
      return new ResourceWithReference(ResourceReferenceKind.CONTAINER, url, "#hc"+container.getScopedId(), container);
    } else {
      String tid = url.substring(1);
      for (ResourceWrapper c : container.children("contained")) {
        if (tid.equals(c.getId())) {
          return new ResourceWithReference(ResourceReferenceKind.CONTAINED, url, "#hc"+c.getScopedId(), c);
        }
      }
    }
    return null;
  }
  
  private ResourceWrapper findContainer(ResourceWrapper resource) {
    ResourceWrapper container = resource;
    while (container != null) {
      if (container.isResource() && container.kind() != ElementKind.ContainedResource) {
        break;
      }
      container = container.parent();
    }
    return container;
  }

  private ResourceWithReference resolveOutside(ResourceWrapper resource, String url, String version, boolean followLinks) throws IOException {
    ResourceWrapper container = findContainer(resource);
    if (container != null) {
      while (container != null) {
        if (container.isResource() && container.fhirType().equals("Bundle")) {
          ResourceWithReference rr = findInBundle(resource, url);
          if (rr != null) {
            return null;
          }
        }
        container = container.parent();
      }
    }
    if (followLinks) {
      // ok, we didn't find it in the current instance, so we go look elsewhere 
      if (context.getResolver() != null) {
        ResourceWithReference rr = context.getResolver().resolve(context, url, version);
        if (rr != null) {
          return rr;
        }
      }
      Resource r = context.getWorker().fetchResource(Resource.class, url, version);
      if (r != null) {
        return new ResourceWithReference(ResourceReferenceKind.EXTERNAL, url, r.getWebPath(), wrap(r));
      }
    }
    return null;
  }
  
  private ResourceWithReference findInBundle(ResourceWrapper resource, String url) {
    if (url.equals("Bundle/"+resource.getId())) {
      return new ResourceWithReference(ResourceReferenceKind.BUNDLE, url, "#"+resource.getScopedId(), resource);
    }
    for (ResourceWrapper entry : resource.children("entry")) {
      if (entry.has("resource")) {
        ResourceWrapper res = entry.child("resource");  
        if (entry.has("fullUrl")) { 
          String fu = entry.primitiveValue("fullUrl");
          if (url.equals(fu)) {
           return new ResourceWithReference(ResourceReferenceKind.BUNDLE, url, "#"+res.getScopedId(), res);
          }
        }
        if ("Bundle".equals(res.fhirType())) {
          ResourceWithReference rr = findInBundle(res, url);
          if (rr != null) {
            return rr;
          }
        }
      }
    }
    return null;
  }

  protected ResourceWithReference resolveReference(ResourceWrapper resource, String url, boolean followLinks) throws IOException {
    if (url == null) {
      return null;
    }

    if (url.startsWith("#")) {
      return resolveContained(resource, url);
    } else {
      String version = null;
      if (url.contains("/_history/")) {
        version = url.substring(url.indexOf("/_history/")+10);
        url = url.substring(0, url.indexOf("/_history/"));
      }
      
      return resolveOutside(resource, url, version, followLinks);
    }

  }

  protected ResourceWithReference resolveReference(ResourceWrapper reference) {
    try {
      if (reference.fhirType().equals("CodeableReference")) {
        if (reference.has("reference")) {
          return resolveReference(reference.child("reference"));
        } else {
          return null;
        }
      } else if (reference.fhirType().equals("Reference")) {
        return resolveReference(reference.getResourceWrapper(), reference.primitiveValue("reference"), true);
      } else {
        return resolveReference(reference.getResourceWrapper(), reference.primitiveValue(), true);
      }
    } catch (IOException e) {
      if (context.isDebug()) {
        e.printStackTrace();        
      }
      return null;
    }
  }
  
  
  protected String makeIdFromBundleEntry(String url) {
    if (url == null) {
      return null;
    }
    if (url.startsWith("urn:uuid:")) {
      return url.substring(9).toLowerCase();
    }
    return fullUrlToAnchor(url);    
  }

  private String fullUrlToAnchor(String url) {
    return url.replace(":", "").replace("/", "_");
  }

  protected void generateCopyright(XhtmlNode x, ResourceWrapper cs) {
    XhtmlNode p = x.para();
    p.b().tx(getContext().formatPhrase(RenderingContext.RESOURCE_COPYRIGHT));
    smartAddText(p, " " + context.getTranslated(cs.child("copyright")));
  }
  
  protected void generateCopyrightTableRow(XhtmlNode tbl, ResourceWrapper cs) {
    XhtmlNode tr = tbl.tr();
    tr.td().b().tx(getContext().formatPhrase(RenderingContext.RESOURCE_COPYRIGHT));
    smartAddText(tr.td(), " " + context.getTranslated(cs.child("copyright")));
  }

  public String displayReference(Resource res, Reference r) throws UnsupportedEncodingException, IOException {
    return (context.formatPhrase(RenderingContext.GENERAL_TODO)); 
   }

   public Base parseType(String string, String type) {
     return null;
   }

   protected String describeStatus(PublicationStatus status, boolean experimental) {
     switch (status) {
     case ACTIVE: return experimental ? (context.formatPhrase(RenderingContext.GENERAL_EXPER)) : (context.formatPhrase(RenderingContext.RES_REND_ACT)); 
     case DRAFT: return (context.formatPhrase(RenderingContext.RES_REND_DRAFT));
     case RETIRED: return (context.formatPhrase(RenderingContext.RES_REND_RET));
     default: return (context.formatPhrase(RenderingContext.RES_REND_UNKNOWN));
     }
   }

   protected void renderCommitteeLink(XhtmlNode x, CanonicalResource cr) {
     String code = ToolingExtensions.readStringExtension(cr, ToolingExtensions.EXT_WORKGROUP);
     CodeSystem cs = context.getWorker().fetchCodeSystem("http://terminology.hl7.org/CodeSystem/hl7-work-group");
     if (cs == null || !cs.hasWebPath())
       x.tx(code);
     else {
       ConceptDefinitionComponent cd = CodeSystemUtilities.findCode(cs.getConcept(), code);
       if (cd == null) {
         x.tx(code);
       } else {
         x.ah(context.prefixLocalHref(cs.getWebPath()+"#"+cs.getId()+"-"+cd.getCode())).tx(cd.getDisplay());
       }
     }
   }

   public static String makeInternalBundleLink(ResourceWrapper bundle, String fullUrl) {
     // are we in a bundle in a bundle? Then the link is scoped
     boolean inBundle = false;
     ResourceWrapper rw = bundle.parent();
     while (rw != null) {
       if (rw.fhirType().equals("Bundle")) {
         inBundle = true;
       }
       rw = rw.parent();
      }
     if (inBundle) {
       return bundle.getScopedId()+"/"+fullUrl.replace(":", "-");
     } else {
       return fullUrl.replace(":", "-");
     }
   }

  public boolean canRender(Resource resource) {
    return true;
  }

  protected XhtmlNode renderResourceTechDetails(ResourceWrapper r, XhtmlNode x) throws UnsupportedEncodingException, FHIRException, IOException {
    return renderResourceTechDetails(r, x, (context.isContained() && r.getId() != null ? "#"+r.getId() : r.getId()));
  }
  
  protected XhtmlNode renderResourceTechDetails(ResourceWrapper r, XhtmlNode x, String desc) throws UnsupportedEncodingException, FHIRException, IOException {
    XhtmlNode p = x.para().attribute("class", "res-header-id");
    String ft = context.getTranslatedCode(r.fhirType(), "http://hl7.org/fhir/fhir-types");
    if (desc == null) { 
      p.b().tx(context.formatPhrase(context.isTechnicalMode() && !isInner() ? RenderingContext.PROF_DRIV_GEN_NARR_TECH : RenderingContext.PROF_DRIV_GEN_NARR, ft, ""));      
    } else {
      p.b().tx(context.formatPhrase(context.isTechnicalMode() && !isInner() ? RenderingContext.PROF_DRIV_GEN_NARR_TECH : RenderingContext.PROF_DRIV_GEN_NARR, ft, desc));
    }

    // first thing we do is lay down the resource anchors. 
    if (!Utilities.noString(r.getId())) {
      if (!context.isSecondaryLang()) {
        String sid = r.getScopedId();
        if (sid != null) {
          if (!context.hasAnchor(sid)) {
            context.addAnchor(sid);
            x.an(context.prefixAnchor(sid));
          }
          sid = "hc"+sid;
          if (!context.hasAnchor(sid)) {
            context.addAnchor(sid);
            x.an(context.prefixAnchor(sid));
          }
        }
      }
      if (context.getLocale() != null) {
        String langSuffix = "-"+context.getLocale().toLanguageTag();
        if (r.getScopedId() != null) {
          String sid = r.getScopedId()+langSuffix;
          if (!context.hasAnchor(sid)) {
            context.addAnchor(sid);
            x.an(context.prefixAnchor(sid));
          }
        }
      }
    }

    if (context.isTechnicalMode()) {
      RenderingStatus status = new RenderingStatus();

      String lang = r.primitiveValue("language"); 
      String ir = r.primitiveValue("implicitRules"); 
      ResourceWrapper meta = r.has("meta") && !r.child("meta").isEmpty() ? r.child("meta") : null;
      ResourceWrapper versionId = meta == null ? null : meta.child("versionId");
      ResourceWrapper lastUpdated = meta == null ? null : meta.child("lastUpdated");
      ResourceWrapper source = meta == null ? null : meta.child("source");

      if (lang != null || versionId != null || lastUpdated != null || ir != null || source != null || meta != null) {
        XhtmlNode div = x.div().style("display: inline-block").style("background-color: #d9e0e7").style("padding: 6px")
            .style("margin: 4px").style("border: 1px solid #8da1b4")
            .style("border-radius: 5px").style("line-height: 60%");

        boolean sfirst = true;
        p = plateStyle(div.para());
        if (versionId != null) {
          p.tx(context.formatPhrase(RenderingContext.RES_REND_VER, versionId.primitiveValue()));
          sfirst = false;
        }
        if (lastUpdated != null) {
          if (!sfirst) {
            p.tx("; ");
          }
          p.tx(context.formatPhrase(RenderingContext.RES_REND_UPDATED, displayDataType(lastUpdated)));
          sfirst = false;
        }
        if (lang != null) {
          if (!sfirst) {
            p.tx("; ");
          }
          p.tx(context.formatPhrase(RenderingContext.RES_REND_LANGUAGE, lang));
          sfirst = false;
        }
        if (source != null) {
          if (!sfirst) {
            p.tx("; ");
          }
          XhtmlNode pp = plateStyle(div.para());
          pp.startScript("source");
          renderDataType(status, pp.param("source"), source);
          pp.execScript(context.formatPhrase(RenderingContext.RES_REND_INFO_SOURCE));
          pp.closeScript();
          sfirst = false;
        }
        if (ir != null) {
          if (!sfirst) {
            p.tx("; ");
          }
          plateStyle(div.para()).b().tx(context.formatPhrase(RenderingContext.RES_REND_SPEC_RULES, ir));     
          sfirst = false;
        }
        if (meta != null) {
          List items = meta.children("profile");
          if (!items.isEmpty()) {
            p = plateStyle(div.para());
            p.tx(Utilities.pluralize(context.formatPhrase(RenderingContext.GENERAL_PROF), items.size())+": ");
            boolean first = true;
            for (ResourceWrapper bw : items) {
              if (first) first = false; else p.tx(", ");
              renderCanonical(status, p, StructureDefinition.class, bw);
            }
          }
          items = meta.children("tag");
          if (!items.isEmpty()) {
            p = plateStyle(div.para());
            p.tx(Utilities.pluralize(context.formatPhrase(RenderingContext.RES_REND_TAG), items.size())+": ");
            boolean first = true;
            for (ResourceWrapper bw : items) {
              if (first) first = false; else p.tx(", ");
              renderCoding(status, p, bw);
            }        
          }
          items = meta.children("security");
          if (!items.isEmpty()) {
            p = plateStyle(div.para());
            p.tx(Utilities.pluralize(context.formatPhrase(RenderingContext.GENERAL_SECURITY_LABEL), items.size())+": ");
            boolean first = true;
            for (ResourceWrapper bw : items) {
              if (first) first = false; else p.tx(", ");
              renderCoding(status, p, bw);
            }        
          }
        }
        return div;
      }
    }
    return null;
  }

  protected XhtmlNode plateStyle(XhtmlNode para) {
    return para.style("margin-bottom: 0px");
  }

//  public void renderOrError(DomainResource dr) {
//    try {
//      render(dr);
//    } catch (Exception e) {
//      XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
//      x.para().tx(context.formatPhrase(RenderingContext.RES_REND_ERROR, e.getMessage())+" ");
//      dr.setText(null);
//      inject(dr, x, NarrativeStatus.GENERATED);   
//    }
//    
//  }
  
  public RendererType getRendererType() {
    return RendererType.NATIVE;
  }
  
  public class TableRowData {
    private Map> cols = new HashMap<>();
    private TableData data;
    
    public void value(String name, ResourceWrapper value) {
      if (!cols.containsKey(name)) {
        cols.put(name, new ArrayList<>());
      }
      if (!data.columns.contains(name)) {
        data.columns.add(name);
      }
      cols.get(name).add(value);
    }

    public boolean hasCol(String name) {
      return cols.containsKey(name);
    }

    public List get(String name) {
      return cols.get(name);
    }
    
  }
  public class TableData {
    private String title;
    private List columns = new ArrayList<>();
    private List rows = new ArrayList<>();
    public TableData(String title) {
      this.title = title;
    }
    public String getTitle() {
      return title;
    }
    public List getColumns() {
      return columns;
    }
    public List getRows() {
      return rows;
    }
    public void addColumn(String name) {
      columns.add(name);
    }
    public TableRowData addRow() {
      TableRowData res = new TableRowData();
      rows.add(res);
      res.data = this;
      return res;
    }
  }


  public void renderTable(RenderingStatus status, TableData provider, XhtmlNode x) throws FHIRFormatError, DefinitionException, IOException {
    List columns = new ArrayList<>();
    for (String name : provider.getColumns()) {
      boolean hasData = false;
      for (TableRowData row : provider.getRows()) {
        if (row.hasCol(name)) {
          hasData = true;
        }
      }
      if (hasData) {
        columns.add(name);
      }
    }
    if (columns.size() > 0) {
      XhtmlNode table = x.table("grid", false);
      
      if (provider.getTitle() != null) {
        table.tr().td().colspan(columns.size()).b().tx(provider.getTitle());
      }
      XhtmlNode tr = table.tr();
      for (String col : columns) {
        tr.th().b().tx(col);
      }
      for (TableRowData row : provider.getRows()) {
        tr = table.tr();
        for (String col : columns) {
          XhtmlNode td = tr.td();
          boolean first = true;
          List list = row.get(col);
          if (list != null) {
            for (ResourceWrapper value : list) {
              if (first) first = false; else td.tx(", ");
              renderDataType(status, td, value);
            }
          }
        }
      }      
    }
  }
  
  public void renderOrError(ResourceWrapper dr) throws IOException {
    try {
      renderResource(dr);
    } catch (Exception e) {
      XhtmlNode x = new XhtmlNode(NodeType.Element, "div");
      x.para().tx("Error rendering: " + e.getMessage());
      inject(dr, x, NarrativeStatus.GENERATED);
    }
  }
  

  public void genSummaryTable(RenderingStatus status, XhtmlNode x, ResourceWrapper cr) throws IOException {
    if (context.isShowSummaryTable() && cr != null) {
      XhtmlNode tbl = x.table("grid", false);
      genSummaryTableContent(status, tbl, cr);
    }
  }
  

  protected void genSummaryTableContent(RenderingStatus status, XhtmlNode tbl, ResourceWrapper cr) throws IOException {
    XhtmlNode tr;
    if (cr.has("url")) {
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_DEFINING_URL)+":");
      tr.td().code().tx(cr.primitiveValue("url"));
    } else if (cr.hasExtension("http://hl7.org/fhir/5.0/StructureDefinition/extension-NamingSystem.url")) {
      status.setExtensions(true);
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_DEFINING_URL)+":");
      tr.td().code().tx(cr.extensionString("http://hl7.org/fhir/5.0/StructureDefinition/extension-NamingSystem.url"));
    } else if (!context.isContained()) {                                          
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_DEFINING_URL));
      tr.td();      
    }
    if (cr.has("version")) {
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_VER)+":");
      renderDataType(status, tr.td(), cr.child("version"));
    } else if (cr.hasExtension("http://terminology.hl7.org/StructureDefinition/ext-namingsystem-version")) {
      status.setExtensions(true);
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_VER)+":");
      renderDataType(status, tr.td(), cr.extensionValue("http://hl7.org/fhir/5.0/StructureDefinition/extension-NamingSystem.version"));
    }

    String name = context.getTranslated(cr.child("name"));
    String title = context.getTranslated(cr.child("title"));
    
    if (name != null) {
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_NAME)+":");
      tr.td().tx(name);
    }
    
    if (title != null && !title.equalsIgnoreCase(name)) {
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_TITLE)+":");
      tr.td().tx(title);
    }

    if (cr.has("status") && !context.isContained()) {
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_STATUS)+":");
      tr.td().tx(describeStatus(status, cr));
    }

    if (cr.has("description")) {
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_DEFINITION)+":");
      tr.td().markdown(context.getTranslated(cr.child("description")), "description");
    }

    if (cr.has("publisher")) {
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.CANON_REND_PUBLISHER)+":");
      buildPublisherLinks( tr.td(), cr);
    }
    
    if (cr.hasExtension(ToolingExtensions.EXT_WORKGROUP)) {
      status.setExtensions(true);
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.CANON_REND_COMMITTEE)+":");
      renderCommitteeLink(tr.td(), cr);
    }

    if (cr.has("copyright")) {
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_COPYRIGHT)+":");
      tr.td().markdown(context.getTranslated(cr.child("copyright")), "copyright");
    }
    
    if (cr.hasExtension(ToolingExtensions.EXT_FMM_LEVEL)) {
      status.setExtensions(true);
      // Use hard-coded spec link to point to current spec because DSTU2 had maturity listed on a different page
      tr = tbl.tr();
      tr.td().ah("http://hl7.org/fhir/versions.html#maturity", "Maturity Level").attribute("class", "fmm").tx(context.formatPhrase(RenderingContext.CANON_REND_COMMITTEE)+":");
      renderDataType(status, tr.td(), cr.extensionValue(ToolingExtensions.EXT_FMM_LEVEL));
    }    
  }



  public void genSummaryTable(RenderingStatus status, XhtmlNode x, CanonicalResource cr) throws IOException {
    if (context.isShowSummaryTable() && cr != null) {
      XhtmlNode tbl = x.table("grid", false);
      genSummaryTableContent(status, tbl, cr);
    }
  }

  
  protected void genSummaryTableContent(RenderingStatus status, XhtmlNode tbl, CanonicalResource cr) throws IOException {
    XhtmlNode tr;
    if (cr.hasUrl()) {
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_DEFINING_URL)+":");
      tr.td().code().tx(cr.getUrl());
    } else if (cr.hasExtension("http://hl7.org/fhir/5.0/StructureDefinition/extension-NamingSystem.url")) {
      status.setExtensions(true);
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_DEFINING_URL));
      tr.td().code().tx(ToolingExtensions.readStringExtension(cr, "http://hl7.org/fhir/5.0/StructureDefinition/extension-NamingSystem.url")+":");
    } else if (!context.isContained()) {                                          
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_DEFINING_URL));
      tr.td();      
    }
    if (cr.hasVersion()) {
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_VER)+":");
      tr.td().tx(cr.getVersion());
    } else if (cr.hasExtension("http://terminology.hl7.org/StructureDefinition/ext-namingsystem-version")) {
      status.setExtensions(true);
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_VER)+":");
      tr.td().tx(ToolingExtensions.readStringExtension(cr, "http://hl7.org/fhir/5.0/StructureDefinition/extension-NamingSystem.version"));
    }

    String name = cr.hasName() ? context.getTranslated(cr.getNameElement()) : null;
    String title = cr.hasTitle() ? context.getTranslated(cr.getTitleElement()) : null;
    
    if (name != null) {
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_NAME)+":");
      tr.td().tx(name);
    }
    
    if (title != null && !title.equalsIgnoreCase(name)) {
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_TITLE)+":");
      tr.td().tx(title);
    }

    if (cr.hasStatus() && !context.isContained()) {
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_STATUS)+":");
      tr.td().tx(describeStatus(status, cr));
    }

    if (cr.hasDescription()) {
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_DEFINITION)+":");
      tr.td().markdown(cr.getDescription(), "description");
    }

    if (cr.hasPublisher()) {
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.CANON_REND_PUBLISHER)+":");
      buildPublisherLinks(tr.td(), cr);
    }
    
    if (cr.hasExtension(ToolingExtensions.EXT_WORKGROUP)) {
      status.setExtensions(true);
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.CANON_REND_COMMITTEE)+":");
      renderCommitteeLink(tr.td(), cr);
    }

    if (cr.hasCopyright()) {
      tr = tbl.tr();
      tr.td().tx(context.formatPhrase(RenderingContext.GENERAL_COPYRIGHT)+":");
      tr.td().markdown(cr.getDescription(), "copyright");      
    }
    
    if (ToolingExtensions.hasExtension(cr, ToolingExtensions.EXT_FMM_LEVEL)) {
      status.setExtensions(true);
      // Use hard-coded spec link to point to current spec because DSTU2 had maturity listed on a different page
      tr = tbl.tr();
      tr.td().ah("http://hl7.org/fhir/versions.html#maturity", "Maturity Level").attribute("class", "fmm").tx(context.formatPhrase(RenderingContext.CANON_REND_COMMITTEE)+":");
      tr.td().tx(ToolingExtensions.readStringExtension(cr, ToolingExtensions.EXT_FMM_LEVEL));
    }    
  }


  protected void renderCommitteeLink(XhtmlNode x, ResourceWrapper cr) {
    String code = cr.extensionString(ToolingExtensions.EXT_WORKGROUP);
    CodeSystem cs = context.getContext().fetchCodeSystem("http://terminology.hl7.org/CodeSystem/hl7-work-group");
    if (cs == null || !cs.hasWebPath())
      x.tx(code);
    else {
      ConceptDefinitionComponent cd = CodeSystemUtilities.findCode(cs.getConcept(), code);
      if (cd == null) {
        x.tx(code);        
      } else {
        x.ah(cs.getWebPath()+"#"+cs.getId()+"-"+cd.getCode()).tx(cd.getDisplay());
      }
    }
  }
  
  private void buildPublisherLinks(XhtmlNode x, CanonicalResource cr) {
    boolean useName = false;
    for (ContactDetail cd : cr.getContact()) {
      if (!cd.hasName()) {
        useName = true;
      }
    }
    boolean first = true;
    if (!useName) {
      x.tx(Utilities.escapeXml(cr.getPublisher())); 
      first = false;          
    }    
    for (ContactDetail cd : cr.getContact()) {
      String name = cd.hasName() ? cd.getName() : cr.getPublisher();
      if (cd.hasTelecom()) {
        if (first) first = false; else x.tx(". ");
        renderContact(x, name, cd.getTelecom());
      }
    }
  }

  private void buildPublisherLinks(XhtmlNode x, ResourceWrapper cr) {
    boolean useName = false;
    for (ResourceWrapper cd : cr.children("contact")) {
      if (!cd.has("name")) {
        useName = true;
      }
    }
    boolean first = true;
    if (!useName) {
      x.tx(Utilities.escapeXml(cr.primitiveValue("publisher")));
      first = false;          
    }    
    for (ResourceWrapper cd : cr.children("contact")) {
      String name = cd.has("name") ? cd.primitiveValue("name") : cr.primitiveValue("publisher");
      if (cd.has("telecom")) {
        if (first) first = false; else x.tx(". ");
        renderContactW(x, name, cd.children("telecom"));
      }
    }
  }
  

  private void renderContactW(XhtmlNode x, String name, List telecom) {
    List urls = new ArrayList<>();
    for (ResourceWrapper t : telecom) {
      if ("url".equals(t.primitiveValue()) && t.has("value")) {
        urls.add(t.primitiveValue("value"));
      }
    }
    if (urls.size() == 1) {
      x.ah(urls.get(0)).tx(name);
    } else { // if (urls.size() == 0) {
      x.tx(name);
    } 
    for (ResourceWrapper t : telecom) {
      String system = t.primitiveValue("system");
      String value = t.primitiveValue("value"); 
      if ("url".equals(system) && value != null && urls.size() != 1) {
        x.tx(", ");
        x.ah(t.primitiveValue("value")).tx("Link");
      }
      if ("email".equals(system) && value != null) {
        x.tx(", ");
        x.ah("mailto:"+t.primitiveValue("value")).tx("Email");
      }
      if ("phone".equals(system) && value != null) {
        x.tx(", ");
        x.tx(t.primitiveValue("value"));
      }
      if ("fax".equals(system) && value != null) {
        x.tx(", ");
        x.tx("Fax:"+t.primitiveValue("value"));
      }
    } 
  }

  private void renderContact(XhtmlNode x, String name, List telecom) {
    List urls = new ArrayList<>();
    for (ContactPoint t : telecom) {
      if (t.getSystem() == ContactPointSystem.URL && t.hasValue()) {
        urls.add(t.getValue());
      }
    }
    if (urls.size() == 1) {
      x.ah(urls.get(0)).tx(name);
    } else { // if (urls.size() == 0) {
      x.tx(name);
    } 
    for (ContactPoint t : telecom) {
      if (t.getSystem() == ContactPointSystem.URL && t.hasValue() && urls.size() != 1) {
        x.tx(", ");
        x.ah(t.getValue()).tx("Link");
      }
      if (t.getSystem() == ContactPointSystem.EMAIL && t.hasValue()) {
        x.tx(", ");
        x.ah("mailto:"+t.getValue()).tx("Email");
      }
      if (t.getSystem() == ContactPointSystem.PHONE && t.hasValue()) {
        x.tx(", ");
        x.tx(t.getValue());
      }
      if (t.getSystem() == ContactPointSystem.FAX && t.hasValue()) {
        x.tx(", ");
        x.tx("Fax:"+t.getValue());
      }
    } 
  }

  protected String describeStatus(RenderingStatus status, ResourceWrapper cr) {
    String s = describeStatus(cr.primitiveValue("status"), cr.primitiveValue("experimental"), cr.child("date"), cr.extensionString("http://hl7.org/fhir/StructureDefinition/valueset-deprecated"));
    if (cr.hasExtension(ToolingExtensions.EXT_STANDARDS_STATUS)) {
      status.setExtensions(true);
      s = s + presentStandardsStatus(cr.extensionString(ToolingExtensions.EXT_STANDARDS_STATUS));
    }
    return s;
  }

  protected String describeStatus(RenderingStatus status, CanonicalResource cr) {
    String s = describeStatus(cr.getStatus(), cr.hasExperimental() ? cr.getExperimental() : false, cr.hasDate() ? cr.getDateElement() : null, ToolingExtensions.readBooleanExtension(cr, "http://hl7.org/fhir/StructureDefinition/valueset-deprecated"));
    if (cr.hasExtension(ToolingExtensions.EXT_STANDARDS_STATUS)) {
      status.setExtensions(true);
      s = s + presentStandardsStatus(ToolingExtensions.readStringExtension(cr, ToolingExtensions.EXT_STANDARDS_STATUS));
    }
    return s;
  }

  private String presentStandardsStatus(String code) {
    String pfx = " (Standards Status: ";
    switch (code) {
    case "draft" : return pfx+"Draft)"; 
    case "normative" : return pfx+"Normative)"; 
    case "trial-use" : return pfx+"Trial Use)"; 
    case "informative" : return pfx+"Informative)"; 
    case "deprecated" : return pfx+"Deprecated)"; 
    case "external" : return pfx+"External)"; 
    }
    return "";
  }

  protected String describeStatus(PublicationStatus status, boolean experimental, DateTimeType dt, Boolean deprecated) {
    String sfx = dt != null ? " as of "+displayDataType(dt) : "";
    if (deprecated != null && deprecated) {
      if (status == PublicationStatus.RETIRED) {
        return "Deprecated + Retired"+sfx;
      } else {
        return "Deprecated"+sfx; 
      }
    } else {
      switch (status) {
      case ACTIVE: return (experimental ? "Experimental" : "Active")+sfx; 
      case DRAFT: return "Draft"+sfx;
      case RETIRED: return "Retired"+sfx;
      default: return "Unknown"+sfx;
      }
    }
  }

  protected String describeStatus(String status, String experimental, ResourceWrapper dt, String deprecated) {
    String sfx = dt != null ? " as of "+displayDataType(dt) : "";
    if ("true".equals(deprecated)) {
      if ("retired".equals(status)) {
        return "Deprecated + Retired"+sfx;
      } else {
        return "Deprecated"+sfx; 
      }
    } else {
      switch (status) {
      case "active": return ("true".equals(experimental) ? "Experimental" : "Active")+sfx; 
      case "draft": return "Draft"+sfx;
      case "retired": return "Retired"+sfx;
      default: return "Unknown"+sfx;
      }
    }
  }
  protected void addContained(RenderingStatus status, XhtmlNode x, List list) throws FHIRFormatError, DefinitionException, FHIRException, IOException, EOperationOutcome {
    for (ResourceWrapper c : list) {
      x.hr();
      String id = c.getScopedId();
      if (!context.hasAnchor(id)) {
        context.addAnchor(id);
        x.an(context.prefixAnchor(id));
      }
      RendererFactory.factory(c, context.forContained()).setInner(true).buildNarrative(status, x, c);
    }
  }

  protected void selfLink(XhtmlNode node, String link) {
    XhtmlNode a = node.addTag("a");
    if (node.hasAttribute("class"))
      node.setAttribute("class", node.getAttribute("class")+" self-link-parent");
    else
      node.setAttribute("class", "self-link-parent");
    a.setAttribute("href", "#"+link);
    a.setAttribute("title", "link to here");
    a.setAttribute("class", "self-link");
    XhtmlNode svg = a.addTag("svg");
    XhtmlNode path = svg.addTag("path");
    String pathData = "M1520 1216q0-40-28-68l-208-208q-28-28-68-28-42 0-72 32 3 3 19 18.5t21.5 21.5 15 19 13 25.5 3.5 27.5q0 40-28 68t-68 28q-15 0-27.5-3.5t-25.5-13-19-15-21.5-21.5-18.5-19q-33 31-33 73 0 40 28 68l206 207q27 27 68 27 40 0 68-26l147-146q28-28 28-67zm-703-705q0-40-28-68l-206-207q-28-28-68-28-39 0-68 27l-147 146q-28 28-28 67 0 40 28 68l208 208q27 27 68 27 42 0 72-31-3-3-19-18.5t-21.5-21.5-15-19-13-25.5-3.5-27.5q0-40 28-68t68-28q15 0 27.5 3.5t25.5 13 19 15 21.5 21.5 18.5 19q33-31 33-73zm895 705q0 120-85 203l-147 146q-83 83-203 83-121 0-204-85l-206-207q-83-83-83-203 0-123 88-209l-88-88q-86 88-208 88-120 0-204-84l-208-208q-84-84-84-204t85-203l147-146q83-83 203-83 121 0 204 85l206 207q83 83 83 203 0 123-88 209l88 88q86-88 208-88 120 0 204 84l208 208q84 84 84 204z";
    svg.attribute("height", "20").attribute("width", "20").attribute("viewBox", "0 0 1792 1792").attribute("class", "self-link");
    path.attribute("d", pathData).attribute("fill", "navy");
  }
};




© 2015 - 2025 Weber Informatics LLC | Privacy Policy