001package org.hl7.fhir.r5.renderers;
002
003import java.util.Date;
004
005import org.hl7.fhir.r5.comparison.VersionComparisonAnnotation;
006import org.hl7.fhir.r5.context.IWorkerContext;
007import org.hl7.fhir.r5.model.Base;
008import org.hl7.fhir.r5.model.DataType;
009import org.hl7.fhir.r5.model.Enumeration;
010import org.hl7.fhir.r5.model.Resource;
011import org.hl7.fhir.r5.renderers.utils.RenderingContext;
012import org.hl7.fhir.r5.renderers.utils.RenderingContext.GenerationRules;
013import org.hl7.fhir.r5.renderers.utils.RenderingContext.KnownLinkType;
014import org.hl7.fhir.r5.renderers.utils.RenderingContext.ResourceRendererMode;
015import org.hl7.fhir.r5.utils.UserDataNames;
016import org.hl7.fhir.r5.renderers.utils.ResourceWrapper;
017import org.hl7.fhir.utilities.*;
018import org.hl7.fhir.utilities.MarkDownProcessor.Dialect;
019import org.hl7.fhir.utilities.validation.ValidationOptions;
020import org.hl7.fhir.utilities.xhtml.NodeType;
021import org.hl7.fhir.utilities.xhtml.XhtmlNode;
022
023/**
024 * Rendering framework:
025 * 
026 *   * boolean render(DomainResource) : produce an HTML representation suitable for runtime / documentation, and insert it into the resource. Return true of any extensions encountered
027 *   * boolean render(XhtmlNode, Resource: produce an HTML representation, and fill out the provided node with it. Return true of any extensions encountered
028 *   * XhtmlNode build(DomainResource): same as render(DomainResource) but also return the XHtmlNode 
029 *   
030 *   * String display(Base) : produce a plan text concise representation that serves to describe the resource
031 *   * void display(XhtmlNode, Base) : produce a plan text concise representation that serves to describe the resource
032 *   
033 *   * 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)  
034 *   
035 * 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
036 *   
037 * @author graha
038 *
039 */
040@MarkedToMoveToAdjunctPackage
041public class Renderer  {
042  
043  public static class RenderingStatus {
044    private boolean extensions;
045
046    public void setExtensions(boolean b) {
047      extensions = b;
048    }
049
050    public boolean getExtensions() {
051      return extensions;
052    }
053
054    public boolean isShowCodeDetails() {
055      // TODO Auto-generated method stub
056      return false;
057    }
058
059  }
060  protected RenderingContext context;
061  
062  public Renderer(RenderingContext context) {
063    this.context = context;
064  }
065
066  public Renderer(IWorkerContext worker) {
067    this.context = new RenderingContext(worker, new MarkDownProcessor(Dialect.COMMON_MARK), ValidationOptions.defaults(), "http://hl7.org/fhir/R5", "", worker.getLocale(), ResourceRendererMode.END_USER, GenerationRules.IG_PUBLISHER);
068  }
069
070
071  protected String formatPhrase(String theMessage, Object... theMessageArguments) {
072    return context.formatPhrase(theMessage, theMessageArguments);
073  }
074
075  public void genStandardsStatus(XhtmlNode td, StandardsStatus ss) {
076    if (ss != null) {
077      td.tx(" ");
078      XhtmlNode a = td.ah(Utilities.pathURL(context.getLink(KnownLinkType.SPEC, true), "versions.html#std-process"), (context.formatPhrase(RenderingContext.REND_STANDARDS, ss.toDisplay())));
079      a.style("padding-left: 3px; padding-right: 3px; border: 1px grey solid; font-weight: bold; color: black; background-color: "+ss.getColor());
080      a.tx(ss.getAbbrev());
081    }
082  }
083
084  protected XhtmlNode renderStatus(Base b, XhtmlNode x) {
085    if (b == null || context.getChangeVersion() == null) {
086      return x;
087    }
088    VersionComparisonAnnotation vca = (VersionComparisonAnnotation) b.getUserData(UserDataNames.COMP_VERSION_ANNOTATION);
089    if (vca == null) {
090      return x;
091    }
092    switch (vca.getType()) {
093    case Added:
094      XhtmlNode spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null);
095      XhtmlNode spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", (context.formatPhrase(RenderingContext.REND_SINCE_ADDED, context.getChangeVersion())));
096      spanInner.img("icon-change-add.png", "icon");
097      spanInner.tx(" "+context.formatPhrase(RenderingContext.REND_ADDED));
098      return spanOuter;
099    case Changed:
100      spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null);
101      String s = context.formatPhrase(RenderingContext.REND_SINCE_CHANGED, context.getChangeVersion());
102      spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", vca.getOriginal() == null ? s : context.formatPhrase(RenderingContext.REND_SINCE_CHANGED_WAS, context.getChangeVersion(), vca.getOriginal()));
103      spanInner.img("icon-change-edit.png", "icon");
104      spanInner.tx(" "+context.formatPhrase(RenderingContext.REND_CHANGED));
105      return spanOuter;
106    case Deleted:
107      spanOuter = x.span("border: solid 1px #dddddd; margin: 2px; padding: 2px", null);
108      spanInner = spanOuter.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", (context.formatPhrase(RenderingContext.GENERAL_REMOVED_SINCE, context.getChangeVersion())));
109      spanInner.img("icon-change-remove.png", "icon");
110      spanInner.tx(" "+context.formatPhrase(RenderingContext.REND_REMOVED));
111      return spanOuter.strikethrough();
112    default:
113      return x;
114    }
115  }
116
117  protected XhtmlNode renderStatusDiv(Base b, XhtmlNode x) {
118    if (b == null || context.getChangeVersion() == null) {
119      return x;
120    }
121    VersionComparisonAnnotation vca = (VersionComparisonAnnotation) b.getUserData(UserDataNames.COMP_VERSION_ANNOTATION);
122    if (vca == null) {
123      return x;
124    }
125    switch (vca.getType()) {
126    case Added:
127      XhtmlNode divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px");
128      XhtmlNode spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", (context.formatPhrase(RenderingContext.REND_SINCE_ADDED, context.getChangeVersion())));
129      spanInner.img("icon-change-add.png", "icon");
130      spanInner.tx(" "+context.formatPhrase(RenderingContext.REND_ADDED));
131      return divOuter;
132    case Changed:
133      divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px");
134      String s = context.formatPhrase(RenderingContext.REND_SINCE_CHANGED, context.getChangeVersion());
135      spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", vca.getOriginal() == null ? s : context.formatPhrase(RenderingContext.REND_SINCE_CHANGED_WAS, context.getChangeVersion(),  vca.getOriginal()));
136      spanInner.img("icon-change-edit.png", "icon");
137      spanInner.tx(" "+context.formatPhrase(RenderingContext.REND_CHANGED));
138      return divOuter;
139    case Deleted:
140      divOuter = x.div("border: solid 1px #dddddd; margin: 2px; padding: 2px");
141      spanInner = divOuter.para().style("margin: 0").span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", (context.formatPhrase(RenderingContext.GENERAL_REMOVED_SINCE, context.getChangeVersion())));
142      spanInner.img("icon-change-remove.png", "icon");
143      spanInner.tx(" "+context.formatPhrase(RenderingContext.REND_REMOVED));
144      return divOuter.strikethrough();
145    default:
146      return x;
147    }
148  }
149  
150
151  protected XhtmlNode renderStatusRow(Base b, XhtmlNode tbl, XhtmlNode tr) {
152    if (b == null || context.getChangeVersion() == null) {
153      return tr.td();
154    }
155    VersionComparisonAnnotation vca = (VersionComparisonAnnotation) b.getUserData(UserDataNames.COMP_VERSION_ANNOTATION);
156    if (vca == null) {
157      return tr.td();
158    }
159    switch (vca.getType()) {
160    case Added:
161      if (tbl.isClass("grid")) {
162        tr.style("border: solid 1px #dddddd; margin: 2px; padding: 2px");
163      }
164      XhtmlNode td = tr.td();
165      XhtmlNode span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", (context.formatPhrase(RenderingContext.REND_ROW_SINCE, context.getChangeVersion())));
166      span.img("icon-change-add.png", "icon");
167      span.tx(" "+ context.formatPhrase(RenderingContext.REND_ADDED));
168      XhtmlNode x = new XhtmlNode(NodeType.Element, "holder");
169      x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", context.formatPhrase(RenderingContext.REND_ROW_SINCE, context.getChangeVersion())).tx(" ");
170      tr.styleCells(x);
171      return td;
172    case Changed:
173      td = tr.td();
174      span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", context.formatPhrase(RenderingContext.REND_ROW_CHANGED_SINCE_WAS, context.getChangeVersion(), vca.getOriginal()));
175      span.img("icon-change-edit.png", "icon");
176      span.tx(" "+ context.formatPhrase(RenderingContext.REND_CHANGED));
177      return td;
178    case Deleted:
179      tr.style("text-decoration: line-through");
180      td = tr.td();
181      span = td.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", context.formatPhrase(RenderingContext.GENERAL_REMOVED_SINCE, context.getChangeVersion()));
182      span.img("icon-change-remove.png", "icon");
183      span.tx(" "+ context.formatPhrase(RenderingContext.REND_REMOVED));
184      x = new XhtmlNode(NodeType.Element, "holder");
185      x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px; text-decoration: none", context.formatPhrase(RenderingContext.REND_ROW_SINCE, context.getChangeVersion())).tx(" ");
186      tr.styleCells(x);
187      return td;
188    default:
189      return tr.td();
190    }
191  }
192
193  /**
194   * return true if there's any actual changes
195   *
196   * @param context
197   * @param base
198   * @param x
199   * @param version
200   * @param metadataFields
201   * @return
202   */
203  public static boolean renderStatusSummary(RenderingContext context, Base base, XhtmlNode x, String version, String... metadataFields) {
204    if (base.hasUserData(UserDataNames.COMP_VERSION_ANNOTATION)) {
205      VersionComparisonAnnotation self = (VersionComparisonAnnotation) base.getUserData(UserDataNames.COMP_VERSION_ANNOTATION);
206      switch (self.getType()) {
207      case Added:
208        XhtmlNode spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", context.formatPhrase(RenderingContext.REND_SINCE_ADDED, version));
209        spanInner.img("icon-change-add.png", "icon");
210        spanInner.tx(" "+context.formatPhrase(RenderingContext.REND_ADDED));
211        return true;
212      case Changed:
213        if (self.getComp().noChangeOtherThanMetadata(metadataFields)) {
214          x.span("color: #eeeeee").tx("n/c");
215          return false;
216        } else {
217          spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px",
218              self.getOriginal() != null ? context.formatPhrase(RenderingContext.REND_SINCE_CHANGED_WAS, version, self.getOriginal()) : context.formatPhrase(RenderingContext.REND_SINCE_CHANGED, version));
219          spanInner.img("icon-change-edit.png", "icon");
220          spanInner.tx(" "+context.formatPhrase(RenderingContext.REND_CHANGED));
221        }
222        return true;
223      case Deleted:
224        spanInner = x.span("background-color: #fff2ff; border-left: solid 3px #ffa0ff; margin: 2px; padding: 2px", context.formatPhrase(RenderingContext.GENERAL_REMOVED_SINCE, version));
225        spanInner.img("icon-change-remove.png", "icon");
226        spanInner.tx(" "+context.formatPhrase(RenderingContext.REND_REMOVED));
227        return true;
228      default:
229        x.span("color: #eeeeee").tx("n/c");
230        return false;
231      }
232    } else {
233      x.span("color: #eeeeee").tx("--");
234    }
235    return false;
236  }
237
238
239  public String egt(@SuppressWarnings("rawtypes") Enumeration<? extends Enum> value) {
240    if (value == null || !value.hasPrimitiveValue()) {
241      return null;
242    } else {
243      return (value == null || !value.hasPrimitiveValue()) ? null : value.asStringValue();
244    }
245  }
246
247  public String toStr(int value) {
248    return Integer.toString(value);
249  }
250  
251  public String toStr(Date value) {
252    return value.toString();
253  }
254  
255  protected ResourceWrapper wrapNC(DataType type) {
256    return ResourceWrapper.forType(context.getContextUtilities(), type);
257  }
258  
259  protected ResourceWrapper wrap(Resource resource) {
260    return ResourceWrapper.forResource(context.getContextUtilities(), resource);
261  }
262  protected ResourceWrapper wrapWC(ResourceWrapper resource, DataType type) {
263    return ResourceWrapper.forType(context.getContextUtilities(), resource, type);
264  }
265  
266  protected String getTranslatedCode(ResourceWrapper child) {   
267    return context.getTranslatedCode(child.primitiveValue(), child.getCodeSystemUri());
268  }
269
270
271  public XhtmlNode xlinkNarrative(XhtmlNode x, ResourceWrapper type) {
272    if (context.isTrackNarrativeSource() && !x.hasUserData("narrative.linked") && type != null) {
273      if (type.hasId()) {
274        x.id(type.getId());
275      } else {
276        String id = context.nextXNKey();
277        x.id(id);
278        type.setId(id);
279      }
280      x.clss("generated");
281      x.setUserData("narrative.linked", true);
282    }
283    return x;
284  }
285
286  public XhtmlNode markBoilerplate(XhtmlNode x) {
287    if (context.isTrackNarrativeSource()) {
288      x.clss("boilerplate");
289    }
290    return x;
291  }
292
293  public XhtmlNode markGenerated(XhtmlNode x) {
294    if (context.isTrackNarrativeSource()) {
295      x.clss("generated");
296    }
297    return x;
298  }
299
300  public XhtmlNode spanIfTracking(XhtmlNode x, ResourceWrapper v) {
301    if (context.isTrackNarrativeSource()) {
302      XhtmlNode span = x.span();
303      xlinkNarrative(span, v);
304      return span;
305    } else {
306      return x;
307    }
308  }
309
310
311  
312}