001package org.hl7.fhir.r5.renderers;
002
003import org.hl7.fhir.r5.comparison.VersionComparisonAnnotation;
004import org.hl7.fhir.r5.context.IWorkerContext;
005import org.hl7.fhir.r5.model.Base;
006import org.hl7.fhir.r5.renderers.utils.RenderingContext;
007import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules;
008import org.hl7.fhir.r5.renderers.utils.RenderingContext.KnownLinkType;
009import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode;
010import org.hl7.fhir.r5.utils.TranslatingUtilities;
011import org.hl7.fhir.utilities.MarkDownProcessor;
012import org.hl7.fhir.utilities.StandardsStatus;
013import org.hl7.fhir.utilities.Utilities;
014import org.hl7.fhir.utilities.MarkDownProcessor.Dialect;
015import org.hl7.fhir.utilities.validation.ValidationOptions;
016import org.hl7.fhir.utilities.xhtml.NodeType;
017import org.hl7.fhir.utilities.xhtml.XhtmlNode;
018
019/**
020 * Rendering framework:
021 * 
022 *   * boolean render(DomainResource) : produce an HTML representation suitable for runtime / documentation, and insert it into the resource. Return true of any extensions encountered
023 *   * boolean render(XhtmlNode, Resource: produce an HTML representation, and fill out the provided node with it. Return true of any extensions encountered
024 *   * XhtmlNode build(DomainResource): same as render(DomainResource) but also return the XHtmlNode 
025 *   
026 *   * String display(Base) : produce a plan text concise representation that serves to describe the resource
027 *   * void display(XhtmlNode, Base) : produce a plan text concise representation that serves to describe the resource
028 *   
029 *   * void describe(XhtmlNode, Resource) : produce a short summary of the resource with key details presented (potentially more verbose than display, but still suitable for a single line)  
030 *   
031 * if not specific code for rendering a resource has been provided, and there's no liquid script to guide it, a generic rendering based onthe profile will be performed
032 *   
033 * @author graha
034 *
035 */
036public class Renderer extends TranslatingUtilities {
037
038  protected RenderingContext context;
039  
040  public Renderer(RenderingContext context) {
041    this.context = context;
042  }
043
044  public Renderer(IWorkerContext worker) {
045    this.context = new RenderingContext(worker, new MarkDownProcessor(Dialect.COMMON_MARK), ValidationOptions.defaults(), "http://hl7.org/fhir/R5", "", null, ResourceRendererMode.END_USER, GenerationRules.IG_PUBLISHER);
046  }
047
048
049  protected static final String RENDER_BUNDLE_HEADER_ROOT = "RENDER_BUNDLE_HEADER_ROOT";
050  protected static final String RENDER_BUNDLE_HEADER_ENTRY = "RENDER_BUNDLE_HEADER_ENTRY";
051  protected static final String RENDER_BUNDLE_HEADER_ENTRY_URL = "RENDER_BUNDLE_HEADER_ENTRY_URL";
052  protected static final String RENDER_BUNDLE_RESOURCE = "RENDER_BUNDLE_RESOURCE";
053  protected static final String RENDER_BUNDLE_SEARCH = "RENDER_BUNDLE_SEARCH";
054  protected static final String RENDER_BUNDLE_SEARCH_MODE = "RENDER_BUNDLE_SEARCH_MODE"; 
055  protected static final String RENDER_BUNDLE_SEARCH_SCORE = "RENDER_BUNDLE_SEARCH_SCORE";
056  protected static final String RENDER_BUNDLE_RESPONSE = "RENDER_BUNDLE_RESPONSE";
057  protected static final String RENDER_BUNDLE_LOCATION = "RENDER_BUNDLE_LOCATION";
058  protected static final String RENDER_BUNDLE_ETAG = "RENDER_BUNDLE_ETAG";
059  protected static final String RENDER_BUNDLE_LAST_MOD = "RENDER_BUNDLE_LAST_MOD";
060  protected static final String RENDER_BUNDLE_REQUEST = "RENDER_BUNDLE_REQUEST";
061  protected static final String RENDER_BUNDLE_IF_NON_MATCH = "RENDER_BUNDLE_IF_NON_MATCH";
062  protected static final String RENDER_BUNDLE_IF_MOD = "RENDER_BUNDLE_IF_MOD";
063  protected static final String RENDER_BUNDLE_IF_MATCH = "RENDER_BUNDLE_IF_MATCH";
064  protected static final String RENDER_BUNDLE_IF_NONE = "RENDER_BUNDLE_IF_NONE";
065  protected static final String RENDER_BUNDLE_DOCUMENT_CONTENT = "RENDER_BUNDLE_DOCUMENT_CONTENT";
066  protected static final String RENDER_BUNDLE_HEADER_DOC_ENTRY_URD = "RENDER_BUNDLE_HEADER_DOC_ENTRY_URD";
067  protected static final String RENDER_BUNDLE_HEADER_DOC_ENTRY_U = "RENDER_BUNDLE_HEADER_DOC_ENTRY_U";
068  protected static final String RENDER_BUNDLE_HEADER_DOC_ENTRY_RD = "RENDER_BUNDLE_HEADER_DOC_ENTRY_RD";
069
070  /** the plan here is to make this have it's own implementation of messages, rather than using the 
071   * validator messages, for better alignment with publisher I18n strategy
072   * 
073   * @param theMessage
074   * @param theMessageArguments
075   * @return
076   */
077  protected String formatMessage(String theMessage, Object... theMessageArguments) {
078    return context.getWorker().formatMessage(theMessage, theMessageArguments);
079  }
080
081  public void genStandardsStatus(XhtmlNode td, StandardsStatus ss) {
082    if (ss != null) {
083      td.tx(" ");
084      XhtmlNode a = td.ah(Utilities.pathURL(context.getLink(KnownLinkType.SPEC), "versions.html#std-process"), "Standards Status = "+ss.toDisplay());
085      a.style("padding-left: 3px; padding-right: 3px; border: 1px grey solid; font-weight: bold; color: black; background-color: "+ss.getColor());
086      a.tx(ss.getAbbrev());
087    }
088  }
089
090  protected XhtmlNode renderStatus(Base b, XhtmlNode x) {
091    if (b == null || context.getChangeVersion() == null) {
092      return x;
093    }
094    VersionComparisonAnnotation vca = (VersionComparisonAnnotation) b.getUserData(VersionComparisonAnnotation.USER_DATA_NAME);
095    if (vca == null) {
096      return x;
097    }
098    switch (vca.getType()) {
099    case Added:
100      XhtmlNode spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null);
101      XhtmlNode spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+context.getChangeVersion());
102      spanInner.img("icon-change-add.png", "icon");
103      spanInner.tx(" Added:");
104      return spanOuter;
105    case Changed:
106      spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null);
107      spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been changed since "+context.getChangeVersion()+(vca.getOriginal() != null ? " (was '"+vca.getOriginal()+"')" : ""));
108      spanInner.img("icon-change-edit.png", "icon");
109      spanInner.tx(" Changed:");
110      return spanOuter;
111    case Deleted:
112      spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null);
113      spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been removed since "+context.getChangeVersion());
114      spanInner.img("icon-change-remove.png", "icon");
115      spanInner.tx(" Removed:");
116      return spanOuter.strikethrough();
117    default:
118      return x;
119    }
120  }
121
122  protected XhtmlNode renderStatusDiv(Base b, XhtmlNode x) {
123    if (b == null || context.getChangeVersion() == null) {
124      return x;
125    }
126    VersionComparisonAnnotation vca = (VersionComparisonAnnotation) b.getUserData(VersionComparisonAnnotation.USER_DATA_NAME);
127    if (vca == null) {
128      return x;
129    }
130    switch (vca.getType()) {
131    case Added:
132      XhtmlNode divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px");
133      XhtmlNode spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+context.getChangeVersion());
134      spanInner.img("icon-change-add.png", "icon");
135      spanInner.tx(" Added:");
136      return divOuter;
137    case Changed:
138      divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px");
139      spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been changed since "+context.getChangeVersion()+(vca.getOriginal() != null ? " (was '"+(vca.getOriginal())+"')" : ""));
140      spanInner.img("icon-change-edit.png", "icon");
141      spanInner.tx(" Changed:");
142      return divOuter;
143    case Deleted:
144      divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px");
145      spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been removed since "+context.getChangeVersion());
146      spanInner.img("icon-change-remove.png", "icon");
147      spanInner.tx(" Removed:");
148      return divOuter.strikethrough();
149    default:
150      return x;
151    }
152  }
153  
154
155  protected XhtmlNode renderStatusRow(Base b, XhtmlNode tbl, XhtmlNode tr) {
156    if (b == null || context.getChangeVersion() == null) {
157      return tr.td();
158    }
159    VersionComparisonAnnotation vca = (VersionComparisonAnnotation) b.getUserData(VersionComparisonAnnotation.USER_DATA_NAME);
160    if (vca == null) {
161      return tr.td();
162    }
163    switch (vca.getType()) {
164    case Added:
165      if (tbl.isClass("grid")) {
166        tr.style("border: solid 1px #dddddd; margin: 2px; padding: 2px");
167      }
168      XhtmlNode td = tr.td();
169      XhtmlNode span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This row of content has been added since "+context.getChangeVersion());
170      span.img("icon-change-add.png", "icon");
171      span.tx(" Added:");
172      XhtmlNode x = new XhtmlNode(NodeType.Element, "holder");
173      x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This row of content has been added since "+context.getChangeVersion()).tx(" ");
174      tr.styleCells(x);
175      return td;
176    case Changed:
177      td = tr.td();
178      span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This row of content has been changed since"+context.getChangeVersion()+(vca.getOriginal() != null ? " (was '"+vca.getOriginal()+"')" : ""));
179      span.img("icon-change-edit.png", "icon");
180      span.tx(" Changed:");
181      return td;
182    case Deleted:
183      tr.style("text-decoration: line-through");
184      td = tr.td();
185      span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been removed since  "+context.getChangeVersion());
186      span.img("icon-change-remove.png", "icon");
187      span.tx(" Removed:");
188      x = new XhtmlNode(NodeType.Element, "holder");
189      x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px; text-decoration: none", "This row of content has been added since  "+context.getChangeVersion()).tx(" ");
190      tr.styleCells(x);
191      return td;
192    default:
193      return tr.td();
194    }
195  }
196
197  public static void renderStatusSummary(Base base, XhtmlNode x, String version, String... metadataFields) {
198    if (base.hasUserData(VersionComparisonAnnotation.USER_DATA_NAME)) {
199      VersionComparisonAnnotation self = (VersionComparisonAnnotation) base.getUserData(VersionComparisonAnnotation.USER_DATA_NAME);
200      switch (self.getType()) {
201      case Added:
202        XhtmlNode spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version);
203        spanInner.img("icon-change-add.png", "icon");
204        spanInner.tx(" Added");
205        return;
206      case Changed:
207        if (self.getComp().noChangeOtherThanMetadata(metadataFields)) {
208          x.span("color: #eeeeee").tx("n/c");
209          return;
210        } else {
211          spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been changed since "+version+(self.getOriginal() != null ? " (was '"+(self.getOriginal())+"')" : ""));
212          spanInner.img("icon-change-edit.png", "icon");
213          spanInner.tx(" Changed");
214        }
215        return;
216      case Deleted:
217        spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", "This content has been added since "+version);
218        spanInner.img("icon-change-remove.png", "icon");
219        spanInner.tx(" Removed");
220        return;
221      default:
222        x.span("color: #eeeeee").tx("n/c");
223        return;
224      }
225    } else {
226      x.span("color: #eeeeee").tx("--");
227    }
228  }
229
230}