001package org.hl7.fhir.r4b.renderers.utils;
002
003import java.io.IOException;
004import java.text.DateFormat;
005import java.time.ZoneId;
006import java.time.format.DateTimeFormatter;
007import java.time.format.FormatStyle;
008import java.util.ArrayList;
009import java.util.List;
010import java.util.Locale;
011import java.util.TimeZone;
012
013import org.hl7.fhir.exceptions.FHIRException;
014import org.hl7.fhir.exceptions.FHIRFormatError;
015import org.hl7.fhir.r4b.conformance.ProfileUtilities;
016import org.hl7.fhir.r4b.conformance.ProfileUtilities.ProfileKnowledgeProvider;
017import org.hl7.fhir.r4b.context.IWorkerContext;
018import org.hl7.fhir.r4b.model.Base;
019import org.hl7.fhir.r4b.model.DomainResource;
020import org.hl7.fhir.r4b.model.Enumerations.FHIRVersion;
021import org.hl7.fhir.r4b.model.FhirPublication;
022import org.hl7.fhir.r4b.renderers.utils.Resolver.IReferenceResolver;
023import org.hl7.fhir.r4b.renderers.utils.Resolver.ResourceContext;
024import org.hl7.fhir.r4b.utils.FHIRPathEngine.IEvaluationContext;
025import org.hl7.fhir.utilities.MarkDownProcessor;
026import org.hl7.fhir.utilities.MarkDownProcessor.Dialect;
027import org.hl7.fhir.utilities.Utilities;
028import org.hl7.fhir.utilities.validation.ValidationOptions;
029
030public class RenderingContext {
031
032  // provides liquid templates, if they are available for the content
033  public interface ILiquidTemplateProvider {
034    String findTemplate(RenderingContext rcontext, DomainResource r);
035    String findTemplate(RenderingContext rcontext, String resourceName);
036  }
037
038  // parses xml to an XML instance. Whatever codes provides this needs to provide something that parses the right version 
039  public interface ITypeParser {
040    Base parseType(String xml, String type) throws FHIRFormatError, IOException, FHIRException ;
041  }
042
043  /**
044   * What kind of user the renderer is targeting - end users, or technical users
045   * 
046   * This affects the way codes and references are rendered
047   * 
048   * @author graha
049   *
050   */
051  public enum ResourceRendererMode {
052    /**
053     * The user has no interest in the contents of the FHIR resource, and just wants to see the data
054     * 
055     */
056    END_USER,
057    
058    /**
059     * The user wants to see the resource, but a technical view so they can see what's going on with the content
060     */
061    TECHNICAL
062  }
063
064  public enum QuestionnaireRendererMode {
065    /**
066     * A visual presentation of the questionnaire, with a set of property panes that can be toggled on and off.
067     * Note that this is not the same as how the questionnaire would like on a form filler, since all dynamic behavior is ignored
068     */
069    FORM,
070
071    /**
072     * a structured tree that presents the content of the questionnaire in a logical fashion
073     */
074    TREE,   
075
076    /**
077     * A structured tree that presents the enableWhen, terminology and expression bindings for the questionnaire 
078     */
079    LOGIC,
080
081    /**
082     * A presentation that lists all the items, with full details about them 
083     */
084    DEFNS, 
085
086    /**
087     * Rendered links to various openly available Form Filler applications that know how to render a questionnaire published in a package 
088     */
089    LINKS
090  }
091
092  private IWorkerContext worker;
093  private MarkDownProcessor markdown;
094  private ResourceRendererMode mode;
095  private IReferenceResolver resolver;
096  private ILiquidTemplateProvider templateProvider;
097  private IEvaluationContext services;
098  private ITypeParser parser;
099
100  private String lang;
101  private String localPrefix; // relative link within local context
102  private String specificationLink;
103  private String selfLink; // absolute link to where the content is to be found (only used in a few circumstances when making external references to tools)
104  private int headerLevelContext;
105  private boolean canonicalUrlsAsLinks;
106  private boolean pretty;
107  private boolean header;
108
109  private ValidationOptions terminologyServiceOptions = new ValidationOptions();
110  private boolean noSlowLookup;
111  private String tooCostlyNoteEmpty;
112  private String tooCostlyNoteNotEmpty;
113  private String tooCostlyNoteEmptyDependent;
114  private String tooCostlyNoteNotEmptyDependent;
115  private List<String> codeSystemPropList = new ArrayList<>();
116
117  private ProfileUtilities profileUtilities;
118  private String definitionsTarget;
119  private String destDir;
120  private boolean inlineGraphics;
121
122  private QuestionnaireRendererMode questionnaireMode = QuestionnaireRendererMode.FORM;
123  private boolean addGeneratedNarrativeHeader = true;
124
125  private FhirPublication targetVersion;
126  private Locale locale;
127  private ZoneId timeZoneId;
128  private DateTimeFormatter dateTimeFormat;
129  private DateTimeFormatter dateFormat;
130  
131  /**
132   * 
133   * @param context - access to all related resources that might be needed
134   * @param markdown - appropriate markdown processing engine 
135   * @param terminologyServiceOptions - options to use when looking up codes
136   * @param specLink - path to FHIR specification
137   * @param lang - langauage to render in
138   */
139  public RenderingContext(IWorkerContext worker, MarkDownProcessor markdown, ValidationOptions terminologyServiceOptions, String specLink, String localPrefix, String lang, ResourceRendererMode mode) {
140    super();
141    this.worker = worker;
142    this.markdown = markdown;
143    this.lang = lang;
144    this.specificationLink = specLink;
145    this.localPrefix = localPrefix;
146    this.mode = mode;
147    if (terminologyServiceOptions != null) {
148      this.terminologyServiceOptions = terminologyServiceOptions;
149    }
150 // default to US locale - discussion here: https://github.com/hapifhir/org.hl7.fhir.core/issues/666
151    this.locale = new Locale.Builder().setLanguageTag("en-US").build(); 
152    profileUtilities = new ProfileUtilities(worker, null, null);
153  }
154
155  public IWorkerContext getContext() {
156    return worker;
157  }
158
159  // -- 2. Markdown support -------------------------------------------------------
160
161  public ProfileUtilities getProfileUtilities() {
162    return profileUtilities;
163  }
164
165  public IWorkerContext getWorker() {
166    return worker;
167  }
168
169  public boolean isCanonicalUrlsAsLinks() {
170    return canonicalUrlsAsLinks;
171  }
172
173  public RenderingContext setCanonicalUrlsAsLinks(boolean canonicalUrlsAsLinks) {
174    this.canonicalUrlsAsLinks = canonicalUrlsAsLinks;
175    return this;
176  }
177
178  public MarkDownProcessor getMarkdown() {
179    if (markdown == null) {
180      markdown = new MarkDownProcessor(Dialect.COMMON_MARK);
181    }
182    return markdown;
183  }
184
185  public String getLang() {
186    return lang;
187  }
188
189  public String getSpecificationLink() {
190    return specificationLink;
191  }
192
193  public String getLocalPrefix() {
194    return localPrefix;
195  }
196
197  public ValidationOptions getTerminologyServiceOptions() {
198    return terminologyServiceOptions;
199  }
200
201
202  public String getTooCostlyNoteEmpty() {
203    return tooCostlyNoteEmpty;
204  }
205
206  public RenderingContext setTooCostlyNoteEmpty(String tooCostlyNoteEmpty) {
207    this.tooCostlyNoteEmpty = tooCostlyNoteEmpty;
208    return this;
209  }
210
211  public String getTooCostlyNoteNotEmpty() {
212    return tooCostlyNoteNotEmpty;
213  }
214
215  public RenderingContext setTooCostlyNoteNotEmpty(String tooCostlyNoteNotEmpty) {
216    this.tooCostlyNoteNotEmpty = tooCostlyNoteNotEmpty;
217    return this;
218  }
219
220  public String getTooCostlyNoteEmptyDependent() {
221    return tooCostlyNoteEmptyDependent;
222  }
223
224  public RenderingContext setTooCostlyNoteEmptyDependent(String tooCostlyNoteEmptyDependent) {
225    this.tooCostlyNoteEmptyDependent = tooCostlyNoteEmptyDependent;
226    return this;
227  }
228
229  public String getTooCostlyNoteNotEmptyDependent() {
230    return tooCostlyNoteNotEmptyDependent;
231  }
232
233  public RenderingContext setTooCostlyNoteNotEmptyDependent(String tooCostlyNoteNotEmptyDependent) {
234    this.tooCostlyNoteNotEmptyDependent = tooCostlyNoteNotEmptyDependent;
235    return this;
236  }
237
238  public int getHeaderLevelContext() {
239    return headerLevelContext;
240  }
241
242  public RenderingContext setHeaderLevelContext(int headerLevelContext) {
243    this.headerLevelContext = headerLevelContext;
244    return this;
245  }
246
247  public IReferenceResolver getResolver() {
248    return resolver;
249  }
250
251  public RenderingContext setResolver(IReferenceResolver resolver) {
252    this.resolver = resolver;
253    return this;
254  }
255
256  public RenderingContext setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) {
257    this.terminologyServiceOptions = terminologyServiceOptions;
258    return this;
259  }
260
261  public boolean isNoSlowLookup() {
262    return noSlowLookup;
263  }
264
265  public RenderingContext setNoSlowLookup(boolean noSlowLookup) {
266    this.noSlowLookup = noSlowLookup;
267    return this;
268  }
269
270  public String getDefinitionsTarget() {
271    return definitionsTarget;
272  }
273
274  public RenderingContext setDefinitionsTarget(String definitionsTarget) {
275    this.definitionsTarget = definitionsTarget;
276    return this;
277  }
278
279  public String getDestDir() {
280    return destDir;
281  }
282
283  public RenderingContext setDestDir(String destDir) {
284    this.destDir = destDir;
285    return this;
286  }
287
288  public RenderingContext setProfileUtilities(ProfileUtilities profileUtilities) {
289    this.profileUtilities = profileUtilities;
290    return this;
291  }
292
293  public ILiquidTemplateProvider getTemplateProvider() {
294    return templateProvider;
295  }
296
297  public RenderingContext setTemplateProvider(ILiquidTemplateProvider templateProvider) {
298    this.templateProvider = templateProvider;
299    return this;
300  }
301
302  public IEvaluationContext getServices() {
303    return services;
304  }
305
306  public RenderingContext setServices(IEvaluationContext services) {
307    this.services = services;
308    return this;
309  }
310
311  public boolean isPretty() {
312    return pretty;
313  }
314
315  public RenderingContext setPretty(boolean pretty) {
316    this.pretty = pretty;
317    return this;
318  }
319
320  public ITypeParser getParser() {
321    return parser;
322  }
323
324  public RenderingContext setParser(ITypeParser parser) {
325    this.parser = parser;
326    return this;
327  }
328
329
330  public List<String> getCodeSystemPropList() {
331    return codeSystemPropList;
332  }
333
334  public RenderingContext setCodeSystemPropList(List<String> codeSystemPropList) {
335    this.codeSystemPropList = codeSystemPropList;
336    return this;
337  }
338
339  public RenderingContext copy() {
340    RenderingContext res = new RenderingContext(worker, markdown, terminologyServiceOptions, specificationLink, localPrefix, lang, mode);
341
342    res.resolver = resolver;
343    res.templateProvider = templateProvider;
344    res.services = services;
345    res.parser = parser;
346
347    res.headerLevelContext = headerLevelContext;
348    res.canonicalUrlsAsLinks = canonicalUrlsAsLinks;
349    res.pretty = pretty;
350
351    res.noSlowLookup = noSlowLookup;
352    res.tooCostlyNoteEmpty = tooCostlyNoteEmpty;
353    res.tooCostlyNoteNotEmpty = tooCostlyNoteNotEmpty;
354    res.tooCostlyNoteEmptyDependent = tooCostlyNoteEmptyDependent;
355    res.tooCostlyNoteNotEmptyDependent = tooCostlyNoteNotEmptyDependent;
356    res.codeSystemPropList.addAll(codeSystemPropList);
357
358    res.profileUtilities = profileUtilities;
359    res.definitionsTarget = definitionsTarget;
360    res.destDir = destDir;
361    res.addGeneratedNarrativeHeader = addGeneratedNarrativeHeader;
362    
363    return res;
364  }
365
366  public boolean isInlineGraphics() {
367    return inlineGraphics;
368  }
369
370  public RenderingContext setInlineGraphics(boolean inlineGraphics) {
371    this.inlineGraphics = inlineGraphics;
372    return this;
373  }
374
375  public boolean isHeader() {
376    return header;
377  }
378
379  public RenderingContext setHeader(boolean header) {
380    this.header = header;
381    return this;
382  }
383
384  public QuestionnaireRendererMode getQuestionnaireMode() {
385    return questionnaireMode;
386  }
387
388  public RenderingContext setQuestionnaireMode(QuestionnaireRendererMode questionnaireMode) {
389    this.questionnaireMode = questionnaireMode;
390    return this;
391  }
392
393  public String getSelfLink() {
394    return selfLink;
395  }
396
397  public RenderingContext setSelfLink(String selfLink) {
398    this.selfLink = selfLink;
399    return this;
400  }
401
402  public String fixReference(String ref) {
403    if (!Utilities.isAbsoluteUrl(ref)) {
404      return (localPrefix == null ? "" : localPrefix)+ref;
405    }
406    if (ref.startsWith("http://hl7.org/fhir") && !ref.substring(20).contains("/")) {
407      return specificationLink+ref.substring(20);
408    }
409    return ref;
410  }
411
412  public RenderingContext setLang(String lang) {
413    this.lang = lang;
414    return this;
415  }
416
417  public RenderingContext setLocalPrefix(String localPrefix) {
418    this.localPrefix = localPrefix;
419    return this;
420  }
421
422  public boolean isAddGeneratedNarrativeHeader() {
423    return addGeneratedNarrativeHeader;
424  }
425
426  public RenderingContext setAddGeneratedNarrativeHeader(boolean addGeneratedNarrativeHeader) {
427    this.addGeneratedNarrativeHeader = addGeneratedNarrativeHeader;
428    return this;
429   }
430
431  public FhirPublication getTargetVersion() {
432    return targetVersion;
433  }
434
435  public void setTargetVersion(FhirPublication targetVersion) {
436    this.targetVersion = targetVersion;
437  }
438
439  public boolean isTechnicalMode() {
440    return mode == ResourceRendererMode.TECHNICAL;
441  }
442
443  public boolean hasLocale() {
444    return locale != null;
445  }
446  
447  public Locale getLocale() {
448    if (locale == null) {
449      return Locale.getDefault();
450    } else { 
451      return locale;
452    }
453  }
454
455  public void setLocale(Locale locale) {
456    this.locale = locale;
457  }
458
459
460  /**
461   * if the timezone is null, the rendering will default to the source timezone
462   * in the resource
463   * 
464   * Note that if you're working server side, the FHIR project recommends the use
465   * of the Date header so that clients know what timezone the server defaults to,
466   * 
467   * There is no standard way for the server to know what the client timezone is. 
468   * In the case where the client timezone is unknown, the timezone should be null
469   *
470   * @return the specified timezone to render in
471   */
472  public ZoneId getTimeZoneId() {
473    return timeZoneId;
474  }
475
476  public void setTimeZoneId(ZoneId timeZoneId) {
477    this.timeZoneId = timeZoneId;
478  }
479
480
481  /**
482   * In the absence of a specified format, the renderers will default to 
483   * the FormatStyle.MEDIUM for the current locale.
484   * 
485   * @return the format to use
486   */
487  public DateTimeFormatter getDateTimeFormat() {
488    return this.dateTimeFormat;
489  }
490
491  public void setDateTimeFormat(DateTimeFormatter dateTimeFormat) {
492    this.dateTimeFormat = dateTimeFormat;
493  }
494
495  /**
496   * In the absence of a specified format, the renderers will default to 
497   * the FormatStyle.MEDIUM for the current locale.
498   * 
499   * @return the format to use
500   */
501  public DateTimeFormatter getDateFormat() {
502    return this.dateFormat;
503  }
504
505  public void setDateFormat(DateTimeFormatter dateFormat) {
506    this.dateFormat = dateFormat;
507  }
508
509  public ResourceRendererMode getMode() {
510    return mode;
511  }
512
513  public void setMode(ResourceRendererMode mode) {
514    this.mode = mode;
515  }
516  
517  
518}