001package org.hl7.fhir.r5.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.r5.conformance.ProfileUtilities;
016import org.hl7.fhir.r5.conformance.ProfileUtilities.ProfileKnowledgeProvider;
017import org.hl7.fhir.r5.context.IWorkerContext;
018import org.hl7.fhir.r5.model.Base;
019import org.hl7.fhir.r5.model.DomainResource;
020import org.hl7.fhir.r5.model.Enumerations.FHIRVersion;
021import org.hl7.fhir.r5.model.FhirPublication;
022import org.hl7.fhir.r5.renderers.utils.Resolver.IReferenceResolver;
023import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext;
024import org.hl7.fhir.r5.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  private DateTimeFormatter dateYearFormat;
131  private DateTimeFormatter dateYearMonthFormat;
132  
133  /**
134   * 
135   * @param context - access to all related resources that might be needed
136   * @param markdown - appropriate markdown processing engine 
137   * @param terminologyServiceOptions - options to use when looking up codes
138   * @param specLink - path to FHIR specification
139   * @param lang - langauage to render in
140   */
141  public RenderingContext(IWorkerContext worker, MarkDownProcessor markdown, ValidationOptions terminologyServiceOptions, String specLink, String localPrefix, String lang, ResourceRendererMode mode) {
142    super();
143    this.worker = worker;
144    this.markdown = markdown;
145    this.lang = lang;
146    this.specificationLink = specLink;
147    this.localPrefix = localPrefix;
148    this.mode = mode;
149    if (terminologyServiceOptions != null) {
150      this.terminologyServiceOptions = terminologyServiceOptions;
151    }
152 // default to US locale - discussion here: https://github.com/hapifhir/org.hl7.fhir.core/issues/666
153    this.locale = new Locale.Builder().setLanguageTag("en-US").build(); 
154    profileUtilities = new ProfileUtilities(worker, null, null);
155  }
156
157  public IWorkerContext getContext() {
158    return worker;
159  }
160
161  // -- 2. Markdown support -------------------------------------------------------
162
163  public ProfileUtilities getProfileUtilities() {
164    return profileUtilities;
165  }
166
167  public IWorkerContext getWorker() {
168    return worker;
169  }
170
171  public boolean isCanonicalUrlsAsLinks() {
172    return canonicalUrlsAsLinks;
173  }
174
175  public RenderingContext setCanonicalUrlsAsLinks(boolean canonicalUrlsAsLinks) {
176    this.canonicalUrlsAsLinks = canonicalUrlsAsLinks;
177    return this;
178  }
179
180  public MarkDownProcessor getMarkdown() {
181    if (markdown == null) {
182      markdown = new MarkDownProcessor(Dialect.COMMON_MARK);
183    }
184    return markdown;
185  }
186
187  public String getLang() {
188    return lang;
189  }
190
191  public String getSpecificationLink() {
192    return specificationLink;
193  }
194
195  public String getLocalPrefix() {
196    return localPrefix;
197  }
198
199  public ValidationOptions getTerminologyServiceOptions() {
200    return terminologyServiceOptions;
201  }
202
203
204  public String getTooCostlyNoteEmpty() {
205    return tooCostlyNoteEmpty;
206  }
207
208  public RenderingContext setTooCostlyNoteEmpty(String tooCostlyNoteEmpty) {
209    this.tooCostlyNoteEmpty = tooCostlyNoteEmpty;
210    return this;
211  }
212
213  public String getTooCostlyNoteNotEmpty() {
214    return tooCostlyNoteNotEmpty;
215  }
216
217  public RenderingContext setTooCostlyNoteNotEmpty(String tooCostlyNoteNotEmpty) {
218    this.tooCostlyNoteNotEmpty = tooCostlyNoteNotEmpty;
219    return this;
220  }
221
222  public String getTooCostlyNoteEmptyDependent() {
223    return tooCostlyNoteEmptyDependent;
224  }
225
226  public RenderingContext setTooCostlyNoteEmptyDependent(String tooCostlyNoteEmptyDependent) {
227    this.tooCostlyNoteEmptyDependent = tooCostlyNoteEmptyDependent;
228    return this;
229  }
230
231  public String getTooCostlyNoteNotEmptyDependent() {
232    return tooCostlyNoteNotEmptyDependent;
233  }
234
235  public RenderingContext setTooCostlyNoteNotEmptyDependent(String tooCostlyNoteNotEmptyDependent) {
236    this.tooCostlyNoteNotEmptyDependent = tooCostlyNoteNotEmptyDependent;
237    return this;
238  }
239
240  public int getHeaderLevelContext() {
241    return headerLevelContext;
242  }
243
244  public RenderingContext setHeaderLevelContext(int headerLevelContext) {
245    this.headerLevelContext = headerLevelContext;
246    return this;
247  }
248
249  public IReferenceResolver getResolver() {
250    return resolver;
251  }
252
253  public RenderingContext setResolver(IReferenceResolver resolver) {
254    this.resolver = resolver;
255    return this;
256  }
257
258  public RenderingContext setTerminologyServiceOptions(ValidationOptions terminologyServiceOptions) {
259    this.terminologyServiceOptions = terminologyServiceOptions;
260    return this;
261  }
262
263  public boolean isNoSlowLookup() {
264    return noSlowLookup;
265  }
266
267  public RenderingContext setNoSlowLookup(boolean noSlowLookup) {
268    this.noSlowLookup = noSlowLookup;
269    return this;
270  }
271
272  public String getDefinitionsTarget() {
273    return definitionsTarget;
274  }
275
276  public RenderingContext setDefinitionsTarget(String definitionsTarget) {
277    this.definitionsTarget = definitionsTarget;
278    return this;
279  }
280
281  public String getDestDir() {
282    return destDir;
283  }
284
285  public RenderingContext setDestDir(String destDir) {
286    this.destDir = destDir;
287    return this;
288  }
289
290  public RenderingContext setProfileUtilities(ProfileUtilities profileUtilities) {
291    this.profileUtilities = profileUtilities;
292    return this;
293  }
294
295  public ILiquidTemplateProvider getTemplateProvider() {
296    return templateProvider;
297  }
298
299  public RenderingContext setTemplateProvider(ILiquidTemplateProvider templateProvider) {
300    this.templateProvider = templateProvider;
301    return this;
302  }
303
304  public IEvaluationContext getServices() {
305    return services;
306  }
307
308  public RenderingContext setServices(IEvaluationContext services) {
309    this.services = services;
310    return this;
311  }
312
313  public boolean isPretty() {
314    return pretty;
315  }
316
317  public RenderingContext setPretty(boolean pretty) {
318    this.pretty = pretty;
319    return this;
320  }
321
322  public ITypeParser getParser() {
323    return parser;
324  }
325
326  public RenderingContext setParser(ITypeParser parser) {
327    this.parser = parser;
328    return this;
329  }
330
331
332  public List<String> getCodeSystemPropList() {
333    return codeSystemPropList;
334  }
335
336  public RenderingContext setCodeSystemPropList(List<String> codeSystemPropList) {
337    this.codeSystemPropList = codeSystemPropList;
338    return this;
339  }
340
341  public RenderingContext copy() {
342    RenderingContext res = new RenderingContext(worker, markdown, terminologyServiceOptions, specificationLink, localPrefix, lang, mode);
343
344    res.resolver = resolver;
345    res.templateProvider = templateProvider;
346    res.services = services;
347    res.parser = parser;
348
349    res.headerLevelContext = headerLevelContext;
350    res.canonicalUrlsAsLinks = canonicalUrlsAsLinks;
351    res.pretty = pretty;
352
353    res.noSlowLookup = noSlowLookup;
354    res.tooCostlyNoteEmpty = tooCostlyNoteEmpty;
355    res.tooCostlyNoteNotEmpty = tooCostlyNoteNotEmpty;
356    res.tooCostlyNoteEmptyDependent = tooCostlyNoteEmptyDependent;
357    res.tooCostlyNoteNotEmptyDependent = tooCostlyNoteNotEmptyDependent;
358    res.codeSystemPropList.addAll(codeSystemPropList);
359
360    res.profileUtilities = profileUtilities;
361    res.definitionsTarget = definitionsTarget;
362    res.destDir = destDir;
363    res.addGeneratedNarrativeHeader = addGeneratedNarrativeHeader;
364    res.questionnaireMode = questionnaireMode;
365    res.header = header;
366    res.selfLink = selfLink;
367    res.inlineGraphics = inlineGraphics;
368    res.timeZoneId = timeZoneId;
369    res.dateTimeFormat = dateTimeFormat;
370    res.dateFormat = dateFormat;
371    res.dateYearFormat = dateYearFormat;
372    res.dateYearMonthFormat = dateYearMonthFormat;
373
374    return res;
375  }
376
377  public boolean isInlineGraphics() {
378    return inlineGraphics;
379  }
380
381  public RenderingContext setInlineGraphics(boolean inlineGraphics) {
382    this.inlineGraphics = inlineGraphics;
383    return this;
384  }
385
386  public boolean isHeader() {
387    return header;
388  }
389
390  public RenderingContext setHeader(boolean header) {
391    this.header = header;
392    return this;
393  }
394
395  public QuestionnaireRendererMode getQuestionnaireMode() {
396    return questionnaireMode;
397  }
398
399  public RenderingContext setQuestionnaireMode(QuestionnaireRendererMode questionnaireMode) {
400    this.questionnaireMode = questionnaireMode;
401    return this;
402  }
403
404  public String getSelfLink() {
405    return selfLink;
406  }
407
408  public RenderingContext setSelfLink(String selfLink) {
409    this.selfLink = selfLink;
410    return this;
411  }
412
413  public String fixReference(String ref) {
414    if (!Utilities.isAbsoluteUrl(ref)) {
415      return (localPrefix == null ? "" : localPrefix)+ref;
416    }
417    if (ref.startsWith("http://hl7.org/fhir") && !ref.substring(20).contains("/")) {
418      return specificationLink+ref.substring(20);
419    }
420    return ref;
421  }
422
423  public RenderingContext setLang(String lang) {
424    this.lang = lang;
425    return this;
426  }
427
428  public RenderingContext setLocalPrefix(String localPrefix) {
429    this.localPrefix = localPrefix;
430    return this;
431  }
432
433  public boolean isAddGeneratedNarrativeHeader() {
434    return addGeneratedNarrativeHeader;
435  }
436
437  public RenderingContext setAddGeneratedNarrativeHeader(boolean addGeneratedNarrativeHeader) {
438    this.addGeneratedNarrativeHeader = addGeneratedNarrativeHeader;
439    return this;
440   }
441
442  public FhirPublication getTargetVersion() {
443    return targetVersion;
444  }
445
446  public void setTargetVersion(FhirPublication targetVersion) {
447    this.targetVersion = targetVersion;
448  }
449
450  public boolean isTechnicalMode() {
451    return mode == ResourceRendererMode.TECHNICAL;
452  }
453
454  public boolean hasLocale() {
455    return locale != null;
456  }
457  
458  public Locale getLocale() {
459    if (locale == null) {
460      return Locale.getDefault();
461    } else { 
462      return locale;
463    }
464  }
465
466  public void setLocale(Locale locale) {
467    this.locale = locale;
468  }
469
470
471  /**
472   * if the timezone is null, the rendering will default to the source timezone
473   * in the resource
474   * 
475   * Note that if you're working server side, the FHIR project recommends the use
476   * of the Date header so that clients know what timezone the server defaults to,
477   * 
478   * There is no standard way for the server to know what the client timezone is. 
479   * In the case where the client timezone is unknown, the timezone should be null
480   *
481   * @return the specified timezone to render in
482   */
483  public ZoneId getTimeZoneId() {
484    return timeZoneId;
485  }
486
487  public void setTimeZoneId(ZoneId timeZoneId) {
488    this.timeZoneId = timeZoneId;
489  }
490
491
492  /**
493   * In the absence of a specified format, the renderers will default to 
494   * the FormatStyle.MEDIUM for the current locale.
495   * 
496   * @return the format to use
497   */
498  public DateTimeFormatter getDateTimeFormat() {
499    return this.dateTimeFormat;
500  }
501
502  public void setDateTimeFormat(DateTimeFormatter dateTimeFormat) {
503    this.dateTimeFormat = dateTimeFormat;
504  }
505
506  public void setDateTimeFormatString(String dateTimeFormat) {
507    this.dateTimeFormat = DateTimeFormatter.ofPattern(dateTimeFormat);
508  }
509
510  /**
511   * In the absence of a specified format, the renderers will default to 
512   * the FormatStyle.MEDIUM for the current locale.
513   * 
514   * @return the format to use
515   */
516  public DateTimeFormatter getDateFormat() {
517    return this.dateFormat;
518  }
519
520  public void setDateFormat(DateTimeFormatter dateFormat) {
521    this.dateFormat = dateFormat;
522  }
523
524  public void setDateFormatString(String dateFormat) {
525    this.dateFormat = DateTimeFormatter.ofPattern(dateFormat);
526  }
527
528  public DateTimeFormatter getDateYearFormat() {
529    return dateYearFormat;
530  }
531
532  public void setDateYearFormat(DateTimeFormatter dateYearFormat) {
533    this.dateYearFormat = dateYearFormat;
534  }
535
536  public void setDateYearFormatString(String dateYearFormat) {
537    this.dateYearFormat = DateTimeFormatter.ofPattern(dateYearFormat);
538  }
539
540  public DateTimeFormatter getDateYearMonthFormat() {
541    return dateYearMonthFormat;
542  }
543
544  public void setDateYearMonthFormat(DateTimeFormatter dateYearMonthFormat) {
545    this.dateYearMonthFormat = dateYearMonthFormat;
546  }
547
548  public void setDateYearMonthFormatString(String dateYearMonthFormat) {
549    this.dateYearMonthFormat = DateTimeFormatter.ofPattern(dateYearMonthFormat);
550  }
551
552  public ResourceRendererMode getMode() {
553    return mode;
554  }
555
556  public void setMode(ResourceRendererMode mode) {
557    this.mode = mode;
558  }
559  
560  
561}